Lay the Almanacka design foundation: tokens, type, shell

New token system in global.css (near-white surfaces, one ink-blue
accent, no sub-12px type), self-hosted Schibsted Grotesk, rewritten
primitives (buttons, fields, modal shell, segmented, disclosure,
skeleton), a borderless 56px header with an SV | EN language toggle,
an Icon component replacing glyphs, page skeletons that keep the
layout during loading, and leave-type colours moved out of TS into
CSS variables. Feature blocks keep their structure for later phases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 18:25:04 +02:00
parent 95444d3bf4
commit d14f86478a
44 changed files with 1358 additions and 485 deletions
+33 -9
View File
@@ -1,22 +1,46 @@
import { describe, it, expect } from 'vitest'
import { getColorForType, getLabelForType } from './leaveTypes'
import { getColorForType, getInkForType, getLabelForType, getTypeModifier } from './leaveTypes'
import { translate } from '@/i18n'
describe('getColorForType', () => {
it('returns the vacation color', () => {
expect(getColorForType('vacation')).toBe('#4CAF50')
it('returns the vacation fill token', () => {
expect(getColorForType('vacation')).toBe('var(--c-leave-vacation)')
})
it('returns the sjukpenning color', () => {
expect(getColorForType('sjukpenning')).toBe('#FF9800')
it('returns the sjukpenning fill token', () => {
expect(getColorForType('sjukpenning')).toBe('var(--c-leave-sjukpenning)')
})
it('returns the lagsta color', () => {
expect(getColorForType('lagsta')).toBe('#2196F3')
it('returns the lagsta fill token', () => {
expect(getColorForType('lagsta')).toBe('var(--c-leave-lagsta)')
})
it('falls back to grey for an unknown type', () => {
expect(getColorForType('something-else')).toBe('#9E9E9E')
it('falls back to the faint text colour for an unknown type', () => {
expect(getColorForType('something-else')).toBe('var(--c-text-faint)')
})
})
describe('getInkForType', () => {
it('returns the matching ink token for each known type', () => {
expect(getInkForType('vacation')).toBe('var(--c-leave-vacation-ink)')
expect(getInkForType('sjukpenning')).toBe('var(--c-leave-sjukpenning-ink)')
expect(getInkForType('lagsta')).toBe('var(--c-leave-lagsta-ink)')
})
it('falls back to surface ink for an unknown type', () => {
expect(getInkForType('something-else')).toBe('var(--c-surface)')
})
})
describe('getTypeModifier', () => {
it('returns a type-* modifier suffix for each known type', () => {
expect(getTypeModifier('vacation')).toBe('type-vacation')
expect(getTypeModifier('sjukpenning')).toBe('type-sjukpenning')
expect(getTypeModifier('lagsta')).toBe('type-lagsta')
})
it('returns type-unknown for an unknown type', () => {
expect(getTypeModifier('something-else')).toBe('type-unknown')
})
})
+31 -6
View File
@@ -1,12 +1,37 @@
import type { TranslationKey } from '@/i18n'
/**
* Leave types are told apart by fill / ink / outline, not by hue — the
* values live as `--c-leave-*` tokens in `styles/global.css` and these
* helpers hand back `var(...)` references so a palette change never needs a
* code change. jsdom preserves `var(...)` strings on inline styles, so tests
* can still assert on them.
*/
const KNOWN_TYPES = ['vacation', 'sjukpenning', 'lagsta'] as const
type KnownType = (typeof KNOWN_TYPES)[number]
function isKnownType(type: string): type is KnownType {
return (KNOWN_TYPES as readonly string[]).includes(type)
}
/** Background fill for a leave type (band, timeline bar, legend swatch). */
export function getColorForType(type: string): string {
const colorMap: Record<string, string> = {
vacation: '#4CAF50',
sjukpenning: '#FF9800',
lagsta: '#2196F3',
}
return colorMap[type] ?? '#9E9E9E'
return isKnownType(type) ? `var(--c-leave-${type})` : 'var(--c-text-faint)'
}
/** Text colour that reads on top of `getColorForType(type)`. */
export function getInkForType(type: string): string {
return isKnownType(type) ? `var(--c-leave-${type}-ink)` : 'var(--c-surface)'
}
/**
* BEM modifier suffix for a leave type, for callers that need a class hook
* (lägsta's outline/hatch can't be expressed as a single inline colour):
* `` `calendar-band--${getTypeModifier(type)}` ``.
*/
export function getTypeModifier(type: string): string {
return isKnownType(type) ? `type-${type}` : 'type-unknown'
}
/**