Files
F-r-ldraledigplannerare/frontend/src/components/common/Icon.tsx
T
jacobh2 d14f86478a 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>
2026-09-19 18:25:04 +02:00

83 lines
2.2 KiB
TypeScript

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>
)
}