@nationaldesignstudio/react 0.0.15 → 0.0.16
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/package.json +3 -2
- package/src/App.css +0 -0
- package/src/App.tsx +7 -0
- package/src/assets/fonts/PPNeueMontreal-Variable.woff2 +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/atoms/accordion/accordion.stories.tsx +228 -0
- package/src/components/atoms/accordion/accordion.tsx +219 -0
- package/src/components/atoms/accordion/index.ts +6 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-linux.png +0 -0
- package/src/components/atoms/button/button.stories.tsx +102 -0
- package/src/components/atoms/button/button.test.tsx +135 -0
- package/src/components/atoms/button/button.tsx +139 -0
- package/src/components/atoms/button/button.visual.test.tsx +102 -0
- package/src/components/atoms/button/icon-button.stories.tsx +166 -0
- package/src/components/atoms/button/icon-button.tsx +120 -0
- package/src/components/atoms/button/index.ts +6 -0
- package/src/components/atoms/ndstudio-footer/index.ts +1 -0
- package/src/components/atoms/ndstudio-footer/ndstudio-footer.tsx +55 -0
- package/src/components/atoms/pager-control/index.ts +5 -0
- package/src/components/atoms/pager-control/pager-control.stories.tsx +209 -0
- package/src/components/atoms/pager-control/pager-control.test.tsx +130 -0
- package/src/components/atoms/pager-control/pager-control.tsx +329 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +82 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +196 -0
- package/src/components/dev-tools/dev-toolbar/index.ts +1 -0
- package/src/components/dev-tools/grid-overlay/grid-overlay.tsx +41 -0
- package/src/components/dev-tools/grid-overlay/index.ts +1 -0
- package/src/components/dev-tools/index.ts +2 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-linux.png +0 -0
- package/src/components/organisms/card/card.stories.tsx +293 -0
- package/src/components/organisms/card/card.test.tsx +245 -0
- package/src/components/organisms/card/card.tsx +225 -0
- package/src/components/organisms/card/card.visual.test.tsx +197 -0
- package/src/components/organisms/card/index.ts +19 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-darwin.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-linux.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-darwin.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-linux.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-darwin.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-linux.png +0 -0
- package/src/components/organisms/navbar/index.ts +18 -0
- package/src/components/organisms/navbar/navbar.stories.tsx +313 -0
- package/src/components/organisms/navbar/navbar.test.tsx +190 -0
- package/src/components/organisms/navbar/navbar.tsx +323 -0
- package/src/components/organisms/navbar/navbar.visual.test.tsx +85 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-darwin.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-linux.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-darwin.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-linux.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-darwin.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-linux.png +0 -0
- package/src/components/organisms/us-gov-banner/index.ts +1 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.stories.tsx +35 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.test.tsx +107 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +73 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.visual.test.tsx +46 -0
- package/src/components/sections/banner/banner.stories.tsx +150 -0
- package/src/components/sections/banner/banner.test.tsx +185 -0
- package/src/components/sections/banner/banner.tsx +130 -0
- package/src/components/sections/banner/index.ts +2 -0
- package/src/components/sections/card-grid/card-grid.stories.tsx +351 -0
- package/src/components/sections/card-grid/card-grid.tsx +116 -0
- package/src/components/sections/card-grid/index.ts +1 -0
- package/src/components/sections/faq-section/faq-section.stories.tsx +453 -0
- package/src/components/sections/faq-section/faq-section.tsx +84 -0
- package/src/components/sections/faq-section/index.ts +2 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-default-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-linux.png +0 -0
- package/src/components/sections/hero/hero.stories.tsx +274 -0
- package/src/components/sections/hero/hero.test.tsx +135 -0
- package/src/components/sections/hero/hero.tsx +453 -0
- package/src/components/sections/hero/hero.visual.test.tsx +140 -0
- package/src/components/sections/hero/index.ts +10 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-linux.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-linux.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-linux.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-linux.png +0 -0
- package/src/components/sections/prose/index.ts +6 -0
- package/src/components/sections/prose/prose.stories.tsx +144 -0
- package/src/components/sections/prose/prose.test.tsx +178 -0
- package/src/components/sections/prose/prose.tsx +88 -0
- package/src/components/sections/prose/prose.visual.test.tsx +105 -0
- package/src/components/sections/river/index.ts +1 -0
- package/src/components/sections/river/river.stories.tsx +237 -0
- package/src/components/sections/river/river.test.tsx +268 -0
- package/src/components/sections/river/river.tsx +173 -0
- package/src/components/sections/tout/index.ts +1 -0
- package/src/components/sections/tout/tout.stories.tsx +171 -0
- package/src/components/sections/tout/tout.test.tsx +242 -0
- package/src/components/sections/tout/tout.tsx +270 -0
- package/src/components/sections/two-column-section/index.ts +5 -0
- package/src/components/sections/two-column-section/two-column-section.stories.tsx +285 -0
- package/src/components/sections/two-column-section/two-column-section.tsx +162 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-event-listener.ts +73 -0
- package/src/index.ts +155 -0
- package/src/lib/theme.ts +1000 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +13 -0
- package/src/stories/GridSystem.stories.tsx +84 -0
- package/src/stories/Introduction.mdx +114 -0
- package/src/stories/ThemeProvider.stories.tsx +357 -0
- package/src/stories/TokenShowcase.stories.tsx +92 -0
- package/src/stories/TokenShowcase.tsx +1429 -0
- package/src/styles.css +11 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/hooks.ts +40 -0
- package/src/theme/index.ts +43 -0
- package/src/theme/utils.ts +104 -0
package/src/styles.css
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "../../tailwind-token-generator/dist/tokens.css";
|
|
3
|
+
|
|
4
|
+
@font-face {
|
|
5
|
+
font-family: "PP Neue Montreal";
|
|
6
|
+
src: url("./assets/fonts/PPNeueMontreal-Variable.woff2")
|
|
7
|
+
format("woff2-variations");
|
|
8
|
+
font-weight: 100 900;
|
|
9
|
+
font-style: normal;
|
|
10
|
+
font-display: swap;
|
|
11
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThemeProvider
|
|
3
|
+
*
|
|
4
|
+
* React context provider for applying theme tokens as CSS variables.
|
|
5
|
+
* Use string theme names for type-safe selection, with "base" as default.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ColorThemeName,
|
|
10
|
+
CSSVariableMap,
|
|
11
|
+
NestedStringRecord,
|
|
12
|
+
SurfaceThemeName,
|
|
13
|
+
TokenModule,
|
|
14
|
+
} from "@nds-design-system/tokens";
|
|
15
|
+
import {
|
|
16
|
+
colorThemes,
|
|
17
|
+
flatToCSSVars,
|
|
18
|
+
flatToNested,
|
|
19
|
+
isColorValue,
|
|
20
|
+
isDimensionValue,
|
|
21
|
+
surfaceThemes,
|
|
22
|
+
} from "@nds-design-system/tokens";
|
|
23
|
+
import { createContext, type ReactNode, useMemo } from "react";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Theme context value
|
|
27
|
+
*/
|
|
28
|
+
export interface ThemeContextValue {
|
|
29
|
+
/** CSS variables map for inline styles */
|
|
30
|
+
cssVars: CSSVariableMap;
|
|
31
|
+
/** Resolved nested tokens */
|
|
32
|
+
tokens: NestedStringRecord;
|
|
33
|
+
/** Current color theme name */
|
|
34
|
+
colorTheme: ColorThemeName;
|
|
35
|
+
/** Current surface theme name */
|
|
36
|
+
surfaceTheme: SurfaceThemeName;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Props for ThemeProvider
|
|
43
|
+
*/
|
|
44
|
+
export interface ThemeProviderProps {
|
|
45
|
+
/** Color theme name (defaults to "base") */
|
|
46
|
+
color?: ColorThemeName;
|
|
47
|
+
/** Surface theme name (defaults to "base") */
|
|
48
|
+
surface?: SurfaceThemeName;
|
|
49
|
+
/** Children to render */
|
|
50
|
+
children: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert a color value to CSS string
|
|
55
|
+
*/
|
|
56
|
+
function colorToCSS(color: { components: number[]; alpha: number }): string {
|
|
57
|
+
const [r, g, b] = color.components;
|
|
58
|
+
if (color.alpha < 1) {
|
|
59
|
+
return `color(srgb ${r} ${g} ${b} / ${color.alpha})`;
|
|
60
|
+
}
|
|
61
|
+
return `color(srgb ${r} ${g} ${b})`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert a token value to CSS string
|
|
66
|
+
*/
|
|
67
|
+
function tokenValueToCSS(value: unknown): string {
|
|
68
|
+
if (isColorValue(value)) {
|
|
69
|
+
return colorToCSS(value);
|
|
70
|
+
}
|
|
71
|
+
if (isDimensionValue(value)) {
|
|
72
|
+
return `${value.value}${value.unit}`;
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === "number") {
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
return String(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Process color tokens to flat key-value pairs (without -- prefix)
|
|
85
|
+
*/
|
|
86
|
+
function processColorTokens(
|
|
87
|
+
colorObj: Record<string, unknown>,
|
|
88
|
+
path: string[] = [],
|
|
89
|
+
): Record<string, string> {
|
|
90
|
+
const result: Record<string, string> = {};
|
|
91
|
+
|
|
92
|
+
for (const [key, value] of Object.entries(colorObj)) {
|
|
93
|
+
if (key.startsWith("$")) continue;
|
|
94
|
+
|
|
95
|
+
const currentPath = [...path, key];
|
|
96
|
+
|
|
97
|
+
if (typeof value === "object" && value !== null) {
|
|
98
|
+
const record = value as Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
if ("$value" in record) {
|
|
101
|
+
// This is a token - key without -- prefix
|
|
102
|
+
const varName = `color-${currentPath.join("-")}`;
|
|
103
|
+
const tokenValue = record.$value;
|
|
104
|
+
|
|
105
|
+
if (typeof tokenValue === "string" && tokenValue.startsWith("{")) {
|
|
106
|
+
// Alias reference - resolve to CSS var
|
|
107
|
+
let refPath = tokenValue.slice(1, -1).replace(/\./g, "-");
|
|
108
|
+
refPath = refPath.replace(/^semantic-color-/, "color-");
|
|
109
|
+
result[varName] = `var(--${refPath})`;
|
|
110
|
+
} else {
|
|
111
|
+
result[varName] = tokenValueToCSS(tokenValue);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// Nested object - recurse
|
|
115
|
+
Object.assign(result, processColorTokens(record, currentPath));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Process surface tokens to flat key-value pairs (without -- prefix)
|
|
125
|
+
*/
|
|
126
|
+
function processSurfaceTokens(
|
|
127
|
+
surfaceObj: Record<string, unknown>,
|
|
128
|
+
path: string[] = [],
|
|
129
|
+
): Record<string, string> {
|
|
130
|
+
const result: Record<string, string> = {};
|
|
131
|
+
|
|
132
|
+
for (const [key, value] of Object.entries(surfaceObj)) {
|
|
133
|
+
if (key.startsWith("$")) continue;
|
|
134
|
+
|
|
135
|
+
const currentPath = [...path, key];
|
|
136
|
+
|
|
137
|
+
if (typeof value === "object" && value !== null) {
|
|
138
|
+
const record = value as Record<string, unknown>;
|
|
139
|
+
|
|
140
|
+
if ("$value" in record) {
|
|
141
|
+
// This is a token
|
|
142
|
+
const tokenValue = record.$value;
|
|
143
|
+
let varName: string;
|
|
144
|
+
let cssValue: string;
|
|
145
|
+
|
|
146
|
+
// Determine variable name based on token type (without -- prefix)
|
|
147
|
+
if (currentPath[currentPath.length - 1] === "radius") {
|
|
148
|
+
// e.g., button.radius -> radius-surface-button
|
|
149
|
+
const componentName = currentPath[0];
|
|
150
|
+
varName = `radius-surface-${componentName}`;
|
|
151
|
+
} else if (currentPath[currentPath.length - 1] === "stroke") {
|
|
152
|
+
// e.g., button.stroke -> surface-button-stroke
|
|
153
|
+
const componentName = currentPath[0];
|
|
154
|
+
varName = `surface-${componentName}-stroke`;
|
|
155
|
+
} else {
|
|
156
|
+
varName = `surface-${currentPath.join("-")}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Resolve value
|
|
160
|
+
if (typeof tokenValue === "string" && tokenValue.startsWith("{")) {
|
|
161
|
+
// Alias reference
|
|
162
|
+
let refPath = tokenValue.slice(1, -1).replace(/\./g, "-");
|
|
163
|
+
refPath = refPath.replace(/^primitive-radii-/, "radius-");
|
|
164
|
+
cssValue = `var(--${refPath})`;
|
|
165
|
+
} else {
|
|
166
|
+
cssValue = tokenValueToCSS(tokenValue);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
result[varName] = cssValue;
|
|
170
|
+
} else {
|
|
171
|
+
// Nested object - recurse
|
|
172
|
+
Object.assign(result, processSurfaceTokens(record, currentPath));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Deep merge two token modules
|
|
182
|
+
*/
|
|
183
|
+
function mergeTokenModules(
|
|
184
|
+
base: TokenModule,
|
|
185
|
+
override: TokenModule,
|
|
186
|
+
): Record<string, unknown> {
|
|
187
|
+
const result: Record<string, unknown> = JSON.parse(JSON.stringify(base));
|
|
188
|
+
deepMerge(result, override as Record<string, unknown>);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Deep merge objects
|
|
194
|
+
*/
|
|
195
|
+
function deepMerge(
|
|
196
|
+
target: Record<string, unknown>,
|
|
197
|
+
source: Record<string, unknown>,
|
|
198
|
+
): void {
|
|
199
|
+
for (const [key, value] of Object.entries(source)) {
|
|
200
|
+
if (
|
|
201
|
+
typeof value === "object" &&
|
|
202
|
+
value !== null &&
|
|
203
|
+
!Array.isArray(value) &&
|
|
204
|
+
typeof target[key] === "object" &&
|
|
205
|
+
target[key] !== null
|
|
206
|
+
) {
|
|
207
|
+
deepMerge(
|
|
208
|
+
target[key] as Record<string, unknown>,
|
|
209
|
+
value as Record<string, unknown>,
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
target[key] = value;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* ThemeProvider component
|
|
219
|
+
*
|
|
220
|
+
* Applies theme tokens as CSS variables using type-safe theme names.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```tsx
|
|
224
|
+
* // Use default base themes
|
|
225
|
+
* <ThemeProvider>
|
|
226
|
+
* <App />
|
|
227
|
+
* </ThemeProvider>
|
|
228
|
+
*
|
|
229
|
+
* // Use specific themes by name
|
|
230
|
+
* <ThemeProvider color="civic" surface="sharp">
|
|
231
|
+
* <App />
|
|
232
|
+
* </ThemeProvider>
|
|
233
|
+
*
|
|
234
|
+
* // Mix and match
|
|
235
|
+
* <ThemeProvider color="institution" surface="soft">
|
|
236
|
+
* <App />
|
|
237
|
+
* </ThemeProvider>
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function ThemeProvider({
|
|
241
|
+
color = "base",
|
|
242
|
+
surface = "base",
|
|
243
|
+
children,
|
|
244
|
+
}: ThemeProviderProps) {
|
|
245
|
+
const { tokens, cssVars } = useMemo(() => {
|
|
246
|
+
const flatTokens: Record<string, string> = {};
|
|
247
|
+
|
|
248
|
+
// Get color theme (merge with base if not base)
|
|
249
|
+
const baseColorModule = colorThemes.base;
|
|
250
|
+
const colorModule = colorThemes[color];
|
|
251
|
+
|
|
252
|
+
const mergedColor =
|
|
253
|
+
color === "base"
|
|
254
|
+
? (baseColorModule as Record<string, unknown>)
|
|
255
|
+
: mergeTokenModules(baseColorModule, colorModule);
|
|
256
|
+
|
|
257
|
+
// Navigate to semantic.color
|
|
258
|
+
const colorTokens =
|
|
259
|
+
(mergedColor as { semantic?: { color?: Record<string, unknown> } })
|
|
260
|
+
?.semantic?.color ?? mergedColor;
|
|
261
|
+
Object.assign(flatTokens, processColorTokens(colorTokens));
|
|
262
|
+
|
|
263
|
+
// Get surface theme (merge with base if not base)
|
|
264
|
+
const baseSurfaceModule = surfaceThemes.base;
|
|
265
|
+
const surfaceModule = surfaceThemes[surface];
|
|
266
|
+
|
|
267
|
+
const mergedSurface =
|
|
268
|
+
surface === "base"
|
|
269
|
+
? (baseSurfaceModule as Record<string, unknown>)
|
|
270
|
+
: mergeTokenModules(baseSurfaceModule, surfaceModule);
|
|
271
|
+
|
|
272
|
+
// Navigate to semantic.surface
|
|
273
|
+
const surfaceTokens =
|
|
274
|
+
(mergedSurface as { semantic?: { surface?: Record<string, unknown> } })
|
|
275
|
+
?.semantic?.surface ?? mergedSurface;
|
|
276
|
+
Object.assign(flatTokens, processSurfaceTokens(surfaceTokens));
|
|
277
|
+
|
|
278
|
+
// Use shared utilities from tokens package
|
|
279
|
+
const nestedTokens = flatToNested(flatTokens);
|
|
280
|
+
const cssVariables = flatToCSSVars(flatTokens);
|
|
281
|
+
|
|
282
|
+
return { tokens: nestedTokens, cssVars: cssVariables };
|
|
283
|
+
}, [color, surface]);
|
|
284
|
+
|
|
285
|
+
const contextValue: ThemeContextValue = {
|
|
286
|
+
cssVars,
|
|
287
|
+
tokens,
|
|
288
|
+
colorTheme: color,
|
|
289
|
+
surfaceTheme: surface,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<ThemeContext.Provider value={contextValue}>
|
|
294
|
+
{children}
|
|
295
|
+
</ThemeContext.Provider>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Hooks
|
|
3
|
+
*
|
|
4
|
+
* React hooks for accessing theme context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
CSSVariableMap,
|
|
9
|
+
NestedStringRecord,
|
|
10
|
+
} from "@nds-design-system/tokens";
|
|
11
|
+
import { useContext } from "react";
|
|
12
|
+
import { ThemeContext, type ThemeContextValue } from "./ThemeProvider";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook to access the theme context
|
|
16
|
+
* @throws Error if used outside of ThemeProvider
|
|
17
|
+
*/
|
|
18
|
+
export function useTheme(): ThemeContextValue {
|
|
19
|
+
const context = useContext(ThemeContext);
|
|
20
|
+
if (!context) {
|
|
21
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
22
|
+
}
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hook to get just the nested tokens
|
|
28
|
+
*/
|
|
29
|
+
export function useThemeTokens(): NestedStringRecord {
|
|
30
|
+
const { tokens } = useTheme();
|
|
31
|
+
return tokens;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook to get CSS variables for inline styles
|
|
36
|
+
*/
|
|
37
|
+
export function useCSSVars(): CSSVariableMap {
|
|
38
|
+
const { cssVars } = useTheme();
|
|
39
|
+
return cssVars;
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Module
|
|
3
|
+
*
|
|
4
|
+
* React components and utilities for theme composition and CSS variable application.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Re-export types from tokens package for convenience
|
|
8
|
+
export type {
|
|
9
|
+
APICollection,
|
|
10
|
+
CollectionDefinition,
|
|
11
|
+
ColorThemeName,
|
|
12
|
+
CSSVariableMap,
|
|
13
|
+
NestedStringRecord,
|
|
14
|
+
SurfaceThemeName,
|
|
15
|
+
ThemeComposition,
|
|
16
|
+
} from "@nds-design-system/tokens";
|
|
17
|
+
// Re-export theme registries for programmatic access
|
|
18
|
+
export {
|
|
19
|
+
colorThemeNames,
|
|
20
|
+
colorThemes,
|
|
21
|
+
surfaceThemeNames,
|
|
22
|
+
surfaceThemes,
|
|
23
|
+
} from "@nds-design-system/tokens";
|
|
24
|
+
|
|
25
|
+
// Hooks
|
|
26
|
+
export { useCSSVars, useTheme, useThemeTokens } from "./hooks";
|
|
27
|
+
// Provider
|
|
28
|
+
export {
|
|
29
|
+
ThemeContext,
|
|
30
|
+
type ThemeContextValue,
|
|
31
|
+
ThemeProvider,
|
|
32
|
+
type ThemeProviderProps,
|
|
33
|
+
} from "./ThemeProvider";
|
|
34
|
+
// Utilities
|
|
35
|
+
export {
|
|
36
|
+
applyTheme,
|
|
37
|
+
createThemeStyle,
|
|
38
|
+
filterCSSVars,
|
|
39
|
+
getToken,
|
|
40
|
+
mergeCSSVars,
|
|
41
|
+
removeTheme,
|
|
42
|
+
toCSSVars,
|
|
43
|
+
} from "./utils";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for working with theme tokens and CSS variables.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
CSSVariableMap,
|
|
9
|
+
NestedStringRecord,
|
|
10
|
+
} from "@nds-design-system/tokens";
|
|
11
|
+
import { nestedToCSSVars } from "@nds-design-system/tokens";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert nested token object to CSS variable map
|
|
15
|
+
* @example
|
|
16
|
+
* toCSSVars({ color: { bg: { page: '#fff' } } })
|
|
17
|
+
* // Returns: { '--color-bg-page': '#fff' }
|
|
18
|
+
*/
|
|
19
|
+
export function toCSSVars(tokens: NestedStringRecord): CSSVariableMap {
|
|
20
|
+
return nestedToCSSVars(tokens);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply CSS variables to an element
|
|
25
|
+
* @example
|
|
26
|
+
* applyTheme(document.body, { '--color-bg-page': '#fff' })
|
|
27
|
+
*/
|
|
28
|
+
export function applyTheme(
|
|
29
|
+
element: HTMLElement,
|
|
30
|
+
cssVars: CSSVariableMap,
|
|
31
|
+
): void {
|
|
32
|
+
for (const [name, value] of Object.entries(cssVars)) {
|
|
33
|
+
element.style.setProperty(name, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Remove CSS variables from an element
|
|
39
|
+
*/
|
|
40
|
+
export function removeTheme(
|
|
41
|
+
element: HTMLElement,
|
|
42
|
+
cssVars: CSSVariableMap,
|
|
43
|
+
): void {
|
|
44
|
+
for (const name of Object.keys(cssVars)) {
|
|
45
|
+
element.style.removeProperty(name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get a specific token value from nested tokens using dot notation
|
|
51
|
+
* @example
|
|
52
|
+
* getToken({ color: { bg: { page: '#fff' } } }, 'color.bg.page')
|
|
53
|
+
* // Returns: '#fff'
|
|
54
|
+
*/
|
|
55
|
+
export function getToken(
|
|
56
|
+
tokens: NestedStringRecord,
|
|
57
|
+
path: string,
|
|
58
|
+
): string | undefined {
|
|
59
|
+
const parts = path.split(".");
|
|
60
|
+
let current: NestedStringRecord | string = tokens;
|
|
61
|
+
|
|
62
|
+
for (const part of parts) {
|
|
63
|
+
if (typeof current === "string") return undefined;
|
|
64
|
+
current = current[part];
|
|
65
|
+
if (current === undefined) return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return typeof current === "string" ? current : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a style object with CSS variables for React inline styles
|
|
73
|
+
* @example
|
|
74
|
+
* <div style={createThemeStyle(cssVars)}>...</div>
|
|
75
|
+
*/
|
|
76
|
+
export function createThemeStyle(cssVars: CSSVariableMap): React.CSSProperties {
|
|
77
|
+
return cssVars as React.CSSProperties;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Merge multiple CSS variable maps
|
|
82
|
+
*/
|
|
83
|
+
export function mergeCSSVars(...maps: CSSVariableMap[]): CSSVariableMap {
|
|
84
|
+
return Object.assign({}, ...maps);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Filter CSS variables by prefix
|
|
89
|
+
* @example
|
|
90
|
+
* filterCSSVars(cssVars, '--color-')
|
|
91
|
+
* // Returns only variables starting with --color-
|
|
92
|
+
*/
|
|
93
|
+
export function filterCSSVars(
|
|
94
|
+
cssVars: CSSVariableMap,
|
|
95
|
+
prefix: string,
|
|
96
|
+
): CSSVariableMap {
|
|
97
|
+
const result: CSSVariableMap = {};
|
|
98
|
+
for (const [name, value] of Object.entries(cssVars)) {
|
|
99
|
+
if (name.startsWith(prefix)) {
|
|
100
|
+
result[name] = value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|