@newtonedev/components 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +506 -0
- package/dist/Button/Button.d.ts +23 -0
- package/dist/Button/Button.d.ts.map +1 -0
- package/dist/Button/Button.styles.d.ts +39 -0
- package/dist/Button/Button.styles.d.ts.map +1 -0
- package/dist/Button/Button.types.d.ts +42 -0
- package/dist/Button/Button.types.d.ts.map +1 -0
- package/dist/Button/index.d.ts +3 -0
- package/dist/Button/index.d.ts.map +1 -0
- package/dist/Card/Card.d.ts +4 -0
- package/dist/Card/Card.d.ts.map +1 -0
- package/dist/Card/Card.styles.d.ts +12 -0
- package/dist/Card/Card.styles.d.ts.map +1 -0
- package/dist/Card/Card.types.d.ts +9 -0
- package/dist/Card/Card.types.d.ts.map +1 -0
- package/dist/Card/index.d.ts +3 -0
- package/dist/Card/index.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.d.ts +14 -0
- package/dist/HueSlider/HueSlider.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.styles.d.ts +28 -0
- package/dist/HueSlider/HueSlider.styles.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.types.d.ts +12 -0
- package/dist/HueSlider/HueSlider.types.d.ts.map +1 -0
- package/dist/HueSlider/index.d.ts +3 -0
- package/dist/HueSlider/index.d.ts.map +1 -0
- package/dist/Select/Select.d.ts +11 -0
- package/dist/Select/Select.d.ts.map +1 -0
- package/dist/Select/Select.styles.d.ts +23 -0
- package/dist/Select/Select.styles.d.ts.map +1 -0
- package/dist/Select/Select.types.d.ts +14 -0
- package/dist/Select/Select.types.d.ts.map +1 -0
- package/dist/Select/index.d.ts +3 -0
- package/dist/Select/index.d.ts.map +1 -0
- package/dist/Slider/Slider.d.ts +4 -0
- package/dist/Slider/Slider.d.ts.map +1 -0
- package/dist/Slider/Slider.styles.d.ts +27 -0
- package/dist/Slider/Slider.styles.d.ts.map +1 -0
- package/dist/Slider/Slider.types.d.ts +13 -0
- package/dist/Slider/Slider.types.d.ts.map +1 -0
- package/dist/Slider/index.d.ts +3 -0
- package/dist/Slider/index.d.ts.map +1 -0
- package/dist/TextInput/TextInput.d.ts +4 -0
- package/dist/TextInput/TextInput.d.ts.map +1 -0
- package/dist/TextInput/TextInput.styles.d.ts +23 -0
- package/dist/TextInput/TextInput.styles.d.ts.map +1 -0
- package/dist/TextInput/TextInput.types.d.ts +7 -0
- package/dist/TextInput/TextInput.types.d.ts.map +1 -0
- package/dist/TextInput/index.d.ts +3 -0
- package/dist/TextInput/index.d.ts.map +1 -0
- package/dist/Toggle/Toggle.d.ts +4 -0
- package/dist/Toggle/Toggle.d.ts.map +1 -0
- package/dist/Toggle/Toggle.styles.d.ts +30 -0
- package/dist/Toggle/Toggle.styles.d.ts.map +1 -0
- package/dist/Toggle/Toggle.types.d.ts +9 -0
- package/dist/Toggle/Toggle.types.d.ts.map +1 -0
- package/dist/Toggle/index.d.ts +3 -0
- package/dist/Toggle/index.d.ts.map +1 -0
- package/dist/index.cjs +736 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +719 -0
- package/dist/index.js.map +1 -0
- package/dist/theme/NewtoneProvider.d.ts +33 -0
- package/dist/theme/NewtoneProvider.d.ts.map +1 -0
- package/dist/theme/defaults.d.ts +7 -0
- package/dist/theme/defaults.d.ts.map +1 -0
- package/dist/theme/types.d.ts +56 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/tokens/computeTokens.d.ts +30 -0
- package/dist/tokens/computeTokens.d.ts.map +1 -0
- package/dist/tokens/types.d.ts +31 -0
- package/dist/tokens/types.d.ts.map +1 -0
- package/dist/tokens/useTokens.d.ts +26 -0
- package/dist/tokens/useTokens.d.ts.map +1 -0
- package/package.json +57 -0
- package/src/Button/Button.styles.ts +100 -0
- package/src/Button/Button.tsx +67 -0
- package/src/Button/Button.types.ts +49 -0
- package/src/Button/index.ts +2 -0
- package/src/Card/Card.styles.ts +16 -0
- package/src/Card/Card.tsx +25 -0
- package/src/Card/Card.types.ts +9 -0
- package/src/Card/index.ts +2 -0
- package/src/HueSlider/HueSlider.styles.ts +77 -0
- package/src/HueSlider/HueSlider.tsx +70 -0
- package/src/HueSlider/HueSlider.types.ts +12 -0
- package/src/HueSlider/index.ts +2 -0
- package/src/Select/Select.styles.ts +29 -0
- package/src/Select/Select.tsx +60 -0
- package/src/Select/Select.types.ts +15 -0
- package/src/Select/index.ts +2 -0
- package/src/Slider/Slider.styles.ts +45 -0
- package/src/Slider/Slider.tsx +57 -0
- package/src/Slider/Slider.types.ts +13 -0
- package/src/Slider/index.ts +2 -0
- package/src/TextInput/TextInput.styles.ts +29 -0
- package/src/TextInput/TextInput.tsx +32 -0
- package/src/TextInput/TextInput.types.ts +7 -0
- package/src/TextInput/index.ts +2 -0
- package/src/Toggle/Toggle.styles.ts +45 -0
- package/src/Toggle/Toggle.tsx +42 -0
- package/src/Toggle/Toggle.types.ts +9 -0
- package/src/Toggle/index.ts +2 -0
- package/src/index.ts +49 -0
- package/src/theme/NewtoneProvider.tsx +65 -0
- package/src/theme/defaults.ts +42 -0
- package/src/theme/types.ts +62 -0
- package/src/tokens/computeTokens.ts +217 -0
- package/src/tokens/types.ts +31 -0
- package/src/tokens/useTokens.ts +42 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { createContext, useState, useMemo, useContext } from 'react';
|
|
2
|
+
import type { NewtoneThemeConfig, NewtoneThemeContext, ColorMode, ThemeName } from './types';
|
|
3
|
+
import { DEFAULT_THEME_CONFIG } from './defaults';
|
|
4
|
+
|
|
5
|
+
const ThemeContext = createContext<NewtoneThemeContext | null>(null);
|
|
6
|
+
|
|
7
|
+
export interface NewtoneProviderProps {
|
|
8
|
+
readonly config?: NewtoneThemeConfig;
|
|
9
|
+
readonly initialMode?: ColorMode;
|
|
10
|
+
readonly initialTheme?: ThemeName;
|
|
11
|
+
readonly children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* NewtoneProvider - Provides theme context to all Newtone components
|
|
16
|
+
*
|
|
17
|
+
* Wrap your app with this provider to enable theme and mode switching.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <NewtoneProvider initialMode="light" initialTheme="neutral">
|
|
22
|
+
* <App />
|
|
23
|
+
* </NewtoneProvider>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function NewtoneProvider({
|
|
27
|
+
config = DEFAULT_THEME_CONFIG,
|
|
28
|
+
initialMode = 'light',
|
|
29
|
+
initialTheme = 'neutral',
|
|
30
|
+
children,
|
|
31
|
+
}: NewtoneProviderProps) {
|
|
32
|
+
const [mode, setMode] = useState<ColorMode>(initialMode);
|
|
33
|
+
const [theme, setTheme] = useState<ThemeName>(initialTheme);
|
|
34
|
+
|
|
35
|
+
const value = useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
config,
|
|
38
|
+
mode,
|
|
39
|
+
theme,
|
|
40
|
+
setMode,
|
|
41
|
+
setTheme,
|
|
42
|
+
}),
|
|
43
|
+
[config, mode, theme]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* useNewtoneTheme - Hook to access theme context
|
|
51
|
+
*
|
|
52
|
+
* Must be used within a NewtoneProvider.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { mode, theme, setMode, setTheme } = useNewtoneTheme();
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function useNewtoneTheme(): NewtoneThemeContext {
|
|
60
|
+
const context = useContext(ThemeContext);
|
|
61
|
+
if (!context) {
|
|
62
|
+
throw new Error('useNewtoneTheme must be used within NewtoneProvider');
|
|
63
|
+
}
|
|
64
|
+
return context;
|
|
65
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { NewtoneThemeConfig } from './types';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_NEUTRAL_HUE,
|
|
4
|
+
DEFAULT_NEUTRAL_SATURATION,
|
|
5
|
+
DEFAULT_ACCENT_HUE,
|
|
6
|
+
DEFAULT_ACCENT_SATURATION,
|
|
7
|
+
DEFAULT_SUCCESS_HUE,
|
|
8
|
+
DEFAULT_SUCCESS_SATURATION,
|
|
9
|
+
DEFAULT_WARNING_HUE,
|
|
10
|
+
DEFAULT_WARNING_SATURATION,
|
|
11
|
+
DEFAULT_ERROR_HUE,
|
|
12
|
+
DEFAULT_ERROR_SATURATION,
|
|
13
|
+
} from 'newtone';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default theme configuration matching playground defaults.
|
|
17
|
+
* Palettes: [0] Neutral, [1] Accent, [2] Success, [3] Warning, [4] Error
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_THEME_CONFIG: NewtoneThemeConfig = {
|
|
20
|
+
colorSystem: {
|
|
21
|
+
dynamicRange: {
|
|
22
|
+
lightest: 1,
|
|
23
|
+
darkest: 1,
|
|
24
|
+
},
|
|
25
|
+
palettes: [
|
|
26
|
+
{ hue: DEFAULT_NEUTRAL_HUE, saturation: DEFAULT_NEUTRAL_SATURATION },
|
|
27
|
+
{ hue: DEFAULT_ACCENT_HUE, saturation: DEFAULT_ACCENT_SATURATION },
|
|
28
|
+
{ hue: DEFAULT_SUCCESS_HUE, saturation: DEFAULT_SUCCESS_SATURATION },
|
|
29
|
+
{ hue: DEFAULT_WARNING_HUE, saturation: DEFAULT_WARNING_SATURATION },
|
|
30
|
+
{ hue: DEFAULT_ERROR_HUE, saturation: DEFAULT_ERROR_SATURATION },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
themes: {
|
|
34
|
+
neutral: { paletteIndex: 0, lightModeNv: 0.95, darkModeNv: 0.1 },
|
|
35
|
+
primary: { paletteIndex: 1, lightModeNv: 0.95, darkModeNv: 0.1 },
|
|
36
|
+
secondary: { paletteIndex: 1, lightModeNv: 0.85, darkModeNv: 0.15 },
|
|
37
|
+
strong: { paletteIndex: 0, lightModeNv: 0.1, darkModeNv: 0.95 },
|
|
38
|
+
},
|
|
39
|
+
elevation: {
|
|
40
|
+
offsets: [-0.02, 0, 0.04], // [sunken, default, elevated]
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { DynamicRange, PaletteConfig } from 'newtone';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Color mode: light or dark
|
|
5
|
+
*/
|
|
6
|
+
export type ColorMode = 'light' | 'dark';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Theme names for different UI contexts
|
|
10
|
+
*/
|
|
11
|
+
export type ThemeName = 'neutral' | 'primary' | 'secondary' | 'strong';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Elevation levels for surfaces
|
|
15
|
+
* 0 = sunken, 1 = default, 2 = elevated
|
|
16
|
+
*/
|
|
17
|
+
export type ElevationLevel = 0 | 1 | 2;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Theme mapping: which palette and normalizedValue to use for backgrounds/text
|
|
21
|
+
* Based on playground theme preview logic
|
|
22
|
+
*/
|
|
23
|
+
export interface ThemeMapping {
|
|
24
|
+
readonly paletteIndex: number; // Index into ColorSystemConfig.palettes
|
|
25
|
+
readonly lightModeNv: number; // normalizedValue for light mode background
|
|
26
|
+
readonly darkModeNv: number; // normalizedValue for dark mode background
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Complete color system configuration
|
|
31
|
+
*/
|
|
32
|
+
export interface ColorSystemConfig {
|
|
33
|
+
readonly dynamicRange: DynamicRange;
|
|
34
|
+
readonly palettes: ReadonlyArray<PaletteConfig>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Newtone theme configuration
|
|
39
|
+
*/
|
|
40
|
+
export interface NewtoneThemeConfig {
|
|
41
|
+
readonly colorSystem: ColorSystemConfig;
|
|
42
|
+
readonly themes: {
|
|
43
|
+
readonly neutral: ThemeMapping;
|
|
44
|
+
readonly primary: ThemeMapping;
|
|
45
|
+
readonly secondary: ThemeMapping;
|
|
46
|
+
readonly strong: ThemeMapping;
|
|
47
|
+
};
|
|
48
|
+
readonly elevation: {
|
|
49
|
+
readonly offsets: readonly [number, number, number]; // NV offsets for [sunken, default, elevated]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Current theme context state
|
|
55
|
+
*/
|
|
56
|
+
export interface NewtoneThemeContext {
|
|
57
|
+
readonly config: NewtoneThemeConfig;
|
|
58
|
+
readonly mode: ColorMode;
|
|
59
|
+
readonly theme: ThemeName;
|
|
60
|
+
readonly setMode: (mode: ColorMode) => void;
|
|
61
|
+
readonly setTheme: (theme: ThemeName) => void;
|
|
62
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { getColor, getColorByContrast } from 'newtone';
|
|
2
|
+
import type { ColorSystemConfig, ColorMode, ThemeMapping, ElevationLevel } from '../theme/types';
|
|
3
|
+
import type { ResolvedTokens } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Compute design tokens for a specific mode/theme/elevation combination.
|
|
7
|
+
*
|
|
8
|
+
* This function calls the Newtone engine to generate all necessary color tokens
|
|
9
|
+
* based on the current theme context. All colors are computed on-demand using
|
|
10
|
+
* the pure functions from the engine.
|
|
11
|
+
*
|
|
12
|
+
* @param config - Complete color system configuration (dynamic range + palettes)
|
|
13
|
+
* @param mode - Current color mode ('light' or 'dark')
|
|
14
|
+
* @param themeMapping - Theme configuration (which palette and NV to use)
|
|
15
|
+
* @param elevation - Elevation level (0=sunken, 1=default, 2=elevated)
|
|
16
|
+
* @param elevationOffsets - NV offsets for each elevation level
|
|
17
|
+
* @returns Resolved design tokens with all necessary colors
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const tokens = computeTokens(
|
|
22
|
+
* config.colorSystem,
|
|
23
|
+
* 'light',
|
|
24
|
+
* config.themes.neutral,
|
|
25
|
+
* 1,
|
|
26
|
+
* config.elevation.offsets
|
|
27
|
+
* );
|
|
28
|
+
* console.log(tokens.background.srgb); // { r: 0.95, g: 0.95, b: 0.95 }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function computeTokens(
|
|
32
|
+
config: ColorSystemConfig,
|
|
33
|
+
mode: ColorMode,
|
|
34
|
+
themeMapping: ThemeMapping,
|
|
35
|
+
elevation: ElevationLevel,
|
|
36
|
+
elevationOffsets: readonly [number, number, number]
|
|
37
|
+
): ResolvedTokens {
|
|
38
|
+
const { dynamicRange, palettes } = config;
|
|
39
|
+
const palette = palettes[themeMapping.paletteIndex];
|
|
40
|
+
|
|
41
|
+
if (!palette) {
|
|
42
|
+
throw new Error(`Palette at index ${themeMapping.paletteIndex} not found`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Determine base NV for this mode + elevation
|
|
46
|
+
const baseNv = mode === 'light' ? themeMapping.lightModeNv : themeMapping.darkModeNv;
|
|
47
|
+
const elevationOffset = elevationOffsets[elevation];
|
|
48
|
+
const backgroundNv = Math.max(0, Math.min(1, baseNv + elevationOffset));
|
|
49
|
+
|
|
50
|
+
// Derive effective text mode from actual background lightness.
|
|
51
|
+
// This handles inverted themes (e.g., strong: dark bg in light mode)
|
|
52
|
+
// where the mode flag doesn't match the background's visual lightness.
|
|
53
|
+
const effectiveTextMode: ColorMode = backgroundNv >= 0.5 ? 'light' : 'dark';
|
|
54
|
+
|
|
55
|
+
// Compute background colors for current elevation
|
|
56
|
+
const background = getColor(
|
|
57
|
+
palette.hue,
|
|
58
|
+
palette.saturation,
|
|
59
|
+
dynamicRange,
|
|
60
|
+
backgroundNv,
|
|
61
|
+
palette.desaturation,
|
|
62
|
+
palette.paletteHueGrading
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Compute elevated surface (always at elevation 2 offset)
|
|
66
|
+
const elevatedNv = Math.max(0, Math.min(1, baseNv + elevationOffsets[2]));
|
|
67
|
+
const backgroundElevated = getColor(
|
|
68
|
+
palette.hue,
|
|
69
|
+
palette.saturation,
|
|
70
|
+
dynamicRange,
|
|
71
|
+
elevatedNv,
|
|
72
|
+
palette.desaturation,
|
|
73
|
+
palette.paletteHueGrading
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Compute sunken surface (always at elevation 0 offset)
|
|
77
|
+
const sunkenNv = Math.max(0, Math.min(1, baseNv + elevationOffsets[0]));
|
|
78
|
+
const backgroundSunken = getColor(
|
|
79
|
+
palette.hue,
|
|
80
|
+
palette.saturation,
|
|
81
|
+
dynamicRange,
|
|
82
|
+
sunkenNv,
|
|
83
|
+
palette.desaturation,
|
|
84
|
+
palette.paletteHueGrading
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Compute text colors with WCAG contrast requirements
|
|
88
|
+
// Primary text: WCAG AA (4.5:1 for body text)
|
|
89
|
+
const textPrimary = getColorByContrast(
|
|
90
|
+
palette.hue,
|
|
91
|
+
palette.saturation,
|
|
92
|
+
dynamicRange,
|
|
93
|
+
4.5,
|
|
94
|
+
effectiveTextMode,
|
|
95
|
+
palette.desaturation,
|
|
96
|
+
palette.paletteHueGrading
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Secondary text: Lower contrast (3.0:1 for captions)
|
|
100
|
+
const textSecondary = getColorByContrast(
|
|
101
|
+
palette.hue,
|
|
102
|
+
palette.saturation,
|
|
103
|
+
dynamicRange,
|
|
104
|
+
3.0,
|
|
105
|
+
effectiveTextMode,
|
|
106
|
+
palette.desaturation,
|
|
107
|
+
palette.paletteHueGrading
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Interactive colors: Use accent palette (index 1)
|
|
111
|
+
const accentPalette = palettes[1];
|
|
112
|
+
|
|
113
|
+
if (!accentPalette) {
|
|
114
|
+
throw new Error('Accent palette (index 1) not found');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Interactive base: WCAG AA contrast against current background
|
|
118
|
+
const interactive = getColorByContrast(
|
|
119
|
+
accentPalette.hue,
|
|
120
|
+
accentPalette.saturation,
|
|
121
|
+
dynamicRange,
|
|
122
|
+
4.5,
|
|
123
|
+
effectiveTextMode,
|
|
124
|
+
accentPalette.desaturation,
|
|
125
|
+
accentPalette.paletteHueGrading
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Hover/active states: Shift NV slightly from interactive base
|
|
129
|
+
// In light mode (light bg), go darker; in dark mode (dark bg), go lighter
|
|
130
|
+
const interactiveNv = effectiveTextMode === 'light' ? 0.3 : 0.7;
|
|
131
|
+
|
|
132
|
+
const interactiveHover = getColor(
|
|
133
|
+
accentPalette.hue,
|
|
134
|
+
accentPalette.saturation,
|
|
135
|
+
dynamicRange,
|
|
136
|
+
interactiveNv + (effectiveTextMode === 'light' ? -0.05 : 0.05),
|
|
137
|
+
accentPalette.desaturation,
|
|
138
|
+
accentPalette.paletteHueGrading
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const interactiveActive = getColor(
|
|
142
|
+
accentPalette.hue,
|
|
143
|
+
accentPalette.saturation,
|
|
144
|
+
dynamicRange,
|
|
145
|
+
interactiveNv + (effectiveTextMode === 'light' ? -0.1 : 0.1),
|
|
146
|
+
accentPalette.desaturation,
|
|
147
|
+
accentPalette.paletteHueGrading
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Border: Subtle contrast from background
|
|
151
|
+
const borderNv = effectiveTextMode === 'light' ? backgroundNv - 0.1 : backgroundNv + 0.1;
|
|
152
|
+
const border = getColor(
|
|
153
|
+
palette.hue,
|
|
154
|
+
palette.saturation,
|
|
155
|
+
dynamicRange,
|
|
156
|
+
Math.max(0, Math.min(1, borderNv)),
|
|
157
|
+
palette.desaturation,
|
|
158
|
+
palette.paletteHueGrading
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Semantic status colors: success (palette 2), warning (palette 3), error (palette 4)
|
|
162
|
+
// Each computed at WCAG AA contrast against the mode background, same as interactive
|
|
163
|
+
const successPalette = palettes[2];
|
|
164
|
+
const warningPalette = palettes[3];
|
|
165
|
+
const errorPalette = palettes[4];
|
|
166
|
+
|
|
167
|
+
const success = successPalette
|
|
168
|
+
? getColorByContrast(
|
|
169
|
+
successPalette.hue,
|
|
170
|
+
successPalette.saturation,
|
|
171
|
+
dynamicRange,
|
|
172
|
+
4.5,
|
|
173
|
+
effectiveTextMode,
|
|
174
|
+
successPalette.desaturation,
|
|
175
|
+
successPalette.paletteHueGrading
|
|
176
|
+
)
|
|
177
|
+
: interactive; // Fallback to interactive if palette missing
|
|
178
|
+
|
|
179
|
+
const warning = warningPalette
|
|
180
|
+
? getColorByContrast(
|
|
181
|
+
warningPalette.hue,
|
|
182
|
+
warningPalette.saturation,
|
|
183
|
+
dynamicRange,
|
|
184
|
+
4.5,
|
|
185
|
+
effectiveTextMode,
|
|
186
|
+
warningPalette.desaturation,
|
|
187
|
+
warningPalette.paletteHueGrading
|
|
188
|
+
)
|
|
189
|
+
: interactive;
|
|
190
|
+
|
|
191
|
+
const error = errorPalette
|
|
192
|
+
? getColorByContrast(
|
|
193
|
+
errorPalette.hue,
|
|
194
|
+
errorPalette.saturation,
|
|
195
|
+
dynamicRange,
|
|
196
|
+
4.5,
|
|
197
|
+
effectiveTextMode,
|
|
198
|
+
errorPalette.desaturation,
|
|
199
|
+
errorPalette.paletteHueGrading
|
|
200
|
+
)
|
|
201
|
+
: interactive;
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
background,
|
|
205
|
+
backgroundElevated,
|
|
206
|
+
backgroundSunken,
|
|
207
|
+
textPrimary,
|
|
208
|
+
textSecondary,
|
|
209
|
+
interactive,
|
|
210
|
+
interactiveHover,
|
|
211
|
+
interactiveActive,
|
|
212
|
+
border,
|
|
213
|
+
success,
|
|
214
|
+
warning,
|
|
215
|
+
error,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ColorResult } from 'newtone';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolved design tokens for a specific mode/theme/elevation combination
|
|
5
|
+
*/
|
|
6
|
+
export interface ResolvedTokens {
|
|
7
|
+
/** Background color for the current surface */
|
|
8
|
+
readonly background: ColorResult;
|
|
9
|
+
/** Background color for elevated surfaces (cards, modals) */
|
|
10
|
+
readonly backgroundElevated: ColorResult;
|
|
11
|
+
/** Background color for sunken surfaces (input fields, wells) */
|
|
12
|
+
readonly backgroundSunken: ColorResult;
|
|
13
|
+
/** Primary text color (high contrast for body text) */
|
|
14
|
+
readonly textPrimary: ColorResult;
|
|
15
|
+
/** Secondary text color (lower contrast for captions, labels) */
|
|
16
|
+
readonly textSecondary: ColorResult;
|
|
17
|
+
/** Interactive element color (buttons, links) */
|
|
18
|
+
readonly interactive: ColorResult;
|
|
19
|
+
/** Interactive element hover state */
|
|
20
|
+
readonly interactiveHover: ColorResult;
|
|
21
|
+
/** Interactive element active/pressed state */
|
|
22
|
+
readonly interactiveActive: ColorResult;
|
|
23
|
+
/** Border color for subtle separators */
|
|
24
|
+
readonly border: ColorResult;
|
|
25
|
+
/** Success state color (from success palette, index 2) */
|
|
26
|
+
readonly success: ColorResult;
|
|
27
|
+
/** Warning state color (from warning palette, index 3) */
|
|
28
|
+
readonly warning: ColorResult;
|
|
29
|
+
/** Error/destructive state color (from error palette, index 4) */
|
|
30
|
+
readonly error: ColorResult;
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useNewtoneTheme } from '../theme/NewtoneProvider';
|
|
3
|
+
import { computeTokens } from './computeTokens';
|
|
4
|
+
import type { ElevationLevel } from '../theme/types';
|
|
5
|
+
import type { ResolvedTokens } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook to compute design tokens for the current theme/mode/elevation.
|
|
9
|
+
*
|
|
10
|
+
* This hook automatically recomputes tokens when the theme configuration,
|
|
11
|
+
* mode, theme name, or elevation changes. Results are memoized to avoid
|
|
12
|
+
* unnecessary computation.
|
|
13
|
+
*
|
|
14
|
+
* @param elevation - Elevation level (0=sunken, 1=default, 2=elevated)
|
|
15
|
+
* @returns Resolved design tokens with all necessary colors
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* function Button() {
|
|
20
|
+
* const tokens = useTokens(1); // Default elevation
|
|
21
|
+
* return (
|
|
22
|
+
* <button style={{ backgroundColor: srgbToHex(tokens.interactive.srgb) }}>
|
|
23
|
+
* Click me
|
|
24
|
+
* </button>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function useTokens(elevation: ElevationLevel = 1): ResolvedTokens {
|
|
30
|
+
const { config, mode, theme } = useNewtoneTheme();
|
|
31
|
+
|
|
32
|
+
return useMemo(() => {
|
|
33
|
+
const themeMapping = config.themes[theme];
|
|
34
|
+
return computeTokens(
|
|
35
|
+
config.colorSystem,
|
|
36
|
+
mode,
|
|
37
|
+
themeMapping,
|
|
38
|
+
elevation,
|
|
39
|
+
config.elevation.offsets
|
|
40
|
+
);
|
|
41
|
+
}, [config, mode, theme, elevation]);
|
|
42
|
+
}
|