@ultimat3/ui 24.0.0 → 25.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CATALOG.md +117 -13
- package/CLAUDE.md +14 -5
- package/README.md +109 -24
- package/package.json +6 -6
- package/src/a11y.ts +3 -3
- package/src/catalog/build-catalog.ts +2 -2
- package/src/components/Accordion.tsx +8 -2
- package/src/components/AppShell.module.scss +105 -4
- package/src/components/AppShell.tsx +48 -10
- package/src/components/BarChart.module.scss +47 -9
- package/src/components/BarChart.tsx +152 -72
- package/src/components/Card.tsx +52 -16
- package/src/components/ChartFrame.module.scss +188 -0
- package/src/components/ChartFrame.tsx +155 -0
- package/src/components/Container.tsx +46 -9
- package/src/components/DataTable.module.scss +190 -0
- package/src/components/DataTable.tsx +124 -17
- package/src/components/Divider.tsx +0 -2
- package/src/components/DonutChart.module.scss +73 -0
- package/src/components/DonutChart.tsx +154 -0
- package/src/components/Dropzone.tsx +9 -1
- package/src/components/ErrorState.tsx +39 -12
- package/src/components/Form.tsx +17 -4
- package/src/components/Gauge.module.scss +48 -0
- package/src/components/Gauge.tsx +67 -0
- package/src/components/Grid.tsx +34 -13
- package/src/components/Image.tsx +2 -2
- package/src/components/InlineBar.module.scss +23 -0
- package/src/components/InlineBar.tsx +31 -0
- package/src/components/LineChart.module.scss +147 -0
- package/src/components/LineChart.tsx +169 -0
- package/src/components/Menu.tsx +9 -5
- package/src/components/PageHeader.tsx +5 -2
- package/src/components/Popover.tsx +2 -2
- package/src/components/Section.tsx +45 -26
- package/src/components/Stack.tsx +59 -18
- package/src/components/Table.module.scss +30 -0
- package/src/components/Table.tsx +14 -3
- package/src/components/Tabs.tsx +2 -2
- package/src/components/Text.tsx +41 -15
- package/src/components/Toaster.tsx +1 -1
- package/src/components/Toolbar.tsx +7 -6
- package/src/components/app-shell-view.ts +6 -4
- package/src/components/bar-chart-view.ts +5 -4
- package/src/components/chart-frame-view.ts +219 -0
- package/src/components/chart-ticks-view.ts +173 -0
- package/src/components/data-table-view.ts +68 -0
- package/src/components/donut-chart-view.ts +120 -0
- package/src/components/file-input-view.ts +1 -1
- package/src/components/gauge-view.ts +43 -0
- package/src/components/heading-node.tsx +62 -0
- package/src/components/image-source.ts +3 -3
- package/src/components/line-chart-view.ts +153 -0
- package/src/components/money-view.ts +2 -7
- package/src/form/form-binding.ts +23 -6
- package/src/form/use-form.ts +3 -3
- package/src/global.scss +9 -0
- package/src/icons/build-icons.ts +15 -6
- package/src/index.ts +45 -9
- package/src/jsx-probe.ts +5 -4
- package/src/theme/brand-declarations.ts +113 -0
- package/src/theme/brand.ts +98 -109
- package/src/theme/context.ts +1 -1
- package/src/theme/preset-scifi.ts +99 -0
- package/src/theme/preset-shape.ts +18 -0
- package/src/theme/presets.ts +15 -0
- package/src/theme/shadow-value.ts +39 -0
- package/src/theme/theme.ts +1 -1
- package/src/toast/toast-store.ts +1 -1
- package/src/toast/use-toasts.ts +1 -1
- package/src/tokens/_colors.scss +20 -0
- package/src/tokens/_effects.scss +59 -0
- package/src/tokens/_index.scss +2 -0
- package/src/tokens/_mixins.scss +6 -4
- package/src/tokens/_shadow.scss +16 -0
- package/src/tokens/_touch.scss +39 -0
- package/src/tokens/_typography.scss +14 -0
- package/src/tokens/color-tokens.ts +133 -0
- package/src/tokens/colour-vision.ts +106 -0
- package/src/tokens/contrast-pairs.ts +6 -1
- package/src/tokens/contrast.ts +1 -1
- package/src/tokens/theme.scss +2 -0
- package/src/tokens/tokens.ts +41 -87
- package/src/components/qr-reader.ts +0 -187
- package/src/fake-dom.ts +0 -206
- package/src/sass-probe.ts +0 -55
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// The touch target: the smallest box a finger can hit reliably. WCAG 2.5.5's 44 CSS px, as rem
|
|
2
|
+
// so a reader's larger text grows the target with it. One rung, in a map so it emits and reads
|
|
3
|
+
// back like every other scale.
|
|
4
|
+
|
|
5
|
+
$touch: (
|
|
6
|
+
target: 2.75rem,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
@mixin emit {
|
|
10
|
+
:root {
|
|
11
|
+
@each $name, $value in $touch {
|
|
12
|
+
--touch-#{$name}: #{$value};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@function touch($name) {
|
|
18
|
+
@return var(--touch-#{$name});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Make the element hittable. Default: the box itself is at least the target, both axes. With
|
|
22
|
+
/// `$extend: true` the BOX keeps its size and an invisible `::after` grows the hit area around it,
|
|
23
|
+
/// centred — for a chart point, a dense icon row, a close glyph whose visual size is the design.
|
|
24
|
+
/// The pseudo-element is the element's own, so its clicks are the element's clicks.
|
|
25
|
+
@mixin touch-target($extend: false) {
|
|
26
|
+
@if $extend {
|
|
27
|
+
position: relative;
|
|
28
|
+
|
|
29
|
+
&::after {
|
|
30
|
+
content: '';
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset-block: min(0px, calc((100% - #{touch(target)}) / 2));
|
|
33
|
+
inset-inline: min(0px, calc((100% - #{touch(target)}) / 2));
|
|
34
|
+
}
|
|
35
|
+
} @else {
|
|
36
|
+
min-block-size: touch(target);
|
|
37
|
+
min-inline-size: touch(target);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -18,6 +18,20 @@ $font-family: (
|
|
|
18
18
|
Consolas,
|
|
19
19
|
monospace,
|
|
20
20
|
),
|
|
21
|
+
// Numbers in columns: a monospace-friendly stack whose figures align without `tnum` support,
|
|
22
|
+
// read through `@include t.data-text`, which adds `tabular-nums` for every family that has it.
|
|
23
|
+
// A slot of its own so a brand can put a proportional face with tabular figures here and keep
|
|
24
|
+
// `mono` for code.
|
|
25
|
+
data: (
|
|
26
|
+
ui-monospace,
|
|
27
|
+
'SFMono-Regular',
|
|
28
|
+
'Cascadia Mono',
|
|
29
|
+
'JetBrains Mono',
|
|
30
|
+
'Roboto Mono',
|
|
31
|
+
Menlo,
|
|
32
|
+
Consolas,
|
|
33
|
+
monospace,
|
|
34
|
+
),
|
|
21
35
|
);
|
|
22
36
|
|
|
23
37
|
$font-size: (
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// The colour half of the typed token mirror: every role, the chart series among them, and their
|
|
2
|
+
// channels per theme. `_colors.scss` is CANONICAL; `tokens.test.ts` fails if the two disagree.
|
|
3
|
+
// Split from `tokens.ts` by size alone — `tokens.ts` re-exports all of it, so there is one import.
|
|
4
|
+
|
|
5
|
+
export type Theme = 'light' | 'dark';
|
|
6
|
+
|
|
7
|
+
export const COLOR_ROLES = [
|
|
8
|
+
'bg',
|
|
9
|
+
'bg-soft',
|
|
10
|
+
'surface',
|
|
11
|
+
'surface-raised',
|
|
12
|
+
'fg',
|
|
13
|
+
'fg-strong',
|
|
14
|
+
'fg-muted',
|
|
15
|
+
'line',
|
|
16
|
+
'scrim',
|
|
17
|
+
'accent',
|
|
18
|
+
'accent-strong',
|
|
19
|
+
'accent-fg',
|
|
20
|
+
'success',
|
|
21
|
+
'success-soft',
|
|
22
|
+
'success-fg',
|
|
23
|
+
'warning',
|
|
24
|
+
'warning-soft',
|
|
25
|
+
'warning-fg',
|
|
26
|
+
'danger',
|
|
27
|
+
'danger-soft',
|
|
28
|
+
'danger-fg',
|
|
29
|
+
'info',
|
|
30
|
+
'info-soft',
|
|
31
|
+
'info-fg',
|
|
32
|
+
'chart-1',
|
|
33
|
+
'chart-2',
|
|
34
|
+
'chart-3',
|
|
35
|
+
'chart-4',
|
|
36
|
+
'chart-5',
|
|
37
|
+
'chart-6',
|
|
38
|
+
'chart-7',
|
|
39
|
+
'chart-8',
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
export type ColorRole = (typeof COLOR_ROLES)[number];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The categorical series, in the order a chart assigns them. Okabe–Ito-derived and measured, not
|
|
46
|
+
* picked: 3:1 against every surface (`CONTRAST_PAIRS`) and apart under protan, deutan and tritan
|
|
47
|
+
* simulation (`colour-vision.test.ts`). A ninth series reuses `chart-1` with a second encoding —
|
|
48
|
+
* a dash, a pattern, a label — never a ninth hue.
|
|
49
|
+
*/
|
|
50
|
+
export const CHART_ROLES = [
|
|
51
|
+
'chart-1',
|
|
52
|
+
'chart-2',
|
|
53
|
+
'chart-3',
|
|
54
|
+
'chart-4',
|
|
55
|
+
'chart-5',
|
|
56
|
+
'chart-6',
|
|
57
|
+
'chart-7',
|
|
58
|
+
'chart-8',
|
|
59
|
+
] as const satisfies readonly ColorRole[];
|
|
60
|
+
|
|
61
|
+
export type ChartRole = (typeof CHART_ROLES)[number];
|
|
62
|
+
|
|
63
|
+
/** Space-separated RGB channels, mirroring `_colors.scss`. */
|
|
64
|
+
export const colorTokens: Readonly<Record<Theme, Readonly<Record<ColorRole, string>>>> = {
|
|
65
|
+
light: {
|
|
66
|
+
bg: '253 246 240',
|
|
67
|
+
'bg-soft': '245 237 230',
|
|
68
|
+
surface: '250 245 241',
|
|
69
|
+
'surface-raised': '255 255 255',
|
|
70
|
+
fg: '38 34 31',
|
|
71
|
+
'fg-strong': '17 15 13',
|
|
72
|
+
'fg-muted': '110 102 94',
|
|
73
|
+
line: '208 198 188',
|
|
74
|
+
scrim: '17 15 13',
|
|
75
|
+
accent: '31 110 178',
|
|
76
|
+
'accent-strong': '21 92 152',
|
|
77
|
+
'accent-fg': '255 255 255',
|
|
78
|
+
success: '21 123 80',
|
|
79
|
+
'success-soft': '222 244 232',
|
|
80
|
+
'success-fg': '255 255 255',
|
|
81
|
+
warning: '155 93 7',
|
|
82
|
+
'warning-soft': '253 240 213',
|
|
83
|
+
'warning-fg': '255 255 255',
|
|
84
|
+
danger: '190 42 42',
|
|
85
|
+
'danger-soft': '253 227 227',
|
|
86
|
+
'danger-fg': '255 255 255',
|
|
87
|
+
info: '31 110 178',
|
|
88
|
+
'info-soft': '224 239 252',
|
|
89
|
+
'info-fg': '255 255 255',
|
|
90
|
+
'chart-1': '0 107 170',
|
|
91
|
+
'chart-2': '189 107 14',
|
|
92
|
+
'chart-3': '0 125 90',
|
|
93
|
+
'chart-4': '189 44 16',
|
|
94
|
+
'chart-5': '170 70 140',
|
|
95
|
+
'chart-6': '47 135 195',
|
|
96
|
+
'chart-7': '118 97 18',
|
|
97
|
+
'chart-8': '60 60 60',
|
|
98
|
+
},
|
|
99
|
+
dark: {
|
|
100
|
+
bg: '18 18 20',
|
|
101
|
+
'bg-soft': '28 28 32',
|
|
102
|
+
surface: '34 34 39',
|
|
103
|
+
'surface-raised': '44 44 50',
|
|
104
|
+
fg: '228 226 222',
|
|
105
|
+
'fg-strong': '248 247 245',
|
|
106
|
+
'fg-muted': '155 151 145',
|
|
107
|
+
line: '72 72 80',
|
|
108
|
+
scrim: '0 0 0',
|
|
109
|
+
accent: '96 170 240',
|
|
110
|
+
'accent-strong': '130 190 248',
|
|
111
|
+
'accent-fg': '16 20 26',
|
|
112
|
+
success: '74 190 130',
|
|
113
|
+
'success-soft': '22 46 34',
|
|
114
|
+
'success-fg': '12 26 18',
|
|
115
|
+
warning: '226 170 66',
|
|
116
|
+
'warning-soft': '52 42 20',
|
|
117
|
+
'warning-fg': '28 20 6',
|
|
118
|
+
danger: '240 110 110',
|
|
119
|
+
'danger-soft': '56 26 26',
|
|
120
|
+
'danger-fg': '30 12 12',
|
|
121
|
+
info: '96 170 240',
|
|
122
|
+
'info-soft': '22 38 56',
|
|
123
|
+
'info-fg': '12 20 30',
|
|
124
|
+
'chart-1': '105 201 255',
|
|
125
|
+
'chart-2': '230 153 0',
|
|
126
|
+
'chart-3': '0 167 113',
|
|
127
|
+
'chart-4': '236 111 60',
|
|
128
|
+
'chart-5': '203 126 189',
|
|
129
|
+
'chart-6': '124 153 252',
|
|
130
|
+
'chart-7': '246 234 72',
|
|
131
|
+
'chart-8': '181 181 180',
|
|
132
|
+
},
|
|
133
|
+
} as const;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Colour-vision simulation and perceptual distance over channel tokens, so "the chart series are
|
|
2
|
+
// colour-blind safe" is a measured claim (`colour-vision.test.ts`) and an app can measure its own
|
|
3
|
+
// series before it ships them. Machado, Oliveira & Fernandes (2009) at full severity, applied in
|
|
4
|
+
// linear sRGB; distance is CIE76 ΔE in CIELAB (D65).
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A simulated dichromat can land a hair outside sRGB; the display clips it. Its own helper, away
|
|
8
|
+
* from the transfer curve, because the clip is a gamut fact, not part of the curve.
|
|
9
|
+
*/
|
|
10
|
+
const inGamut = (linear: number): number => Math.min(1, Math.max(0, linear));
|
|
11
|
+
|
|
12
|
+
import { linearize, parseChannels } from './contrast';
|
|
13
|
+
|
|
14
|
+
/** The three dichromacies. `typical` is the identity, so one loop covers every reader. */
|
|
15
|
+
export const COLOUR_VISIONS = ['protan', 'deutan', 'tritan'] as const;
|
|
16
|
+
export type ColourVision = (typeof COLOUR_VISIONS)[number] | 'typical';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The floor two series must stay apart by, in ΔE76, under every vision. ~2.3 is a just-noticeable
|
|
20
|
+
* difference for two large flat patches; a chart compares thin lines and small slices with a gap
|
|
21
|
+
* between them, so the floor is set well above it. Measured headroom (`As of 2026-10`): the
|
|
22
|
+
* shipped light palette's closest pair is 10.7, its dark one 20.1.
|
|
23
|
+
*/
|
|
24
|
+
export const CHART_DISTINCT_MIN = 10;
|
|
25
|
+
|
|
26
|
+
type Matrix = readonly [
|
|
27
|
+
readonly [number, number, number],
|
|
28
|
+
readonly [number, number, number],
|
|
29
|
+
readonly [number, number, number],
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const MACHADO: Readonly<Record<(typeof COLOUR_VISIONS)[number], Matrix>> = {
|
|
33
|
+
protan: [
|
|
34
|
+
[0.152286, 1.052583, -0.204868],
|
|
35
|
+
[0.114503, 0.786281, 0.099216],
|
|
36
|
+
[-0.003882, -0.048116, 1.051998],
|
|
37
|
+
],
|
|
38
|
+
deutan: [
|
|
39
|
+
[0.367322, 0.860646, -0.227968],
|
|
40
|
+
[0.280085, 0.672501, 0.047413],
|
|
41
|
+
[-0.01182, 0.04294, 0.968881],
|
|
42
|
+
],
|
|
43
|
+
tritan: [
|
|
44
|
+
[1.255528, -0.076749, -0.178779],
|
|
45
|
+
[-0.078411, 0.930809, 0.147602],
|
|
46
|
+
[0.004733, 0.691367, 0.3039],
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Linear light back to an sRGB channel — the inverse of contrast.ts's `linearize`. */
|
|
51
|
+
const toChannel = (linear: number): number => {
|
|
52
|
+
const v = inGamut(linear);
|
|
53
|
+
const encoded = v <= 0.0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055;
|
|
54
|
+
return Math.round(encoded * 255);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** `"R G B"` as a reader with `vision` sees it, in the same channel spelling. */
|
|
58
|
+
export function simulateColourVision(channels: string, vision: ColourVision): string {
|
|
59
|
+
if (vision === 'typical') return channels;
|
|
60
|
+
const [r, g, b] = parseChannels(channels).map(linearize) as [number, number, number];
|
|
61
|
+
return MACHADO[vision].map((row) => toChannel(row[0] * r + row[1] * g + row[2] * b)).join(' ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const labF = (t: number): number =>
|
|
65
|
+
t > 216 / 24389 ? Math.cbrt(t) : ((24389 / 27) * t) / 116 + 16 / 116;
|
|
66
|
+
|
|
67
|
+
function lab(channels: string): readonly [number, number, number] {
|
|
68
|
+
const [r, g, b] = parseChannels(channels).map(linearize) as [number, number, number];
|
|
69
|
+
const x = labF((0.4124 * r + 0.3576 * g + 0.1805 * b) / 0.95047);
|
|
70
|
+
const y = labF(0.2126 * r + 0.7152 * g + 0.0722 * b);
|
|
71
|
+
const z = labF((0.0193 * r + 0.1192 * g + 0.9505 * b) / 1.08883);
|
|
72
|
+
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** CIE76 ΔE between two channel strings. 0 is identical; black to white is 100. */
|
|
76
|
+
export function colourDistance(a: string, b: string): number {
|
|
77
|
+
const [l1, a1, b1] = lab(a);
|
|
78
|
+
const [l2, a2, b2] = lab(b);
|
|
79
|
+
return Math.hypot(l1 - l2, a1 - a2, b1 - b2);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ClosestPair {
|
|
83
|
+
/** Indexes into the palette, `a < b`. */
|
|
84
|
+
readonly a: number;
|
|
85
|
+
readonly b: number;
|
|
86
|
+
readonly vision: ColourVision;
|
|
87
|
+
readonly distance: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The two series hardest to tell apart, and for whom. One answer rather than a matrix: a palette
|
|
92
|
+
* is as safe as its closest pair, and the pair is what an author has to move.
|
|
93
|
+
*/
|
|
94
|
+
export function closestChartPair(palette: readonly string[]): ClosestPair {
|
|
95
|
+
let closest: ClosestPair = { a: 0, b: 0, vision: 'typical', distance: Number.POSITIVE_INFINITY };
|
|
96
|
+
for (const vision of ['typical', ...COLOUR_VISIONS] as const) {
|
|
97
|
+
const seen = palette.map((channels) => simulateColourVision(channels, vision));
|
|
98
|
+
for (let a = 0; a < seen.length; a += 1) {
|
|
99
|
+
for (let b = a + 1; b < seen.length; b += 1) {
|
|
100
|
+
const distance = colourDistance(seen[a] as string, seen[b] as string);
|
|
101
|
+
if (distance < closest.distance) closest = { a, b, vision, distance };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return closest;
|
|
106
|
+
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// benchmark (EN 301 549, ADA Title II, the EAA). 4.5:1 for body text (1.4.3), 3:1 for large text
|
|
12
12
|
// and non-text UI (1.4.11).
|
|
13
13
|
|
|
14
|
+
import { CHART_ROLES, type ColorRole } from './color-tokens';
|
|
14
15
|
import { AA_LARGE, AA_TEXT } from './contrast';
|
|
15
|
-
import type { ColorRole } from './tokens';
|
|
16
16
|
|
|
17
17
|
/** What a page, a card and a raised panel are made of. */
|
|
18
18
|
const SURFACES: readonly ColorRole[] = ['bg', 'bg-soft', 'surface', 'surface-raised'];
|
|
@@ -68,4 +68,9 @@ export const CONTRAST_PAIRS: readonly ContrastPair[] = [
|
|
|
68
68
|
...STATUS.flatMap((status) => onSurfaces(status, AA_TEXT, 'status text on a surface')),
|
|
69
69
|
...onSurfaces('accent', AA_LARGE, 'the focus ring against a surface'),
|
|
70
70
|
...onSurfaces('line', VISIBLE_EDGE, 'a border or divider against a surface'),
|
|
71
|
+
// A series mark is non-text UI (1.4.11), and a dashboard puts the same series on the page, in a
|
|
72
|
+
// card and in a raised popover — so every surface, not the one a chart usually sits on.
|
|
73
|
+
...CHART_ROLES.flatMap((role) =>
|
|
74
|
+
onSurfaces(role, AA_LARGE, `the ${role} series mark — a bar, line or slice — on a surface`),
|
|
75
|
+
),
|
|
71
76
|
];
|
package/src/tokens/contrast.ts
CHANGED
|
@@ -31,7 +31,7 @@ export function parseChannels(value: string): Channels {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// sRGB → linear, then the ITU-R BT.709 luma weights WCAG specifies.
|
|
34
|
-
function linearize(channel: number): number {
|
|
34
|
+
export function linearize(channel: number): number {
|
|
35
35
|
const v = channel / 255;
|
|
36
36
|
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
37
37
|
}
|
package/src/tokens/theme.scss
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
@use 'shadow';
|
|
9
9
|
@use 'space';
|
|
10
10
|
@use 'stroke';
|
|
11
|
+
@use 'touch';
|
|
11
12
|
@use 'typography';
|
|
12
13
|
@use 'z';
|
|
13
14
|
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
@include space.emit;
|
|
17
18
|
@include radius.emit;
|
|
18
19
|
@include stroke.emit;
|
|
20
|
+
@include touch.emit;
|
|
19
21
|
@include typography.emit;
|
|
20
22
|
@include motion.emit;
|
|
21
23
|
@include z.emit;
|
package/src/tokens/tokens.ts
CHANGED
|
@@ -5,93 +5,10 @@
|
|
|
5
5
|
// `tokens.test.ts` (run by `x verify`) fails if the two ever disagree.
|
|
6
6
|
|
|
7
7
|
import { unknownTokenError } from '../errors';
|
|
8
|
+
import { COLOR_ROLES, type ColorRole, colorTokens, type Theme } from './color-tokens';
|
|
8
9
|
|
|
9
|
-
export type Theme
|
|
10
|
-
|
|
11
|
-
export const COLOR_ROLES = [
|
|
12
|
-
'bg',
|
|
13
|
-
'bg-soft',
|
|
14
|
-
'surface',
|
|
15
|
-
'surface-raised',
|
|
16
|
-
'fg',
|
|
17
|
-
'fg-strong',
|
|
18
|
-
'fg-muted',
|
|
19
|
-
'line',
|
|
20
|
-
'scrim',
|
|
21
|
-
'accent',
|
|
22
|
-
'accent-strong',
|
|
23
|
-
'accent-fg',
|
|
24
|
-
'success',
|
|
25
|
-
'success-soft',
|
|
26
|
-
'success-fg',
|
|
27
|
-
'warning',
|
|
28
|
-
'warning-soft',
|
|
29
|
-
'warning-fg',
|
|
30
|
-
'danger',
|
|
31
|
-
'danger-soft',
|
|
32
|
-
'danger-fg',
|
|
33
|
-
'info',
|
|
34
|
-
'info-soft',
|
|
35
|
-
'info-fg',
|
|
36
|
-
] as const;
|
|
37
|
-
|
|
38
|
-
export type ColorRole = (typeof COLOR_ROLES)[number];
|
|
39
|
-
|
|
40
|
-
/** Space-separated RGB channels, mirroring `_colors.scss`. */
|
|
41
|
-
export const colorTokens: Readonly<Record<Theme, Readonly<Record<ColorRole, string>>>> = {
|
|
42
|
-
light: {
|
|
43
|
-
bg: '253 246 240',
|
|
44
|
-
'bg-soft': '245 237 230',
|
|
45
|
-
surface: '250 245 241',
|
|
46
|
-
'surface-raised': '255 255 255',
|
|
47
|
-
fg: '38 34 31',
|
|
48
|
-
'fg-strong': '17 15 13',
|
|
49
|
-
'fg-muted': '110 102 94',
|
|
50
|
-
line: '208 198 188',
|
|
51
|
-
scrim: '17 15 13',
|
|
52
|
-
accent: '31 110 178',
|
|
53
|
-
'accent-strong': '21 92 152',
|
|
54
|
-
'accent-fg': '255 255 255',
|
|
55
|
-
success: '21 123 80',
|
|
56
|
-
'success-soft': '222 244 232',
|
|
57
|
-
'success-fg': '255 255 255',
|
|
58
|
-
warning: '155 93 7',
|
|
59
|
-
'warning-soft': '253 240 213',
|
|
60
|
-
'warning-fg': '255 255 255',
|
|
61
|
-
danger: '190 42 42',
|
|
62
|
-
'danger-soft': '253 227 227',
|
|
63
|
-
'danger-fg': '255 255 255',
|
|
64
|
-
info: '31 110 178',
|
|
65
|
-
'info-soft': '224 239 252',
|
|
66
|
-
'info-fg': '255 255 255',
|
|
67
|
-
},
|
|
68
|
-
dark: {
|
|
69
|
-
bg: '18 18 20',
|
|
70
|
-
'bg-soft': '28 28 32',
|
|
71
|
-
surface: '34 34 39',
|
|
72
|
-
'surface-raised': '44 44 50',
|
|
73
|
-
fg: '228 226 222',
|
|
74
|
-
'fg-strong': '248 247 245',
|
|
75
|
-
'fg-muted': '155 151 145',
|
|
76
|
-
line: '72 72 80',
|
|
77
|
-
scrim: '0 0 0',
|
|
78
|
-
accent: '96 170 240',
|
|
79
|
-
'accent-strong': '130 190 248',
|
|
80
|
-
'accent-fg': '16 20 26',
|
|
81
|
-
success: '74 190 130',
|
|
82
|
-
'success-soft': '22 46 34',
|
|
83
|
-
'success-fg': '12 26 18',
|
|
84
|
-
warning: '226 170 66',
|
|
85
|
-
'warning-soft': '52 42 20',
|
|
86
|
-
'warning-fg': '28 20 6',
|
|
87
|
-
danger: '240 110 110',
|
|
88
|
-
'danger-soft': '56 26 26',
|
|
89
|
-
'danger-fg': '30 12 12',
|
|
90
|
-
info: '96 170 240',
|
|
91
|
-
'info-soft': '22 38 56',
|
|
92
|
-
'info-fg': '12 20 30',
|
|
93
|
-
},
|
|
94
|
-
} as const;
|
|
10
|
+
export type { ChartRole, ColorRole, Theme } from './color-tokens';
|
|
11
|
+
export { CHART_ROLES, COLOR_ROLES, colorTokens } from './color-tokens';
|
|
95
12
|
|
|
96
13
|
export const spaceTokens = {
|
|
97
14
|
'0': '0',
|
|
@@ -153,13 +70,39 @@ export const easingTokens = {
|
|
|
153
70
|
spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
154
71
|
} as const;
|
|
155
72
|
|
|
156
|
-
|
|
73
|
+
/**
|
|
74
|
+
* The elevation rungs, then the two accent-drawn families: `glow-*` (a halo, no offset — a focused
|
|
75
|
+
* or live edge) and `tinted-*` (a lift whose shadow carries the accent hue — coloured elevation).
|
|
76
|
+
*/
|
|
77
|
+
export const SHADOW_NAMES = [
|
|
78
|
+
'xs',
|
|
79
|
+
'sm',
|
|
80
|
+
'md',
|
|
81
|
+
'lg',
|
|
82
|
+
'xl',
|
|
83
|
+
'glow-sm',
|
|
84
|
+
'glow-md',
|
|
85
|
+
'glow-lg',
|
|
86
|
+
'tinted-sm',
|
|
87
|
+
'tinted-md',
|
|
88
|
+
'tinted-lg',
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
export type ShadowName = (typeof SHADOW_NAMES)[number];
|
|
92
|
+
|
|
93
|
+
export const shadowTokens: Readonly<Record<Theme, Readonly<Record<ShadowName, string>>>> = {
|
|
157
94
|
light: {
|
|
158
95
|
xs: '0 1px 2px rgb(17 15 13 / 0.06)',
|
|
159
96
|
sm: '0 2px 6px rgb(17 15 13 / 0.08)',
|
|
160
97
|
md: '0 4px 14px rgb(17 15 13 / 0.1)',
|
|
161
98
|
lg: '0 12px 32px rgb(17 15 13 / 0.14)',
|
|
162
99
|
xl: '0 24px 56px rgb(17 15 13 / 0.18)',
|
|
100
|
+
'glow-sm': '0 0 6px rgb(var(--color-accent) / 0.28)',
|
|
101
|
+
'glow-md': '0 0 14px rgb(var(--color-accent) / 0.26)',
|
|
102
|
+
'glow-lg': '0 0 28px rgb(var(--color-accent) / 0.24)',
|
|
103
|
+
'tinted-sm': '0 2px 8px rgb(var(--color-accent) / 0.14)',
|
|
104
|
+
'tinted-md': '0 6px 18px rgb(var(--color-accent) / 0.16)',
|
|
105
|
+
'tinted-lg': '0 14px 36px rgb(var(--color-accent) / 0.18)',
|
|
163
106
|
},
|
|
164
107
|
dark: {
|
|
165
108
|
xs: '0 1px 2px rgb(0 0 0 / 0.4)',
|
|
@@ -167,9 +110,20 @@ export const shadowTokens: Readonly<Record<Theme, Readonly<Record<string, string
|
|
|
167
110
|
md: '0 6px 20px rgb(0 0 0 / 0.56)',
|
|
168
111
|
lg: '0 16px 40px rgb(0 0 0 / 0.64)',
|
|
169
112
|
xl: '0 28px 64px rgb(0 0 0 / 0.72)',
|
|
113
|
+
'glow-sm': '0 0 6px rgb(var(--color-accent) / 0.45)',
|
|
114
|
+
'glow-md': '0 0 14px rgb(var(--color-accent) / 0.4)',
|
|
115
|
+
'glow-lg': '0 0 28px rgb(var(--color-accent) / 0.35)',
|
|
116
|
+
'tinted-sm': '0 2px 8px rgb(var(--color-accent) / 0.24)',
|
|
117
|
+
'tinted-md': '0 6px 18px rgb(var(--color-accent) / 0.28)',
|
|
118
|
+
'tinted-lg': '0 14px 36px rgb(var(--color-accent) / 0.32)',
|
|
170
119
|
},
|
|
171
120
|
} as const;
|
|
172
121
|
|
|
122
|
+
/** WCAG 2.5.5's 44 CSS px, as rem so a reader's larger text grows the target with it. */
|
|
123
|
+
export const touchTokens = {
|
|
124
|
+
target: '2.75rem',
|
|
125
|
+
} as const;
|
|
126
|
+
|
|
173
127
|
export const breakpointTokens = {
|
|
174
128
|
sm: '480px',
|
|
175
129
|
md: '768px',
|