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:
@@ -0,0 +1,82 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
/**
|
||||
* The app's icon set — a handful of 24-unit line glyphs, inlined so there is
|
||||
* no icon font or sprite request. Every icon is a 1.5 stroke in
|
||||
* `currentColor`, so it takes the colour of the text next to it, and is
|
||||
* `aria-hidden`: the control that contains it carries the accessible name.
|
||||
*/
|
||||
export type IconName =
|
||||
| 'chevron-left'
|
||||
| 'chevron-right'
|
||||
| 'close'
|
||||
| 'pin'
|
||||
| 'check'
|
||||
| 'briefcase'
|
||||
| 'ban'
|
||||
| 'help'
|
||||
| 'clock'
|
||||
| 'plus'
|
||||
| 'external'
|
||||
| 'calendar'
|
||||
|
||||
const CIRCLE = 'M12 3a9 9 0 1 0 0 18 9 9 0 1 0 0-18z'
|
||||
|
||||
const PATHS: Record<IconName, readonly string[]> = {
|
||||
'chevron-left': ['M15 18l-6-6 6-6'],
|
||||
'chevron-right': ['M9 18l6-6-6-6'],
|
||||
close: ['M18 6L6 18', 'M6 6l12 12'],
|
||||
pin: ['M12 17v5', 'M9 3h6l-1 7 3 3H7l3-3-1-7z'],
|
||||
check: ['M5 12l5 5L20 7'],
|
||||
briefcase: [
|
||||
'M5 7h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2z',
|
||||
'M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2',
|
||||
'M3 12h18',
|
||||
],
|
||||
ban: [CIRCLE, 'M5.6 5.6l12.8 12.8'],
|
||||
help: [CIRCLE, 'M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.7.4-1 1-1 1.7', 'M12 17h.01'],
|
||||
clock: [CIRCLE, 'M12 7v5l3 2'],
|
||||
plus: ['M12 5v14', 'M5 12h14'],
|
||||
external: ['M14 4h6v6', 'M20 4l-9 9', 'M18 13v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5'],
|
||||
calendar: [
|
||||
'M5 5h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z',
|
||||
'M3 10h18',
|
||||
'M8 3v4',
|
||||
'M16 3v4',
|
||||
],
|
||||
}
|
||||
|
||||
/** Every icon name, in one place, so a test can render each of them. */
|
||||
export const ICON_NAMES = Object.keys(PATHS) as IconName[]
|
||||
|
||||
export type IconSize = 16 | 20 | 24
|
||||
|
||||
export interface IconProps {
|
||||
name: IconName
|
||||
/** Rendered size in px — matches --icon-sm / --icon-md / --icon-lg. */
|
||||
size?: IconSize | undefined
|
||||
className?: string | undefined
|
||||
}
|
||||
|
||||
export function Icon({ name, size = 16, className }: IconProps): ReactNode {
|
||||
return (
|
||||
<svg
|
||||
className={className ? `icon ${className}` : 'icon'}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-icon={name}
|
||||
>
|
||||
{PATHS[name].map((d) => (
|
||||
<path key={d} d={d} />
|
||||
))}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user