@newtonedev/components 0.1.5 → 0.1.7
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/dist/composites/actions/Button/Button.d.ts.map +1 -1
- package/dist/composites/actions/Button/Button.styles.d.ts +3 -1
- package/dist/composites/actions/Button/Button.styles.d.ts.map +1 -1
- package/dist/index.cjs +603 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +599 -251
- package/dist/index.js.map +1 -1
- package/dist/primitives/Frame/Frame.d.ts +2 -3
- package/dist/primitives/Frame/Frame.d.ts.map +1 -1
- package/dist/primitives/Frame/Frame.types.d.ts +4 -15
- package/dist/primitives/Frame/Frame.types.d.ts.map +1 -1
- package/dist/primitives/Icon/Icon.d.ts +1 -1
- package/dist/primitives/Icon/Icon.d.ts.map +1 -1
- package/dist/primitives/Icon/Icon.types.d.ts +7 -12
- package/dist/primitives/Icon/Icon.types.d.ts.map +1 -1
- package/dist/primitives/Text/Text.d.ts.map +1 -1
- package/dist/primitives/Text/Text.types.d.ts +9 -4
- package/dist/primitives/Text/Text.types.d.ts.map +1 -1
- package/dist/primitives/Wrapper/Wrapper.d.ts +1 -1
- package/dist/primitives/Wrapper/Wrapper.types.d.ts +1 -1
- package/dist/registry/icons.d.ts +7 -0
- package/dist/registry/icons.d.ts.map +1 -0
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/types.d.ts +1 -1
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/theme/FrameContext.d.ts +7 -5
- package/dist/theme/FrameContext.d.ts.map +1 -1
- package/dist/theme/NewtoneProvider.d.ts +5 -6
- package/dist/theme/NewtoneProvider.d.ts.map +1 -1
- package/dist/theme/defaults.d.ts.map +1 -1
- package/dist/theme/types.d.ts +38 -24
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/tokens/computeTokens.d.ts +82 -7
- package/dist/tokens/computeTokens.d.ts.map +1 -1
- package/dist/tokens/types.d.ts +58 -16
- package/dist/tokens/types.d.ts.map +1 -1
- package/dist/tokens/useTokens.d.ts +2 -23
- package/dist/tokens/useTokens.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/composites/actions/Button/Button.styles.ts +53 -80
- package/src/composites/actions/Button/Button.tsx +6 -2
- package/src/composites/form-controls/Select/SelectOption.tsx +2 -2
- package/src/composites/form-controls/Toggle/Toggle.styles.ts +1 -1
- package/src/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.ts +1 -1
- package/src/composites/range-inputs/Slider/Slider.styles.ts +2 -2
- package/src/index.ts +13 -4
- package/src/primitives/Frame/Frame.tsx +10 -18
- package/src/primitives/Frame/Frame.types.ts +5 -17
- package/src/primitives/Icon/Icon.tsx +4 -6
- package/src/primitives/Icon/Icon.types.ts +7 -14
- package/src/primitives/Text/Text.tsx +18 -8
- package/src/primitives/Text/Text.types.ts +9 -4
- package/src/primitives/Wrapper/Wrapper.tsx +1 -1
- package/src/primitives/Wrapper/Wrapper.types.ts +1 -1
- package/src/registry/icons.ts +111 -0
- package/src/registry/index.ts +3 -0
- package/src/registry/registry.ts +40 -24
- package/src/registry/types.ts +1 -1
- package/src/theme/FrameContext.tsx +7 -5
- package/src/theme/NewtoneProvider.tsx +5 -10
- package/src/theme/defaults.ts +0 -9
- package/src/theme/types.ts +53 -26
- package/src/tokens/computeTokens.ts +338 -116
- package/src/tokens/types.ts +74 -16
- package/src/tokens/useTokens.ts +16 -33
|
@@ -1,7 +1,119 @@
|
|
|
1
|
-
import { getColor
|
|
1
|
+
import { getColor } from 'newtone';
|
|
2
2
|
import type { PaletteConfig } from 'newtone';
|
|
3
|
-
import type { ColorSystemConfig, ColorMode,
|
|
4
|
-
import type { ResolvedTokens } from './types';
|
|
3
|
+
import type { ColorSystemConfig, ColorMode, ElevationLevel, FontConfig, TokenOverrides } from '../theme/types';
|
|
4
|
+
import type { ResolvedTokens, PaletteTokens } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-mode fallback defaults shape shared by all palette defaults.
|
|
8
|
+
*/
|
|
9
|
+
export type PaletteDefaults = {
|
|
10
|
+
readonly light: {
|
|
11
|
+
readonly background: { readonly elevated: number; readonly ground: number; readonly sunken: number };
|
|
12
|
+
readonly text: { readonly primary: number; readonly secondary: number; readonly tertiary: number; readonly disabled: number };
|
|
13
|
+
readonly action: { readonly enabled: number; readonly hovered: number; readonly pressed: number };
|
|
14
|
+
readonly border: { readonly enabled: number; readonly focused: number; readonly filled: number };
|
|
15
|
+
};
|
|
16
|
+
readonly dark: {
|
|
17
|
+
readonly background: { readonly elevated: number; readonly ground: number; readonly sunken: number };
|
|
18
|
+
readonly text: { readonly primary: number; readonly secondary: number; readonly tertiary: number; readonly disabled: number };
|
|
19
|
+
readonly action: { readonly enabled: number; readonly hovered: number; readonly pressed: number };
|
|
20
|
+
readonly border: { readonly enabled: number; readonly focused: number; readonly filled: number };
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Per-mode fallback defaults for the neutral palette (normalized scale).
|
|
26
|
+
* Single source of truth — consumed by computeTokens and the admin Token Tuner.
|
|
27
|
+
*
|
|
28
|
+
* Light: 0 = lightest, 1 = darkest. Dark: 0 = darkest, 1 = lightest.
|
|
29
|
+
* Structured by token group so all per-palette defaults follow the same shape.
|
|
30
|
+
*/
|
|
31
|
+
export const NEUTRAL_DEFAULTS: PaletteDefaults = {
|
|
32
|
+
light: {
|
|
33
|
+
background: { elevated: 0, ground: 0.03, sunken: 0.06 },
|
|
34
|
+
text: { primary: 0.9, secondary: 0.7, tertiary: 0.5, disabled: 0.3 },
|
|
35
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
36
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
37
|
+
},
|
|
38
|
+
dark: {
|
|
39
|
+
background: { elevated: 0.24, ground: 0.20, sunken: 0.16 },
|
|
40
|
+
text: { primary: 1.0, secondary: 0.85, tertiary: 0.7, disabled: 0.55 },
|
|
41
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
42
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Accent palette defaults. Initial values match neutral — will be tuned per-palette later.
|
|
48
|
+
*/
|
|
49
|
+
export const ACCENT_DEFAULTS: PaletteDefaults = {
|
|
50
|
+
light: {
|
|
51
|
+
background: { elevated: 0, ground: 0.03, sunken: 0.06 },
|
|
52
|
+
text: { primary: 0.9, secondary: 0.7, tertiary: 0.5, disabled: 0.3 },
|
|
53
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
54
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
55
|
+
},
|
|
56
|
+
dark: {
|
|
57
|
+
background: { elevated: 0.24, ground: 0.20, sunken: 0.16 },
|
|
58
|
+
text: { primary: 1.0, secondary: 0.85, tertiary: 0.7, disabled: 0.55 },
|
|
59
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
60
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Success palette defaults. Initial values match neutral — will be tuned per-palette later.
|
|
66
|
+
*/
|
|
67
|
+
export const SUCCESS_DEFAULTS: PaletteDefaults = {
|
|
68
|
+
light: {
|
|
69
|
+
background: { elevated: 0, ground: 0.03, sunken: 0.06 },
|
|
70
|
+
text: { primary: 0.9, secondary: 0.7, tertiary: 0.5, disabled: 0.3 },
|
|
71
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
72
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
73
|
+
},
|
|
74
|
+
dark: {
|
|
75
|
+
background: { elevated: 0.24, ground: 0.20, sunken: 0.16 },
|
|
76
|
+
text: { primary: 1.0, secondary: 0.85, tertiary: 0.7, disabled: 0.55 },
|
|
77
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
78
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Warning palette defaults. Initial values match neutral — will be tuned per-palette later.
|
|
84
|
+
*/
|
|
85
|
+
export const WARNING_DEFAULTS: PaletteDefaults = {
|
|
86
|
+
light: {
|
|
87
|
+
background: { elevated: 0, ground: 0.03, sunken: 0.06 },
|
|
88
|
+
text: { primary: 0.9, secondary: 0.7, tertiary: 0.5, disabled: 0.3 },
|
|
89
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
90
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
91
|
+
},
|
|
92
|
+
dark: {
|
|
93
|
+
background: { elevated: 0.24, ground: 0.20, sunken: 0.16 },
|
|
94
|
+
text: { primary: 1.0, secondary: 0.85, tertiary: 0.7, disabled: 0.55 },
|
|
95
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
96
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Error palette defaults. Initial values match neutral — will be tuned per-palette later.
|
|
102
|
+
*/
|
|
103
|
+
export const ERROR_DEFAULTS: PaletteDefaults = {
|
|
104
|
+
light: {
|
|
105
|
+
background: { elevated: 0, ground: 0.03, sunken: 0.06 },
|
|
106
|
+
text: { primary: 0.9, secondary: 0.7, tertiary: 0.5, disabled: 0.3 },
|
|
107
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
108
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
109
|
+
},
|
|
110
|
+
dark: {
|
|
111
|
+
background: { elevated: 0.24, ground: 0.20, sunken: 0.16 },
|
|
112
|
+
text: { primary: 1.0, secondary: 0.85, tertiary: 0.7, disabled: 0.55 },
|
|
113
|
+
action: { enabled: 0.04, hovered: 0.06, pressed: 0.08 },
|
|
114
|
+
border: { enabled: 0.08, focused: 0.16, filled: 0.24 },
|
|
115
|
+
},
|
|
116
|
+
};
|
|
5
117
|
|
|
6
118
|
/**
|
|
7
119
|
* Convert FontConfig to CSS font-family string
|
|
@@ -11,6 +123,104 @@ function fontConfigToFamily(font: FontConfig): string {
|
|
|
11
123
|
return `${family}, ${font.fallback}`;
|
|
12
124
|
}
|
|
13
125
|
|
|
126
|
+
const clamp = (n: number) => Math.max(0, Math.min(1, n));
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Compute the complete PaletteTokens for a non-neutral palette.
|
|
130
|
+
*
|
|
131
|
+
* Uses the palette's hue/saturation with positions from the palette's defaults,
|
|
132
|
+
* plus fill tokens at the palette's key NV position.
|
|
133
|
+
*
|
|
134
|
+
* @param palette - The palette config (hue, saturation, key NV, etc.)
|
|
135
|
+
* @param defaults - Per-mode defaults for this palette (positions + offsets)
|
|
136
|
+
* @param mode - Current color mode
|
|
137
|
+
* @param elevation - Current elevation level
|
|
138
|
+
* @param dynamicRange - Global dynamic range
|
|
139
|
+
* @param elevationDelta - Compensation delta (current surface vs elevated reference)
|
|
140
|
+
* @param effectiveTextMode - Derived from actual background lightness
|
|
141
|
+
* @param autoAccentNv - Auto-derived key NV fallback
|
|
142
|
+
* @param neutralTextPrimary - Neutral textPrimary for onFill (dark text on light fills)
|
|
143
|
+
* @param neutralBgElevated - Neutral backgroundElevated for onFill (light text on dark fills)
|
|
144
|
+
*/
|
|
145
|
+
function computePaletteTokens(
|
|
146
|
+
palette: PaletteConfig,
|
|
147
|
+
defaults: PaletteDefaults,
|
|
148
|
+
mode: ColorMode,
|
|
149
|
+
elevation: ElevationLevel,
|
|
150
|
+
dynamicRange: { readonly lightest: number; readonly darkest: number },
|
|
151
|
+
elevationDelta: number,
|
|
152
|
+
effectiveTextMode: ColorMode,
|
|
153
|
+
autoAccentNv: number,
|
|
154
|
+
neutralTextPrimary: ReturnType<typeof getColor>,
|
|
155
|
+
neutralBgElevated: ReturnType<typeof getColor>,
|
|
156
|
+
): PaletteTokens {
|
|
157
|
+
const modeDefaults = defaults[mode];
|
|
158
|
+
const toEngineNv = (nv: number) => mode === 'light' ? 1 - nv : nv;
|
|
159
|
+
const textToEngineNv = (nv: number) => effectiveTextMode === 'light' ? 1 - nv : nv;
|
|
160
|
+
|
|
161
|
+
const colorAt = (engineNv: number) => getColor(
|
|
162
|
+
palette.hue, palette.saturation, dynamicRange,
|
|
163
|
+
clamp(engineNv), palette.desaturation, palette.paletteHueGrading
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// --- Fill: key color (user-chosen or auto-derived), elevation-compensated ---
|
|
167
|
+
const resolveKeyNv = (p: PaletteConfig): number | undefined =>
|
|
168
|
+
mode === 'dark' ? p.keyNormalizedValueDark : p.keyNormalizedValue;
|
|
169
|
+
|
|
170
|
+
const keyNv = resolveKeyNv(palette);
|
|
171
|
+
const fillBaseNv = keyNv ?? autoAccentNv;
|
|
172
|
+
const fillNv = clamp(fillBaseNv + elevationDelta);
|
|
173
|
+
const fill = colorAt(fillNv);
|
|
174
|
+
|
|
175
|
+
const hoverDir = effectiveTextMode === 'light' ? -modeDefaults.action.hovered : modeDefaults.action.hovered;
|
|
176
|
+
const activeDir = effectiveTextMode === 'light' ? -modeDefaults.action.pressed : modeDefaults.action.pressed;
|
|
177
|
+
const fillHover = colorAt(clamp(fillNv + hoverDir));
|
|
178
|
+
const fillActive = colorAt(clamp(fillNv + activeDir));
|
|
179
|
+
|
|
180
|
+
// onFill: high-contrast text on the fill color
|
|
181
|
+
const onFill = fill.oklch.L > 0.6 ? neutralTextPrimary : neutralBgElevated;
|
|
182
|
+
|
|
183
|
+
// --- Surface: palette-tinted backgrounds at standard positions ---
|
|
184
|
+
const bgNormalized = elevation === 2
|
|
185
|
+
? modeDefaults.background.elevated
|
|
186
|
+
: elevation === 1
|
|
187
|
+
? modeDefaults.background.ground
|
|
188
|
+
: modeDefaults.background.sunken;
|
|
189
|
+
const bgNv = clamp(toEngineNv(bgNormalized));
|
|
190
|
+
const background = colorAt(bgNv);
|
|
191
|
+
const backgroundElevated = colorAt(clamp(toEngineNv(modeDefaults.background.elevated)));
|
|
192
|
+
const backgroundSunken = colorAt(clamp(toEngineNv(modeDefaults.background.sunken)));
|
|
193
|
+
|
|
194
|
+
// --- Interactive surface: offset from palette background ---
|
|
195
|
+
const interactiveOffset = modeDefaults.action.enabled;
|
|
196
|
+
const interactiveNv = clamp(bgNv + (effectiveTextMode === 'light' ? -interactiveOffset : interactiveOffset));
|
|
197
|
+
const backgroundInteractive = colorAt(interactiveNv);
|
|
198
|
+
|
|
199
|
+
const hoverShift = modeDefaults.action.hovered;
|
|
200
|
+
const activeShift = modeDefaults.action.pressed;
|
|
201
|
+
const backgroundInteractiveHover = colorAt(clamp(interactiveNv + (effectiveTextMode === 'light' ? -hoverShift : hoverShift)));
|
|
202
|
+
const backgroundInteractiveActive = colorAt(clamp(interactiveNv + (effectiveTextMode === 'light' ? -activeShift : activeShift)));
|
|
203
|
+
|
|
204
|
+
// --- Text: palette-hued text at standard positions, elevation-compensated ---
|
|
205
|
+
const textPrimary = colorAt(clamp(textToEngineNv(modeDefaults.text.primary) + elevationDelta));
|
|
206
|
+
const textSecondary = colorAt(clamp(textToEngineNv(modeDefaults.text.secondary) + elevationDelta));
|
|
207
|
+
const textTertiary = colorAt(clamp(textToEngineNv(modeDefaults.text.tertiary) + elevationDelta));
|
|
208
|
+
const textDisabled = colorAt(clamp(textToEngineNv(modeDefaults.text.disabled) + elevationDelta));
|
|
209
|
+
|
|
210
|
+
// --- Border: offset from palette background ---
|
|
211
|
+
const borderOffset = modeDefaults.border.enabled;
|
|
212
|
+
const borderNv = effectiveTextMode === 'light' ? bgNv - borderOffset : bgNv + borderOffset;
|
|
213
|
+
const border = colorAt(clamp(borderNv));
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
fill, fillHover, fillActive, onFill,
|
|
217
|
+
background, backgroundElevated, backgroundSunken,
|
|
218
|
+
backgroundInteractive, backgroundInteractiveHover, backgroundInteractiveActive,
|
|
219
|
+
textPrimary, textSecondary, textTertiary, textDisabled,
|
|
220
|
+
border,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
14
224
|
/**
|
|
15
225
|
* Compute design tokens for a specific mode/theme/elevation combination.
|
|
16
226
|
*
|
|
@@ -18,11 +228,13 @@ function fontConfigToFamily(font: FontConfig): string {
|
|
|
18
228
|
* based on the current theme context. All colors are computed on-demand using
|
|
19
229
|
* the pure functions from the engine.
|
|
20
230
|
*
|
|
231
|
+
* Background surfaces use absolute positions from NEUTRAL_DEFAULTS (or tokenOverrides
|
|
232
|
+
* when present). Elevation compensation is derived from the difference between
|
|
233
|
+
* the current surface and the elevated reference surface.
|
|
234
|
+
*
|
|
21
235
|
* @param config - Complete color system configuration (dynamic range + palettes)
|
|
22
236
|
* @param mode - Current color mode ('light' or 'dark')
|
|
23
|
-
* @param themeMapping - Theme configuration (which palette and NV to use)
|
|
24
237
|
* @param elevation - Elevation level (0=sunken, 1=default, 2=elevated)
|
|
25
|
-
* @param elevationOffsets - NV offsets for each elevation level
|
|
26
238
|
* @param spacing - Spacing scale for paddings, gaps, and margins
|
|
27
239
|
* @param radius - Border radius scale for component roundness
|
|
28
240
|
* @param typography - Typography configuration with fonts and scales
|
|
@@ -34,9 +246,7 @@ function fontConfigToFamily(font: FontConfig): string {
|
|
|
34
246
|
* const tokens = computeTokens(
|
|
35
247
|
* config.colorSystem,
|
|
36
248
|
* 'light',
|
|
37
|
-
* config.themes.neutral,
|
|
38
249
|
* 1,
|
|
39
|
-
* config.elevation.offsets,
|
|
40
250
|
* config.spacing,
|
|
41
251
|
* config.radius,
|
|
42
252
|
* config.typography,
|
|
@@ -48,9 +258,7 @@ function fontConfigToFamily(font: FontConfig): string {
|
|
|
48
258
|
export function computeTokens(
|
|
49
259
|
config: ColorSystemConfig,
|
|
50
260
|
mode: ColorMode,
|
|
51
|
-
themeMapping: ThemeMapping,
|
|
52
261
|
elevation: ElevationLevel,
|
|
53
|
-
elevationOffsets: readonly [number, number, number],
|
|
54
262
|
spacing: { readonly '00': number; readonly '02': number; readonly '04': number; readonly '06': number; readonly '08': number; readonly '10': number; readonly '12': number; readonly '16': number; readonly '20': number; readonly '24': number; readonly '32': number; readonly '40': number; readonly '48': number },
|
|
55
263
|
radius: { readonly none: number; readonly sm: number; readonly md: number; readonly lg: number; readonly xl: number; readonly pill: 999 },
|
|
56
264
|
typography: {
|
|
@@ -63,19 +271,44 @@ export function computeTokens(
|
|
|
63
271
|
readonly variant: 'outlined' | 'rounded' | 'sharp';
|
|
64
272
|
readonly weight: 100 | 200 | 300 | 400 | 500 | 600 | 700;
|
|
65
273
|
readonly autoGrade: boolean;
|
|
66
|
-
}
|
|
274
|
+
},
|
|
275
|
+
tokenOverrides?: TokenOverrides
|
|
67
276
|
): ResolvedTokens {
|
|
68
277
|
const { dynamicRange, palettes } = config;
|
|
69
|
-
const palette = palettes[
|
|
278
|
+
const palette = palettes[0]; // Always neutral palette for backgrounds
|
|
70
279
|
|
|
71
280
|
if (!palette) {
|
|
72
|
-
throw new Error(
|
|
281
|
+
throw new Error('Neutral palette (index 0) not found');
|
|
73
282
|
}
|
|
74
283
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
284
|
+
const neutralDefaults = NEUTRAL_DEFAULTS[mode];
|
|
285
|
+
|
|
286
|
+
// --- Mode-specific normalized field resolution ---
|
|
287
|
+
const toEngineNv = (nv: number) => mode === 'light' ? 1 - nv : nv;
|
|
288
|
+
const bgElevatedNorm = mode === 'light' ? tokenOverrides?.backgroundElevated : tokenOverrides?.backgroundElevatedDark;
|
|
289
|
+
const bgDefaultNorm = mode === 'light' ? tokenOverrides?.backgroundDefault : tokenOverrides?.backgroundDefaultDark;
|
|
290
|
+
const bgSunkenNorm = mode === 'light' ? tokenOverrides?.backgroundSunken : tokenOverrides?.backgroundSunkenDark;
|
|
291
|
+
const textPrimaryNorm = mode === 'light' ? tokenOverrides?.textPrimaryNormalized : tokenOverrides?.textPrimaryNormalizedDark;
|
|
292
|
+
const textSecondaryNorm = mode === 'light' ? tokenOverrides?.textSecondaryNormalized : tokenOverrides?.textSecondaryNormalizedDark;
|
|
293
|
+
const textTertiaryNorm = mode === 'light' ? tokenOverrides?.textTertiaryNormalized : tokenOverrides?.textTertiaryNormalizedDark;
|
|
294
|
+
const textDisabledNorm = mode === 'light' ? tokenOverrides?.textDisabledNormalized : tokenOverrides?.textDisabledNormalizedDark;
|
|
295
|
+
|
|
296
|
+
// --- Background NV resolution ---
|
|
297
|
+
// Absolute positions from tokenOverrides or NEUTRAL_DEFAULTS.
|
|
298
|
+
// Ground = Background/01 (elevated). Everything diverges from there.
|
|
299
|
+
const bgNormalized = elevation === 2
|
|
300
|
+
? (bgElevatedNorm ?? neutralDefaults.background.elevated)
|
|
301
|
+
: elevation === 1
|
|
302
|
+
? (bgDefaultNorm ?? neutralDefaults.background.ground)
|
|
303
|
+
: (bgSunkenNorm ?? neutralDefaults.background.sunken);
|
|
304
|
+
const backgroundNv = clamp(toEngineNv(bgNormalized));
|
|
305
|
+
const elevatedNv = clamp(toEngineNv(bgElevatedNorm ?? neutralDefaults.background.elevated));
|
|
306
|
+
const sunkenNv = clamp(toEngineNv(bgSunkenNorm ?? neutralDefaults.background.sunken));
|
|
307
|
+
|
|
308
|
+
// Elevation compensation: how far the current surface is from the reference (bg01/elevated).
|
|
309
|
+
// Tokens designed at bg01 shift by this delta on deeper surfaces to preserve perceptual contrast.
|
|
310
|
+
// Always 0 on bg01. Negative on bg02/bg03 in both modes (surface is darker → tokens darken too).
|
|
311
|
+
const elevationDelta = backgroundNv - elevatedNv;
|
|
79
312
|
|
|
80
313
|
// Derive effective text mode from actual background lightness.
|
|
81
314
|
// This handles inverted themes (e.g., strong: dark bg in light mode)
|
|
@@ -92,8 +325,7 @@ export function computeTokens(
|
|
|
92
325
|
palette.paletteHueGrading
|
|
93
326
|
);
|
|
94
327
|
|
|
95
|
-
// Compute elevated surface
|
|
96
|
-
const elevatedNv = Math.max(0, Math.min(1, baseNv + elevationOffsets[2]));
|
|
328
|
+
// Compute elevated surface
|
|
97
329
|
const backgroundElevated = getColor(
|
|
98
330
|
palette.hue,
|
|
99
331
|
palette.saturation,
|
|
@@ -103,8 +335,7 @@ export function computeTokens(
|
|
|
103
335
|
palette.paletteHueGrading
|
|
104
336
|
);
|
|
105
337
|
|
|
106
|
-
// Compute sunken surface
|
|
107
|
-
const sunkenNv = Math.max(0, Math.min(1, baseNv + elevationOffsets[0]));
|
|
338
|
+
// Compute sunken surface
|
|
108
339
|
const backgroundSunken = getColor(
|
|
109
340
|
palette.hue,
|
|
110
341
|
palette.saturation,
|
|
@@ -114,12 +345,26 @@ export function computeTokens(
|
|
|
114
345
|
palette.paletteHueGrading
|
|
115
346
|
);
|
|
116
347
|
|
|
117
|
-
//
|
|
348
|
+
// --- Tunable constants (overridable via tokenOverrides, per-mode) ---
|
|
349
|
+
// All values are magnitudes — direction auto-inverts per effectiveTextMode
|
|
350
|
+
// (darker in light mode, lighter in dark mode)
|
|
351
|
+
const INTERACTIVE_COMPONENT_OFFSET = mode === 'light'
|
|
352
|
+
? (tokenOverrides?.interactiveComponentOffset ?? neutralDefaults.action.enabled)
|
|
353
|
+
: (tokenOverrides?.interactiveComponentOffsetDark ?? neutralDefaults.action.enabled);
|
|
354
|
+
const HOVER_SHIFT = mode === 'light'
|
|
355
|
+
? (tokenOverrides?.hoverShift ?? neutralDefaults.action.hovered)
|
|
356
|
+
: (tokenOverrides?.hoverShiftDark ?? neutralDefaults.action.hovered);
|
|
357
|
+
const ACTIVE_SHIFT = mode === 'light'
|
|
358
|
+
? (tokenOverrides?.activeShift ?? neutralDefaults.action.pressed)
|
|
359
|
+
: (tokenOverrides?.activeShiftDark ?? neutralDefaults.action.pressed);
|
|
360
|
+
const BORDER_OFFSET = mode === 'light'
|
|
361
|
+
? (tokenOverrides?.borderOffset ?? neutralDefaults.border.enabled)
|
|
362
|
+
: (tokenOverrides?.borderOffsetDark ?? neutralDefaults.border.enabled);
|
|
363
|
+
// Compute interactive component background (FIXED NV offset from current elevation)
|
|
118
364
|
// Unlike backgroundElevated/backgroundSunken which use discrete levels, this uses a fixed
|
|
119
365
|
// luminosity offset to ensure CONSISTENT visual contrast across all elevations (-2 to 2).
|
|
120
366
|
// Used by: Button (neutral primary variant) and future components with neutral filled backgrounds.
|
|
121
|
-
const
|
|
122
|
-
const interactiveComponentNv = Math.max(0, Math.min(1, backgroundNv + INTERACTIVE_COMPONENT_OFFSET));
|
|
367
|
+
const interactiveComponentNv = clamp(backgroundNv + (effectiveTextMode === 'light' ? -INTERACTIVE_COMPONENT_OFFSET : INTERACTIVE_COMPONENT_OFFSET));
|
|
123
368
|
const backgroundInteractive = getColor(
|
|
124
369
|
palette.hue,
|
|
125
370
|
palette.saturation,
|
|
@@ -129,141 +374,118 @@ export function computeTokens(
|
|
|
129
374
|
palette.paletteHueGrading
|
|
130
375
|
);
|
|
131
376
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
const
|
|
377
|
+
// Neutral hover/active: shift from interactive component base (same direction as accent hover/active)
|
|
378
|
+
const neutralHoverNv = clamp(interactiveComponentNv + (effectiveTextMode === 'light' ? -HOVER_SHIFT : HOVER_SHIFT));
|
|
379
|
+
const backgroundInteractiveHover = getColor(
|
|
135
380
|
palette.hue,
|
|
136
381
|
palette.saturation,
|
|
137
382
|
dynamicRange,
|
|
138
|
-
|
|
139
|
-
effectiveTextMode,
|
|
383
|
+
neutralHoverNv,
|
|
140
384
|
palette.desaturation,
|
|
141
|
-
palette.paletteHueGrading
|
|
142
|
-
background,
|
|
385
|
+
palette.paletteHueGrading
|
|
143
386
|
);
|
|
144
387
|
|
|
145
|
-
|
|
146
|
-
const
|
|
388
|
+
const neutralActiveNv = clamp(interactiveComponentNv + (effectiveTextMode === 'light' ? -ACTIVE_SHIFT : ACTIVE_SHIFT));
|
|
389
|
+
const backgroundInteractiveActive = getColor(
|
|
147
390
|
palette.hue,
|
|
148
391
|
palette.saturation,
|
|
149
392
|
dynamicRange,
|
|
150
|
-
|
|
151
|
-
effectiveTextMode,
|
|
393
|
+
neutralActiveNv,
|
|
152
394
|
palette.desaturation,
|
|
153
|
-
palette.paletteHueGrading
|
|
154
|
-
background,
|
|
395
|
+
palette.paletteHueGrading
|
|
155
396
|
);
|
|
156
397
|
|
|
157
|
-
//
|
|
158
|
-
|
|
398
|
+
// --- Text color resolution (elevation-compensated) ---
|
|
399
|
+
// Normalized positions define text contrast at bg01 (elevated). On deeper surfaces,
|
|
400
|
+
// elevationDelta shifts text toward the background to preserve the same contrast distance.
|
|
401
|
+
// Uses effectiveTextMode for NV conversion so inverted themes (e.g., strong: dark bg in
|
|
402
|
+
// light mode) auto-correct text direction — light text on dark bg, dark text on light bg.
|
|
403
|
+
const textToEngineNv = (nv: number) => effectiveTextMode === 'light' ? 1 - nv : nv;
|
|
159
404
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
405
|
+
const textPrimary = getColor(palette.hue, palette.saturation, dynamicRange,
|
|
406
|
+
clamp(textToEngineNv(textPrimaryNorm ?? neutralDefaults.text.primary) + elevationDelta),
|
|
407
|
+
palette.desaturation, palette.paletteHueGrading);
|
|
163
408
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
409
|
+
const textSecondary = getColor(palette.hue, palette.saturation, dynamicRange,
|
|
410
|
+
clamp(textToEngineNv(textSecondaryNorm ?? neutralDefaults.text.secondary) + elevationDelta),
|
|
411
|
+
palette.desaturation, palette.paletteHueGrading);
|
|
167
412
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
? getColor(
|
|
172
|
-
accentPalette.hue,
|
|
173
|
-
accentPalette.saturation,
|
|
174
|
-
dynamicRange,
|
|
175
|
-
accentKeyNv,
|
|
176
|
-
accentPalette.desaturation,
|
|
177
|
-
accentPalette.paletteHueGrading
|
|
178
|
-
)
|
|
179
|
-
: getColorByContrast(
|
|
180
|
-
accentPalette.hue,
|
|
181
|
-
accentPalette.saturation,
|
|
182
|
-
dynamicRange,
|
|
183
|
-
4.5,
|
|
184
|
-
effectiveTextMode,
|
|
185
|
-
accentPalette.desaturation,
|
|
186
|
-
accentPalette.paletteHueGrading,
|
|
187
|
-
background,
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
// Hover/active states: Shift NV slightly from interactive base
|
|
191
|
-
// In light mode (light bg), go darker; in dark mode (dark bg), go lighter
|
|
192
|
-
const interactiveNv = accentKeyNv ?? (effectiveTextMode === 'light' ? 0.3 : 0.7);
|
|
193
|
-
|
|
194
|
-
const interactiveHover = getColor(
|
|
195
|
-
accentPalette.hue,
|
|
196
|
-
accentPalette.saturation,
|
|
197
|
-
dynamicRange,
|
|
198
|
-
interactiveNv + (effectiveTextMode === 'light' ? -0.05 : 0.05),
|
|
199
|
-
accentPalette.desaturation,
|
|
200
|
-
accentPalette.paletteHueGrading
|
|
201
|
-
);
|
|
413
|
+
const textTertiary = getColor(palette.hue, palette.saturation, dynamicRange,
|
|
414
|
+
clamp(textToEngineNv(textTertiaryNorm ?? neutralDefaults.text.tertiary) + elevationDelta),
|
|
415
|
+
palette.desaturation, palette.paletteHueGrading);
|
|
202
416
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
dynamicRange,
|
|
207
|
-
interactiveNv + (effectiveTextMode === 'light' ? -0.1 : 0.1),
|
|
208
|
-
accentPalette.desaturation,
|
|
209
|
-
accentPalette.paletteHueGrading
|
|
210
|
-
);
|
|
417
|
+
const textDisabled = getColor(palette.hue, palette.saturation, dynamicRange,
|
|
418
|
+
clamp(textToEngineNv(textDisabledNorm ?? neutralDefaults.text.disabled) + elevationDelta),
|
|
419
|
+
palette.desaturation, palette.paletteHueGrading);
|
|
211
420
|
|
|
212
421
|
// Border: Subtle contrast from background
|
|
213
|
-
const borderNv = effectiveTextMode === 'light' ? backgroundNv -
|
|
422
|
+
const borderNv = effectiveTextMode === 'light' ? backgroundNv - BORDER_OFFSET : backgroundNv + BORDER_OFFSET;
|
|
214
423
|
const border = getColor(
|
|
215
424
|
palette.hue,
|
|
216
425
|
palette.saturation,
|
|
217
426
|
dynamicRange,
|
|
218
|
-
|
|
427
|
+
clamp(borderNv),
|
|
219
428
|
palette.desaturation,
|
|
220
429
|
palette.paletteHueGrading
|
|
221
430
|
);
|
|
222
431
|
|
|
223
|
-
//
|
|
224
|
-
//
|
|
432
|
+
// --- Per-palette token computation ---
|
|
433
|
+
// Auto accent NV: derived from text primary position (used when no explicit key color is set)
|
|
434
|
+
const autoAccentNv = clamp(textToEngineNv(textPrimaryNorm ?? neutralDefaults.text.primary));
|
|
435
|
+
|
|
436
|
+
const accentPalette = palettes[1];
|
|
437
|
+
if (!accentPalette) {
|
|
438
|
+
throw new Error('Accent palette (index 1) not found');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const accent = computePaletteTokens(
|
|
442
|
+
accentPalette, ACCENT_DEFAULTS, mode, elevation, dynamicRange,
|
|
443
|
+
elevationDelta, effectiveTextMode, autoAccentNv,
|
|
444
|
+
textPrimary, backgroundElevated,
|
|
445
|
+
);
|
|
446
|
+
|
|
225
447
|
const successPalette = palettes[2];
|
|
226
448
|
const warningPalette = palettes[3];
|
|
227
449
|
const errorPalette = palettes[4];
|
|
228
450
|
|
|
229
|
-
|
|
451
|
+
// Semantic palettes: fall back to accent palette if not present
|
|
230
452
|
const success = successPalette
|
|
231
|
-
? (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
:
|
|
237
|
-
|
|
238
|
-
const warningKeyNv = warningPalette ? resolveKeyNv(warningPalette) : undefined;
|
|
453
|
+
? computePaletteTokens(
|
|
454
|
+
successPalette, SUCCESS_DEFAULTS, mode, elevation, dynamicRange,
|
|
455
|
+
elevationDelta, effectiveTextMode, autoAccentNv,
|
|
456
|
+
textPrimary, backgroundElevated,
|
|
457
|
+
)
|
|
458
|
+
: accent;
|
|
459
|
+
|
|
239
460
|
const warning = warningPalette
|
|
240
|
-
? (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
:
|
|
246
|
-
|
|
247
|
-
const errorKeyNv = errorPalette ? resolveKeyNv(errorPalette) : undefined;
|
|
461
|
+
? computePaletteTokens(
|
|
462
|
+
warningPalette, WARNING_DEFAULTS, mode, elevation, dynamicRange,
|
|
463
|
+
elevationDelta, effectiveTextMode, autoAccentNv,
|
|
464
|
+
textPrimary, backgroundElevated,
|
|
465
|
+
)
|
|
466
|
+
: accent;
|
|
467
|
+
|
|
248
468
|
const error = errorPalette
|
|
249
|
-
? (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
:
|
|
469
|
+
? computePaletteTokens(
|
|
470
|
+
errorPalette, ERROR_DEFAULTS, mode, elevation, dynamicRange,
|
|
471
|
+
elevationDelta, effectiveTextMode, autoAccentNv,
|
|
472
|
+
textPrimary, backgroundElevated,
|
|
473
|
+
)
|
|
474
|
+
: accent;
|
|
255
475
|
|
|
256
476
|
return {
|
|
257
477
|
background,
|
|
258
478
|
backgroundElevated,
|
|
259
479
|
backgroundSunken,
|
|
260
480
|
backgroundInteractive,
|
|
481
|
+
backgroundInteractiveHover,
|
|
482
|
+
backgroundInteractiveActive,
|
|
261
483
|
textPrimary,
|
|
262
484
|
textSecondary,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
interactiveActive,
|
|
485
|
+
textTertiary,
|
|
486
|
+
textDisabled,
|
|
266
487
|
border,
|
|
488
|
+
accent,
|
|
267
489
|
success,
|
|
268
490
|
warning,
|
|
269
491
|
error,
|