Redraw the calendar as an almanac: gridless cells, typed bands

Day cells lose their borders and fills; red days, klämdagar and
Saturdays are told apart by the numeral's ink with a 12px R/K mark,
today is a filled circle, overrides and exclusions mark the numeral.
Bands carry their funding type by fill, ink and hatching and their
owner by a left cap. The preset rail becomes a chip row, the legend a
help popover, the period popover's three delete buttons fold behind
one "Ta bort…", and solo users pick a type directly in the day modal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBRLomdLK9jF7wU7a8AA6H
This commit is contained in:
2026-09-19 19:02:02 +02:00
parent d14f86478a
commit dbef997da4
28 changed files with 1486 additions and 1109 deletions
+30 -12
View File
@@ -24,40 +24,52 @@ import { calendarDayCell, goToCalendarMonth } from '../support/calendarNav'
* element actually painted with it — i.e. exactly what `toHaveCSS` compares
* against. Reading the token instead of a hardcoded literal means a future
* restyle that moves the palette can't silently desync this spec from
* calendar.css: whatever `--c-danger-weak`/`--c-weekend-bg` resolve to at
* test time is what's asserted.
* calendar.css: whatever `--c-danger`/`--c-text-faint` resolve to at test
* time is what's asserted.
*/
async function resolveCssVar(page: Page, varName: string): Promise<string> {
return page.evaluate((name) => {
const probe = document.createElement('div')
probe.style.backgroundColor = `var(${name})`
probe.style.color = `var(${name})`
document.body.appendChild(probe)
const resolved = getComputedStyle(probe).backgroundColor
const resolved = getComputedStyle(probe).color
probe.remove()
return resolved
}, varName)
}
/** The day's numeral — the element that carries the red/Saturday colour in
* the Almanacka design (cells themselves have no fill). */
function dayNumeral(cell: ReturnType<typeof calendarDayCell>) {
return cell.locator('.calendar-day__number')
}
test.describe('red days, klämdagar and the combined-holiday-name trap', () => {
test('Easter Sunday (Påskdagen) renders as a named red day', async ({ page }) => {
const easter = nextEasterOnOrAfter(new Date())
await goToCalendarMonth(page, easter)
const redDayBg = await resolveCssVar(page, '--c-danger-weak') // .calendar-day--red-day
const redInk = await resolveCssVar(page, '--c-danger') // .calendar-day--red-day .calendar-day__number
const redFill = await resolveCssVar(page, '--c-danger-weak')
const cell = calendarDayCell(page, toIsoDate(easter))
await expect(cell).toHaveClass(/calendar-day--red-day/)
await expect(cell).toHaveCSS('background-color', redDayBg)
// The numeral carries the colour; the cell itself has no red fill.
await expect(dayNumeral(cell)).toHaveCSS('color', redInk)
await expect(cell).not.toHaveCSS('background-color', redFill)
await expect(cell.getByTestId('calendar-day-type-badge')).toHaveText('R')
await expect(cell.getByTestId('calendar-day-holiday-name')).toHaveText('Påskdagen')
})
test('Whit Sunday (Pingstdagen) renders as a named red day', async ({ page }) => {
const whitsun = whitSunday(nextEasterOnOrAfter(new Date()))
await goToCalendarMonth(page, whitsun)
const redDayBg = await resolveCssVar(page, '--c-danger-weak') // .calendar-day--red-day
const redInk = await resolveCssVar(page, '--c-danger') // .calendar-day--red-day .calendar-day__number
const redFill = await resolveCssVar(page, '--c-danger-weak')
const cell = calendarDayCell(page, toIsoDate(whitsun))
await expect(cell).toHaveClass(/calendar-day--red-day/)
await expect(cell).toHaveCSS('background-color', redDayBg)
await expect(dayNumeral(cell)).toHaveCSS('color', redInk)
await expect(cell).not.toHaveCSS('background-color', redFill)
await expect(cell.getByTestId('calendar-day-holiday-name')).toHaveText('Pingstdagen')
})
@@ -77,18 +89,24 @@ test.describe('red days, klämdagar and the combined-holiday-name trap', () => {
// that first Mon–Sun week row (see Calendar.tsx's `startOffset`) — so
// this never depends on the two dates sharing a calendar month.
await goToCalendarMonth(page, plainSunday)
const redDayBg = await resolveCssVar(page, '--c-danger-weak') // .calendar-day--red-day
const weekendBg = await resolveCssVar(page, '--c-weekend-bg') // .calendar-day--weekend
const redInk = await resolveCssVar(page, '--c-danger') // .calendar-day--red-day .calendar-day__number
const redFill = await resolveCssVar(page, '--c-danger-weak')
const saturdayInk = await resolveCssVar(page, '--c-text-faint') // .calendar-day--weekend .calendar-day__number
const sundayCell = calendarDayCell(page, toIsoDate(plainSunday))
await expect(sundayCell).toHaveClass(/calendar-day--red-day/)
await expect(sundayCell).not.toHaveClass(/calendar-day--weekend/)
await expect(sundayCell).toHaveCSS('background-color', redDayBg)
await expect(dayNumeral(sundayCell)).toHaveCSS('color', redInk)
await expect(sundayCell).not.toHaveCSS('background-color', redFill)
await expect(sundayCell.getByTestId('calendar-day-type-badge')).toHaveText('R')
await expect(sundayCell.getByTestId('calendar-day-holiday-name')).toHaveCount(0)
const saturdayCell = calendarDayCell(page, toIsoDate(plainSaturday))
await expect(saturdayCell).toHaveClass(/calendar-day--weekend/)
await expect(saturdayCell).not.toHaveClass(/calendar-day--red-day/)
await expect(saturdayCell).toHaveCSS('background-color', weekendBg)
// A plain Saturday is a faint numeral, not a red one, and no fill.
await expect(dayNumeral(saturdayCell)).toHaveCSS('color', saturdayInk)
await expect(dayNumeral(saturdayCell)).not.toHaveCSS('color', redInk)
await expect(saturdayCell).not.toHaveCSS('background-color', redFill)
})
})