@titan-design/react-ui 0.4.0 → 0.6.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/dist/MesoStatusCard-CQk71hSi.d.mts +77 -0
- package/dist/MesoStatusCard-CQk71hSi.d.ts +77 -0
- package/dist/bodymap.d.mts +246 -3
- package/dist/bodymap.d.ts +246 -3
- package/dist/bodymap.js +232 -185
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +1963 -11
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +193 -744
- package/dist/index.d.ts +193 -744
- package/dist/index.js +1551 -1856
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10770 -8213
- package/dist/index.mjs.map +1 -1
- package/dist/pages-D7Jlssvp.d.ts +791 -0
- package/dist/pages-MO0JCySL.d.mts +791 -0
- package/dist/pages.d.mts +4 -0
- package/dist/pages.d.ts +4 -0
- package/dist/pages.js +6694 -0
- package/dist/pages.js.map +1 -0
- package/dist/pages.mjs +6674 -0
- package/dist/pages.mjs.map +1 -0
- package/dist/theme/index.d.mts +473 -211
- package/dist/theme/index.d.ts +473 -211
- package/dist/theme/index.js +341 -127
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +2085 -2
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +148 -139
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +582 -1
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +11 -7
- package/src/components/custom/DateTime/DateTime.stories.tsx +82 -6
- package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
- package/src/components/custom/DateTime/DateTime.tsx +55 -15
- package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.tsx +6 -3
- package/src/components/custom/Scatter/Scatter.tsx +3 -5
- package/src/components/custom/Treemap/Treemap.tsx +3 -5
- package/src/components/custom/Typography/Typography.stories.tsx +45 -27
- package/src/components/custom/Typography/Typography.test.tsx +13 -3
- package/src/components/custom/Typography/Typography.tsx +5 -0
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +2 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +1 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +6 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
- package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
- package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.tsx +7 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +19 -5
- package/src/components/custom/Workout/InputBar.tsx +2 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +112 -60
- package/src/components/custom/Workout/IntensityBar.tsx +113 -58
- package/src/components/custom/Workout/MesoCard.tsx +8 -3
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
- package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
- package/src/components/custom/Workout/PlaceholderStrip.tsx +5 -9
- package/src/components/custom/Workout/PrBadge.tsx +7 -4
- package/src/components/custom/Workout/PrHistoryModal.tsx +6 -5
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +2 -1
- package/src/components/custom/Workout/README.md +50 -0
- package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
- package/src/components/custom/Workout/RestTimer.test.tsx +41 -10
- package/src/components/custom/Workout/RestTimer.tsx +50 -44
- package/src/components/custom/Workout/SetRow.tsx +12 -2
- package/src/components/custom/Workout/Sparkline.tsx +2 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
- package/src/components/custom/Workout/StatusDot.tsx +12 -9
- package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
- package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +69 -2
- package/src/components/custom/Workout/TempoDisplay.test.tsx +35 -0
- package/src/components/custom/Workout/TempoDisplay.tsx +109 -3
- package/src/components/custom/Workout/VelocityStrip.test.tsx +89 -2
- package/src/components/custom/Workout/VelocityStrip.tsx +203 -38
- package/src/components/custom/Workout/WeekRow.test.tsx +2 -2
- package/src/components/custom/Workout/WeekRow.tsx +3 -2
- package/src/components/custom/Workout/WeightBadge.test.tsx +2 -2
- package/src/components/custom/Workout/WeightBadge.tsx +8 -4
- package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
- package/src/components/custom/Workout/icons.tsx +7 -0
- package/src/components/custom/Workout/index.ts +38 -47
- package/src/components/icons/SvgIcon.tsx +50 -0
- package/src/components/icons/icons.stories.tsx +57 -0
- package/src/components/icons/icons.test.tsx +43 -0
- package/src/components/icons/icons.tsx +45 -0
- package/src/components/icons/index.ts +4 -0
- package/src/components/index.ts +6 -0
- package/src/components/shell/BrandLockup.stories.tsx +27 -0
- package/src/components/shell/BrandLockup.test.tsx +23 -0
- package/src/components/shell/BrandLockup.tsx +47 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
- package/src/components/shell/DeviceIndicator.test.tsx +26 -0
- package/src/components/shell/DeviceIndicator.tsx +52 -0
- package/src/components/shell/DeviceMenu.stories.tsx +47 -0
- package/src/components/shell/DeviceMenu.test.tsx +40 -0
- package/src/components/shell/DeviceMenu.tsx +65 -0
- package/src/components/shell/DeviceRow.stories.tsx +38 -0
- package/src/components/shell/DeviceRow.test.tsx +37 -0
- package/src/components/shell/DeviceRow.tsx +58 -0
- package/src/components/shell/README.md +86 -0
- package/src/components/shell/SessionStatePill.stories.tsx +27 -0
- package/src/components/shell/SessionStatePill.test.tsx +25 -0
- package/src/components/shell/SessionStatePill.tsx +46 -0
- package/src/components/shell/TopBar.stories.tsx +49 -0
- package/src/components/shell/TopBar.test.tsx +36 -0
- package/src/components/shell/TopBar.tsx +89 -0
- package/src/components/shell/index.ts +8 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +37 -1
- package/src/components/ui/indicator/Indicator.test.tsx +19 -1
- package/src/components/ui/indicator/Indicator.tsx +45 -6
- package/src/components/ui/indicator/index.ts +1 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -2
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
- package/src/index.ts +3 -2
- package/src/pages.ts +61 -0
- package/src/test/stories-smoke.test.tsx +50 -0
- package/src/theme/ColorSystem.stories.tsx +323 -0
- package/src/theme/Colors.stories.tsx +69 -1
- package/src/theme/barrel.ts +54 -0
- package/src/theme/config.ts +16 -3
- package/src/theme/extracted-colors-dataviz.ts +21 -0
- package/src/theme/extracted-colors-ui.ts +20 -0
- package/src/theme/global.css +64 -52
- package/src/theme/gradients.test.ts +25 -0
- package/src/theme/gradients.ts +36 -0
- package/src/theme/index.ts +8 -43
- package/src/theme/native-theming-guard.test.ts +117 -0
- package/src/theme/tokens/primitives.test.ts +232 -0
- package/src/theme/tokens/primitives.ts +247 -0
- package/src/theme/tokens/semantic.ts +93 -81
- package/src/theme/workout-tokens.ts +22 -21
- package/src/utils/avatar-color.ts +3 -3
- package/tailwind.config.js +10 -3
- package/dist/bodymap-BhG55xHM.d.mts +0 -318
- package/dist/bodymap-BhG55xHM.d.ts +0 -318
- package/dist/chunk-2SISKTWM.mjs +0 -1027
- package/dist/chunk-2SISKTWM.mjs.map +0 -1
- package/dist/chunk-7XPB4NAO.mjs +0 -1013
- package/dist/chunk-7XPB4NAO.mjs.map +0 -1
- package/dist/chunk-SNVKL5WZ.mjs +0 -883
- package/dist/chunk-SNVKL5WZ.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { View, Text } from 'react-native'
|
|
3
|
-
import { primitiveColors, discreteRainbow } from './tokens/primitives'
|
|
3
|
+
import { primitiveColors, discreteRainbow, primitiveRamps, categoricalPalette } from './tokens/primitives'
|
|
4
4
|
import { semanticColorsLight, semanticColorsDark } from './tokens/semantic'
|
|
5
5
|
|
|
6
6
|
const meta: Meta = {
|
|
@@ -236,6 +236,74 @@ export const DataVisualizationColors: StoryObj = {
|
|
|
236
236
|
),
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
function CategoricalRow({ name, colors }: { name: string; colors: readonly string[] }) {
|
|
240
|
+
return (
|
|
241
|
+
<View style={{ marginBottom: 24 }}>
|
|
242
|
+
<Text className="text-lg font-bold text-text-primary mb-3">
|
|
243
|
+
{name} <Text className="text-text-secondary text-sm">({colors.length})</Text>
|
|
244
|
+
</Text>
|
|
245
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
|
|
246
|
+
{colors.map((color, index) => (
|
|
247
|
+
<View key={index} style={{ alignItems: 'center' }}>
|
|
248
|
+
<View
|
|
249
|
+
style={{
|
|
250
|
+
width: 56,
|
|
251
|
+
height: 56,
|
|
252
|
+
borderRadius: 8,
|
|
253
|
+
backgroundColor: color,
|
|
254
|
+
borderWidth: 1,
|
|
255
|
+
borderColor: '#374151',
|
|
256
|
+
}}
|
|
257
|
+
/>
|
|
258
|
+
<Text className="text-text-secondary text-xs mt-1">{color.toUpperCase()}</Text>
|
|
259
|
+
</View>
|
|
260
|
+
))}
|
|
261
|
+
</View>
|
|
262
|
+
</View>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const TonalRamps: StoryObj = {
|
|
267
|
+
render: () => (
|
|
268
|
+
<View style={{ padding: 24 }}>
|
|
269
|
+
<Text className="text-2xl font-bold text-text-primary mb-6">
|
|
270
|
+
Tonal Ramps (Foundations)
|
|
271
|
+
</Text>
|
|
272
|
+
<Text className="text-text-secondary mb-6">
|
|
273
|
+
Seven OKLCH-generated hue ramps, 11 perceptual steps each (50 → 950). Built with hue-torsion and a
|
|
274
|
+
chroma arc; amber absorbs the former yellow (lemon → warm body) and cyan absorbs the former steel.
|
|
275
|
+
</Text>
|
|
276
|
+
|
|
277
|
+
<ColorScale name="Red" colors={primitiveRamps.red} />
|
|
278
|
+
<ColorScale name="Orange" colors={primitiveRamps.orange} />
|
|
279
|
+
<ColorScale name="Amber" colors={primitiveRamps.amber} />
|
|
280
|
+
<ColorScale name="Green" colors={primitiveRamps.green} />
|
|
281
|
+
<ColorScale name="Cyan" colors={primitiveRamps.cyan} />
|
|
282
|
+
<ColorScale name="Blue" colors={primitiveRamps.blue} />
|
|
283
|
+
<ColorScale name="Magenta" colors={primitiveRamps.magenta} />
|
|
284
|
+
</View>
|
|
285
|
+
),
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export const CategoricalPalette: StoryObj = {
|
|
289
|
+
render: () => (
|
|
290
|
+
<View style={{ padding: 24 }}>
|
|
291
|
+
<Text className="text-2xl font-bold text-text-primary mb-6">
|
|
292
|
+
Categorical Palette
|
|
293
|
+
</Text>
|
|
294
|
+
<Text className="text-text-secondary mb-6">
|
|
295
|
+
Ordered, colorblind-safe qualitative series (worst-case deut/protanopia floor 8 through the first
|
|
296
|
+
six colors; the 7th is extended — pair it with a legend). Series index is stable. Use{' '}
|
|
297
|
+
<Text className="font-semibold">default</Text> on neutral/light surfaces (also legible under black
|
|
298
|
+
text) and <Text className="font-semibold">dark</Text> where white text sits on the swatch.
|
|
299
|
+
</Text>
|
|
300
|
+
|
|
301
|
+
<CategoricalRow name="Default" colors={categoricalPalette.default} />
|
|
302
|
+
<CategoricalRow name="Dark (white text)" colors={categoricalPalette.dark} />
|
|
303
|
+
</View>
|
|
304
|
+
),
|
|
305
|
+
}
|
|
306
|
+
|
|
239
307
|
export const BorderColors: StoryObj = {
|
|
240
308
|
render: () => (
|
|
241
309
|
<View style={{ padding: 24 }}>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Theme surface WITHOUT the ThemeProvider value (dep hygiene, titan 0.5.0).
|
|
2
|
+
//
|
|
3
|
+
// `ThemeProvider` imports `nativewind` (`vars()`). Re-exporting it from the root
|
|
4
|
+
// barrel dragged the whole nativewind import chain into every consumer of
|
|
5
|
+
// `@titan-design/react-ui` — the web SPA paid for a native-only provider. This
|
|
6
|
+
// file is the nativewind-free theme surface: the root barrel re-exports THIS,
|
|
7
|
+
// and the `/theme` subpath (`./index.ts`) layers the `ThemeProvider` value on
|
|
8
|
+
// top for the native consumers that deliberately import it.
|
|
9
|
+
export * from './tokens'
|
|
10
|
+
export * from './config'
|
|
11
|
+
export * from './shadows'
|
|
12
|
+
export * from './elevation'
|
|
13
|
+
export * from './manifest'
|
|
14
|
+
export { resolveColor } from './resolve-color'
|
|
15
|
+
export { linearGradient, surfaceGradient, type GradientStyle } from './gradients'
|
|
16
|
+
|
|
17
|
+
// ThemeProvider TYPES may stay on the root (erased at build — no nativewind
|
|
18
|
+
// pull). Only the ThemeProvider VALUE moves to the `/theme` subpath.
|
|
19
|
+
export type { ThemeProviderProps, ThemeProviderMode } from './ThemeProvider'
|
|
20
|
+
|
|
21
|
+
// Re-export commonly used items
|
|
22
|
+
export { getSemanticColors, type ThemeMode } from './tokens/semantic'
|
|
23
|
+
export { getThemeCSSVars, gluestackConfig } from './config'
|
|
24
|
+
export {
|
|
25
|
+
neumorphicShadows,
|
|
26
|
+
// Color math utilities (HSV-based)
|
|
27
|
+
lighten,
|
|
28
|
+
darken,
|
|
29
|
+
getHoverColors,
|
|
30
|
+
isDark,
|
|
31
|
+
hexToRgb,
|
|
32
|
+
rgbToHsv,
|
|
33
|
+
// Shadow creation functions
|
|
34
|
+
createRaisedShadow,
|
|
35
|
+
createRaisedHoverShadow,
|
|
36
|
+
createPressedShadow,
|
|
37
|
+
createPressedHoverShadow,
|
|
38
|
+
createFlatShadow,
|
|
39
|
+
shadowColors,
|
|
40
|
+
type ShadowIntensity,
|
|
41
|
+
type ShadowSurface,
|
|
42
|
+
} from './shadows'
|
|
43
|
+
export {
|
|
44
|
+
getElevationConfig,
|
|
45
|
+
getElevationSurface,
|
|
46
|
+
getElevationShadow,
|
|
47
|
+
getBaseSurfaceColor,
|
|
48
|
+
getValidatedElevation,
|
|
49
|
+
componentElevationRanges,
|
|
50
|
+
type ElevationLevel,
|
|
51
|
+
type ElevationConfig,
|
|
52
|
+
type ComponentType,
|
|
53
|
+
} from './elevation'
|
|
54
|
+
export * from './presets'
|
package/src/theme/config.ts
CHANGED
|
@@ -14,10 +14,11 @@ import { semanticColorsLight, semanticColorsDark, type ThemeMode } from './token
|
|
|
14
14
|
const themeIndependentCSSVars = {
|
|
15
15
|
// RGB decomposed values for glow shadows
|
|
16
16
|
'--color-brand-primary-rgb': '255, 121, 0',
|
|
17
|
-
'--color-brand-secondary-rgb': '
|
|
18
|
-
'--color-status-success-rgb': '
|
|
17
|
+
'--color-brand-secondary-rgb': '48, 123, 155',
|
|
18
|
+
'--color-status-success-rgb': '46, 213, 115',
|
|
19
19
|
'--color-status-error-rgb': '209, 67, 67',
|
|
20
|
-
'--color-status-
|
|
20
|
+
'--color-status-error-vivid-rgb': '255, 71, 87',
|
|
21
|
+
'--color-status-warning-rgb': '249, 180, 21',
|
|
21
22
|
'--color-status-info-rgb': '33, 150, 243',
|
|
22
23
|
'--color-background-base-rgb': '16, 16, 16',
|
|
23
24
|
|
|
@@ -59,6 +60,11 @@ export const lightThemeCSSVars = {
|
|
|
59
60
|
'--color-status-info': semanticColorsLight['status-info'],
|
|
60
61
|
'--color-status-info-subtle': semanticColorsLight['status-info-subtle'],
|
|
61
62
|
|
|
63
|
+
'--color-status-error-vivid': semanticColorsLight['status-error-vivid'],
|
|
64
|
+
'--color-status-error-vivid-light': semanticColorsLight['status-error-vivid-light'],
|
|
65
|
+
'--color-status-error-vivid-dark': semanticColorsLight['status-error-vivid-dark'],
|
|
66
|
+
'--color-status-error-vivid-subtle': semanticColorsLight['status-error-vivid-subtle'],
|
|
67
|
+
|
|
62
68
|
'--color-text-primary': semanticColorsLight['text-primary'],
|
|
63
69
|
'--color-text-secondary': semanticColorsLight['text-secondary'],
|
|
64
70
|
'--color-text-tertiary': semanticColorsLight['text-tertiary'],
|
|
@@ -77,6 +83,7 @@ export const lightThemeCSSVars = {
|
|
|
77
83
|
'--color-border-default': semanticColorsLight['border-default'],
|
|
78
84
|
'--color-border-subtle': semanticColorsLight['border-subtle'],
|
|
79
85
|
'--color-border-strong': semanticColorsLight['border-strong'],
|
|
86
|
+
'--color-border-prominent': semanticColorsLight['border-prominent'],
|
|
80
87
|
'--color-border-focus': semanticColorsLight['border-focus'],
|
|
81
88
|
|
|
82
89
|
'--color-interactive-hover': semanticColorsLight['interactive-hover'],
|
|
@@ -172,6 +179,11 @@ export const darkThemeCSSVars = {
|
|
|
172
179
|
'--color-status-info': semanticColorsDark['status-info'],
|
|
173
180
|
'--color-status-info-subtle': semanticColorsDark['status-info-subtle'],
|
|
174
181
|
|
|
182
|
+
'--color-status-error-vivid': semanticColorsDark['status-error-vivid'],
|
|
183
|
+
'--color-status-error-vivid-light': semanticColorsDark['status-error-vivid-light'],
|
|
184
|
+
'--color-status-error-vivid-dark': semanticColorsDark['status-error-vivid-dark'],
|
|
185
|
+
'--color-status-error-vivid-subtle': semanticColorsDark['status-error-vivid-subtle'],
|
|
186
|
+
|
|
175
187
|
'--color-text-primary': semanticColorsDark['text-primary'],
|
|
176
188
|
'--color-text-secondary': semanticColorsDark['text-secondary'],
|
|
177
189
|
'--color-text-tertiary': semanticColorsDark['text-tertiary'],
|
|
@@ -190,6 +202,7 @@ export const darkThemeCSSVars = {
|
|
|
190
202
|
'--color-border-default': semanticColorsDark['border-default'],
|
|
191
203
|
'--color-border-subtle': semanticColorsDark['border-subtle'],
|
|
192
204
|
'--color-border-strong': semanticColorsDark['border-strong'],
|
|
205
|
+
'--color-border-prominent': semanticColorsDark['border-prominent'],
|
|
193
206
|
'--color-border-focus': semanticColorsDark['border-focus'],
|
|
194
207
|
|
|
195
208
|
'--color-interactive-hover': semanticColorsDark['interactive-hover'],
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-viz / status / brand fills, sourced from the color foundations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { categoricalPalette, primitiveRamps as ramp } from './tokens/primitives'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Titan categorical fallback palette shared by Treemap and Scatter — the
|
|
9
|
+
* canonical {@link categoricalPalette} (ordered, nested-stable, CVD-safe).
|
|
10
|
+
* Components index into it modulo length, so the 7-color series is sufficient.
|
|
11
|
+
*/
|
|
12
|
+
export const DATAVIZ_CATEGORICAL_PALETTE = categoricalPalette.default
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* MesoCard / MesoStatusCard 3px top-accent gradient stops (dark → primary →
|
|
16
|
+
* light), on the orange ramp that brand-primary (orange-400) lives in.
|
|
17
|
+
*/
|
|
18
|
+
export const MESO_ACCENT_GRADIENT_DARK = ramp.orange[500]
|
|
19
|
+
export const MESO_ACCENT_GRADIENT_LIGHT = ramp.orange[300]
|
|
20
|
+
|
|
21
|
+
export const WORKOUT_PILL_DELOAD = ramp.magenta[600]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI-atom fills, sourced from the color foundations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { categoricalPalette, primitiveRamps as ramp } from './tokens/primitives'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Spinner `color="primary"` fill — keeps the spinner a cool, on-palette
|
|
9
|
+
* loading accent.
|
|
10
|
+
*/
|
|
11
|
+
export const SPINNER_PRIMARY = ramp.cyan[600]
|
|
12
|
+
|
|
13
|
+
export const SPINNER_SECONDARY = ramp.green[400]
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Categorical avatar palette — deterministic per-name fill, the canonical
|
|
17
|
+
* ordered {@link categoricalPalette} (CVD-safe). Order is preserved for stable
|
|
18
|
+
* hashing.
|
|
19
|
+
*/
|
|
20
|
+
export const AVATAR_CATEGORICAL_COLORS = categoricalPalette.default
|
package/src/theme/global.css
CHANGED
|
@@ -16,36 +16,42 @@
|
|
|
16
16
|
/* Dark mode (default) */
|
|
17
17
|
:root {
|
|
18
18
|
--color-brand-primary: #FF7900;
|
|
19
|
-
--color-brand-primary-light: #
|
|
20
|
-
--color-brand-primary-dark: #
|
|
19
|
+
--color-brand-primary-light: #FFA063;
|
|
20
|
+
--color-brand-primary-dark: #DA5F00;
|
|
21
21
|
--color-brand-primary-subtle: rgba(255, 121, 0, 0.12);
|
|
22
|
-
--color-brand-secondary: #
|
|
23
|
-
--color-brand-secondary-light: #
|
|
24
|
-
--color-brand-secondary-dark: #
|
|
25
|
-
--color-brand-secondary-subtle: rgba(
|
|
26
|
-
--color-brand-primary-hover: #
|
|
27
|
-
--color-brand-primary-active: #
|
|
28
|
-
--color-brand-secondary-hover: #
|
|
29
|
-
--color-brand-secondary-active: #
|
|
22
|
+
--color-brand-secondary: #307B9B;
|
|
23
|
+
--color-brand-secondary-light: #2697B7;
|
|
24
|
+
--color-brand-secondary-dark: #2A617F;
|
|
25
|
+
--color-brand-secondary-subtle: rgba(48, 123, 155, 0.12);
|
|
26
|
+
--color-brand-primary-hover: #FFA063;
|
|
27
|
+
--color-brand-primary-active: #FFC7A2;
|
|
28
|
+
--color-brand-secondary-hover: #2697B7;
|
|
29
|
+
--color-brand-secondary-active: #01B5D1;
|
|
30
30
|
|
|
31
31
|
--color-on-brand-primary: #FFFFFF;
|
|
32
32
|
--color-on-brand-secondary: #FFFFFF;
|
|
33
33
|
|
|
34
|
-
--color-status-success: #
|
|
35
|
-
--color-status-success-light: #
|
|
36
|
-
--color-status-success-dark: #
|
|
37
|
-
--color-status-success-subtle: rgba(
|
|
34
|
+
--color-status-success: #2ED573;
|
|
35
|
+
--color-status-success-light: #58F69E;
|
|
36
|
+
--color-status-success-dark: #298732;
|
|
37
|
+
--color-status-success-subtle: rgba(46, 213, 115, 0.12);
|
|
38
38
|
--color-status-error: #D14343;
|
|
39
|
-
--color-status-error-light: #
|
|
40
|
-
--color-status-error-dark: #
|
|
39
|
+
--color-status-error-light: #E05254;
|
|
40
|
+
--color-status-error-dark: #A4221C;
|
|
41
41
|
--color-status-error-subtle: rgba(209, 67, 67, 0.12);
|
|
42
|
-
--color-status-
|
|
43
|
-
--color-status-
|
|
44
|
-
--color-status-
|
|
45
|
-
--color-status-
|
|
42
|
+
--color-status-error-vivid: #FF4757;
|
|
43
|
+
--color-status-error-vivid-light: #FF6070;
|
|
44
|
+
--color-status-error-vivid-dark: #C42539;
|
|
45
|
+
--color-status-error-vivid-subtle: rgba(255, 71, 87, 0.12);
|
|
46
|
+
--color-status-error-vivid-rgb: 255, 71, 87;
|
|
47
|
+
--color-border-prominent: #3C3C3C;
|
|
48
|
+
--color-status-warning: #F9B415;
|
|
49
|
+
--color-status-warning-light: #FFD352;
|
|
50
|
+
--color-status-warning-dark: #C27400;
|
|
51
|
+
--color-status-warning-subtle: rgba(249, 180, 21, 0.12);
|
|
46
52
|
--color-status-info: #2196F3;
|
|
47
|
-
--color-status-info-light: #
|
|
48
|
-
--color-status-info-dark: #
|
|
53
|
+
--color-status-info-light: #78C2FF;
|
|
54
|
+
--color-status-info-dark: #1072CB;
|
|
49
55
|
--color-status-info-subtle: rgba(33, 150, 243, 0.12);
|
|
50
56
|
|
|
51
57
|
--color-on-status-success: #FFFFFF;
|
|
@@ -119,10 +125,10 @@
|
|
|
119
125
|
|
|
120
126
|
/* RGB decomposed values for glow shadows */
|
|
121
127
|
--color-brand-primary-rgb: 255, 121, 0;
|
|
122
|
-
--color-brand-secondary-rgb:
|
|
123
|
-
--color-status-success-rgb:
|
|
128
|
+
--color-brand-secondary-rgb: 48, 123, 155;
|
|
129
|
+
--color-status-success-rgb: 46, 213, 115;
|
|
124
130
|
--color-status-error-rgb: 209, 67, 67;
|
|
125
|
-
--color-status-warning-rgb:
|
|
131
|
+
--color-status-warning-rgb: 249, 180, 21;
|
|
126
132
|
--color-status-info-rgb: 33, 150, 243;
|
|
127
133
|
--color-background-base-rgb: 16, 16, 16;
|
|
128
134
|
|
|
@@ -145,37 +151,43 @@
|
|
|
145
151
|
.light,
|
|
146
152
|
:root.light {
|
|
147
153
|
--color-brand-primary: #FF7900;
|
|
148
|
-
--color-brand-primary-light: #
|
|
149
|
-
--color-brand-primary-dark: #
|
|
154
|
+
--color-brand-primary-light: #FFA063;
|
|
155
|
+
--color-brand-primary-dark: #DA5F00;
|
|
150
156
|
--color-brand-primary-subtle: rgba(255, 121, 0, 0.08);
|
|
151
|
-
--color-brand-secondary: #
|
|
152
|
-
--color-brand-secondary-light: #
|
|
153
|
-
--color-brand-secondary-dark: #
|
|
154
|
-
--color-brand-secondary-subtle: rgba(
|
|
155
|
-
--color-brand-primary-hover: #
|
|
156
|
-
--color-brand-primary-active: #
|
|
157
|
-
--color-brand-secondary-hover: #
|
|
158
|
-
--color-brand-secondary-active: #
|
|
157
|
+
--color-brand-secondary: #307B9B;
|
|
158
|
+
--color-brand-secondary-light: #2697B7;
|
|
159
|
+
--color-brand-secondary-dark: #2A617F;
|
|
160
|
+
--color-brand-secondary-subtle: rgba(48, 123, 155, 0.08);
|
|
161
|
+
--color-brand-primary-hover: #DA5F00;
|
|
162
|
+
--color-brand-primary-active: #B94A00;
|
|
163
|
+
--color-brand-secondary-hover: #2A617F;
|
|
164
|
+
--color-brand-secondary-active: #22465F;
|
|
159
165
|
|
|
160
166
|
--color-on-brand-primary: #FFFFFF;
|
|
161
167
|
--color-on-brand-secondary: #FFFFFF;
|
|
162
168
|
|
|
163
|
-
--color-status-success: #
|
|
164
|
-
--color-status-success-light: #
|
|
165
|
-
--color-status-success-dark: #
|
|
166
|
-
--color-status-success-subtle: #
|
|
169
|
+
--color-status-success: #2ED573;
|
|
170
|
+
--color-status-success-light: #58F69E;
|
|
171
|
+
--color-status-success-dark: #298732;
|
|
172
|
+
--color-status-success-subtle: #E3FFEE;
|
|
167
173
|
--color-status-error: #D14343;
|
|
168
|
-
--color-status-error-light: #
|
|
169
|
-
--color-status-error-dark: #
|
|
170
|
-
--color-status-error-subtle: #
|
|
171
|
-
--color-status-
|
|
172
|
-
--color-status-
|
|
173
|
-
--color-status-
|
|
174
|
-
--color-status-
|
|
174
|
+
--color-status-error-light: #E05254;
|
|
175
|
+
--color-status-error-dark: #A4221C;
|
|
176
|
+
--color-status-error-subtle: #FFF4F4;
|
|
177
|
+
--color-status-error-vivid: #FF4757;
|
|
178
|
+
--color-status-error-vivid-light: #FF6070;
|
|
179
|
+
--color-status-error-vivid-dark: #C42539;
|
|
180
|
+
--color-status-error-vivid-subtle: rgba(255, 71, 87, 0.12);
|
|
181
|
+
--color-status-error-vivid-rgb: 255, 71, 87;
|
|
182
|
+
--color-border-prominent: #9CA3AF;
|
|
183
|
+
--color-status-warning: #F9B415;
|
|
184
|
+
--color-status-warning-light: #FFD352;
|
|
185
|
+
--color-status-warning-dark: #C27400;
|
|
186
|
+
--color-status-warning-subtle: #FFF7DD;
|
|
175
187
|
--color-status-info: #2196F3;
|
|
176
|
-
--color-status-info-light: #
|
|
177
|
-
--color-status-info-dark: #
|
|
178
|
-
--color-status-info-subtle: #
|
|
188
|
+
--color-status-info-light: #78C2FF;
|
|
189
|
+
--color-status-info-dark: #1072CB;
|
|
190
|
+
--color-status-info-subtle: #EFF8FF;
|
|
179
191
|
|
|
180
192
|
--color-on-status-success: #FFFFFF;
|
|
181
193
|
--color-on-status-error: #FFFFFF;
|
|
@@ -248,10 +260,10 @@
|
|
|
248
260
|
|
|
249
261
|
/* RGB decomposed values for glow shadows */
|
|
250
262
|
--color-brand-primary-rgb: 255, 121, 0;
|
|
251
|
-
--color-brand-secondary-rgb:
|
|
252
|
-
--color-status-success-rgb:
|
|
263
|
+
--color-brand-secondary-rgb: 48, 123, 155;
|
|
264
|
+
--color-status-success-rgb: 46, 213, 115;
|
|
253
265
|
--color-status-error-rgb: 209, 67, 67;
|
|
254
|
-
--color-status-warning-rgb:
|
|
266
|
+
--color-status-warning-rgb: 249, 180, 21;
|
|
255
267
|
--color-status-info-rgb: 33, 150, 243;
|
|
256
268
|
--color-background-base-rgb: 16, 16, 16;
|
|
257
269
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { linearGradient, surfaceGradient } from './gradients'
|
|
3
|
+
|
|
4
|
+
describe('gradients', () => {
|
|
5
|
+
it('builds a linear-gradient backgroundImage from two tokens (web CSS vars)', () => {
|
|
6
|
+
const style = linearGradient('surface-elevated', 'background-base', 180)
|
|
7
|
+
expect(style.backgroundImage).toBe(
|
|
8
|
+
'linear-gradient(180deg, var(--color-surface-elevated), var(--color-background-base))'
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('respects a custom angle', () => {
|
|
13
|
+
expect(linearGradient('surface-base', 'background-base', 90).backgroundImage).toContain('90deg')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('defaults to a 180deg angle', () => {
|
|
17
|
+
expect(linearGradient('surface-base', 'background-base').backgroundImage).toContain('180deg')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('surfaceGradient.chrome is the elevated → base chrome wash', () => {
|
|
21
|
+
expect(surfaceGradient.chrome().backgroundImage).toBe(
|
|
22
|
+
'linear-gradient(180deg, var(--color-surface-elevated), var(--color-background-base))'
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveColor } from './resolve-color'
|
|
2
|
+
import { getSemanticColors, type ThemeMode } from './tokens/semantic'
|
|
3
|
+
|
|
4
|
+
type ColorToken = keyof ReturnType<typeof getSemanticColors>
|
|
5
|
+
|
|
6
|
+
/** A `backgroundImage` style — web-only (RN ignores it); type it loosely for RN style arrays. */
|
|
7
|
+
export type GradientStyle = { backgroundImage: string }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A linear-gradient `backgroundImage` built from two semantic color tokens.
|
|
11
|
+
*
|
|
12
|
+
* Themeable + native-safe via {@link resolveColor}: on web the stops are CSS
|
|
13
|
+
* variables (light/dark switching keeps working); on native it resolves to hex.
|
|
14
|
+
* `backgroundImage` is a **web-only** paint, so pair this with a solid
|
|
15
|
+
* `bg-*` className fallback for native (see the shell TopBar).
|
|
16
|
+
*/
|
|
17
|
+
export function linearGradient(
|
|
18
|
+
from: ColorToken,
|
|
19
|
+
to: ColorToken,
|
|
20
|
+
angle = 180,
|
|
21
|
+
mode: ThemeMode = 'dark'
|
|
22
|
+
): GradientStyle {
|
|
23
|
+
return {
|
|
24
|
+
backgroundImage: `linear-gradient(${angle}deg, ${resolveColor(from, mode)}, ${resolveColor(to, mode)})`,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Named surface gradients — the shared substrate for gradient fills. Replaces
|
|
30
|
+
* per-component inline `linear-gradient` strings.
|
|
31
|
+
*/
|
|
32
|
+
export const surfaceGradient = {
|
|
33
|
+
/** Chrome bands (top bar, headers): elevated → base, a subtle dark wash. */
|
|
34
|
+
chrome: (mode: ThemeMode = 'dark'): GradientStyle =>
|
|
35
|
+
linearGradient('surface-elevated', 'background-base', 180, mode),
|
|
36
|
+
}
|
package/src/theme/index.ts
CHANGED
|
@@ -1,43 +1,8 @@
|
|
|
1
|
-
// Theme
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
export {
|
|
9
|
-
|
|
10
|
-
// Re-export commonly used items
|
|
11
|
-
export { getSemanticColors, type ThemeMode } from './tokens/semantic'
|
|
12
|
-
export { getThemeCSSVars, gluestackConfig } from './config'
|
|
13
|
-
export {
|
|
14
|
-
neumorphicShadows,
|
|
15
|
-
// Color math utilities (HSV-based)
|
|
16
|
-
lighten,
|
|
17
|
-
darken,
|
|
18
|
-
getHoverColors,
|
|
19
|
-
isDark,
|
|
20
|
-
hexToRgb,
|
|
21
|
-
rgbToHsv,
|
|
22
|
-
// Shadow creation functions
|
|
23
|
-
createRaisedShadow,
|
|
24
|
-
createRaisedHoverShadow,
|
|
25
|
-
createPressedShadow,
|
|
26
|
-
createPressedHoverShadow,
|
|
27
|
-
createFlatShadow,
|
|
28
|
-
shadowColors,
|
|
29
|
-
type ShadowIntensity,
|
|
30
|
-
type ShadowSurface,
|
|
31
|
-
} from './shadows'
|
|
32
|
-
export {
|
|
33
|
-
getElevationConfig,
|
|
34
|
-
getElevationSurface,
|
|
35
|
-
getElevationShadow,
|
|
36
|
-
getBaseSurfaceColor,
|
|
37
|
-
getValidatedElevation,
|
|
38
|
-
componentElevationRanges,
|
|
39
|
-
type ElevationLevel,
|
|
40
|
-
type ElevationConfig,
|
|
41
|
-
type ComponentType,
|
|
42
|
-
} from './elevation'
|
|
43
|
-
export * from './presets'
|
|
1
|
+
// Theme subpath entry: @titan-design/react-ui/theme
|
|
2
|
+
//
|
|
3
|
+
// The full theme surface. This is the nativewind-free `./barrel` PLUS the
|
|
4
|
+
// `ThemeProvider` value — which imports `nativewind` and therefore does NOT
|
|
5
|
+
// belong on the root barrel (that split happened in titan 0.5.0). Native
|
|
6
|
+
// consumers wrap their app root in `<ThemeProvider>` and import it from HERE.
|
|
7
|
+
export * from './barrel'
|
|
8
|
+
export { ThemeProvider } from './ThemeProvider'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { darkThemeCSSVars, lightThemeCSSVars } from './config'
|
|
6
|
+
import { getSemanticColors } from './tokens/semantic'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Native-theming guard (TD-04.11 / VW-20).
|
|
10
|
+
*
|
|
11
|
+
* On native, titan resolves colors two ways, and BOTH can silently render black
|
|
12
|
+
* if a token is undefined:
|
|
13
|
+
* 1. `className` (e.g. `text-text-secondary`) → tailwind config → a
|
|
14
|
+
* `var(--color-*)` custom property that `ThemeProvider` registers at runtime
|
|
15
|
+
* via nativewind `vars(darkThemeCSSVars)`. A tailwind color pointing at a var
|
|
16
|
+
* the theme maps don't define is unset on native → black text/fill.
|
|
17
|
+
* 2. `resolveColor(token)` → `getSemanticColors(mode)[token]` hex on native /
|
|
18
|
+
* `var(--color-${token})` on web. A token missing a hex value, or whose
|
|
19
|
+
* `--color-<token>` var is undefined, renders black (native) or unset (web).
|
|
20
|
+
*
|
|
21
|
+
* This is the exact class of bug that shipped in the 09.33a swap and was only
|
|
22
|
+
* caught on-device. `config.completeness.test.ts` proves the theme maps mirror
|
|
23
|
+
* global.css (the definition side); this guard covers the USAGE side — every
|
|
24
|
+
* color token the design system references must resolve to a defined token in
|
|
25
|
+
* both modes. Static (no render), so it runs in the fast project.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const require = createRequire(import.meta.url)
|
|
29
|
+
const uiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const tailwindConfig = require(path.join(uiRoot, 'tailwind.config.js')) as any
|
|
33
|
+
const colorTree = tailwindConfig.theme?.extend?.colors ?? {}
|
|
34
|
+
|
|
35
|
+
// Flatten the nested tailwind color tree into `class-suffix -> leaf value`.
|
|
36
|
+
// `DEFAULT` collapses onto its parent (tailwind's `brand.primary.DEFAULT` -> the
|
|
37
|
+
// `brand-primary` class).
|
|
38
|
+
function flattenColors(
|
|
39
|
+
node: Record<string, unknown>,
|
|
40
|
+
prefix: string[] = [],
|
|
41
|
+
): Record<string, string> {
|
|
42
|
+
const out: Record<string, string> = {}
|
|
43
|
+
for (const [key, value] of Object.entries(node)) {
|
|
44
|
+
const nextPrefix = key === 'DEFAULT' ? prefix : [...prefix, key]
|
|
45
|
+
if (value && typeof value === 'object') {
|
|
46
|
+
Object.assign(out, flattenColors(value as Record<string, unknown>, nextPrefix))
|
|
47
|
+
} else if (typeof value === 'string') {
|
|
48
|
+
out[nextPrefix.join('-')] = value
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const colorLeaves = flattenColors(colorTree)
|
|
55
|
+
const definedDark = new Set(Object.keys(darkThemeCSSVars))
|
|
56
|
+
const definedLight = new Set(Object.keys(lightThemeCSSVars))
|
|
57
|
+
|
|
58
|
+
function varReference(value: string): string | null {
|
|
59
|
+
const match = value.match(/var\((--color-[a-z0-9-]+)\)/)
|
|
60
|
+
return match ? match[1] : null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Accept hex, rgb/rgba, and hsl/hsla; reject empty/undefined. (No token should be
|
|
64
|
+
// literally "#000"/"black" in these maps, but that's a design choice, not a
|
|
65
|
+
// resolution bug, so we only assert a *valid, present* color here.)
|
|
66
|
+
const VALID_COLOR = /^(#[0-9a-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\))$/i
|
|
67
|
+
|
|
68
|
+
describe('native theming guard (no color token renders black on native)', () => {
|
|
69
|
+
it('sanity: tailwind colors and theme maps flattened to non-trivial sets', () => {
|
|
70
|
+
expect(Object.keys(colorLeaves).length).toBeGreaterThan(50)
|
|
71
|
+
expect(definedDark.size).toBeGreaterThan(90)
|
|
72
|
+
expect(definedLight.size).toBe(definedDark.size)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('every tailwind color className resolves to a var defined in BOTH native vars maps', () => {
|
|
76
|
+
const undefinedDark: string[] = []
|
|
77
|
+
const undefinedLight: string[] = []
|
|
78
|
+
for (const [suffix, value] of Object.entries(colorLeaves)) {
|
|
79
|
+
const ref = varReference(value)
|
|
80
|
+
if (!ref) continue // literal color (nativewind preset default) — always resolves
|
|
81
|
+
if (!definedDark.has(ref)) undefinedDark.push(`${suffix} -> ${value}`)
|
|
82
|
+
if (!definedLight.has(ref)) undefinedLight.push(`${suffix} -> ${value}`)
|
|
83
|
+
}
|
|
84
|
+
expect(
|
|
85
|
+
undefinedDark,
|
|
86
|
+
'tailwind colors point at vars missing from darkThemeCSSVars (className renders black on native dark)',
|
|
87
|
+
).toEqual([])
|
|
88
|
+
expect(
|
|
89
|
+
undefinedLight,
|
|
90
|
+
'tailwind colors point at vars missing from lightThemeCSSVars (className renders black on native light)',
|
|
91
|
+
).toEqual([])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('every resolveColor/getSemanticColors token has a valid hex AND a matching --color-<token> var', () => {
|
|
95
|
+
for (const mode of ['dark', 'light'] as const) {
|
|
96
|
+
const colors = getSemanticColors(mode)
|
|
97
|
+
const defined = mode === 'dark' ? definedDark : definedLight
|
|
98
|
+
const invalidValues: string[] = []
|
|
99
|
+
const missingVars: string[] = []
|
|
100
|
+
for (const [token, value] of Object.entries(colors)) {
|
|
101
|
+
if (typeof value !== 'string' || !VALID_COLOR.test(value)) {
|
|
102
|
+
invalidValues.push(`${token} = ${JSON.stringify(value)}`)
|
|
103
|
+
}
|
|
104
|
+
// resolveColor's web path returns `var(--color-${token})`; that var must exist.
|
|
105
|
+
if (!defined.has(`--color-${token}`)) missingVars.push(token)
|
|
106
|
+
}
|
|
107
|
+
expect(
|
|
108
|
+
invalidValues,
|
|
109
|
+
`getSemanticColors('${mode}') has tokens with no valid color (renders black/unset on native)`,
|
|
110
|
+
).toEqual([])
|
|
111
|
+
expect(
|
|
112
|
+
missingVars,
|
|
113
|
+
`resolveColor tokens missing a --color-<token> in ${mode} vars (renders unset on web)`,
|
|
114
|
+
).toEqual([])
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
})
|