@titan-design/react-ui 0.2.6 → 0.2.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/chunk-3VLOBS6O.mjs +846 -0
- package/dist/chunk-3VLOBS6O.mjs.map +1 -0
- package/dist/{chunk-UXVRPOME.mjs → chunk-5SSKGS6E.mjs} +3 -783
- package/dist/chunk-5SSKGS6E.mjs.map +1 -0
- package/dist/index.d.mts +1005 -4
- package/dist/index.d.ts +1005 -4
- package/dist/index.js +6043 -213
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5901 -177
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +57 -1
- package/dist/theme/index.d.ts +57 -1
- package/dist/theme/index.js +63 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +2 -1
- package/dist/theme/tokens-css.d.mts +18 -0
- package/dist/theme/tokens-css.d.ts +18 -0
- package/dist/theme/tokens-css.js +289 -0
- package/dist/theme/tokens-css.js.map +1 -0
- package/dist/theme/tokens-css.mjs +24 -0
- package/dist/theme/tokens-css.mjs.map +1 -0
- package/package.json +16 -5
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +45 -0
- package/src/components/custom/Gauge/Gauge.test.tsx +71 -0
- package/src/components/custom/Gauge/Gauge.tsx +167 -0
- package/src/components/custom/Gauge/index.ts +2 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +85 -0
- package/src/components/custom/Scatter/Scatter.test.tsx +96 -0
- package/src/components/custom/Scatter/Scatter.tsx +260 -0
- package/src/components/custom/Scatter/index.ts +2 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.stories.tsx +60 -0
- package/src/components/custom/Treemap/Treemap.test.tsx +67 -0
- package/src/components/custom/Treemap/Treemap.tsx +192 -0
- package/src/components/custom/Treemap/index.ts +2 -0
- package/src/components/custom/Workout/BaseBadge.stories.tsx +75 -0
- package/src/components/custom/Workout/BaseBadge.test.tsx +102 -0
- package/src/components/custom/Workout/BaseBadge.tsx +113 -0
- package/src/components/custom/Workout/BodyMap.stories.tsx +121 -0
- package/src/components/custom/Workout/BodyMap.test.tsx +124 -0
- package/src/components/custom/Workout/BodyMap.tsx +348 -0
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +171 -0
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +225 -0
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +478 -0
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +125 -0
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +193 -0
- package/src/components/custom/Workout/CapacityBandChart.tsx +493 -0
- package/src/components/custom/Workout/DeviationBar.stories.tsx +58 -0
- package/src/components/custom/Workout/DeviationBar.test.tsx +86 -0
- package/src/components/custom/Workout/DeviationBar.tsx +78 -0
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +186 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +319 -0
- package/src/components/custom/Workout/ExerciseCard.tsx +429 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +75 -0
- package/src/components/custom/Workout/InputBar.test.tsx +116 -0
- package/src/components/custom/Workout/InputBar.tsx +163 -0
- package/src/components/custom/Workout/IntensityBar.stories.tsx +60 -0
- package/src/components/custom/Workout/IntensityBar.test.tsx +113 -0
- package/src/components/custom/Workout/IntensityBar.tsx +166 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +152 -0
- package/src/components/custom/Workout/MesoCard.test.tsx +170 -0
- package/src/components/custom/Workout/MesoCard.tsx +235 -0
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +85 -0
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +141 -0
- package/src/components/custom/Workout/MesoProgressBar.tsx +161 -0
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +126 -0
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +209 -0
- package/src/components/custom/Workout/MesoStatusCard.tsx +456 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +118 -0
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +91 -0
- package/src/components/custom/Workout/MuscleGroupChip.tsx +92 -0
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +94 -0
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +83 -0
- package/src/components/custom/Workout/PlaceholderStrip.tsx +91 -0
- package/src/components/custom/Workout/PrBadge.stories.tsx +105 -0
- package/src/components/custom/Workout/PrBadge.test.tsx +111 -0
- package/src/components/custom/Workout/PrBadge.tsx +103 -0
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +116 -0
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +161 -0
- package/src/components/custom/Workout/PrHistoryModal.tsx +244 -0
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +126 -0
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +181 -0
- package/src/components/custom/Workout/ReadinessCheck.tsx +266 -0
- package/src/components/custom/Workout/RestTimer.tsx +8 -6
- package/src/components/custom/Workout/SetRow.stories.tsx +165 -0
- package/src/components/custom/Workout/SetRow.test.tsx +222 -0
- package/src/components/custom/Workout/SetRow.tsx +253 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +125 -0
- package/src/components/custom/Workout/Sparkline.test.tsx +113 -0
- package/src/components/custom/Workout/Sparkline.tsx +188 -0
- package/src/components/custom/Workout/StatusDot.stories.tsx +150 -0
- package/src/components/custom/Workout/StatusDot.test.tsx +165 -0
- package/src/components/custom/Workout/StatusDot.tsx +159 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +130 -0
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +217 -0
- package/src/components/custom/Workout/StrengthTrendChart.tsx +628 -0
- package/src/components/custom/Workout/SupersetWrapper.tsx +1 -2
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +66 -0
- package/src/components/custom/Workout/TempoDisplay.test.tsx +90 -0
- package/src/components/custom/Workout/TempoDisplay.tsx +207 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +125 -0
- package/src/components/custom/Workout/VelocityStrip.test.tsx +234 -0
- package/src/components/custom/Workout/VelocityStrip.tsx +287 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +121 -0
- package/src/components/custom/Workout/WeekRow.test.tsx +117 -0
- package/src/components/custom/Workout/WeekRow.tsx +140 -0
- package/src/components/custom/Workout/WeightBadge.stories.tsx +87 -0
- package/src/components/custom/Workout/WeightBadge.test.tsx +164 -0
- package/src/components/custom/Workout/WeightBadge.tsx +113 -0
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +135 -0
- package/src/components/custom/Workout/WorkoutCard.test.tsx +172 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +226 -0
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +63 -0
- package/src/components/custom/Workout/WorkoutPill.test.tsx +153 -0
- package/src/components/custom/Workout/WorkoutPill.tsx +177 -0
- package/src/components/custom/Workout/index.ts +113 -0
- package/src/components/custom/Workout/muscleTaxonomy.ts +252 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +2 -2
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/card/Card.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +4 -4
- package/src/components/ui/select/Select.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +4 -4
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +1 -1
- package/src/examples/react-hook-form/ControlledFields.tsx +171 -0
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +51 -0
- package/src/examples/react-hook-form/ReactHookForm.test.tsx +61 -0
- package/src/examples/react-hook-form/RhfContactForm.tsx +99 -0
- package/src/stories/PresetShowcase.stories.tsx +15 -15
- package/src/theme/index.ts +2 -1
- package/src/theme/manifest/css-property-manifest.example.json +79 -0
- package/src/theme/manifest/css-property-manifest.schema.json +93 -0
- package/src/theme/manifest/css-property-manifest.test.ts +81 -0
- package/src/theme/manifest/css-property-manifest.types.ts +49 -0
- package/src/theme/manifest/css-property-manifest.validate.ts +84 -0
- package/src/theme/manifest/index.ts +11 -0
- package/src/theme/tokens-css.test.ts +28 -0
- package/src/theme/tokens-css.ts +34 -0
- package/src/theme/workout-tokens.ts +61 -0
- package/dist/chunk-UXVRPOME.mjs.map +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { readFileSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import {
|
|
5
|
+
isCssPropertyManifest,
|
|
6
|
+
validateCssPropertyManifest,
|
|
7
|
+
} from './css-property-manifest.validate'
|
|
8
|
+
|
|
9
|
+
const readJson = (fileName: string): unknown =>
|
|
10
|
+
JSON.parse(readFileSync(join(__dirname, fileName), 'utf-8'))
|
|
11
|
+
|
|
12
|
+
describe('css-property-manifest.schema.json', () => {
|
|
13
|
+
it('is well-formed JSON Schema with the documented top-level shape', () => {
|
|
14
|
+
const schema = readJson('css-property-manifest.schema.json') as Record<string, unknown>
|
|
15
|
+
|
|
16
|
+
expect(schema.$schema).toBe('https://json-schema.org/draft/2020-12/schema')
|
|
17
|
+
expect(schema.required).toEqual(['component', 'properties'])
|
|
18
|
+
expect(schema.$defs).toHaveProperty('cssPropertyEntry')
|
|
19
|
+
expect(schema.$defs).toHaveProperty('variantOverride')
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('validateCssPropertyManifest', () => {
|
|
24
|
+
it('accepts the button example manifest', () => {
|
|
25
|
+
const example = readJson('css-property-manifest.example.json')
|
|
26
|
+
|
|
27
|
+
const result = validateCssPropertyManifest(example)
|
|
28
|
+
|
|
29
|
+
expect(result).toEqual({ valid: true, errors: [] })
|
|
30
|
+
expect(isCssPropertyManifest(example)).toBe(true)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('rejects a manifest missing required top-level fields', () => {
|
|
34
|
+
const result = validateCssPropertyManifest({ component: 'button' })
|
|
35
|
+
|
|
36
|
+
expect(result.valid).toBe(false)
|
|
37
|
+
expect(result.errors).toContain('properties must be a non-empty array')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('rejects a property entry missing a resolvedValue', () => {
|
|
41
|
+
const candidate = {
|
|
42
|
+
component: 'button',
|
|
43
|
+
properties: [{ property: 'backgroundColor', token: 'brand-primary' }],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = validateCssPropertyManifest(candidate)
|
|
47
|
+
|
|
48
|
+
expect(result.valid).toBe(false)
|
|
49
|
+
expect(result.errors).toContain('properties[0].resolvedValue must be a non-empty string')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('rejects a variantOverride missing variant axes', () => {
|
|
53
|
+
const candidate = {
|
|
54
|
+
component: 'button',
|
|
55
|
+
properties: [
|
|
56
|
+
{
|
|
57
|
+
property: 'backgroundColor',
|
|
58
|
+
token: 'brand-primary',
|
|
59
|
+
resolvedValue: 'rgb(255, 121, 0)',
|
|
60
|
+
variantOverrides: [{ resolvedValue: 'rgba(0, 0, 0, 0)' }],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = validateCssPropertyManifest(candidate)
|
|
66
|
+
|
|
67
|
+
expect(result.valid).toBe(false)
|
|
68
|
+
expect(result.errors).toContain(
|
|
69
|
+
'properties[0].variantOverrides[0].variant must be a non-empty string-keyed object'
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('allows token to be null for literal (non-tokenized) values', () => {
|
|
74
|
+
const candidate = {
|
|
75
|
+
component: 'button',
|
|
76
|
+
properties: [{ property: 'borderRadius', token: null, resolvedValue: '6px' }],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
expect(validateCssPropertyManifest(candidate)).toEqual({ valid: true, errors: [] })
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript mirror of css-property-manifest.schema.json.
|
|
3
|
+
*
|
|
4
|
+
* A manifest records, per component, which visual CSS properties are tracked,
|
|
5
|
+
* which design token each one resolves from, the expected computed style
|
|
6
|
+
* value, and any per-variant overrides. Manifests are the fixtures consumed
|
|
7
|
+
* by Playwright computed-style assertions (see packages/ui/tests/visual).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Variant axis name/value pairs, e.g. { variant: 'solid', color: 'primary', size: 'md' }. */
|
|
11
|
+
export type VariantAxes = Record<string, string>
|
|
12
|
+
|
|
13
|
+
/** A resolvedValue/token override that applies when the given variant axes match. */
|
|
14
|
+
export interface VariantOverride {
|
|
15
|
+
/** Partial variant axis values that trigger this override, merged onto baseVariant. */
|
|
16
|
+
variant: VariantAxes
|
|
17
|
+
/** Semantic token key for this override, or null for a literal (non-tokenized) value. */
|
|
18
|
+
token?: string | null
|
|
19
|
+
/** CSS custom property backing the token for this override, if any. */
|
|
20
|
+
cssVariable?: string | null
|
|
21
|
+
/** Expected getComputedStyle value when the variant axes match. */
|
|
22
|
+
resolvedValue: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A single tracked CSS property for a component. */
|
|
26
|
+
export interface CssPropertyEntry {
|
|
27
|
+
/** CSS property name in camelCase, as returned by window.getComputedStyle (e.g. "backgroundColor"). */
|
|
28
|
+
property: string
|
|
29
|
+
/** Semantic token key this property is sourced from, or null if not backed by a token. */
|
|
30
|
+
token: string | null
|
|
31
|
+
/** CSS custom property backing the token (e.g. "--color-brand-primary"), if any. */
|
|
32
|
+
cssVariable?: string | null
|
|
33
|
+
/** Expected computed style value for baseVariant. */
|
|
34
|
+
resolvedValue: string
|
|
35
|
+
/** Overrides of token/resolvedValue for specific variant axis combinations. */
|
|
36
|
+
variantOverrides?: VariantOverride[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The full per-component CSS property manifest. */
|
|
40
|
+
export interface CssPropertyManifest {
|
|
41
|
+
/** Component identifier matching its src/components/ui/{component} directory and Storybook id. */
|
|
42
|
+
component: string
|
|
43
|
+
/** Optional human-readable note about what this manifest covers. */
|
|
44
|
+
description?: string
|
|
45
|
+
/** Variant axis values each property's top-level resolvedValue was captured against. */
|
|
46
|
+
baseVariant?: VariantAxes
|
|
47
|
+
/** Tracked CSS properties for this component. */
|
|
48
|
+
properties: CssPropertyEntry[]
|
|
49
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CssPropertyManifest, VariantAxes } from './css-property-manifest.types'
|
|
2
|
+
|
|
3
|
+
/** Result of validating a candidate manifest: valid iff errors is empty. */
|
|
4
|
+
export interface ManifestValidationResult {
|
|
5
|
+
valid: boolean
|
|
6
|
+
errors: string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
10
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
11
|
+
|
|
12
|
+
const isVariantAxes = (value: unknown): value is VariantAxes =>
|
|
13
|
+
isPlainObject(value) &&
|
|
14
|
+
Object.keys(value).length > 0 &&
|
|
15
|
+
Object.values(value).every((axisValue) => typeof axisValue === 'string')
|
|
16
|
+
|
|
17
|
+
function collectVariantOverrideErrors(override: unknown, path: string): string[] {
|
|
18
|
+
if (!isPlainObject(override)) return [`${path} must be an object`]
|
|
19
|
+
const errors: string[] = []
|
|
20
|
+
if (!isVariantAxes(override.variant))
|
|
21
|
+
errors.push(`${path}.variant must be a non-empty string-keyed object`)
|
|
22
|
+
if (
|
|
23
|
+
override.token !== undefined &&
|
|
24
|
+
override.token !== null &&
|
|
25
|
+
typeof override.token !== 'string'
|
|
26
|
+
) {
|
|
27
|
+
errors.push(`${path}.token must be a string or null`)
|
|
28
|
+
}
|
|
29
|
+
if (typeof override.resolvedValue !== 'string' || override.resolvedValue.length === 0) {
|
|
30
|
+
errors.push(`${path}.resolvedValue must be a non-empty string`)
|
|
31
|
+
}
|
|
32
|
+
return errors
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function collectPropertyErrors(entry: unknown, path: string): string[] {
|
|
36
|
+
if (!isPlainObject(entry)) return [`${path} must be an object`]
|
|
37
|
+
const errors: string[] = []
|
|
38
|
+
if (typeof entry.property !== 'string' || entry.property.length === 0) {
|
|
39
|
+
errors.push(`${path}.property must be a non-empty string`)
|
|
40
|
+
}
|
|
41
|
+
if (entry.token !== null && typeof entry.token !== 'string') {
|
|
42
|
+
errors.push(`${path}.token must be a string or null`)
|
|
43
|
+
}
|
|
44
|
+
if (typeof entry.resolvedValue !== 'string' || entry.resolvedValue.length === 0) {
|
|
45
|
+
errors.push(`${path}.resolvedValue must be a non-empty string`)
|
|
46
|
+
}
|
|
47
|
+
if (entry.variantOverrides !== undefined) {
|
|
48
|
+
if (!Array.isArray(entry.variantOverrides)) {
|
|
49
|
+
errors.push(`${path}.variantOverrides must be an array`)
|
|
50
|
+
} else {
|
|
51
|
+
entry.variantOverrides.forEach((override, i) =>
|
|
52
|
+
errors.push(...collectVariantOverrideErrors(override, `${path}.variantOverrides[${i}]`))
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return errors
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Structurally validates a candidate value against the CssPropertyManifest shape. */
|
|
60
|
+
export function validateCssPropertyManifest(candidate: unknown): ManifestValidationResult {
|
|
61
|
+
if (!isPlainObject(candidate)) return { valid: false, errors: ['manifest must be an object'] }
|
|
62
|
+
|
|
63
|
+
const errors: string[] = []
|
|
64
|
+
if (typeof candidate.component !== 'string' || candidate.component.length === 0) {
|
|
65
|
+
errors.push('component must be a non-empty string')
|
|
66
|
+
}
|
|
67
|
+
if (candidate.baseVariant !== undefined && !isVariantAxes(candidate.baseVariant)) {
|
|
68
|
+
errors.push('baseVariant must be a non-empty string-keyed object')
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(candidate.properties) || candidate.properties.length === 0) {
|
|
71
|
+
errors.push('properties must be a non-empty array')
|
|
72
|
+
} else {
|
|
73
|
+
candidate.properties.forEach((entry, i) =>
|
|
74
|
+
errors.push(...collectPropertyErrors(entry, `properties[${i}]`))
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { valid: errors.length === 0, errors }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Type guard variant of validateCssPropertyManifest for use in narrowing contexts. */
|
|
82
|
+
export function isCssPropertyManifest(candidate: unknown): candidate is CssPropertyManifest {
|
|
83
|
+
return validateCssPropertyManifest(candidate).valid
|
|
84
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
CssPropertyEntry,
|
|
3
|
+
CssPropertyManifest,
|
|
4
|
+
VariantAxes,
|
|
5
|
+
VariantOverride,
|
|
6
|
+
} from './css-property-manifest.types'
|
|
7
|
+
export {
|
|
8
|
+
isCssPropertyManifest,
|
|
9
|
+
validateCssPropertyManifest,
|
|
10
|
+
type ManifestValidationResult,
|
|
11
|
+
} from './css-property-manifest.validate'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { generateTokensCss } from './tokens-css'
|
|
3
|
+
import { darkThemeCSSVars, lightThemeCSSVars } from './config'
|
|
4
|
+
|
|
5
|
+
describe('generateTokensCss', () => {
|
|
6
|
+
const css = generateTokensCss()
|
|
7
|
+
|
|
8
|
+
it('emits a default :root block and a .light override block', () => {
|
|
9
|
+
expect(css).toContain(':root {')
|
|
10
|
+
expect(css).toContain('.light, :root.light {')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('emits every dark token in :root with its exact value', () => {
|
|
14
|
+
for (const [name, value] of Object.entries(darkThemeCSSVars)) {
|
|
15
|
+
expect(css).toContain(` ${name}: ${value};`)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('emits every light token with its exact value', () => {
|
|
20
|
+
for (const [name, value] of Object.entries(lightThemeCSSVars)) {
|
|
21
|
+
expect(css).toContain(` ${name}: ${value};`)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('is deterministic across invocations', () => {
|
|
26
|
+
expect(generateTokensCss()).toBe(css)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token CSS Generator
|
|
3
|
+
*
|
|
4
|
+
* Emits a static `:root {}` CSS block from the canonical theme token maps
|
|
5
|
+
* (`darkThemeCSSVars` / `lightThemeCSSVars`). HTML prototypes import the
|
|
6
|
+
* generated stylesheet so they always resolve to the exact same values as
|
|
7
|
+
* the design system. Pure codegen: deterministic and diffable against source.
|
|
8
|
+
*
|
|
9
|
+
* Dark mode is the default (`:root`); light mode is activated with `.light`,
|
|
10
|
+
* matching the convention in `theme/global.css`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { darkThemeCSSVars, lightThemeCSSVars } from './config'
|
|
14
|
+
|
|
15
|
+
type CSSVarMap = Record<string, string>
|
|
16
|
+
|
|
17
|
+
const GENERATED_HEADER = '/* Generated from theme token maps. Do not edit by hand. */'
|
|
18
|
+
|
|
19
|
+
function formatBlock(selector: string, vars: CSSVarMap): string {
|
|
20
|
+
const declarations = Object.entries(vars)
|
|
21
|
+
.map(([name, value]) => ` ${name}: ${value};`)
|
|
22
|
+
.join('\n')
|
|
23
|
+
return `${selector} {\n${declarations}\n}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the full `tokens.css` contents: a default (dark) `:root` block plus a
|
|
28
|
+
* `.light` override block, driven entirely by the exported token maps.
|
|
29
|
+
*/
|
|
30
|
+
export function generateTokensCss(): string {
|
|
31
|
+
const root = formatBlock(':root', darkThemeCSSVars)
|
|
32
|
+
const light = formatBlock('.light, :root.light', lightThemeCSSVars)
|
|
33
|
+
return `${GENERATED_HEADER}\n\n${root}\n\n${light}\n`
|
|
34
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workout-specific tokens not yet in the main Tailwind config.
|
|
3
|
+
* Use these inline instead of Tailwind classes.
|
|
4
|
+
*/
|
|
5
|
+
export const WORKOUT_TOKENS = {
|
|
6
|
+
// Velocity zones (not in Tailwind)
|
|
7
|
+
velocity: {
|
|
8
|
+
green: '#2ed573',
|
|
9
|
+
yellow: '#ffd43b',
|
|
10
|
+
orange: '#ffa502',
|
|
11
|
+
red: '#ff4757',
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
// BodyMap volume heatmap scale (drive dynamic SVG fills, so inline values).
|
|
15
|
+
// Maps weekly-volume status against MEV/MAV/MRV landmarks to a fill color.
|
|
16
|
+
heatmap: {
|
|
17
|
+
none: '#E0E0E0', // gray — no training data
|
|
18
|
+
under: '#4A90D9', // cool blue — below MEV
|
|
19
|
+
maintenance: '#F5C842', // warm yellow — MEV to MAV
|
|
20
|
+
productive: '#4CAF50', // green — MAV to MRV
|
|
21
|
+
approaching: '#FFA502', // orange — near MRV
|
|
22
|
+
over: '#FF6B35', // red-orange — over MRV
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Badge border-radius (rounded-sm is 4px, we need 2px)
|
|
26
|
+
badgeRadius: 2,
|
|
27
|
+
|
|
28
|
+
// Border colors (use inline to avoid 'border' class pitfall)
|
|
29
|
+
border: {
|
|
30
|
+
default: '#1F1F1F',
|
|
31
|
+
strong: '#2C2C2C',
|
|
32
|
+
subtle: '#1C1C1C',
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Surface colors for inline use
|
|
36
|
+
surface: {
|
|
37
|
+
raised: '#1C1C1C',
|
|
38
|
+
elevated: '#191919',
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Intensity bar specific
|
|
42
|
+
intensity: {
|
|
43
|
+
track: '#333333',
|
|
44
|
+
over1: '#D14343',
|
|
45
|
+
over2: '#A62626',
|
|
46
|
+
over3: '#7A1C1C',
|
|
47
|
+
targetLine: 'rgba(33, 150, 243, 0.5)',
|
|
48
|
+
atTargetGlow:
|
|
49
|
+
'0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Placeholder strip
|
|
53
|
+
placeholder: {
|
|
54
|
+
fill: '#3A3A3A',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Deviation bar
|
|
58
|
+
deviation: {
|
|
59
|
+
track: '#333333',
|
|
60
|
+
},
|
|
61
|
+
} as const
|