@delacour/react-native-ui 0.1.0-alpha.20260925053522
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/README.md +113 -0
- package/package.json +125 -0
- package/src/components/accordion/AGENTS.md +193 -0
- package/src/components/accordion/accordion-content.tsx +125 -0
- package/src/components/accordion/accordion-description.tsx +17 -0
- package/src/components/accordion/accordion-indicator.tsx +104 -0
- package/src/components/accordion/accordion-item.tsx +78 -0
- package/src/components/accordion/accordion-title.tsx +18 -0
- package/src/components/accordion/accordion-trigger.tsx +148 -0
- package/src/components/accordion/accordion.context.tsx +153 -0
- package/src/components/accordion/accordion.tsx +253 -0
- package/src/components/accordion/accordion.types.ts +11 -0
- package/src/components/accordion/accordion.variants.test.ts +434 -0
- package/src/components/accordion/accordion.variants.ts +358 -0
- package/src/components/accordion/index.ts +45 -0
- package/src/components/badge/AGENTS.md +83 -0
- package/src/components/badge/badge-close-button.tsx +48 -0
- package/src/components/badge/badge-end-content.tsx +15 -0
- package/src/components/badge/badge-label.tsx +24 -0
- package/src/components/badge/badge-start-content.tsx +16 -0
- package/src/components/badge/badge.context.tsx +64 -0
- package/src/components/badge/badge.tsx +192 -0
- package/src/components/badge/badge.types.ts +10 -0
- package/src/components/badge/badge.variants.test.ts +269 -0
- package/src/components/badge/badge.variants.ts +238 -0
- package/src/components/badge/index.ts +17 -0
- package/src/components/bottom-sheet/AGENTS.md +242 -0
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +246 -0
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +150 -0
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +105 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +293 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +220 -0
- package/src/components/bottom-sheet/index.ts +40 -0
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
- package/src/components/button/AGENTS.md +217 -0
- package/src/components/button/button-end-content.tsx +15 -0
- package/src/components/button/button-group-separator.tsx +37 -0
- package/src/components/button/button-group-text.tsx +101 -0
- package/src/components/button/button-group.tsx +158 -0
- package/src/components/button/button-label.tsx +24 -0
- package/src/components/button/button-start-content.tsx +15 -0
- package/src/components/button/button.context.tsx +187 -0
- package/src/components/button/button.tsx +273 -0
- package/src/components/button/button.types.ts +10 -0
- package/src/components/button/button.variants.test.ts +811 -0
- package/src/components/button/button.variants.ts +480 -0
- package/src/components/button/index.ts +50 -0
- package/src/components/chart/AGENTS.md +210 -0
- package/src/components/chart/chart-area.tsx +93 -0
- package/src/components/chart/chart-bar.tsx +103 -0
- package/src/components/chart/chart-bars.tsx +75 -0
- package/src/components/chart/chart-candlestick.tsx +60 -0
- package/src/components/chart/chart-grid.tsx +33 -0
- package/src/components/chart/chart-legend.tsx +41 -0
- package/src/components/chart/chart-line.tsx +50 -0
- package/src/components/chart/chart-scatter.tsx +35 -0
- package/src/components/chart/chart-tooltip-dot.tsx +70 -0
- package/src/components/chart/chart-tooltip-x.tsx +121 -0
- package/src/components/chart/chart-tooltip-y.tsx +55 -0
- package/src/components/chart/chart-tooltip.tsx +133 -0
- package/src/components/chart/chart-x-axis.tsx +27 -0
- package/src/components/chart/chart-y-axis.tsx +21 -0
- package/src/components/chart/chart.context.tsx +127 -0
- package/src/components/chart/chart.tsx +448 -0
- package/src/components/chart/chart.types.ts +108 -0
- package/src/components/chart/chart.variants.test.ts +893 -0
- package/src/components/chart/chart.variants.ts +726 -0
- package/src/components/chart/index.ts +103 -0
- package/src/components/chart/pie-chart-center.tsx +40 -0
- package/src/components/chart/pie-chart-label.tsx +56 -0
- package/src/components/chart/pie-chart-slice.tsx +45 -0
- package/src/components/chart/pie-chart-tooltip.tsx +85 -0
- package/src/components/chart/pie-chart.context.tsx +77 -0
- package/src/components/chart/pie-chart.tsx +247 -0
- package/src/components/chart/use-chart-font.ts +25 -0
- package/src/components/chart/use-chart-palette.ts +38 -0
- package/src/components/checkbox/AGENTS.md +185 -0
- package/src/components/checkbox/checkbox-box.tsx +152 -0
- package/src/components/checkbox/checkbox-group.tsx +107 -0
- package/src/components/checkbox/checkbox-label.tsx +34 -0
- package/src/components/checkbox/checkbox.context.tsx +136 -0
- package/src/components/checkbox/checkbox.tsx +258 -0
- package/src/components/checkbox/checkbox.types.ts +14 -0
- package/src/components/checkbox/checkbox.variants.test.ts +634 -0
- package/src/components/checkbox/checkbox.variants.ts +484 -0
- package/src/components/checkbox/index.ts +42 -0
- package/src/components/field/AGENTS.md +112 -0
- package/src/components/field/field-content.tsx +24 -0
- package/src/components/field/field-description.tsx +29 -0
- package/src/components/field/field-error.tsx +61 -0
- package/src/components/field/field-group.tsx +22 -0
- package/src/components/field/field-label.tsx +47 -0
- package/src/components/field/field-legend.tsx +34 -0
- package/src/components/field/field-separator.tsx +81 -0
- package/src/components/field/field-set.tsx +22 -0
- package/src/components/field/field.context.tsx +95 -0
- package/src/components/field/field.tsx +157 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +240 -0
- package/src/components/field/field.variants.ts +156 -0
- package/src/components/field/index.ts +22 -0
- package/src/components/icon/AGENTS.md +104 -0
- package/src/components/icon/icon.context.tsx +29 -0
- package/src/components/icon/icon.tsx +92 -0
- package/src/components/icon/icon.variants.test.ts +111 -0
- package/src/components/icon/icon.variants.ts +79 -0
- package/src/components/icon/index.ts +11 -0
- package/src/components/input/AGENTS.md +126 -0
- package/src/components/input/index.ts +27 -0
- package/src/components/input/input-group-decorator.tsx +84 -0
- package/src/components/input/input-group-prefix.tsx +16 -0
- package/src/components/input/input-group-suffix.tsx +15 -0
- package/src/components/input/input-group.tsx +140 -0
- package/src/components/input/input.context.tsx +87 -0
- package/src/components/input/input.tsx +184 -0
- package/src/components/input/input.types.ts +10 -0
- package/src/components/input/input.variants.test.ts +369 -0
- package/src/components/input/input.variants.ts +329 -0
- package/src/components/list-group/AGENTS.md +67 -0
- package/src/components/list-group/index.ts +20 -0
- package/src/components/list-group/list-group-item-content.tsx +10 -0
- package/src/components/list-group/list-group-item-description.tsx +12 -0
- package/src/components/list-group/list-group-item-prefix.tsx +30 -0
- package/src/components/list-group/list-group-item-suffix.tsx +55 -0
- package/src/components/list-group/list-group-item-title.tsx +17 -0
- package/src/components/list-group/list-group-item.tsx +87 -0
- package/src/components/list-group/list-group.context.tsx +65 -0
- package/src/components/list-group/list-group.tsx +116 -0
- package/src/components/list-group/list-group.types.ts +14 -0
- package/src/components/list-group/list-group.variants.test.ts +223 -0
- package/src/components/list-group/list-group.variants.ts +107 -0
- package/src/components/pressable/AGENTS.md +74 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +261 -0
- package/src/components/pressable/pressable.variants.test.ts +128 -0
- package/src/components/pressable/pressable.variants.ts +80 -0
- package/src/components/provider/AGENTS.md +90 -0
- package/src/components/provider/index.ts +1 -0
- package/src/components/provider/provider.tsx +105 -0
- package/src/components/radio/AGENTS.md +245 -0
- package/src/components/radio/index.ts +35 -0
- package/src/components/radio/radio-group.tsx +126 -0
- package/src/components/radio/radio-indicator.tsx +91 -0
- package/src/components/radio/radio-label.tsx +39 -0
- package/src/components/radio/radio.context.tsx +132 -0
- package/src/components/radio/radio.tsx +215 -0
- package/src/components/radio/radio.variants.test.ts +580 -0
- package/src/components/radio/radio.variants.ts +271 -0
- package/src/components/screen/AGENTS.md +287 -0
- package/src/components/screen/index.ts +85 -0
- package/src/components/screen/screen-chat-list.tsx +485 -0
- package/src/components/screen/screen-content.tsx +69 -0
- package/src/components/screen/screen-debug.ts +43 -0
- package/src/components/screen/screen-error.tsx +61 -0
- package/src/components/screen/screen-flat-list.tsx +88 -0
- package/src/components/screen/screen-footer-background.tsx +60 -0
- package/src/components/screen/screen-footer.tsx +170 -0
- package/src/components/screen/screen-header.tsx +36 -0
- package/src/components/screen/screen-legend-list.tsx +105 -0
- package/src/components/screen/screen-list-component.tsx +23 -0
- package/src/components/screen/screen-loading.tsx +50 -0
- package/src/components/screen/screen-navbar-back-button.tsx +79 -0
- package/src/components/screen/screen-navbar-background.tsx +45 -0
- package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
- package/src/components/screen/screen-navbar-title.tsx +31 -0
- package/src/components/screen/screen-navbar.tsx +154 -0
- package/src/components/screen/screen-root.tsx +38 -0
- package/src/components/screen/screen-scroll-area.tsx +116 -0
- package/src/components/screen/screen-scroll-shadow.tsx +186 -0
- package/src/components/screen/screen-section-list.tsx +81 -0
- package/src/components/screen/screen-view.tsx +59 -0
- package/src/components/screen/screen.context.tsx +167 -0
- package/src/components/screen/screen.tsx +114 -0
- package/src/components/screen/screen.types.ts +66 -0
- package/src/components/screen/screen.variants.test.ts +602 -0
- package/src/components/screen/screen.variants.ts +475 -0
- package/src/components/screen/use-screen-scroll-insets.ts +218 -0
- package/src/components/separator/AGENTS.md +30 -0
- package/src/components/separator/index.ts +8 -0
- package/src/components/separator/separator.tsx +85 -0
- package/src/components/slider/AGENTS.md +274 -0
- package/src/components/slider/index.ts +50 -0
- package/src/components/slider/slider-fill.tsx +68 -0
- package/src/components/slider/slider-output.tsx +55 -0
- package/src/components/slider/slider-thumb.tsx +193 -0
- package/src/components/slider/slider-track.tsx +233 -0
- package/src/components/slider/slider.context.tsx +118 -0
- package/src/components/slider/slider.tsx +321 -0
- package/src/components/slider/slider.types.ts +26 -0
- package/src/components/slider/slider.variants.test.ts +856 -0
- package/src/components/slider/slider.variants.ts +661 -0
- package/src/components/spinner/AGENTS.md +73 -0
- package/src/components/spinner/index.ts +17 -0
- package/src/components/spinner/spinner-arc.tsx +90 -0
- package/src/components/spinner/spinner-content.tsx +63 -0
- package/src/components/spinner/spinner.context.tsx +52 -0
- package/src/components/spinner/spinner.tsx +128 -0
- package/src/components/spinner/spinner.variants.test.ts +273 -0
- package/src/components/spinner/spinner.variants.ts +187 -0
- package/src/components/switch/AGENTS.md +213 -0
- package/src/components/switch/index.ts +42 -0
- package/src/components/switch/switch-content.tsx +111 -0
- package/src/components/switch/switch-end-content.tsx +18 -0
- package/src/components/switch/switch-start-content.tsx +20 -0
- package/src/components/switch/switch-thumb.tsx +102 -0
- package/src/components/switch/switch.context.tsx +79 -0
- package/src/components/switch/switch.tsx +423 -0
- package/src/components/switch/switch.types.ts +14 -0
- package/src/components/switch/switch.variants.test.ts +570 -0
- package/src/components/switch/switch.variants.ts +511 -0
- package/src/components/tabs/AGENTS.md +287 -0
- package/src/components/tabs/index.ts +69 -0
- package/src/components/tabs/tabs-content.tsx +65 -0
- package/src/components/tabs/tabs-indicator.tsx +97 -0
- package/src/components/tabs/tabs-label.tsx +62 -0
- package/src/components/tabs/tabs-list.tsx +139 -0
- package/src/components/tabs/tabs-pager.tsx +59 -0
- package/src/components/tabs/tabs-scroll-view.tsx +171 -0
- package/src/components/tabs/tabs-separator.tsx +73 -0
- package/src/components/tabs/tabs-trigger.tsx +210 -0
- package/src/components/tabs/tabs.context.tsx +294 -0
- package/src/components/tabs/tabs.tsx +435 -0
- package/src/components/tabs/tabs.types.ts +13 -0
- package/src/components/tabs/tabs.variants.test.ts +1020 -0
- package/src/components/tabs/tabs.variants.ts +670 -0
- package/src/components/text/AGENTS.md +95 -0
- package/src/components/text/index.ts +25 -0
- package/src/components/text/text.context.tsx +60 -0
- package/src/components/text/text.tsx +251 -0
- package/src/components/text/text.variants.test.ts +422 -0
- package/src/components/text/text.variants.ts +282 -0
- package/src/display-name.test.ts +145 -0
- package/src/docs.test.ts +98 -0
- package/src/expo/navigation-theme.tsx +58 -0
- package/src/hooks/use-controllable-state.ts +45 -0
- package/src/hooks/use-keyboard-state-sync.tsx +147 -0
- package/src/hooks/use-navigation-theme.ts +78 -0
- package/src/hooks/use-theme-color.ts +44 -0
- package/src/icons/central.ts +1 -0
- package/src/lib/cn.test.ts +136 -0
- package/src/lib/cn.ts +29 -0
- package/src/lib/color.test.ts +80 -0
- package/src/lib/color.ts +79 -0
- package/src/lib/compose-refs.test.ts +64 -0
- package/src/lib/compose-refs.ts +37 -0
- package/src/lib/keyboard-animation.test.ts +33 -0
- package/src/lib/keyboard-animation.ts +28 -0
- package/src/lib/merge-props.test.ts +82 -0
- package/src/lib/merge-props.ts +47 -0
- package/src/lib/navigation-theme.test.ts +49 -0
- package/src/lib/navigation-theme.ts +53 -0
- package/src/lib/slot.tsx +45 -0
- package/src/lib/tv.ts +20 -0
- package/src/styles/base.css +2 -0
- package/src/styles/geometry.test.ts +117 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme-tokens.test.ts +252 -0
- package/src/styles/theme.css +447 -0
- package/src/styles/tokens.css +132 -0
- package/src/styles/tokens.test.ts +181 -0
- package/src/styles/tokens.ts +86 -0
- package/src/uniwind-env.d.ts +1 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The reader every suite that checks a token against `theme.css` shares, and
|
|
7
|
+
* the tests that pin the file's own shape.
|
|
8
|
+
*
|
|
9
|
+
* It lives in a `.test.ts` on purpose. Nine suites need to know what the
|
|
10
|
+
* palette declares, and nine hand-rolled regexes drifted from each other and
|
|
11
|
+
* from the file. It also reads from disk, so it could never ship to a React
|
|
12
|
+
* Native app — and the registry builder skips `*.test.ts`, which keeps it out
|
|
13
|
+
* of what `delacour add styles` copies. See `packages/cli/src/registry/classify.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Import it from a sibling suite:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { declaredTokens } from "../../styles/theme-tokens.test";
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const THEME_CSS = readFileSync(join(import.meta.dirname, "theme.css"), "utf-8");
|
|
22
|
+
export const TOKENS_CSS = readFileSync(join(import.meta.dirname, "tokens.css"), "utf-8");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* `--radius` in points.
|
|
26
|
+
*
|
|
27
|
+
* Declared in `rem` because that is what a theme generator emits; React Native
|
|
28
|
+
* has no root font size, and Uniwind resolves one `rem` as sixteen points.
|
|
29
|
+
*/
|
|
30
|
+
export const REM_PX = 16;
|
|
31
|
+
|
|
32
|
+
export const RADIUS_BASE_PX = (() => {
|
|
33
|
+
const value = TOKENS_CSS.match(/--radius:\s*([\d.]+)(rem|px);/);
|
|
34
|
+
if (!value) throw new Error("tokens.css declares no --radius");
|
|
35
|
+
|
|
36
|
+
return Number(value[1]) * (value[2] === "rem" ? REM_PX : 1);
|
|
37
|
+
})();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What one step of the generic corner scale multiplies `--radius` by.
|
|
41
|
+
*
|
|
42
|
+
* The scale is `@theme inline`, so no `--radius-*` variable survives to runtime
|
|
43
|
+
* — only `--radius` does. Anything that has to compute a corner in JavaScript
|
|
44
|
+
* reads the base and applies its own multiplier, and pins that multiplier
|
|
45
|
+
* against this. `Checkbox`'s concentric fill is the one case.
|
|
46
|
+
*/
|
|
47
|
+
export function radiusMultiplier(step: string): number {
|
|
48
|
+
const match = TOKENS_CSS.match(new RegExp(`--radius-${step}:\\s*([^;]+);`));
|
|
49
|
+
if (!match?.[1]) throw new Error(`tokens.css declares no --radius-${step}`);
|
|
50
|
+
|
|
51
|
+
const value = match[1].trim();
|
|
52
|
+
if (value === "var(--radius)") return 1;
|
|
53
|
+
|
|
54
|
+
const multiplier = value.match(/calc\(var\(--radius\)\s*\*\s*([\d.]+)\)/)?.[1];
|
|
55
|
+
if (multiplier === undefined) throw new Error(`--radius-${step} is not derived from --radius: ${value}`);
|
|
56
|
+
|
|
57
|
+
return Number(multiplier);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A step of the generic corner scale in points, at the default `--radius`. */
|
|
61
|
+
export function radiusPx(step: string): number {
|
|
62
|
+
return RADIUS_BASE_PX * radiusMultiplier(step);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ThemeVariant = "dark" | "light";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The body of one `@variant` block, brace-matched.
|
|
69
|
+
*
|
|
70
|
+
* Counting braces rather than splitting on the first `}`: the block holds
|
|
71
|
+
* comments and functional values, and a split would silently truncate the
|
|
72
|
+
* palette to whatever came before the first one — which reads as "the theme
|
|
73
|
+
* declares nothing", and every assertion built on it would pass vacuously.
|
|
74
|
+
*/
|
|
75
|
+
export function themeBlock(variant: ThemeVariant): string {
|
|
76
|
+
const marker = `@variant ${variant} {`;
|
|
77
|
+
const start = THEME_CSS.indexOf(marker);
|
|
78
|
+
if (start === -1) throw new Error(`theme.css declares no @variant ${variant} block`);
|
|
79
|
+
|
|
80
|
+
let depth = 1;
|
|
81
|
+
let index = start + marker.length;
|
|
82
|
+
|
|
83
|
+
while (index < THEME_CSS.length && depth > 0) {
|
|
84
|
+
const character = THEME_CSS[index];
|
|
85
|
+
if (character === "{") depth += 1;
|
|
86
|
+
if (character === "}") depth -= 1;
|
|
87
|
+
index += 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (depth !== 0) throw new Error(`theme.css never closes its @variant ${variant} block`);
|
|
91
|
+
|
|
92
|
+
return THEME_CSS.slice(start + marker.length, index - 1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Every token name one variant declares, without its `--` prefix.
|
|
97
|
+
*
|
|
98
|
+
* These are the RAW shadcn names — `destructive`, not `color-destructive`.
|
|
99
|
+
* `theme.css` maps raw onto the `--color-*` namespace through `@theme inline`,
|
|
100
|
+
* and a token missing from a variant resolves to nothing at runtime: the colour
|
|
101
|
+
* silently falls back, in one theme only.
|
|
102
|
+
*/
|
|
103
|
+
export function declaredTokens(variant: ThemeVariant): Set<string> {
|
|
104
|
+
const names = new Set<string>();
|
|
105
|
+
|
|
106
|
+
for (const [, name] of themeBlock(variant).matchAll(/^\s*--([\w-]+):/gm)) {
|
|
107
|
+
names.add(name);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return names;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The value one variant gives a token, or `undefined` if it declares none. */
|
|
114
|
+
export function tokenValue(variant: ThemeVariant, token: string): string | undefined {
|
|
115
|
+
return themeBlock(variant)
|
|
116
|
+
.match(new RegExp(`--${token}:\\s*([^;]+);`))?.[1]
|
|
117
|
+
?.trim();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* How many of the two variants declare a token — 0, 1 or 2.
|
|
122
|
+
*
|
|
123
|
+
* Anything but 2 is a bug a component cannot see: a token declared once
|
|
124
|
+
* resolves in one theme and silently falls back in the other.
|
|
125
|
+
*/
|
|
126
|
+
export function declarationCount(token: string): number {
|
|
127
|
+
return (["light", "dark"] as const).filter((variant) => tokenValue(variant, token) !== undefined).length;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Every name the `@theme inline` block aliases, with its `--` prefix. */
|
|
131
|
+
export function aliasedTokens(): Set<string> {
|
|
132
|
+
const marker = "@theme inline {";
|
|
133
|
+
const block = THEME_CSS.slice(THEME_CSS.indexOf(marker) + marker.length);
|
|
134
|
+
const names = new Set<string>();
|
|
135
|
+
|
|
136
|
+
for (const [, name] of block.matchAll(/^\s*(--[\w-]+):\s*var\(/gm)) {
|
|
137
|
+
names.add(name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return names;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Declared but deliberately not aliased.
|
|
145
|
+
*
|
|
146
|
+
* A theme generator emits a bare `--shadow` alongside the scale, and dropping
|
|
147
|
+
* it would make a paste lossy — but Tailwind's namespace is `--shadow-*`, so
|
|
148
|
+
* there is no utility for it to back.
|
|
149
|
+
*/
|
|
150
|
+
const UNALIASED = new Set(["shadow"]);
|
|
151
|
+
|
|
152
|
+
const LIGHT = declaredTokens("light");
|
|
153
|
+
const DARK = declaredTokens("dark");
|
|
154
|
+
|
|
155
|
+
describe("the theme.css reader", () => {
|
|
156
|
+
// Every assertion below is worth nothing if the parse found no palette.
|
|
157
|
+
test("finds a populated block for both variants", () => {
|
|
158
|
+
expect(LIGHT.size).toBeGreaterThan(30);
|
|
159
|
+
expect(DARK.size).toBeGreaterThan(30);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("brace-matches rather than stopping at the first closing brace", () => {
|
|
163
|
+
expect(LIGHT.has("overlay")).toBe(true);
|
|
164
|
+
expect(DARK.has("overlay")).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("the palette", () => {
|
|
169
|
+
// Uniwind refuses to build otherwise — "All themes must have the same
|
|
170
|
+
// variables" — so this fails here, in a second, rather than in Metro.
|
|
171
|
+
test("declares exactly the same names in both variants", () => {
|
|
172
|
+
expect([...LIGHT].sort()).toEqual([...DARK].sort());
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("gives every token a value in both variants", () => {
|
|
176
|
+
for (const token of LIGHT) {
|
|
177
|
+
expect(tokenValue("light", token)).toBeTruthy();
|
|
178
|
+
expect(tokenValue("dark", token)).toBeTruthy();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("uses shadcn's names, not the ones this package used before", () => {
|
|
183
|
+
for (const shadcn of ["background", "foreground", "primary", "destructive", "border", "input", "ring"]) {
|
|
184
|
+
expect(LIGHT.has(shadcn)).toBe(true);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const renamed of ["danger", "danger-foreground", "danger-soft", "danger-soft-foreground"]) {
|
|
188
|
+
expect(LIGHT.has(renamed)).toBe(false);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("pairs every -foreground with a surface of the same name", () => {
|
|
193
|
+
for (const token of LIGHT) {
|
|
194
|
+
if (!token.endsWith("-foreground")) continue;
|
|
195
|
+
expect(LIGHT.has(token.slice(0, -"-foreground".length))).toBe(true);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("the @theme inline block", () => {
|
|
201
|
+
// `inline` emits no variable of its own — it substitutes the raw value into
|
|
202
|
+
// each utility. A token missing here is declared but unreachable: no `bg-*`
|
|
203
|
+
// exists for it, and nothing anywhere reports that.
|
|
204
|
+
test("aliases every token the variants declare", () => {
|
|
205
|
+
for (const token of LIGHT) {
|
|
206
|
+
if (UNALIASED.has(token)) continue;
|
|
207
|
+
|
|
208
|
+
const aliases = aliasedTokens();
|
|
209
|
+
const reachable = aliases.has(`--color-${token}`) || aliases.has(`--${token}`);
|
|
210
|
+
expect(reachable).toBe(true);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("aliases nothing the variants do not declare", () => {
|
|
215
|
+
// The fonts are the exception: they are split by platform rather than by
|
|
216
|
+
// theme, in their own `:root` block, because no one family name works on
|
|
217
|
+
// both iOS and Android.
|
|
218
|
+
const fonts = new Set(["--font-sans", "--font-serif", "--font-mono"]);
|
|
219
|
+
|
|
220
|
+
for (const alias of aliasedTokens()) {
|
|
221
|
+
if (fonts.has(alias)) continue;
|
|
222
|
+
|
|
223
|
+
const token = alias.replace(/^--(color-)?/, "");
|
|
224
|
+
expect(LIGHT.has(token)).toBe(true);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("the corner scale", () => {
|
|
230
|
+
const STEPS = ["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"] as const;
|
|
231
|
+
|
|
232
|
+
test("derives every step from --radius", () => {
|
|
233
|
+
for (const step of STEPS) expect(radiusMultiplier(step)).toBeGreaterThan(0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("ascends, so a larger name is never a tighter corner", () => {
|
|
237
|
+
const multipliers = STEPS.map(radiusMultiplier);
|
|
238
|
+
expect([...multipliers].sort((a, b) => a - b)).toEqual(multipliers);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("puts lg exactly on --radius, which is what a pasted theme sets", () => {
|
|
242
|
+
expect(radiusMultiplier("lg")).toBe(1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// `--radius-button-*` is a shape, not a step: half the button's own height,
|
|
246
|
+
// so the button stays a capsule however a consumer retunes `--radius`.
|
|
247
|
+
test("leaves the button's corner out of it", () => {
|
|
248
|
+
for (const step of ["button-sm", "button-md", "button-lg"]) {
|
|
249
|
+
expect(() => radiusMultiplier(step)).toThrow();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* THE ONE FILE THAT IS YOURS.
|
|
3
|
+
*
|
|
4
|
+
* Everything else in this folder is plumbing: `base.css` pulls in the engines,
|
|
5
|
+
* `tokens.css` names the sizes the utilities are minted from, `index.css`
|
|
6
|
+
* stitches them together. This file is the theme, and it is meant to be
|
|
7
|
+
* replaced whole:
|
|
8
|
+
*
|
|
9
|
+
* - Build one at https://ui.delacour.co.nz/theme and paste the `theme.css`
|
|
10
|
+
* tab over this file. Nothing else to run.
|
|
11
|
+
* - Have a shadcn or tweakcn `globals.css`? Paste it over this file and run
|
|
12
|
+
* `delacour theme`. It rewrites the file in place — see below for why a
|
|
13
|
+
* paste alone is not enough.
|
|
14
|
+
*
|
|
15
|
+
* The palette is shadcn's, in shadcn's two-layer shape. Layer one is a raw
|
|
16
|
+
* semantic name — `--primary`, `--destructive` — declared once per theme.
|
|
17
|
+
* Layer two is the `@theme inline` block at the bottom, which maps each raw
|
|
18
|
+
* name onto the `--color-*` Tailwind reads to mint `bg-primary`,
|
|
19
|
+
* `text-primary`, `border-primary`.
|
|
20
|
+
*
|
|
21
|
+
* Two layers rather than one so a web app's theme can be moved here almost
|
|
22
|
+
* unchanged: shadcn, tweakcn and every theme generator built on them emit
|
|
23
|
+
* exactly these raw names. The one thing that cannot survive a copy/paste is
|
|
24
|
+
* the wrapper: Uniwind reads a theme only from `@variant light` /
|
|
25
|
+
* `@variant dark`, and treats a literal `.dark { … }` as a utility class
|
|
26
|
+
* named `dark` that silently does nothing. `delacour theme` rewrites the
|
|
27
|
+
* wrapper and fills in the tokens shadcn has no name for.
|
|
28
|
+
*
|
|
29
|
+
* `@theme inline` emits NO `--color-*` variable of its own; it substitutes the
|
|
30
|
+
* raw `var(--primary)` into each utility. That is why `useThemeColor` looks up
|
|
31
|
+
* the raw name — see `hooks/use-theme-color.ts`.
|
|
32
|
+
*/
|
|
33
|
+
@layer theme {
|
|
34
|
+
:root {
|
|
35
|
+
@variant light {
|
|
36
|
+
/*
|
|
37
|
+
* The page sits BELOW the card, in both themes.
|
|
38
|
+
*
|
|
39
|
+
* shadcn ships `background` and `card` as the same white, because on
|
|
40
|
+
* the web a card is separated by a shadow. Nothing here casts one, so
|
|
41
|
+
* a `ListGroup` on a page was white on white and read as a hairline
|
|
42
|
+
* border around nothing — while the same component in dark, where the
|
|
43
|
+
* ramp does step, read as a raised surface. One component, two
|
|
44
|
+
* different ideas of what a card is.
|
|
45
|
+
*
|
|
46
|
+
* This is the near-white step the ramp already carries for `sidebar`,
|
|
47
|
+
* so it invents no colour and collides with nothing: `secondary` and
|
|
48
|
+
* `muted` sit lower at 0.97, and `card` keeps the white above it.
|
|
49
|
+
* Light elevation is deliberately a fraction of dark's — on a light
|
|
50
|
+
* ground a surface lifts by a percent or two, where in dark it lifts
|
|
51
|
+
* by seven.
|
|
52
|
+
*/
|
|
53
|
+
--background: oklch(0.985 0 0);
|
|
54
|
+
--foreground: oklch(0.145 0 0);
|
|
55
|
+
--card: oklch(1 0 0);
|
|
56
|
+
--card-foreground: oklch(0.145 0 0);
|
|
57
|
+
--popover: oklch(1 0 0);
|
|
58
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
59
|
+
|
|
60
|
+
--primary: oklch(0.205 0 0);
|
|
61
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
62
|
+
--secondary: oklch(0.97 0 0);
|
|
63
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
64
|
+
--muted: oklch(0.97 0 0);
|
|
65
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
66
|
+
--accent: oklch(0.97 0 0);
|
|
67
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
68
|
+
|
|
69
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
70
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
71
|
+
|
|
72
|
+
--border: oklch(0.922 0 0);
|
|
73
|
+
--input: oklch(0.922 0 0);
|
|
74
|
+
--ring: oklch(0.708 0 0);
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* The neutral ramp shadcn ships today, in both themes: five greys, not
|
|
78
|
+
* five hues. It is what a fresh `shadcn init` writes and what the
|
|
79
|
+
* default preset on /theme resolves to — `emit.test.ts` holds the
|
|
80
|
+
* two equal. An accent recolours all five.
|
|
81
|
+
*/
|
|
82
|
+
--chart-1: oklch(0.87 0 0);
|
|
83
|
+
--chart-2: oklch(0.556 0 0);
|
|
84
|
+
--chart-3: oklch(0.439 0 0);
|
|
85
|
+
--chart-4: oklch(0.371 0 0);
|
|
86
|
+
--chart-5: oklch(0.269 0 0);
|
|
87
|
+
|
|
88
|
+
--sidebar: oklch(0.985 0 0);
|
|
89
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
90
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
91
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
92
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
93
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
94
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
95
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
* A surface that sits ABOVE `muted`, in both themes.
|
|
99
|
+
*
|
|
100
|
+
* In light that is now exactly what `card` is, so this follows it —
|
|
101
|
+
* it used to follow `background`, which was the same white, and would
|
|
102
|
+
* now sink below the card and land a hair above `muted`. `Tabs`'
|
|
103
|
+
* selected capsule is what this exists for, and it has to read as
|
|
104
|
+
* raised out of a muted track.
|
|
105
|
+
*
|
|
106
|
+
* `card` still cannot do the job in dark, where it is *darker* than
|
|
107
|
+
* `muted` and nothing else in the ramp is lighter — hence the mix
|
|
108
|
+
* there. Derived rather than literal in both, so a pasted palette
|
|
109
|
+
* drags it along.
|
|
110
|
+
*/
|
|
111
|
+
--elevated: var(--card);
|
|
112
|
+
--elevated-foreground: var(--foreground);
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* A fill quieter than `secondary`. shadcn has no counterpart —
|
|
116
|
+
* its `secondary`, `muted` and `accent` are one colour — so this is
|
|
117
|
+
* derived as half a step further from the page than `secondary`,
|
|
118
|
+
* which quiets it in light and in dark alike.
|
|
119
|
+
*/
|
|
120
|
+
--tertiary: color-mix(in oklch, var(--secondary) 50%, var(--background));
|
|
121
|
+
--tertiary-foreground: color-mix(in oklch, var(--foreground) 50%, var(--muted-foreground));
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* The three state colours shadcn does not define, and the tinted
|
|
125
|
+
* `-soft` pair every state colour carries.
|
|
126
|
+
*
|
|
127
|
+
* A `-soft` surface is the state colour laid over the page, and its
|
|
128
|
+
* foreground is that colour darkened toward the text — so all eight
|
|
129
|
+
* follow a pasted `--background` and `--foreground` without being
|
|
130
|
+
* restated. The dark variant inverts the second rule: over a dark
|
|
131
|
+
* tint the state colour already reads, and mixing it toward a
|
|
132
|
+
* near-white foreground would only wash it out.
|
|
133
|
+
*/
|
|
134
|
+
--success: oklch(0.696 0.17 162.48);
|
|
135
|
+
--success-foreground: oklch(0.985 0 0);
|
|
136
|
+
--warning: oklch(0.769 0.188 70.08);
|
|
137
|
+
--warning-foreground: oklch(0.216 0.006 56.043);
|
|
138
|
+
--info: oklch(0.623 0.188 259.81);
|
|
139
|
+
--info-foreground: oklch(0.985 0 0);
|
|
140
|
+
|
|
141
|
+
--destructive-soft: color-mix(in oklch, var(--destructive) 8%, var(--background));
|
|
142
|
+
--destructive-soft-foreground: color-mix(in oklch, var(--destructive) 70%, var(--foreground));
|
|
143
|
+
--success-soft: color-mix(in oklch, var(--success) 8%, var(--background));
|
|
144
|
+
--success-soft-foreground: color-mix(in oklch, var(--success) 70%, var(--foreground));
|
|
145
|
+
--warning-soft: color-mix(in oklch, var(--warning) 8%, var(--background));
|
|
146
|
+
--warning-soft-foreground: color-mix(in oklch, var(--warning) 70%, var(--foreground));
|
|
147
|
+
--info-soft: color-mix(in oklch, var(--info) 8%, var(--background));
|
|
148
|
+
--info-soft-foreground: color-mix(in oklch, var(--info) 70%, var(--foreground));
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* The scrim a BottomSheet draws over the app it covers.
|
|
152
|
+
*
|
|
153
|
+
* A surface token with no `-foreground`: nothing is ever drawn on it,
|
|
154
|
+
* and the sheet in front carries its own. It is not `bg-black/50` at a
|
|
155
|
+
* call site because a pure-black scrim over a near-black dark theme is
|
|
156
|
+
* nearly invisible — the two variants below differ on purpose, and the
|
|
157
|
+
* alpha lives with the colour so a caller never has to restate it.
|
|
158
|
+
*
|
|
159
|
+
* Literal black rather than derived: a scrim is black in both themes,
|
|
160
|
+
* and deriving it from `--foreground` would paint a white one in dark.
|
|
161
|
+
*/
|
|
162
|
+
--overlay: oklch(0 0 0 / 45%);
|
|
163
|
+
|
|
164
|
+
/*
|
|
165
|
+
* Carried so a pasted theme survives intact. No component in this
|
|
166
|
+
* package draws a shadow — three tests assert it — so nothing reads
|
|
167
|
+
* these today; a consumer's own `shadow-md` will.
|
|
168
|
+
*/
|
|
169
|
+
--shadow: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
|
|
170
|
+
--shadow-2xs: 0 1px 3px 0 hsl(0 0% 0% / 0.05);
|
|
171
|
+
--shadow-xs: 0 1px 3px 0 hsl(0 0% 0% / 0.05);
|
|
172
|
+
--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
|
|
173
|
+
--shadow-md: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
|
|
174
|
+
--shadow-lg: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
|
|
175
|
+
--shadow-xl: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
|
|
176
|
+
--shadow-2xl: 0 1px 3px 0 hsl(0 0% 0% / 0.25);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@variant dark {
|
|
180
|
+
--background: oklch(0.145 0 0);
|
|
181
|
+
--foreground: oklch(0.985 0 0);
|
|
182
|
+
--card: oklch(0.205 0 0);
|
|
183
|
+
--card-foreground: oklch(0.985 0 0);
|
|
184
|
+
--popover: oklch(0.205 0 0);
|
|
185
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
186
|
+
|
|
187
|
+
--primary: oklch(0.922 0 0);
|
|
188
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
189
|
+
--secondary: oklch(0.269 0 0);
|
|
190
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
191
|
+
--muted: oklch(0.269 0 0);
|
|
192
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
193
|
+
--accent: oklch(0.269 0 0);
|
|
194
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
195
|
+
|
|
196
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
197
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
198
|
+
|
|
199
|
+
--border: oklch(1 0 0 / 10%);
|
|
200
|
+
--input: oklch(1 0 0 / 15%);
|
|
201
|
+
--ring: oklch(0.556 0 0);
|
|
202
|
+
|
|
203
|
+
--chart-1: oklch(0.87 0 0);
|
|
204
|
+
--chart-2: oklch(0.556 0 0);
|
|
205
|
+
--chart-3: oklch(0.439 0 0);
|
|
206
|
+
--chart-4: oklch(0.371 0 0);
|
|
207
|
+
--chart-5: oklch(0.269 0 0);
|
|
208
|
+
|
|
209
|
+
--sidebar: oklch(0.205 0 0);
|
|
210
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
211
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
212
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
213
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
214
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
215
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
216
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
217
|
+
|
|
218
|
+
--elevated: color-mix(in oklch, var(--muted) 88%, var(--foreground));
|
|
219
|
+
--elevated-foreground: var(--foreground);
|
|
220
|
+
|
|
221
|
+
--tertiary: color-mix(in oklch, var(--secondary) 50%, var(--background));
|
|
222
|
+
--tertiary-foreground: color-mix(in oklch, var(--foreground) 50%, var(--muted-foreground));
|
|
223
|
+
|
|
224
|
+
--success: oklch(0.696 0.17 162.48);
|
|
225
|
+
--success-foreground: oklch(0.205 0 0);
|
|
226
|
+
--warning: oklch(0.769 0.188 70.08);
|
|
227
|
+
--warning-foreground: oklch(0.216 0.006 56.043);
|
|
228
|
+
--info: oklch(0.623 0.188 259.81);
|
|
229
|
+
--info-foreground: oklch(0.985 0 0);
|
|
230
|
+
|
|
231
|
+
--destructive-soft: color-mix(in oklch, var(--destructive) 14%, var(--background));
|
|
232
|
+
--destructive-soft-foreground: var(--destructive);
|
|
233
|
+
--success-soft: color-mix(in oklch, var(--success) 14%, var(--background));
|
|
234
|
+
--success-soft-foreground: var(--success);
|
|
235
|
+
--warning-soft: color-mix(in oklch, var(--warning) 14%, var(--background));
|
|
236
|
+
--warning-soft-foreground: var(--warning);
|
|
237
|
+
--info-soft: color-mix(in oklch, var(--info) 14%, var(--background));
|
|
238
|
+
--info-soft-foreground: var(--info);
|
|
239
|
+
|
|
240
|
+
--overlay: oklch(0 0 0 / 65%);
|
|
241
|
+
|
|
242
|
+
--shadow: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
|
|
243
|
+
--shadow-2xs: 0 1px 3px 0 hsl(0 0% 0% / 0.05);
|
|
244
|
+
--shadow-xs: 0 1px 3px 0 hsl(0 0% 0% / 0.05);
|
|
245
|
+
--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
|
|
246
|
+
--shadow-md: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
|
|
247
|
+
--shadow-lg: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
|
|
248
|
+
--shadow-xl: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
|
|
249
|
+
--shadow-2xl: 0 1px 3px 0 hsl(0 0% 0% / 0.25);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@layer theme {
|
|
255
|
+
:root {
|
|
256
|
+
/*
|
|
257
|
+
* The three font families, split by platform rather than by theme.
|
|
258
|
+
*
|
|
259
|
+
* Tailwind's defaults are comma-separated web stacks, and React Native's
|
|
260
|
+
* `fontFamily` takes a single family name with no fallback list — the
|
|
261
|
+
* whole string would resolve to nothing and quietly fall back to the
|
|
262
|
+
* system sans, which is the opposite of what `Text.Code` is for. This is
|
|
263
|
+
* also why `delacour theme` takes only the FIRST family out of a pasted
|
|
264
|
+
* stack rather than carrying it whole.
|
|
265
|
+
*
|
|
266
|
+
* No single name works on both platforms: iOS has Menlo and Georgia and
|
|
267
|
+
* not `monospace`, Android the reverse. Every name here ships with the
|
|
268
|
+
* OS, so nothing has to be loaded through `expo-font` and a consuming app
|
|
269
|
+
* needs no plugin.
|
|
270
|
+
*
|
|
271
|
+
* This cannot live in `tokens.css`: platform selection needs `@variant`,
|
|
272
|
+
* and `@theme {}` accepts custom properties only. It also needs the
|
|
273
|
+
* `noDuplicateCustomProperties` exemption `biome.jsonc` scopes to this
|
|
274
|
+
* file — the same one `light` and `dark` already rely on.
|
|
275
|
+
*/
|
|
276
|
+
@variant ios {
|
|
277
|
+
--font-sans: "System";
|
|
278
|
+
--font-serif: "Georgia";
|
|
279
|
+
--font-mono: "Menlo";
|
|
280
|
+
--font-heading: "System";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@variant android {
|
|
284
|
+
--font-sans: "sans-serif";
|
|
285
|
+
--font-serif: "serif";
|
|
286
|
+
--font-mono: "monospace";
|
|
287
|
+
--font-heading: "sans-serif";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@layer theme {
|
|
293
|
+
:root {
|
|
294
|
+
/*
|
|
295
|
+
* The geometry, restated so it can be retuned at runtime.
|
|
296
|
+
*
|
|
297
|
+
* Every number here is already declared in `tokens.css`, and that copy
|
|
298
|
+
* is the one `apps/web` reads — the docs site imports only that file.
|
|
299
|
+
* This one exists because of how Uniwind compiles a variable: one
|
|
300
|
+
* declared in a plain `@theme` block is resolved and INLINED at build
|
|
301
|
+
* time, so `h-button-md` would carry a literal 44 and no runtime
|
|
302
|
+
* override could ever reach it. A variable declared inside a `@variant`
|
|
303
|
+
* compiles to a getter against the live store instead. That is the
|
|
304
|
+
* whole difference between a kit whose density can be changed by an app
|
|
305
|
+
* and one whose density is a build artefact.
|
|
306
|
+
*
|
|
307
|
+
* `native` rather than `light`/`dark` on purpose: it is one of Uniwind's
|
|
308
|
+
* registered platform variants, and the store merges it into the base
|
|
309
|
+
* variables every theme inherits — so one block covers both themes,
|
|
310
|
+
* where a theme variant would have to be written out twice and kept in
|
|
311
|
+
* step. None of these are light/dark dependent; a corner does not change
|
|
312
|
+
* shape at dusk.
|
|
313
|
+
*
|
|
314
|
+
* `styles/geometry.test.ts` asserts this block and `tokens.css` agree,
|
|
315
|
+
* because two hand-maintained copies of one number always drift.
|
|
316
|
+
*/
|
|
317
|
+
@variant native {
|
|
318
|
+
--radius: 0.625rem;
|
|
319
|
+
|
|
320
|
+
--spacing-button-sm: 36px;
|
|
321
|
+
--spacing-button-md: 44px;
|
|
322
|
+
--spacing-button-lg: 52px;
|
|
323
|
+
|
|
324
|
+
--text-button-sm: 14px;
|
|
325
|
+
--text-button-md: 16px;
|
|
326
|
+
--text-button-lg: 18px;
|
|
327
|
+
|
|
328
|
+
--radius-button-sm: 18px;
|
|
329
|
+
--radius-button-md: 22px;
|
|
330
|
+
--radius-button-lg: 26px;
|
|
331
|
+
|
|
332
|
+
--spacing-input-sm: 36px;
|
|
333
|
+
--spacing-input-md: 44px;
|
|
334
|
+
--spacing-input-lg: 52px;
|
|
335
|
+
|
|
336
|
+
--text-input-sm: 14px;
|
|
337
|
+
--text-input-md: 16px;
|
|
338
|
+
--text-input-lg: 18px;
|
|
339
|
+
|
|
340
|
+
--spacing-icon-xs: 14px;
|
|
341
|
+
--spacing-icon-sm: 16px;
|
|
342
|
+
--spacing-icon-md: 18px;
|
|
343
|
+
--spacing-icon-lg: 20px;
|
|
344
|
+
--spacing-icon-xl: 24px;
|
|
345
|
+
--spacing-icon-2xl: 32px;
|
|
346
|
+
|
|
347
|
+
--spacing-navbar-row: 56px;
|
|
348
|
+
--spacing-screen-gutter: 20px;
|
|
349
|
+
|
|
350
|
+
--spacing-chart-sm: 160px;
|
|
351
|
+
--spacing-chart-md: 220px;
|
|
352
|
+
--spacing-chart-lg: 280px;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
* Every raw name above, mapped onto the namespace Tailwind mints utilities
|
|
359
|
+
* from. `inline` is what makes a utility read `var(--primary)` directly rather
|
|
360
|
+
* than a copy taken at `:root`, which is what lets the `@variant` swap above
|
|
361
|
+
* reach it.
|
|
362
|
+
*
|
|
363
|
+
* A name added to a `@variant` block and forgotten here is declared but
|
|
364
|
+
* unreachable — no `bg-*` will exist for it. `styles/theme-tokens.test.ts`
|
|
365
|
+
* fails on the gap.
|
|
366
|
+
*/
|
|
367
|
+
@theme inline {
|
|
368
|
+
--color-background: var(--background);
|
|
369
|
+
--color-foreground: var(--foreground);
|
|
370
|
+
--color-card: var(--card);
|
|
371
|
+
--color-card-foreground: var(--card-foreground);
|
|
372
|
+
--color-popover: var(--popover);
|
|
373
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
374
|
+
|
|
375
|
+
--color-primary: var(--primary);
|
|
376
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
377
|
+
--color-secondary: var(--secondary);
|
|
378
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
379
|
+
--color-muted: var(--muted);
|
|
380
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
381
|
+
--color-accent: var(--accent);
|
|
382
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
383
|
+
|
|
384
|
+
--color-destructive: var(--destructive);
|
|
385
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
386
|
+
|
|
387
|
+
--color-border: var(--border);
|
|
388
|
+
--color-input: var(--input);
|
|
389
|
+
--color-ring: var(--ring);
|
|
390
|
+
|
|
391
|
+
--color-chart-1: var(--chart-1);
|
|
392
|
+
--color-chart-2: var(--chart-2);
|
|
393
|
+
--color-chart-3: var(--chart-3);
|
|
394
|
+
--color-chart-4: var(--chart-4);
|
|
395
|
+
--color-chart-5: var(--chart-5);
|
|
396
|
+
|
|
397
|
+
--color-sidebar: var(--sidebar);
|
|
398
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
399
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
400
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
401
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
402
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
403
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
404
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
405
|
+
|
|
406
|
+
--color-elevated: var(--elevated);
|
|
407
|
+
--color-elevated-foreground: var(--elevated-foreground);
|
|
408
|
+
--color-tertiary: var(--tertiary);
|
|
409
|
+
--color-tertiary-foreground: var(--tertiary-foreground);
|
|
410
|
+
|
|
411
|
+
--color-destructive-soft: var(--destructive-soft);
|
|
412
|
+
--color-destructive-soft-foreground: var(--destructive-soft-foreground);
|
|
413
|
+
--color-success: var(--success);
|
|
414
|
+
--color-success-foreground: var(--success-foreground);
|
|
415
|
+
--color-success-soft: var(--success-soft);
|
|
416
|
+
--color-success-soft-foreground: var(--success-soft-foreground);
|
|
417
|
+
--color-warning: var(--warning);
|
|
418
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
419
|
+
--color-warning-soft: var(--warning-soft);
|
|
420
|
+
--color-warning-soft-foreground: var(--warning-soft-foreground);
|
|
421
|
+
--color-info: var(--info);
|
|
422
|
+
--color-info-foreground: var(--info-foreground);
|
|
423
|
+
--color-info-soft: var(--info-soft);
|
|
424
|
+
--color-info-soft-foreground: var(--info-soft-foreground);
|
|
425
|
+
|
|
426
|
+
--color-overlay: var(--overlay);
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* The fonts are deliberately absent.
|
|
430
|
+
*
|
|
431
|
+
* Tailwind already declares `--font-sans`, `--font-serif` and `--font-mono`
|
|
432
|
+
* in its own theme, so `font-mono` already reads `var(--font-mono)` and the
|
|
433
|
+
* platform block above already overrides it. Aliasing them here the way
|
|
434
|
+
* shadcn does would redefine each one AS ITSELF — Uniwind compiles that to
|
|
435
|
+
* `vars["--font-mono"] => vars["--font-mono"]`, a variable that recurses
|
|
436
|
+
* instead of resolving, and every `font-*` utility silently draws nothing.
|
|
437
|
+
* shadcn needs the alias only because its fonts live outside `@theme`.
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
--shadow-2xs: var(--shadow-2xs);
|
|
441
|
+
--shadow-xs: var(--shadow-xs);
|
|
442
|
+
--shadow-sm: var(--shadow-sm);
|
|
443
|
+
--shadow-md: var(--shadow-md);
|
|
444
|
+
--shadow-lg: var(--shadow-lg);
|
|
445
|
+
--shadow-xl: var(--shadow-xl);
|
|
446
|
+
--shadow-2xl: var(--shadow-2xl);
|
|
447
|
+
}
|