Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,35 @@ describe('<EventTimelinePremium />', () => {
});
});

it('should display recurrence icon only for recurring events', () => {
const recurringEvent = EventBuilder.new()
.title('Recurring timeline event')
.singleDay('2025-07-03T09:00:00Z')
.resource(engineering)
.recurrent('DAILY')
.build();
const singleEvent = EventBuilder.new()
.title('Single timeline event')
.singleDay('2025-07-03T11:00:00Z')
.resource(engineering)
.build();

renderTimeline({ events: [recurringEvent, singleEvent], view: 'days' });

const recurringEventElements = screen.getAllByLabelText(recurringEvent.title);
expect(recurringEventElements.length).to.be.greaterThan(0);
recurringEventElements.forEach((element) => {
expect(
element.querySelector(`.${eventTimelinePremiumClasses.eventRecurringIcon}`),
).not.to.equal(null);
});

const singleEventElement = screen.getByLabelText(singleEvent.title);
expect(
singleEventElement.querySelector(`.${eventTimelinePremiumClasses.eventRecurringIcon}`),
).to.equal(null);
});

it('should render events correctly in the time view', () => {
const totalWidth = 6144; // 96 hours * 64px
const hourBoundaries = { start: 9 * 64, end: 10 * 64 }; // 9:00 - 10:00
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const useUtilityClasses = (classes: Partial<EventTimelinePremiumClasses> | undef
event: ['event'],
eventPlaceholder: ['eventPlaceholder'],
eventResizeHandler: ['eventResizeHandler'],
eventContent: ['eventContent'],
eventLinesClamp: ['eventLinesClamp'],
eventRecurringIcon: ['eventRecurringIcon'],
timeHeader: ['timeHeader'],
timeHeaderCell: ['timeHeaderCell'],
timeHeaderDayLabel: ['timeHeaderDayLabel'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import SvgIcon from '@mui/material/SvgIcon';
import { useStore } from '@base-ui/utils/store';
import { useId } from '@base-ui/utils/useId';
import { TimelineGrid } from '@mui/x-scheduler-headless-premium/timeline-grid';
Expand All @@ -15,6 +16,8 @@ const ARROW_DEPTH = 8; // px - depth of the chevron point
const LEFT_ARROW_CLIP = `polygon(${ARROW_DEPTH}px 0, 100% 0, 100% 100%, ${ARROW_DEPTH}px 100%, 0 50%)`;
const RIGHT_ARROW_CLIP = `polygon(0 0, calc(100% - ${ARROW_DEPTH}px) 0, 100% 50%, calc(100% - ${ARROW_DEPTH}px) 100%, 0 100%)`;
const BOTH_ARROWS_CLIP = `polygon(${ARROW_DEPTH}px 0, calc(100% - ${ARROW_DEPTH}px) 0, 100% 50%, calc(100% - ${ARROW_DEPTH}px) 100%, ${ARROW_DEPTH}px 100%, 0 50%)`;
const REPEAT_ROUNDED_ICON_PATH =
'M7 7h10v1.79c0 .45.54.67.85.35l2.79-2.79c.2-.2.2-.51 0-.71l-2.79-2.79c-.31-.31-.85-.09-.85.36V5H6c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1zm10 10H7v-1.79c0-.45-.54-.67-.85-.35l-2.79 2.79c-.2.2-.2.51 0 .71l2.79 2.79c.31.31.85.09.85-.36V19h11c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1s-1 .45-1 1z';

const EventTimelinePremiumEventRoot = styled('div', {
name: 'MuiEventTimeline',
Expand Down Expand Up @@ -85,6 +88,8 @@ const EventTimelinePremiumEventLinesClamp = styled('span', {
name: 'MuiEventTimeline',
slot: 'EventLinesClamp',
})({
flexGrow: 1,
minWidth: 0,
display: '-webkit-box',
WebkitLineClamp: 'var(--number-of-lines)',
WebkitBoxOrient: 'vertical',
Expand All @@ -94,6 +99,23 @@ const EventTimelinePremiumEventLinesClamp = styled('span', {
overflowWrap: 'break-word',
});

const EventTimelinePremiumEventContent = styled('div', {
name: 'MuiEventTimeline',
slot: 'EventContent',
})(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5),
minWidth: 0,
}));

const EventTimelinePremiumEventRecurringIcon = styled(SvgIcon, {
name: 'MuiEventTimeline',
slot: 'EventRecurringIcon',
})({
flexShrink: 0,
});

const EventTimelinePremiumEventResizeHandler = styled(TimelineGrid.EventResizeHandler, {
name: 'MuiEventTimeline',
slot: 'EventResizeHandler',
Expand Down Expand Up @@ -132,6 +154,7 @@ export const EventTimelinePremiumEvent = React.forwardRef(function EventTimeline
);
const isEndResizable = useStore(store, schedulerEventSelectors.isResizable, occurrence.id, 'end');
const color = useStore(store, schedulerEventSelectors.color, occurrence.id);
const isRecurring = useStore(store, schedulerEventSelectors.isRecurring, occurrence.id);

// Feature hooks
const id = useId(idProp);
Expand Down Expand Up @@ -159,9 +182,20 @@ export const EventTimelinePremiumEvent = React.forwardRef(function EventTimeline
{...sharedProps}
className={clsx(sharedProps.className, classes.eventPlaceholder)}
>
<EventTimelinePremiumEventLinesClamp className={classes.eventLinesClamp}>
{occurrence.title}
</EventTimelinePremiumEventLinesClamp>
<EventTimelinePremiumEventContent className={classes.eventContent}>
<EventTimelinePremiumEventLinesClamp className={classes.eventLinesClamp}>
{occurrence.title}
</EventTimelinePremiumEventLinesClamp>
{isRecurring && (
<EventTimelinePremiumEventRecurringIcon
className={classes.eventRecurringIcon}
fontSize="small"
aria-hidden="true"
>
<path d={REPEAT_ROUNDED_ICON_PATH} />
</EventTimelinePremiumEventRecurringIcon>
)}
</EventTimelinePremiumEventContent>
</TimelineGrid.EventPlaceholder>
);
}
Expand All @@ -182,9 +216,20 @@ export const EventTimelinePremiumEvent = React.forwardRef(function EventTimeline
className={classes.eventResizeHandler}
/>
)}
<EventTimelinePremiumEventLinesClamp className={classes.eventLinesClamp}>
{occurrence.title}
</EventTimelinePremiumEventLinesClamp>
<EventTimelinePremiumEventContent className={classes.eventContent}>
<EventTimelinePremiumEventLinesClamp className={classes.eventLinesClamp}>
{occurrence.title}
</EventTimelinePremiumEventLinesClamp>
{isRecurring && (
<EventTimelinePremiumEventRecurringIcon
className={classes.eventRecurringIcon}
fontSize="small"
aria-hidden="true"
>
<path d={REPEAT_ROUNDED_ICON_PATH} />
</EventTimelinePremiumEventRecurringIcon>
)}
</EventTimelinePremiumEventContent>
{isEndResizable && (
<EventTimelinePremiumEventResizeHandler side="end" className={classes.eventResizeHandler} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ export interface EventTimelinePremiumClasses extends EventDialogClasses {
eventPlaceholder: string;
/** Styles applied to event resize handler elements. */
eventResizeHandler: string;
/** Styles applied to event content elements. */
eventContent: string;
/** Styles applied to event lines clamp elements. */
eventLinesClamp: string;
/** Styles applied to event recurring icon elements. */
eventRecurringIcon: string;
/** Styles applied to the time header root element. */
timeHeader: string;
/** Styles applied to time header cell elements. */
Expand Down Expand Up @@ -119,7 +123,9 @@ export const eventTimelinePremiumClasses: EventTimelinePremiumClasses = generate
'event',
'eventPlaceholder',
'eventResizeHandler',
'eventContent',
'eventLinesClamp',
'eventRecurringIcon',
'timeHeader',
'timeHeaderCell',
'timeHeaderDayLabel',
Expand Down
Loading