mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 08:15:41 +00:00
fix: show plugin day labels as dots in the phone month view and keep the mini calendar dot visible on today
This commit is contained in:
@@ -194,18 +194,27 @@ describe('calendar-day-header and mini-calendar-marks', () => {
|
||||
expect(headerOf('2026-09-09').querySelector('[data-plugin-surface="calendar-day-header"]')).toHaveTextContent('Feast day');
|
||||
});
|
||||
|
||||
it('shows the label in the month cell header on desktop only', () => {
|
||||
act(() => setDayLabels('hol', 'Holidays', [{ date: '2026-09-09', text: 'Feast day' }]));
|
||||
it('shows the label in the month cell header, as a dot on phones', () => {
|
||||
act(() => setDayLabels('hol', 'Holidays', [
|
||||
{ date: '2026-09-09', text: 'Feast day' },
|
||||
{ date: '2026-09-10', dot: true, title: 'Dot only' },
|
||||
]));
|
||||
const month = { ...week, windowKey: 'month:2026-09-09', rangeStart: new Date(2026, 7, 31), rangeEnd: new Date(2026, 9, 4) };
|
||||
const { unmount } = render(<CalendarMonthView {...month} />);
|
||||
|
||||
const labels = document.querySelectorAll('[data-plugin-surface="calendar-day-header"]');
|
||||
expect(labels).toHaveLength(1);
|
||||
expect(labels[0]).toHaveTextContent('Feast day');
|
||||
expect(labels[0].closest('[role="gridcell"]')?.getAttribute('aria-label')).toMatch(/9/);
|
||||
unmount();
|
||||
|
||||
render(<CalendarMonthView {...month} isMobile />);
|
||||
expect(document.querySelector('[data-plugin-surface="calendar-day-header"]')).toBeNull();
|
||||
const dots = [...document.querySelectorAll<HTMLElement>('[data-plugin-surface="calendar-day-header"]')];
|
||||
expect(dots).toHaveLength(2);
|
||||
for (const dot of dots) expect(dot).toHaveAttribute('data-compact');
|
||||
expect(dots[0].closest('[role="gridcell"]')?.getAttribute('aria-label')).toMatch(/9/);
|
||||
expect(dots[0].title).toBe('Feast day (Holidays)');
|
||||
expect(dots[1].title).toBe('Dot only (Holidays)');
|
||||
});
|
||||
|
||||
it('marks days with a dot in the small month calendar', () => {
|
||||
@@ -224,4 +233,22 @@ describe('calendar-day-header and mini-calendar-marks', () => {
|
||||
expect(dots[0].closest('button')).toHaveTextContent('9');
|
||||
expect(dots[0].closest('button')).toHaveTextContent('A feast');
|
||||
});
|
||||
|
||||
it('keeps the dot visible on today, whose filled circle has white text', () => {
|
||||
vi.useFakeTimers({ toFake: ['Date'] });
|
||||
vi.setSystemTime(new Date(2026, 8, 9, 12));
|
||||
try {
|
||||
act(() => setDayLabels('hol', 'Holidays', [{ date: '2026-09-09', dot: true, title: 'A feast' }]));
|
||||
render(
|
||||
<MiniCalendar selectedDate={new Date(2026, 8, 1)} displayMonth={new Date(2026, 8, 1)} onSelectDate={vi.fn()} onChangeMonth={vi.fn()} />,
|
||||
);
|
||||
const dot = document.querySelector<HTMLElement>('[data-plugin-surface="mini-calendar-marks"]')!;
|
||||
expect(dot.closest('button')).toHaveAttribute('aria-current', 'date');
|
||||
// Its own colour, not the button's text colour, which is white on today.
|
||||
expect(dot.className).not.toMatch(/bg-current/);
|
||||
expect(dot.className).toMatch(/ring-background/);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -380,7 +380,8 @@ export function CalendarMonthView({
|
||||
>
|
||||
{formatDayNumber(day)}
|
||||
</button>
|
||||
{!isMobile && <PluginDayLabel surface="calendar-day-header" date={key} className="min-w-0 flex-1" />}
|
||||
{/* Phones have no room for the text: a dot stands in for it. */}
|
||||
<PluginDayLabel surface="calendar-day-header" date={key} compact={isMobile} className={isMobile ? undefined : "min-w-0 flex-1"} />
|
||||
</div>
|
||||
{isMobile && !showChips ? (
|
||||
<div className="flex items-center justify-center gap-0.5 flex-wrap">
|
||||
|
||||
@@ -27,10 +27,16 @@ interface PluginDayLabelProps {
|
||||
/** YYYY-MM-DD */
|
||||
date: string;
|
||||
className?: string;
|
||||
/**
|
||||
* calendar-day-header only: no room for text (the phone month view), so a
|
||||
* dot stands for the day's labels; their text is its tooltip and is read
|
||||
* out by screen readers.
|
||||
*/
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
/** What plugins put on a calendar day. Renders nothing without labels. */
|
||||
export function PluginDayLabel({ surface, date, className }: PluginDayLabelProps) {
|
||||
export function PluginDayLabel({ surface, date, className, compact }: PluginDayLabelProps) {
|
||||
const labels = useDayLabels(date);
|
||||
|
||||
if (surface === "mini-calendar-marks") {
|
||||
@@ -42,13 +48,29 @@ export function PluginDayLabel({ surface, date, className }: PluginDayLabelProps
|
||||
aria-hidden="true"
|
||||
data-plugin-surface="mini-calendar-marks"
|
||||
title={tooltip(marks)}
|
||||
className={cn("pointer-events-none absolute top-0.5 end-0.5 h-1 w-1 rounded-full bg-current opacity-70", className)}
|
||||
className={cn("pointer-events-none absolute top-0 end-0 h-1.5 w-1.5 rounded-full bg-muted-foreground ring-1 ring-background", className)}
|
||||
/>
|
||||
<span className="sr-only">{marks.map((l) => l.title ?? l.text ?? l.pluginName).join(", ")}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
const shown = labels.filter((l) => l.text || l.dot);
|
||||
if (shown.length === 0) return null;
|
||||
return (
|
||||
<span
|
||||
data-plugin-surface="calendar-day-header"
|
||||
data-compact=""
|
||||
title={tooltip(shown)}
|
||||
className={cn("inline-flex flex-shrink-0", className)}
|
||||
>
|
||||
<span aria-hidden="true" className="block h-1.5 w-1.5 rounded-full bg-muted-foreground" />
|
||||
<span className="sr-only">{shown.map((l) => l.text ?? l.title ?? l.pluginName).join(", ")}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const withText = labels.filter((l) => l.text);
|
||||
if (withText.length === 0) return null;
|
||||
return (
|
||||
|
||||
@@ -89,7 +89,7 @@ export const SLOT_CATALOG = {
|
||||
'calendar-event-actions': S('calendar', 'frame', ['ui:calendar-action'], { eventData: 'calendar:read', setVirtualLocation: null }, 'buttons in the event form', true),
|
||||
'calendar-event-detail': S('calendar', 'frame', ['ui:calendar-action'], { event: 'calendar:read' }, 'a section in an event\'s details'),
|
||||
'calendar-event-form': S('calendar', 'frame', ['ui:calendar-action'], { event: 'calendar:read' }, 'a section in the form for a new or edited event'),
|
||||
'calendar-day-header': S('calendar', 'data', ['ui:calendar-action'], { date: null }, 'a short label under a day\'s name in the day and week views (api.ui.setDayLabels)'),
|
||||
'calendar-day-header': S('calendar', 'data', ['ui:calendar-action'], { date: null }, 'a short label under a day\'s name in the day, week and month views; a dot in the phone month view (api.ui.setDayLabels)'),
|
||||
'mini-calendar-marks': S('calendar', 'data', ['ui:calendar-action'], { date: null }, 'dots on days in the small month calendar (api.ui.setDayLabels)'),
|
||||
|
||||
// ─── Contacts ──────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user