@titan-design/react-ui 0.6.0 → 0.8.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/bodymap.js +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +956 -128
- package/dist/index.d.ts +956 -128
- package/dist/index.js +2873 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2826 -982
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1659 -799
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1660 -800
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Typography/Typography.stories.tsx +1 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +85 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +193 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +155 -59
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +169 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +262 -56
- package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +2 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +86 -3
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +47 -22
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
|
@@ -10,7 +10,7 @@ import { RhfContactForm, type ContactFormValues } from './RhfContactForm'
|
|
|
10
10
|
* `Controller` render-prop idiom. See `ControlledFields.tsx` for the adapters.
|
|
11
11
|
*/
|
|
12
12
|
const meta: Meta<typeof RhfContactForm> = {
|
|
13
|
-
title: '
|
|
13
|
+
title: 'Lab/Recipes/React Hook Form',
|
|
14
14
|
component: RhfContactForm,
|
|
15
15
|
tags: ['autodocs'],
|
|
16
16
|
parameters: {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
|
+
import { renderHook, act } from '@testing-library/react'
|
|
3
|
+
import { useTimer, formatDuration } from './useTimer'
|
|
4
|
+
|
|
5
|
+
describe('formatDuration', () => {
|
|
6
|
+
it('formats zero as 0:00', () => {
|
|
7
|
+
expect(formatDuration(0)).toBe('0:00')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('pads seconds with a leading zero', () => {
|
|
11
|
+
expect(formatDuration(65000)).toBe('1:05')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('rounds partial seconds up', () => {
|
|
15
|
+
expect(formatDuration(4500)).toBe('0:05')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('clamps negative input to 0:00', () => {
|
|
19
|
+
expect(formatDuration(-1000)).toBe('0:00')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('formats multi-minute durations', () => {
|
|
23
|
+
expect(formatDuration(150000)).toBe('2:30')
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('useTimer (controlled, down)', () => {
|
|
28
|
+
it('derives remaining, progress, and label from elapsedMs', () => {
|
|
29
|
+
const { result } = renderHook(() =>
|
|
30
|
+
useTimer({ mode: 'down', durationMs: 100000, elapsedMs: 30000 })
|
|
31
|
+
)
|
|
32
|
+
expect(result.current.remainingMs).toBe(70000)
|
|
33
|
+
expect(result.current.progress).toBeCloseTo(0.3)
|
|
34
|
+
expect(result.current.label).toBe('1:10')
|
|
35
|
+
expect(result.current.done).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('clamps remaining to 0 and progress to 1 when elapsed exceeds duration', () => {
|
|
39
|
+
const { result } = renderHook(() =>
|
|
40
|
+
useTimer({ mode: 'down', durationMs: 100000, elapsedMs: 200000 })
|
|
41
|
+
)
|
|
42
|
+
expect(result.current.remainingMs).toBe(0)
|
|
43
|
+
expect(result.current.progress).toBe(1)
|
|
44
|
+
expect(result.current.label).toBe('0:00')
|
|
45
|
+
expect(result.current.done).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('reports progress 1 and done for a zero-duration countdown', () => {
|
|
49
|
+
const { result } = renderHook(() => useTimer({ mode: 'down', durationMs: 0, elapsedMs: 0 }))
|
|
50
|
+
expect(result.current.progress).toBe(1)
|
|
51
|
+
expect(result.current.done).toBe(true)
|
|
52
|
+
expect(result.current.label).toBe('0:00')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('useTimer (controlled, up)', () => {
|
|
57
|
+
it('counts elapsed upward and computes progress against duration', () => {
|
|
58
|
+
const { result } = renderHook(() =>
|
|
59
|
+
useTimer({ mode: 'up', durationMs: 120000, elapsedMs: 30000 })
|
|
60
|
+
)
|
|
61
|
+
expect(result.current.elapsedMs).toBe(30000)
|
|
62
|
+
expect(result.current.progress).toBeCloseTo(0.25)
|
|
63
|
+
expect(result.current.label).toBe('0:30')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('reports progress 0 and never done without a duration', () => {
|
|
67
|
+
const { result } = renderHook(() => useTimer({ mode: 'up', elapsedMs: 45000 }))
|
|
68
|
+
expect(result.current.progress).toBe(0)
|
|
69
|
+
expect(result.current.done).toBe(false)
|
|
70
|
+
expect(result.current.label).toBe('0:45')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('useTimer (self-ticking)', () => {
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
vi.useRealTimers()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('advances elapsed on each interval while running', () => {
|
|
80
|
+
vi.useFakeTimers()
|
|
81
|
+
const { result } = renderHook(() =>
|
|
82
|
+
useTimer({ mode: 'up', autoTick: true, running: true, tickMs: 1000 })
|
|
83
|
+
)
|
|
84
|
+
expect(result.current.elapsedMs).toBe(0)
|
|
85
|
+
act(() => {
|
|
86
|
+
vi.advanceTimersByTime(3000)
|
|
87
|
+
})
|
|
88
|
+
expect(result.current.elapsedMs).toBe(3000)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('counts a down-timer toward 0 and reports done', () => {
|
|
92
|
+
vi.useFakeTimers()
|
|
93
|
+
const { result } = renderHook(() =>
|
|
94
|
+
useTimer({ mode: 'down', durationMs: 3000, autoTick: true, running: true })
|
|
95
|
+
)
|
|
96
|
+
expect(result.current.label).toBe('0:03')
|
|
97
|
+
act(() => {
|
|
98
|
+
vi.advanceTimersByTime(3000)
|
|
99
|
+
})
|
|
100
|
+
expect(result.current.remainingMs).toBe(0)
|
|
101
|
+
expect(result.current.label).toBe('0:00')
|
|
102
|
+
expect(result.current.done).toBe(true)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('does not tick when not running', () => {
|
|
106
|
+
vi.useFakeTimers()
|
|
107
|
+
const { result } = renderHook(() => useTimer({ mode: 'up', autoTick: true, running: false }))
|
|
108
|
+
act(() => {
|
|
109
|
+
vi.advanceTimersByTime(5000)
|
|
110
|
+
})
|
|
111
|
+
expect(result.current.elapsedMs).toBe(0)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('does not tick when a controlled elapsedMs is supplied', () => {
|
|
115
|
+
vi.useFakeTimers()
|
|
116
|
+
const { result } = renderHook(() =>
|
|
117
|
+
useTimer({ mode: 'up', autoTick: true, running: true, elapsedMs: 10000 })
|
|
118
|
+
)
|
|
119
|
+
act(() => {
|
|
120
|
+
vi.advanceTimersByTime(5000)
|
|
121
|
+
})
|
|
122
|
+
expect(result.current.elapsedMs).toBe(10000)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('clears the interval on unmount', () => {
|
|
126
|
+
vi.useFakeTimers()
|
|
127
|
+
const clearSpy = vi.spyOn(global, 'clearInterval')
|
|
128
|
+
const { unmount } = renderHook(() => useTimer({ mode: 'up', autoTick: true, running: true }))
|
|
129
|
+
unmount()
|
|
130
|
+
expect(clearSpy).toHaveBeenCalled()
|
|
131
|
+
clearSpy.mockRestore()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('stops ticking after running flips to false', () => {
|
|
135
|
+
vi.useFakeTimers()
|
|
136
|
+
const { result, rerender } = renderHook(
|
|
137
|
+
({ running }) => useTimer({ mode: 'up', autoTick: true, running }),
|
|
138
|
+
{ initialProps: { running: true } }
|
|
139
|
+
)
|
|
140
|
+
act(() => {
|
|
141
|
+
vi.advanceTimersByTime(2000)
|
|
142
|
+
})
|
|
143
|
+
expect(result.current.elapsedMs).toBe(2000)
|
|
144
|
+
rerender({ running: false })
|
|
145
|
+
act(() => {
|
|
146
|
+
vi.advanceTimersByTime(5000)
|
|
147
|
+
})
|
|
148
|
+
expect(result.current.elapsedMs).toBe(2000)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export type TimerMode = 'up' | 'down'
|
|
4
|
+
|
|
5
|
+
export interface UseTimerOptions {
|
|
6
|
+
/** 'up' counts elapsed upward; 'down' counts remaining toward 0. Default 'up'. */
|
|
7
|
+
mode?: TimerMode
|
|
8
|
+
/** Total duration in ms. Required for 'down' mode and for progress in 'up' mode. */
|
|
9
|
+
durationMs?: number
|
|
10
|
+
/** Controlled elapsed time in ms. When provided, the hook derives everything from it and never ticks. */
|
|
11
|
+
elapsedMs?: number
|
|
12
|
+
/** Self-tick only advances while running. */
|
|
13
|
+
running?: boolean
|
|
14
|
+
/** Enable the internal interval. Ignored when `elapsedMs` is controlled. */
|
|
15
|
+
autoTick?: boolean
|
|
16
|
+
/** Interval step in ms. Default 1000. */
|
|
17
|
+
tickMs?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TimerState {
|
|
21
|
+
elapsedMs: number
|
|
22
|
+
remainingMs: number
|
|
23
|
+
/** 0..1, clamped. */
|
|
24
|
+
progress: number
|
|
25
|
+
/** mm:ss for the active direction (remaining when 'down', elapsed when 'up'). */
|
|
26
|
+
label: string
|
|
27
|
+
/** True once a down-timer reaches 0 (or an up-timer passes its duration). */
|
|
28
|
+
done: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Format a millisecond duration as mm:ss, rounding partial seconds up. */
|
|
32
|
+
export function formatDuration(ms: number): string {
|
|
33
|
+
const totalSeconds = Math.max(0, Math.ceil(ms / 1000))
|
|
34
|
+
const minutes = Math.floor(totalSeconds / 60)
|
|
35
|
+
const seconds = totalSeconds % 60
|
|
36
|
+
return `${minutes}:${String(seconds).padStart(2, '0')}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useTimer(options: UseTimerOptions = {}): TimerState {
|
|
40
|
+
const {
|
|
41
|
+
mode = 'up',
|
|
42
|
+
durationMs,
|
|
43
|
+
elapsedMs: controlledElapsedMs,
|
|
44
|
+
running = false,
|
|
45
|
+
autoTick = false,
|
|
46
|
+
tickMs = 1000,
|
|
47
|
+
} = options
|
|
48
|
+
|
|
49
|
+
const isControlled = controlledElapsedMs != null
|
|
50
|
+
const [internalElapsedMs, setInternalElapsedMs] = useState(0)
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (isControlled || !autoTick || !running) return
|
|
54
|
+
const id = setInterval(() => {
|
|
55
|
+
setInternalElapsedMs((prev) => prev + tickMs)
|
|
56
|
+
}, tickMs)
|
|
57
|
+
return () => clearInterval(id)
|
|
58
|
+
}, [isControlled, autoTick, running, tickMs])
|
|
59
|
+
|
|
60
|
+
const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs
|
|
61
|
+
|
|
62
|
+
const hasDuration = durationMs != null && durationMs > 0
|
|
63
|
+
const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0
|
|
64
|
+
|
|
65
|
+
let progress: number
|
|
66
|
+
if (hasDuration) {
|
|
67
|
+
progress = Math.min(1, Math.max(0, elapsedMs / durationMs))
|
|
68
|
+
} else {
|
|
69
|
+
// A zero/undefined duration is trivially complete for a countdown, empty for a stopwatch.
|
|
70
|
+
progress = mode === 'down' ? 1 : 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const label = formatDuration(mode === 'down' ? remainingMs : elapsedMs)
|
|
74
|
+
const done = durationMs != null && elapsedMs >= durationMs
|
|
75
|
+
|
|
76
|
+
return { elapsedMs, remainingMs, progress, label, done }
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
package/src/pages.ts
CHANGED
|
@@ -438,7 +438,7 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
|
|
438
438
|
// ---------------------------------------------------------------------------
|
|
439
439
|
|
|
440
440
|
const meta: Meta = {
|
|
441
|
-
title: 'Theme
|
|
441
|
+
title: 'Lab/Recipes/Theme Preset Showcase',
|
|
442
442
|
parameters: {
|
|
443
443
|
layout: 'fullscreen',
|
|
444
444
|
},
|
|
@@ -4,7 +4,7 @@ import { primitiveColors, discreteRainbow, primitiveRamps, categoricalPalette }
|
|
|
4
4
|
import { semanticColorsLight, semanticColorsDark } from './tokens/semantic'
|
|
5
5
|
|
|
6
6
|
const meta: Meta = {
|
|
7
|
-
title: '
|
|
7
|
+
title: 'Foundations/Color/Palette',
|
|
8
8
|
tags: ['autodocs'],
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -79,16 +79,13 @@ export const PrimitiveColors: StoryObj = {
|
|
|
79
79
|
Primitive Colors
|
|
80
80
|
</Text>
|
|
81
81
|
<Text className="text-text-secondary mb-6">
|
|
82
|
-
Raw color values.
|
|
82
|
+
Raw color values. Chromatic hues live as OKLCH tonal ramps (see the Tonal Ramps story);
|
|
83
|
+
these are the achromatic and support scales. Use semantic tokens when building components.
|
|
83
84
|
</Text>
|
|
84
85
|
|
|
85
|
-
<ColorScale name="Orange (Primary Brand)" colors={primitiveColors.accent} />
|
|
86
|
-
<ColorScale name="Steel (Secondary Brand)" colors={primitiveColors.steel} />
|
|
87
86
|
<ColorScale name="Blue" colors={primitiveColors.blue} />
|
|
88
|
-
<ColorScale name="
|
|
89
|
-
<ColorScale name="
|
|
90
|
-
<ColorScale name="Red (Error)" colors={primitiveColors.red} />
|
|
91
|
-
<ColorScale name="Sky (Info)" colors={primitiveColors.sky} />
|
|
87
|
+
<ColorScale name="Red" colors={primitiveColors.red} />
|
|
88
|
+
<ColorScale name="Red Vivid" colors={primitiveColors.redVivid} />
|
|
92
89
|
<ColorScale name="Neutral" colors={primitiveColors.neutral} />
|
|
93
90
|
<ColorScale name="Charcoal (Dark Backgrounds)" colors={primitiveColors.charcoal} />
|
|
94
91
|
</View>
|
package/src/theme/config.ts
CHANGED
|
@@ -53,6 +53,8 @@ export const lightThemeCSSVars = {
|
|
|
53
53
|
|
|
54
54
|
'--color-status-success': semanticColorsLight['status-success'],
|
|
55
55
|
'--color-status-success-subtle': semanticColorsLight['status-success-subtle'],
|
|
56
|
+
'--color-status-live': semanticColorsLight['status-live'],
|
|
57
|
+
'--color-status-live-muted': semanticColorsLight['status-live-muted'],
|
|
56
58
|
'--color-status-error': semanticColorsLight['status-error'],
|
|
57
59
|
'--color-status-error-subtle': semanticColorsLight['status-error-subtle'],
|
|
58
60
|
'--color-status-warning': semanticColorsLight['status-warning'],
|
|
@@ -172,6 +174,8 @@ export const darkThemeCSSVars = {
|
|
|
172
174
|
|
|
173
175
|
'--color-status-success': semanticColorsDark['status-success'],
|
|
174
176
|
'--color-status-success-subtle': semanticColorsDark['status-success-subtle'],
|
|
177
|
+
'--color-status-live': semanticColorsDark['status-live'],
|
|
178
|
+
'--color-status-live-muted': semanticColorsDark['status-live-muted'],
|
|
175
179
|
'--color-status-error': semanticColorsDark['status-error'],
|
|
176
180
|
'--color-status-error-subtle': semanticColorsDark['status-error-subtle'],
|
|
177
181
|
'--color-status-warning': semanticColorsDark['status-warning'],
|
package/src/theme/global.css
CHANGED
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
--color-status-success-light: #58F69E;
|
|
36
36
|
--color-status-success-dark: #298732;
|
|
37
37
|
--color-status-success-subtle: rgba(46, 213, 115, 0.12);
|
|
38
|
+
--color-status-live: #2ED573;
|
|
39
|
+
--color-status-live-muted: #22A444;
|
|
38
40
|
--color-status-error: #D14343;
|
|
39
41
|
--color-status-error-light: #E05254;
|
|
40
42
|
--color-status-error-dark: #A4221C;
|
|
@@ -170,6 +172,8 @@
|
|
|
170
172
|
--color-status-success-light: #58F69E;
|
|
171
173
|
--color-status-success-dark: #298732;
|
|
172
174
|
--color-status-success-subtle: #E3FFEE;
|
|
175
|
+
--color-status-live: #2ED573;
|
|
176
|
+
--color-status-live-muted: #22A444;
|
|
173
177
|
--color-status-error: #D14343;
|
|
174
178
|
--color-status-error-light: #E05254;
|
|
175
179
|
--color-status-error-dark: #A4221C;
|
|
@@ -369,7 +373,7 @@
|
|
|
369
373
|
z-index: 0;
|
|
370
374
|
background:
|
|
371
375
|
radial-gradient(ellipse 140% 100% at 15% 5%, rgba(var(--color-brand-primary-rgb, 255, 121, 0), 0.06) 0%, transparent 45%),
|
|
372
|
-
radial-gradient(ellipse 120% 80% at 85% 95%, rgba(var(--color-brand-secondary-rgb,
|
|
376
|
+
radial-gradient(ellipse 120% 80% at 85% 95%, rgba(var(--color-brand-secondary-rgb, 48, 123, 155), 0.04) 0%, transparent 45%);
|
|
373
377
|
}
|
|
374
378
|
|
|
375
379
|
/* Film grain overlay for vintage texture */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export const primitiveColors = {
|
|
9
|
-
// Blue scale
|
|
9
|
+
// Blue scale
|
|
10
10
|
blue: {
|
|
11
11
|
50: '#EFF6FF',
|
|
12
12
|
100: '#DBEAFE',
|
|
@@ -14,110 +14,40 @@ export const primitiveColors = {
|
|
|
14
14
|
300: '#93C5FD',
|
|
15
15
|
400: '#60A5FA',
|
|
16
16
|
500: '#3B82F6',
|
|
17
|
-
600: '#5048E5',
|
|
18
|
-
700: '#3832A0',
|
|
17
|
+
600: '#5048E5',
|
|
18
|
+
700: '#3832A0',
|
|
19
19
|
800: '#1E40AF',
|
|
20
20
|
900: '#1E3A8A',
|
|
21
21
|
},
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
green: {
|
|
25
|
-
50: '#ECFDF5',
|
|
26
|
-
100: '#D1FAE5',
|
|
27
|
-
200: '#A7F3D0',
|
|
28
|
-
300: '#6EE7B7',
|
|
29
|
-
400: '#3FC79A', // Secondary light
|
|
30
|
-
500: '#10B981', // Secondary main
|
|
31
|
-
600: '#059669',
|
|
32
|
-
700: '#0B815A', // Secondary dark
|
|
33
|
-
800: '#065F46',
|
|
34
|
-
900: '#064E3B',
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
// Teal scale (success)
|
|
38
|
-
teal: {
|
|
39
|
-
50: '#F0FDFA',
|
|
40
|
-
100: '#CCFBF1',
|
|
41
|
-
200: '#99F6E4',
|
|
42
|
-
300: '#5EEAD4',
|
|
43
|
-
400: '#43C6B7', // Success light
|
|
44
|
-
500: '#14B8A6', // Success main
|
|
45
|
-
600: '#0D9488',
|
|
46
|
-
700: '#0E8074', // Success dark
|
|
47
|
-
800: '#115E59',
|
|
48
|
-
900: '#134E4A',
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
// Red scale (error)
|
|
23
|
+
// Red scale
|
|
52
24
|
red: {
|
|
53
25
|
50: '#FEF2F2',
|
|
54
26
|
100: '#FEE2E2',
|
|
55
27
|
200: '#FECACA',
|
|
56
28
|
300: '#FCA5A5',
|
|
57
|
-
400: '#DA6868',
|
|
29
|
+
400: '#DA6868',
|
|
58
30
|
500: '#EF4444',
|
|
59
|
-
600: '#D14343',
|
|
60
|
-
700: '#922E2E',
|
|
31
|
+
600: '#D14343',
|
|
32
|
+
700: '#922E2E',
|
|
61
33
|
800: '#991B1B',
|
|
62
34
|
900: '#7F1D1D',
|
|
63
35
|
},
|
|
64
36
|
|
|
65
|
-
// Vivid
|
|
66
|
-
greenVivid: {
|
|
67
|
-
50: '#E9FBF0',
|
|
68
|
-
100: '#C8F7DA',
|
|
69
|
-
200: '#9FF0BF',
|
|
70
|
-
300: '#6BE79D',
|
|
71
|
-
400: '#47DE84', // Success vivid light
|
|
72
|
-
500: '#2ED573', // Success vivid main
|
|
73
|
-
600: '#22B85F',
|
|
74
|
-
700: '#1A9950', // Success vivid dark
|
|
75
|
-
800: '#157A40',
|
|
76
|
-
900: '#105C30',
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
// Vivid red scale (alert)
|
|
37
|
+
// Vivid red scale
|
|
80
38
|
redVivid: {
|
|
81
39
|
50: '#FFECEE',
|
|
82
40
|
100: '#FFD6DB',
|
|
83
41
|
200: '#FFB3BC',
|
|
84
42
|
300: '#FF8593',
|
|
85
|
-
400: '#FF6070',
|
|
86
|
-
500: '#FF4757',
|
|
43
|
+
400: '#FF6070',
|
|
44
|
+
500: '#FF4757',
|
|
87
45
|
600: '#E63548',
|
|
88
|
-
700: '#C42539',
|
|
46
|
+
700: '#C42539',
|
|
89
47
|
800: '#9E1C2C',
|
|
90
48
|
900: '#7A1520',
|
|
91
49
|
},
|
|
92
50
|
|
|
93
|
-
// Amber scale (warning)
|
|
94
|
-
amber: {
|
|
95
|
-
50: '#FFFBEB',
|
|
96
|
-
100: '#FEF3C7',
|
|
97
|
-
200: '#FDE68A',
|
|
98
|
-
300: '#FCD34D',
|
|
99
|
-
400: '#FFBF4C', // Warning light
|
|
100
|
-
500: '#FFB020', // Warning main
|
|
101
|
-
600: '#B27B16', // Warning dark
|
|
102
|
-
700: '#B45309',
|
|
103
|
-
800: '#92400E',
|
|
104
|
-
900: '#78350F',
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
// Sky/Blue scale (info)
|
|
108
|
-
sky: {
|
|
109
|
-
50: '#F0F9FF',
|
|
110
|
-
100: '#E0F2FE',
|
|
111
|
-
200: '#BAE6FD',
|
|
112
|
-
300: '#7DD3FC',
|
|
113
|
-
400: '#64B6F7', // Info light
|
|
114
|
-
500: '#2196F3', // Info main
|
|
115
|
-
600: '#0284C7',
|
|
116
|
-
700: '#0B79D0', // Info dark
|
|
117
|
-
800: '#075985',
|
|
118
|
-
900: '#0C4A6E',
|
|
119
|
-
},
|
|
120
|
-
|
|
121
51
|
// Neutral/Gray scale
|
|
122
52
|
neutral: {
|
|
123
53
|
50: '#FAFAFA',
|
|
@@ -133,19 +63,6 @@ export const primitiveColors = {
|
|
|
133
63
|
950: '#030712',
|
|
134
64
|
},
|
|
135
65
|
|
|
136
|
-
// Orange scale (accent/brand color)
|
|
137
|
-
accent: {
|
|
138
|
-
100: '#FFA860',
|
|
139
|
-
200: '#FF9630',
|
|
140
|
-
300: '#FF8500',
|
|
141
|
-
400: '#FF7900',
|
|
142
|
-
500: '#F56C00',
|
|
143
|
-
600: '#E06D10',
|
|
144
|
-
700: '#D0620C',
|
|
145
|
-
800: '#B75500',
|
|
146
|
-
900: '#A34900',
|
|
147
|
-
},
|
|
148
|
-
|
|
149
66
|
// Charcoal scale (dark backgrounds)
|
|
150
67
|
charcoal: {
|
|
151
68
|
0: '#6E6E6E',
|
|
@@ -161,19 +78,6 @@ export const primitiveColors = {
|
|
|
161
78
|
900: '#101010',
|
|
162
79
|
},
|
|
163
80
|
|
|
164
|
-
// Steel scale (cool accent)
|
|
165
|
-
steel: {
|
|
166
|
-
100: '#8AA4B8',
|
|
167
|
-
200: '#7894A8',
|
|
168
|
-
300: '#678498',
|
|
169
|
-
400: '#557488',
|
|
170
|
-
500: '#406D87',
|
|
171
|
-
600: '#39617A',
|
|
172
|
-
700: '#32556D',
|
|
173
|
-
800: '#243D53',
|
|
174
|
-
900: '#1D3146',
|
|
175
|
-
},
|
|
176
|
-
|
|
177
81
|
// Pure colors
|
|
178
82
|
white: '#FFFFFF',
|
|
179
83
|
black: '#000000',
|
|
@@ -46,6 +46,10 @@ export const semanticColorsLight = {
|
|
|
46
46
|
'status-success-dark': ramp.green[600],
|
|
47
47
|
'status-success-subtle': ramp.green[50],
|
|
48
48
|
|
|
49
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
50
|
+
'status-live': ramp.green[300],
|
|
51
|
+
'status-live-muted': ramp.green[500],
|
|
52
|
+
|
|
49
53
|
'status-error': ramp.red[600],
|
|
50
54
|
'status-error-light': ramp.red[500],
|
|
51
55
|
'status-error-dark': ramp.red[700],
|
|
@@ -176,6 +180,10 @@ export const semanticColorsDark = {
|
|
|
176
180
|
'status-success-dark': ramp.green[600],
|
|
177
181
|
'status-success-subtle': 'rgba(46, 213, 115, 0.12)',
|
|
178
182
|
|
|
183
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
184
|
+
'status-live': ramp.green[300],
|
|
185
|
+
'status-live-muted': ramp.green[500],
|
|
186
|
+
|
|
179
187
|
'status-error': ramp.red[600],
|
|
180
188
|
'status-error-light': ramp.red[500],
|
|
181
189
|
'status-error-dark': ramp.red[700],
|
|
@@ -8,14 +8,18 @@ export const WORKOUT_TOKENS = {
|
|
|
8
8
|
// Canonical 4-band performance scale — the single source for BOTH the
|
|
9
9
|
// VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
|
|
10
10
|
// is intentionally inverted between the two consumers: for velocity, green =
|
|
11
|
-
// fastest/best; for RPE, green = easiest. This is the [0,
|
|
12
|
-
// the canonical `sequentialEffort` primitive
|
|
13
|
-
//
|
|
11
|
+
// fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
|
|
12
|
+
// the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
|
|
13
|
+
// (green → gold → orange → red). The earlier [0,1,2,4] spent two of its four
|
|
14
|
+
// bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
|
|
15
|
+
// yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
|
|
16
|
+
// instead gives four cleanly separable bands that hold at 3px strip scale.
|
|
17
|
+
// Consumed inline (RN).
|
|
14
18
|
scale: {
|
|
15
|
-
green: sequentialEffort[0],
|
|
16
|
-
yellow: sequentialEffort[
|
|
17
|
-
orange: sequentialEffort[
|
|
18
|
-
red: sequentialEffort[4],
|
|
19
|
+
green: sequentialEffort[0], // green-300
|
|
20
|
+
yellow: sequentialEffort[2], // amber-300 (gold)
|
|
21
|
+
orange: sequentialEffort[3], // orange-400 (true orange)
|
|
22
|
+
red: sequentialEffort[4], // red-600
|
|
19
23
|
},
|
|
20
24
|
|
|
21
25
|
// BodyMap volume heatmap — the canonical `divergingScale` (under → optimal →
|
package/tailwind.config.js
CHANGED
|
@@ -38,6 +38,11 @@ module.exports = {
|
|
|
38
38
|
dark: 'var(--color-status-success-dark)',
|
|
39
39
|
subtle: 'var(--color-status-success-subtle)',
|
|
40
40
|
},
|
|
41
|
+
// Live-session accent — own role, decoupled from success
|
|
42
|
+
live: {
|
|
43
|
+
DEFAULT: 'var(--color-status-live)',
|
|
44
|
+
muted: 'var(--color-status-live-muted)',
|
|
45
|
+
},
|
|
41
46
|
error: {
|
|
42
47
|
DEFAULT: 'var(--color-status-error)',
|
|
43
48
|
light: 'var(--color-status-error-light)',
|