@titan-design/react-ui 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -47
- package/dist/bodymap.js +768 -701
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +770 -703
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1319 -216
- package/dist/index.d.ts +1319 -216
- package/dist/index.js +5996 -4498
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5773 -4357
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-CvsFqNtx.d.mts → pages-CpW3OB3g.d.mts} +21 -3
- package/dist/{pages-B1hqnGIC.d.ts → pages-_3Ajpiqu.d.ts} +21 -3
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1435 -1428
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1443 -1436
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +195 -134
- package/dist/theme/index.d.ts +195 -134
- package/dist/theme/index.js +215 -300
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +202 -296
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +62 -5
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +62 -5
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +73 -5
- package/dist/theme/tokens.d.ts +73 -5
- package/dist/theme/tokens.js +55 -7
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +55 -8
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/audits/2026-09-08-critique-addenda.md +51 -0
- package/docs/audits/2026-09-08-library-critique.md +212 -0
- package/docs/library-roadmap.md +99 -0
- package/package.json +1 -1
- package/src/arch/arch-graph.freshness.test.ts +52 -0
- package/src/arch/arch-graph.json +5021 -1617
- package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
- package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
- package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
- package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
- package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
- package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
- package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
- package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
- package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
- package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
- package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
- package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +181 -0
- package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
- package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
- package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
- package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
- package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
- package/src/components/custom/ActiveWork/README.md +305 -0
- package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
- package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
- package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
- package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
- package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
- package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
- package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
- package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
- package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
- package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
- package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
- package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
- package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
- package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
- package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
- package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
- package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
- package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
- package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
- package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
- package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
- package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
- package/src/components/custom/ActiveWork/format-time.ts +32 -0
- package/src/components/custom/ActiveWork/index.ts +19 -0
- package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
- package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
- package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
- package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +4 -3
- package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
- package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
- package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
- package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
- package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
- package/src/components/custom/Fatigue/README.md +40 -3
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
- package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
- package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
- package/src/components/custom/Fatigue/index.ts +17 -5
- package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
- package/src/components/custom/Fatigue/panel-layout.ts +185 -0
- package/src/components/custom/Gauge/Gauge.stories.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.tsx +4 -1
- package/src/components/custom/Metric/Metric.stories.tsx +10 -1
- package/src/components/custom/Metric/Metric.test.tsx +1 -3
- package/src/components/custom/Metric/Metric.tsx +5 -20
- package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
- package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
- package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
- package/src/components/custom/Prose/README.md +58 -0
- package/src/components/custom/Prose/index.ts +1 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +46 -8
- package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
- package/src/components/custom/Scatter/Scatter.tsx +116 -14
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +40 -7
- package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
- package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
- package/src/components/custom/Table/Table.stories.tsx +98 -15
- package/src/components/custom/Table/Table.test.tsx +273 -86
- package/src/components/custom/Table/Table.tsx +159 -67
- package/src/components/custom/Table/column-fit.test.ts +75 -0
- package/src/components/custom/Table/column-fit.ts +116 -0
- package/src/components/custom/Table/index.ts +9 -0
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
- package/src/components/custom/Treemap/Treemap.stories.tsx +2 -2
- package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +7 -2
- package/src/components/custom/Typography/Typography.stories.tsx +28 -1
- package/src/components/custom/Typography/Typography.test.tsx +34 -0
- package/src/components/custom/Typography/Typography.tsx +6 -0
- package/src/components/custom/Typography/index.ts +1 -8
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +9 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +19 -7
- package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
- package/src/components/custom/Workout/BaseBadge.tsx +3 -0
- package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
- package/src/components/custom/Workout/BodyMap.tsx +3 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +16 -4
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +45 -9
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +10 -8
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
- package/src/components/custom/Workout/CapacityBandChart.tsx +139 -108
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
- package/src/components/custom/Workout/DeviationBar.tsx +21 -16
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +182 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +3 -2
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -7
- package/src/components/custom/Workout/ExerciseCard.test.tsx +13 -3
- package/src/components/custom/Workout/ExerciseCard.tsx +42 -51
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +7 -7
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseHeading.tsx +8 -5
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -4
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
- package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
- package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
- package/src/components/custom/Workout/IntensityBar.tsx +4 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
- package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
- package/src/components/custom/Workout/MesoCard.tsx +12 -9
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
- package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
- package/src/components/custom/Workout/MesoStatusCard.tsx +9 -23
- package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
- package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
- package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
- package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
- package/src/components/custom/Workout/PrBadge.tsx +13 -17
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +4 -1
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
- package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
- package/src/components/custom/Workout/README.md +11 -6
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
- package/src/components/custom/Workout/ReadinessCheck.tsx +19 -3
- package/src/components/custom/Workout/RestTimer.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
- package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SegmentedBar.tsx +6 -2
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SessionHeader.tsx +12 -12
- package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
- package/src/components/custom/Workout/SessionRail.tsx +7 -9
- package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
- package/src/components/custom/Workout/SetBar.test.tsx +49 -4
- package/src/components/custom/Workout/SetBar.tsx +38 -12
- package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
- package/src/components/custom/Workout/SetRow.test.tsx +16 -2
- package/src/components/custom/Workout/SetRow.tsx +61 -23
- package/src/components/custom/Workout/SetStrip.stories.tsx +60 -5
- package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
- package/src/components/custom/Workout/SetStrip.tsx +24 -4
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
- package/src/components/custom/Workout/SetsRepsLoad.tsx +3 -5
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
- package/src/components/custom/Workout/Sparkline.tsx +14 -18
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
- package/src/components/custom/Workout/StatusDot.tsx +93 -63
- package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
- package/src/components/custom/Workout/StatusPill.tsx +27 -38
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +9 -3
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
- package/src/components/custom/Workout/StrengthTrendChart.tsx +31 -30
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
- package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +22 -7
- package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +7 -4
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +3 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
- package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
- package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
- package/src/components/custom/Workout/WeekRow.tsx +23 -16
- package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
- package/src/components/custom/Workout/WeightBadge.tsx +28 -27
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
- package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
- package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +7 -6
- package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
- package/src/components/custom/Workout/index.ts +5 -0
- package/src/components/custom/Workout/metricText.stories.tsx +6 -5
- package/src/components/custom/Workout/metricText.tsx +4 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +207 -26
- package/src/components/custom/charts/README.md +61 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +30 -1
- package/src/components/custom/charts/SetBarChart.tsx +42 -4
- package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
- package/src/components/custom/charts/SparkBars.test.tsx +69 -0
- package/src/components/custom/charts/SparkBars.tsx +85 -0
- package/src/components/custom/charts/index.ts +3 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
- package/src/components/custom/stepper/Stepper.test.tsx +1 -7
- package/src/components/custom/stepper/Stepper.tsx +13 -28
- package/src/components/icons/icons.stories.tsx +1 -1
- package/src/components/icons/icons.tsx +51 -0
- package/src/components/shell/AppShell.stories.tsx +150 -0
- package/src/components/shell/AppShell.test.tsx +70 -0
- package/src/components/shell/AppShell.tsx +90 -0
- package/src/components/shell/BrandLockup.stories.tsx +66 -5
- package/src/components/shell/BrandLockup.test.tsx +34 -1
- package/src/components/shell/BrandLockup.tsx +27 -11
- package/src/components/shell/NavItem.stories.tsx +4 -4
- package/src/components/shell/NavItem.test.tsx +17 -0
- package/src/components/shell/NavItem.tsx +17 -5
- package/src/components/shell/README.md +89 -26
- package/src/components/shell/SideNav.stories.tsx +38 -7
- package/src/components/shell/SideNav.test.tsx +16 -8
- package/src/components/shell/SideNav.tsx +17 -17
- package/src/components/shell/TopBar.stories.tsx +44 -19
- package/src/components/shell/TopBar.test.tsx +34 -12
- package/src/components/shell/TopBar.tsx +61 -40
- package/src/components/shell/brands.tsx +66 -0
- package/src/components/shell/index.ts +9 -8
- package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
- package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
- package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
- package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
- package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
- package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
- package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
- package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
- package/src/components/shell/workout/WorkoutShell.tsx +77 -0
- package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
- package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
- package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
- package/src/components/shell/workout/index.ts +9 -0
- package/src/components/shell/workout/workoutNavItems.tsx +16 -0
- package/src/components/ui/README.md +110 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
- package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -10
- package/src/components/ui/badge/Badge.stories.tsx +83 -28
- package/src/components/ui/badge/Badge.test.tsx +19 -3
- package/src/components/ui/badge/Badge.tsx +48 -55
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
- package/src/components/ui/button/Button.stories.tsx +46 -12
- package/src/components/ui/button/Button.tsx +32 -25
- package/src/components/ui/card/Card.stories.tsx +90 -94
- package/src/components/ui/card/Card.test.tsx +80 -6
- package/src/components/ui/card/Card.tsx +150 -203
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +6 -26
- package/src/components/ui/chip/Chip.stories.tsx +37 -13
- package/src/components/ui/chip/Chip.test.tsx +10 -6
- package/src/components/ui/chip/Chip.tsx +76 -91
- package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
- package/src/components/ui/collapse/Collapse.tsx +6 -11
- package/src/components/ui/data-row/DataRow.stories.tsx +9 -2
- package/src/components/ui/data-row/DataRow.test.tsx +5 -15
- package/src/components/ui/data-row/DataRow.tsx +18 -7
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.tsx +1 -5
- package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
- package/src/components/ui/drawer/Drawer.tsx +13 -30
- package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
- package/src/components/ui/form-field/FormField.stories.tsx +6 -20
- package/src/components/ui/form-field/FormField.test.tsx +1 -5
- package/src/components/ui/form-field/FormField.tsx +10 -46
- package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
- package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
- package/src/components/ui/help-tip/HelpTip.tsx +21 -22
- package/src/components/ui/help-tip/index.ts +7 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
- package/src/components/ui/icon-box/IconBox.tsx +2 -12
- package/src/components/ui/indicator/Indicator.stories.tsx +1 -1
- package/src/components/ui/indicator/Indicator.tsx +20 -14
- package/src/components/ui/input/Input.stories.tsx +3 -11
- package/src/components/ui/input/Input.test.tsx +2 -10
- package/src/components/ui/input/Input.tsx +10 -25
- package/src/components/ui/input/index.ts +7 -1
- package/src/components/ui/link/Link.stories.tsx +19 -7
- package/src/components/ui/list-item/ListItem.stories.tsx +11 -2
- package/src/components/ui/list-item/ListItem.tsx +2 -9
- package/src/components/ui/menu/Menu.stories.tsx +21 -15
- package/src/components/ui/menu/Menu.test.tsx +2 -11
- package/src/components/ui/menu/Menu.tsx +12 -14
- package/src/components/ui/menu/index.ts +8 -1
- package/src/components/ui/modal/Modal.stories.tsx +2 -2
- package/src/components/ui/modal/Modal.tsx +29 -19
- package/src/components/ui/pill/Pill.stories.tsx +58 -23
- package/src/components/ui/pill/Pill.test.tsx +101 -3
- package/src/components/ui/pill/Pill.tsx +164 -49
- package/src/components/ui/pill/index.ts +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +5 -7
- package/src/components/ui/popover/Popover.test.tsx +1 -6
- package/src/components/ui/popover/Popover.tsx +20 -15
- package/src/components/ui/popover/index.ts +7 -1
- package/src/components/ui/progress/Progress.stories.tsx +3 -12
- package/src/components/ui/progress/Progress.test.tsx +1 -5
- package/src/components/ui/radio/Radio.stories.tsx +42 -21
- package/src/components/ui/radio/Radio.test.tsx +6 -2
- package/src/components/ui/radio/Radio.tsx +23 -22
- package/src/components/ui/section/Section.stories.tsx +5 -14
- package/src/components/ui/section/Section.test.tsx +1 -6
- package/src/components/ui/section/Section.tsx +1 -3
- package/src/components/ui/section/index.ts +1 -5
- package/src/components/ui/select/Select.stories.tsx +3 -13
- package/src/components/ui/select/Select.test.tsx +2 -11
- package/src/components/ui/select/Select.tsx +14 -14
- package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
- package/src/components/ui/skeleton/Skeleton.tsx +3 -14
- package/src/components/ui/skeleton/index.ts +1 -7
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +1 -4
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +17 -5
- package/src/components/ui/surface/Surface.test.tsx +88 -28
- package/src/components/ui/surface/Surface.tsx +37 -87
- package/src/components/ui/surface/SurfaceContext.ts +22 -62
- package/src/components/ui/surface/index.ts +8 -0
- package/src/components/ui/surface/resolveSurface.ts +98 -0
- package/src/components/ui/surface/surface.contract.test.ts +6 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.tsx +7 -6
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +7 -22
- package/src/components/ui/tabs/index.ts +1 -7
- package/src/components/ui/tile/Tile.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.tsx +3 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toast/Toast.test.tsx +2 -8
- package/src/components/ui/toast/Toast.tsx +43 -53
- package/src/components/ui/toast/index.ts +0 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +25 -43
- package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
- package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
- package/src/components/ui/tooltip/Tooltip.tsx +55 -36
- package/src/components/ui/tooltip/index.ts +1 -1
- package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
- package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
- package/src/components/ui/trigger/index.ts +2 -0
- package/src/stories/PresetShowcase.stories.tsx +100 -47
- package/src/test/no-device-internals.test.ts +3 -3
- package/src/theme/ChoosingTokens.stories.tsx +48 -0
- package/src/theme/ColorPalettes.stories.tsx +62 -45
- package/src/theme/ColorPrimitives.stories.tsx +35 -35
- package/src/theme/Depth.stories.tsx +132 -65
- package/src/theme/DepthCalibration.stories.tsx +23 -21
- package/src/theme/ThemeProvider.test.tsx +4 -4
- package/src/theme/barrel.ts +7 -6
- package/src/theme/color-stories.coverage.test.ts +1 -1
- package/src/theme/color-utils.ts +41 -29
- package/src/theme/config.completeness.test.ts +1 -1
- package/src/theme/config.ts +28 -0
- package/src/theme/elevation.test.ts +82 -56
- package/src/theme/elevation.ts +77 -561
- package/src/theme/global.css +30 -2
- package/src/theme/grey-ramp.test.ts +24 -8
- package/src/theme/lift.test.ts +39 -0
- package/src/theme/lift.ts +102 -0
- package/src/theme/materials.test.ts +5 -2
- package/src/theme/materials.ts +38 -4
- package/src/theme/native-theming-guard.test.ts +5 -5
- package/src/theme/presets/apply.ts +4 -0
- package/src/theme/presets/audiobook.ts +5 -2
- package/src/theme/presets/default.ts +4 -0
- package/src/theme/presets/index.ts +14 -1
- package/src/theme/presets/types.ts +1 -1
- package/src/theme/semantic-contrast.test.ts +13 -4
- package/src/theme/status-distinctness.test.ts +66 -0
- package/src/theme/surface-planes.test.ts +37 -0
- package/src/theme/surface-planes.ts +68 -0
- package/src/theme/token-export-guard.test.ts +1 -3
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +65 -33
- package/src/theme/tokens/semantic.ts +71 -37
- package/src/theme/wash-ladder.test.ts +58 -0
- package/src/theme/workout-tokens.ts +1 -7
- package/src/types/jest-axe.d.ts +2 -6
- package/src/types/raw-md.d.ts +5 -0
- package/src/utils/colors.ts +4 -4
- package/src/utils/form.ts +1 -3
- package/src/utils/index.ts +2 -7
- package/src/utils/number-format.ts +17 -0
- package/src/utils/workout-format.ts +15 -6
- package/src/web-jsx/jsx-runtime.ts +1 -5
- package/tailwind.config.js +5 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
- package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
- package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
- package/src/components/shell/DashboardShell.stories.tsx +0 -65
- package/src/components/shell/DashboardShell.tsx +0 -75
- package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
- package/src/theme/elevation.stories.tsx +0 -346
- package/src/utils/useTheme.ts +0 -72
- /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
- /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
- /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
- /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
package/src/theme/global.css
CHANGED
|
@@ -19,10 +19,14 @@
|
|
|
19
19
|
--color-brand-primary-light: #FFA063;
|
|
20
20
|
--color-brand-primary-dark: #DA5F00;
|
|
21
21
|
--color-brand-primary-subtle: rgba(255, 121, 0, 0.12);
|
|
22
|
+
--color-brand-primary-muted: rgba(255, 121, 0, 0.30);
|
|
23
|
+
--color-brand-primary-strong: rgba(255, 121, 0, 0.50);
|
|
22
24
|
--color-brand-secondary: #307B9B;
|
|
23
25
|
--color-brand-secondary-light: #2697B7;
|
|
24
26
|
--color-brand-secondary-dark: #2A617F;
|
|
25
27
|
--color-brand-secondary-subtle: rgba(48, 123, 155, 0.12);
|
|
28
|
+
--color-brand-secondary-muted: rgba(48, 123, 155, 0.30);
|
|
29
|
+
--color-brand-secondary-strong: rgba(48, 123, 155, 0.50);
|
|
26
30
|
--color-brand-primary-hover: #FFA063;
|
|
27
31
|
--color-brand-primary-active: #FFC7A2;
|
|
28
32
|
--color-brand-secondary-hover: #2697B7;
|
|
@@ -35,26 +39,36 @@
|
|
|
35
39
|
--color-status-success-light: #58F69E;
|
|
36
40
|
--color-status-success-dark: #298732;
|
|
37
41
|
--color-status-success-subtle: rgba(46, 213, 115, 0.12);
|
|
42
|
+
--color-status-success-muted: rgba(46, 213, 115, 0.30);
|
|
43
|
+
--color-status-success-strong: rgba(46, 213, 115, 0.50);
|
|
38
44
|
--color-status-live: #2ED573;
|
|
39
45
|
--color-status-live-muted: #22A444;
|
|
40
46
|
--color-status-error: #D14343;
|
|
41
47
|
--color-status-error-light: #E05254;
|
|
42
48
|
--color-status-error-dark: #A4221C;
|
|
43
49
|
--color-status-error-subtle: rgba(209, 67, 67, 0.12);
|
|
44
|
-
--color-status-error-
|
|
50
|
+
--color-status-error-muted: rgba(209, 67, 67, 0.30);
|
|
51
|
+
--color-status-error-strong: rgba(209, 67, 67, 0.50);
|
|
52
|
+
--color-status-error-vivid: #FF4757;
|
|
45
53
|
--color-status-error-vivid-light: #E05254;
|
|
46
54
|
--color-status-error-vivid-dark: #A4221C;
|
|
47
55
|
--color-status-error-vivid-subtle: rgba(255, 71, 87, 0.12);
|
|
56
|
+
--color-status-error-vivid-muted: rgba(255, 71, 87, 0.30);
|
|
57
|
+
--color-status-error-vivid-strong: rgba(255, 71, 87, 0.50);
|
|
48
58
|
--color-status-error-vivid-rgb: 255, 71, 87;
|
|
49
59
|
--color-border-prominent: #424140;
|
|
50
60
|
--color-status-warning: #F9B415;
|
|
51
61
|
--color-status-warning-light: #FFD352;
|
|
52
62
|
--color-status-warning-dark: #C27400;
|
|
53
63
|
--color-status-warning-subtle: rgba(249, 180, 21, 0.12);
|
|
64
|
+
--color-status-warning-muted: rgba(249, 180, 21, 0.30);
|
|
65
|
+
--color-status-warning-strong: rgba(249, 180, 21, 0.50);
|
|
54
66
|
--color-status-info: #2196F3;
|
|
55
67
|
--color-status-info-light: #78C2FF;
|
|
56
68
|
--color-status-info-dark: #1072CB;
|
|
57
69
|
--color-status-info-subtle: rgba(33, 150, 243, 0.12);
|
|
70
|
+
--color-status-info-muted: rgba(33, 150, 243, 0.30);
|
|
71
|
+
--color-status-info-strong: rgba(33, 150, 243, 0.50);
|
|
58
72
|
|
|
59
73
|
--color-on-status-success: #FFFFFF;
|
|
60
74
|
--color-on-status-error: #FFFFFF;
|
|
@@ -171,10 +185,14 @@
|
|
|
171
185
|
--color-brand-primary-light: #FFA063;
|
|
172
186
|
--color-brand-primary-dark: #DA5F00;
|
|
173
187
|
--color-brand-primary-subtle: rgba(255, 121, 0, 0.08);
|
|
188
|
+
--color-brand-primary-muted: rgba(255, 121, 0, 0.30);
|
|
189
|
+
--color-brand-primary-strong: rgba(255, 121, 0, 0.50);
|
|
174
190
|
--color-brand-secondary: #307B9B;
|
|
175
191
|
--color-brand-secondary-light: #2697B7;
|
|
176
192
|
--color-brand-secondary-dark: #2A617F;
|
|
177
193
|
--color-brand-secondary-subtle: rgba(48, 123, 155, 0.08);
|
|
194
|
+
--color-brand-secondary-muted: rgba(48, 123, 155, 0.30);
|
|
195
|
+
--color-brand-secondary-strong: rgba(48, 123, 155, 0.50);
|
|
178
196
|
--color-brand-primary-hover: #DA5F00;
|
|
179
197
|
--color-brand-primary-active: #B94A00;
|
|
180
198
|
--color-brand-secondary-hover: #2A617F;
|
|
@@ -187,26 +205,36 @@
|
|
|
187
205
|
--color-status-success-light: #58F69E;
|
|
188
206
|
--color-status-success-dark: #298732;
|
|
189
207
|
--color-status-success-subtle: #E3FFEE;
|
|
208
|
+
--color-status-success-muted: rgba(46, 213, 115, 0.30);
|
|
209
|
+
--color-status-success-strong: rgba(46, 213, 115, 0.50);
|
|
190
210
|
--color-status-live: #2ED573;
|
|
191
211
|
--color-status-live-muted: #22A444;
|
|
192
212
|
--color-status-error: #D14343;
|
|
193
213
|
--color-status-error-light: #E05254;
|
|
194
214
|
--color-status-error-dark: #A4221C;
|
|
195
215
|
--color-status-error-subtle: #FFF4F4;
|
|
196
|
-
--color-status-error-
|
|
216
|
+
--color-status-error-muted: rgba(209, 67, 67, 0.30);
|
|
217
|
+
--color-status-error-strong: rgba(209, 67, 67, 0.50);
|
|
218
|
+
--color-status-error-vivid: #FF4757;
|
|
197
219
|
--color-status-error-vivid-light: #E05254;
|
|
198
220
|
--color-status-error-vivid-dark: #A4221C;
|
|
199
221
|
--color-status-error-vivid-subtle: rgba(255, 71, 87, 0.12);
|
|
222
|
+
--color-status-error-vivid-muted: rgba(255, 71, 87, 0.30);
|
|
223
|
+
--color-status-error-vivid-strong: rgba(255, 71, 87, 0.50);
|
|
200
224
|
--color-status-error-vivid-rgb: 255, 71, 87;
|
|
201
225
|
--color-border-prominent: #A29F9D;
|
|
202
226
|
--color-status-warning: #F9B415;
|
|
203
227
|
--color-status-warning-light: #FFD352;
|
|
204
228
|
--color-status-warning-dark: #C27400;
|
|
205
229
|
--color-status-warning-subtle: #FFF7DD;
|
|
230
|
+
--color-status-warning-muted: rgba(249, 180, 21, 0.30);
|
|
231
|
+
--color-status-warning-strong: rgba(249, 180, 21, 0.50);
|
|
206
232
|
--color-status-info: #2196F3;
|
|
207
233
|
--color-status-info-light: #78C2FF;
|
|
208
234
|
--color-status-info-dark: #1072CB;
|
|
209
235
|
--color-status-info-subtle: #EFF8FF;
|
|
236
|
+
--color-status-info-muted: rgba(33, 150, 243, 0.30);
|
|
237
|
+
--color-status-info-strong: rgba(33, 150, 243, 0.50);
|
|
210
238
|
|
|
211
239
|
--color-on-status-success: #FFFFFF;
|
|
212
240
|
--color-on-status-error: #FFFFFF;
|
|
@@ -11,7 +11,6 @@ import { execFileSync } from 'node:child_process'
|
|
|
11
11
|
import { join } from 'node:path'
|
|
12
12
|
import { greyRamp, SURFACE_PLANE_STEPS, primitiveRamps } from './tokens/primitives'
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
// ── colour math (CIELAB D65 + WCAG relative luminance) ──────────────────────
|
|
16
15
|
const hex2rgb = (h: string): [number, number, number] => {
|
|
17
16
|
const n = parseInt(h.slice(1), 16)
|
|
@@ -42,9 +41,15 @@ const warmth = (hex: string) => {
|
|
|
42
41
|
|
|
43
42
|
/** Machado-2009 dichromacy, severity 1.0. */
|
|
44
43
|
const CVD = {
|
|
45
|
-
deuteranopia: [
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
deuteranopia: [
|
|
45
|
+
0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881,
|
|
46
|
+
],
|
|
47
|
+
protanopia: [
|
|
48
|
+
0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998,
|
|
49
|
+
],
|
|
50
|
+
tritanopia: [
|
|
51
|
+
1.255528, -0.076749, -0.178779, -0.078411, 0.930809, 0.147602, 0.004733, 0.691367, 0.3039,
|
|
52
|
+
],
|
|
48
53
|
} as const
|
|
49
54
|
function simulateCvd(m: readonly number[], hex: string): string {
|
|
50
55
|
const [r, g, b] = hex2rgb(hex)
|
|
@@ -56,13 +61,19 @@ function simulateCvd(m: readonly number[], hex: string): string {
|
|
|
56
61
|
return (
|
|
57
62
|
'#' +
|
|
58
63
|
out
|
|
59
|
-
.map((v) =>
|
|
64
|
+
.map((v) =>
|
|
65
|
+
Math.round(Math.max(0, Math.min(255, v)))
|
|
66
|
+
.toString(16)
|
|
67
|
+
.padStart(2, '0')
|
|
68
|
+
)
|
|
60
69
|
.join('')
|
|
61
70
|
.toUpperCase()
|
|
62
71
|
)
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
const STEPS = Object.keys(greyRamp)
|
|
74
|
+
const STEPS = Object.keys(greyRamp)
|
|
75
|
+
.map(Number)
|
|
76
|
+
.sort((a, b) => a - b)
|
|
66
77
|
const hexAt = (step: number) => greyRamp[step as keyof typeof greyRamp]
|
|
67
78
|
|
|
68
79
|
describe('greyRamp — shape', () => {
|
|
@@ -82,7 +93,9 @@ describe('greyRamp — shape', () => {
|
|
|
82
93
|
it('has no two steps a reader could not tell apart', () => {
|
|
83
94
|
for (let i = 1; i < STEPS.length; i++) {
|
|
84
95
|
const d = dE76(hexAt(STEPS[i - 1]), hexAt(STEPS[i]))
|
|
85
|
-
expect(d, `steps ${STEPS[i - 1]}/${STEPS[i]} collapse at ΔE ${d.toFixed(2)}`).toBeGreaterThan(
|
|
96
|
+
expect(d, `steps ${STEPS[i - 1]}/${STEPS[i]} collapse at ΔE ${d.toFixed(2)}`).toBeGreaterThan(
|
|
97
|
+
1.5
|
|
98
|
+
)
|
|
86
99
|
}
|
|
87
100
|
})
|
|
88
101
|
|
|
@@ -150,7 +163,10 @@ describe('greyRamp — accessibility gate (TD-07.15)', () => {
|
|
|
150
163
|
// Adjacent steps must stay separable AFTER simulation, or the depth
|
|
151
164
|
// ordering of the surface stack stops reading for those viewers.
|
|
152
165
|
for (let i = 1; i < STEPS.length; i++) {
|
|
153
|
-
const d = dE76(
|
|
166
|
+
const d = dE76(
|
|
167
|
+
simulateCvd(matrix, hexAt(STEPS[i - 1])),
|
|
168
|
+
simulateCvd(matrix, hexAt(STEPS[i]))
|
|
169
|
+
)
|
|
154
170
|
expect(d, `${mode}: grey-${STEPS[i - 1]}/${STEPS[i]} merge`).toBeGreaterThan(1.5)
|
|
155
171
|
}
|
|
156
172
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { liftShadow, liftStyle, LIFT_RIM_ALPHA, FLOATING_LIFT_MIN } from './lift'
|
|
3
|
+
|
|
4
|
+
describe('liftShadow', () => {
|
|
5
|
+
it('leads with the card rim on dark: one grade under the paperSheet hero rim', () => {
|
|
6
|
+
expect(liftShadow(1, 'dark')).toMatch(/^inset 0 1px 0 rgba\(255,255,255,0\.12\)/)
|
|
7
|
+
expect(LIFT_RIM_ALPHA.dark).toBe(0.12)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('omits the rim when asked, keeping the ambient layers', () => {
|
|
11
|
+
const shadow = liftShadow(2, 'dark', { rim: 0 })
|
|
12
|
+
expect(shadow).not.toContain('inset')
|
|
13
|
+
expect(shadow.split(', ').length).toBe(2)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('honours a rim override (a hero surface can ask for the paperSheet rim)', () => {
|
|
17
|
+
expect(liftShadow(2, 'dark', { rim: 0.2 })).toContain('rgba(255,255,255,0.20)')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('casts a lighter shadow on a light surface, same geometry', () => {
|
|
21
|
+
const dark = liftShadow(3, 'dark', { rim: 0 }).split(', ')
|
|
22
|
+
const light = liftShadow(3, 'light', { rim: 0 }).split(', ')
|
|
23
|
+
expect(light.length).toBe(dark.length)
|
|
24
|
+
const alpha = (layer: string) => Number(/rgba\(0,0,0,([\d.]+)\)/.exec(layer)![1])
|
|
25
|
+
for (let i = 0; i < dark.length; i++) expect(alpha(light[i])).toBeLessThan(alpha(dark[i]))
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('uses three ambient layers from the floating threshold up', () => {
|
|
29
|
+
expect(liftShadow(FLOATING_LIFT_MIN, 'dark', { rim: 0 }).split(', ').length).toBe(3)
|
|
30
|
+
expect(liftShadow(3, 'dark', { rim: 0 }).split(', ').length).toBe(2)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('liftStyle', () => {
|
|
35
|
+
it('returns a web boxShadow under the RNW alias', () => {
|
|
36
|
+
const style = liftStyle(2) as { boxShadow?: string }
|
|
37
|
+
expect(style.boxShadow).toContain('inset 0 1px 0')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LIFT — the treatment a plane wears when it sits above its neighbour.
|
|
3
|
+
*
|
|
4
|
+
* Tone says which plane a container sits on; lift says it is resting ON the one
|
|
5
|
+
* below. Two cues, both lit from above:
|
|
6
|
+
*
|
|
7
|
+
* RIM a crisp 1px top rim-light. The one neumorphism half that survives
|
|
8
|
+
* near black. paperSheet's hero rim is 0.20 (VW-99 run 2); a card is
|
|
9
|
+
* not a hero, so the default lift sits one grade quieter at 0.12,
|
|
10
|
+
* chosen against 0.20 side by side in Lab/Depth (2026-09-08).
|
|
11
|
+
* AMBIENT a soft shadow cast onto the plane below, growing with the number of
|
|
12
|
+
* planes crossed. Content lifts (1–3) are tight; floating lifts (4–5)
|
|
13
|
+
* are large and soft, describing separation from the page rather than
|
|
14
|
+
* rank in a stack.
|
|
15
|
+
*
|
|
16
|
+
* Neither cue is a hairline ring: a ring is an edge, not a lift, and stays the
|
|
17
|
+
* divider's job. Web carries the multi-layer boxShadow; native gets the single
|
|
18
|
+
* shadow* set. The tone underneath is load-bearing on its own either way.
|
|
19
|
+
*/
|
|
20
|
+
import { Platform, type ViewStyle } from 'react-native'
|
|
21
|
+
import type { ThemeMode } from './tokens/semantic'
|
|
22
|
+
|
|
23
|
+
export type LiftStep = 1 | 2 | 3 | 4 | 5
|
|
24
|
+
|
|
25
|
+
/** The lowest lift that is FLOATING (menu, popover, toast) rather than content. */
|
|
26
|
+
export const FLOATING_LIFT_MIN: LiftStep = 4
|
|
27
|
+
|
|
28
|
+
/** Rim-light alpha per mode. Light mode is a placeholder pending its own pass. */
|
|
29
|
+
export const LIFT_RIM_ALPHA: Record<ThemeMode, number> = { dark: 0.12, light: 0.9 }
|
|
30
|
+
|
|
31
|
+
interface ShadowLayer {
|
|
32
|
+
y: number
|
|
33
|
+
blur: number
|
|
34
|
+
alpha: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const AMBIENT: Record<LiftStep, ShadowLayer[]> = {
|
|
38
|
+
1: [
|
|
39
|
+
{ y: 1, blur: 2, alpha: 0.35 },
|
|
40
|
+
{ y: 2, blur: 6, alpha: 0.3 },
|
|
41
|
+
],
|
|
42
|
+
2: [
|
|
43
|
+
{ y: 1, blur: 2, alpha: 0.35 },
|
|
44
|
+
{ y: 4, blur: 12, alpha: 0.32 },
|
|
45
|
+
],
|
|
46
|
+
3: [
|
|
47
|
+
{ y: 2, blur: 4, alpha: 0.35 },
|
|
48
|
+
{ y: 8, blur: 22, alpha: 0.35 },
|
|
49
|
+
],
|
|
50
|
+
4: [
|
|
51
|
+
{ y: 1, blur: 2, alpha: 0.35 },
|
|
52
|
+
{ y: 10, blur: 24, alpha: 0.3 },
|
|
53
|
+
{ y: 24, blur: 48, alpha: 0.2 },
|
|
54
|
+
],
|
|
55
|
+
5: [
|
|
56
|
+
{ y: 2, blur: 4, alpha: 0.4 },
|
|
57
|
+
{ y: 16, blur: 32, alpha: 0.35 },
|
|
58
|
+
{ y: 32, blur: 64, alpha: 0.25 },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A light surface casts a lighter shadow: same geometry, scaled alpha. */
|
|
63
|
+
const AMBIENT_ALPHA_SCALE: Record<ThemeMode, number> = { dark: 1, light: 0.4 }
|
|
64
|
+
|
|
65
|
+
export interface LiftOptions {
|
|
66
|
+
/** Override the rim alpha. `0` drops the rim and leaves the ambient shadow alone. */
|
|
67
|
+
rim?: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function ambientLayers(step: LiftStep, mode: ThemeMode): string[] {
|
|
71
|
+
const scale = AMBIENT_ALPHA_SCALE[mode]
|
|
72
|
+
return AMBIENT[step].map(
|
|
73
|
+
({ y, blur, alpha }) => `0 ${y}px ${blur}px rgba(0,0,0,${(alpha * scale).toFixed(2)})`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The web boxShadow string for a lift: rim first, then the ambient layers. */
|
|
78
|
+
export function liftShadow(step: LiftStep, mode: ThemeMode = 'dark', opts: LiftOptions = {}) {
|
|
79
|
+
const rim = opts.rim ?? LIFT_RIM_ALPHA[mode]
|
|
80
|
+
const layers = ambientLayers(step, mode)
|
|
81
|
+
if (rim > 0) layers.unshift(`inset 0 1px 0 rgba(255,255,255,${rim.toFixed(2)})`)
|
|
82
|
+
return layers.join(', ')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Platform style for a lift. Compose after `backgroundColor`; never instead of it. */
|
|
86
|
+
export function liftStyle(
|
|
87
|
+
step: LiftStep,
|
|
88
|
+
mode: ThemeMode = 'dark',
|
|
89
|
+
opts: LiftOptions = {}
|
|
90
|
+
): ViewStyle {
|
|
91
|
+
const deepest = AMBIENT[step][AMBIENT[step].length - 1]
|
|
92
|
+
return Platform.select({
|
|
93
|
+
web: { boxShadow: liftShadow(step, mode, opts) } as unknown as ViewStyle,
|
|
94
|
+
default: {
|
|
95
|
+
shadowColor: '#000',
|
|
96
|
+
shadowOffset: { width: 0, height: Math.round(deepest.y / 2) },
|
|
97
|
+
shadowOpacity: deepest.alpha * AMBIENT_ALPHA_SCALE[mode],
|
|
98
|
+
shadowRadius: deepest.blur / 2,
|
|
99
|
+
elevation: step,
|
|
100
|
+
},
|
|
101
|
+
}) as ViewStyle
|
|
102
|
+
}
|
|
@@ -15,7 +15,8 @@ import { describe, it, expect } from 'vitest'
|
|
|
15
15
|
import { grainOpacityForTone, grainForTone, paperSheet, insetWell, barPaper } from './materials'
|
|
16
16
|
import { greyRamp } from './tokens/primitives'
|
|
17
17
|
|
|
18
|
-
const styleOf = (s: Record<string, unknown>) =>
|
|
18
|
+
const styleOf = (s: Record<string, unknown>) =>
|
|
19
|
+
s as { backgroundImage?: string; boxShadow?: string; backgroundColor?: string }
|
|
19
20
|
|
|
20
21
|
describe('grain', () => {
|
|
21
22
|
it('scales opacity with the tone it sits on', () => {
|
|
@@ -75,7 +76,9 @@ describe('insetWell', () => {
|
|
|
75
76
|
// a darker rectangle. It is the light on the floor that says "below".
|
|
76
77
|
const shadow = styleOf(insetWell(greyRamp[950])).boxShadow!
|
|
77
78
|
expect(shadow).toContain('inset 0 2px')
|
|
78
|
-
expect(shadow, 'missing the bottom floor rim').toContain(
|
|
79
|
+
expect(shadow, 'missing the bottom floor rim').toContain(
|
|
80
|
+
'inset 0 -1px 0 rgba(255,255,255,0.12)'
|
|
81
|
+
)
|
|
79
82
|
})
|
|
80
83
|
|
|
81
84
|
it('is entirely inset — a well must not cast outward', () => {
|
package/src/theme/materials.ts
CHANGED
|
@@ -54,10 +54,12 @@
|
|
|
54
54
|
*
|
|
55
55
|
* Kept: grain, rim-light, contact shadow.
|
|
56
56
|
*
|
|
57
|
-
* See sources/design/surface-system-north-star.md §4 and
|
|
58
|
-
* sources/runbooks/VW-99-depth-wall-calibration.md
|
|
57
|
+
* See `sources/design/surface-system-north-star.md` §4 and
|
|
58
|
+
* `sources/runbooks/VW-99-depth-wall-calibration.md`. Those paths are relative to
|
|
59
|
+
* the `voltras-workspace` active-work initiative (`active-work open voltras-workspace`), not this repo —
|
|
60
|
+
* titan-design has no `sources/` directory, so they do not resolve from here.
|
|
59
61
|
*/
|
|
60
|
-
import type { ViewStyle } from 'react-native'
|
|
62
|
+
import type { TextStyle, ViewStyle } from 'react-native'
|
|
61
63
|
import { getSemanticColors } from './tokens/semantic'
|
|
62
64
|
|
|
63
65
|
const c = getSemanticColors('dark')
|
|
@@ -66,7 +68,13 @@ const c = getSemanticColors('dark')
|
|
|
66
68
|
|
|
67
69
|
function parseHex(hex: string): [number, number, number] | null {
|
|
68
70
|
const h = hex.replace('#', '')
|
|
69
|
-
const full =
|
|
71
|
+
const full =
|
|
72
|
+
h.length === 3
|
|
73
|
+
? h
|
|
74
|
+
.split('')
|
|
75
|
+
.map((ch) => ch + ch)
|
|
76
|
+
.join('')
|
|
77
|
+
: h
|
|
70
78
|
const rgb: [number, number, number] = [
|
|
71
79
|
parseInt(full.slice(0, 2), 16),
|
|
72
80
|
parseInt(full.slice(2, 4), 16),
|
|
@@ -209,3 +217,29 @@ export function barPaper(color: string, flip = false): ViewStyle {
|
|
|
209
217
|
: 'inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)',
|
|
210
218
|
} as unknown as ViewStyle
|
|
211
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* A POST-IT card: a matte tinted sheet with a crisp rim, a defined contact
|
|
223
|
+
* shadow, and a deliberate CANT (`deg`) so it reads as a stuck note — where a
|
|
224
|
+
* small unit of data wants to feel pinned to the surface (alerts, next-set). A
|
|
225
|
+
* degree or two, not a fraction, so the tilt is intentional rather than a bug.
|
|
226
|
+
*/
|
|
227
|
+
export function postIt(tone: string = c['surface-raised'], deg = -1.5): ViewStyle {
|
|
228
|
+
return {
|
|
229
|
+
backgroundColor: tone,
|
|
230
|
+
backgroundImage: grainForTone(tone),
|
|
231
|
+
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.12), 0 6px 14px rgba(0,0,0,0.45)',
|
|
232
|
+
transform: [{ rotate: `${deg}deg` }],
|
|
233
|
+
} as unknown as ViewStyle
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* DEBOSSED (engraved) text: a 1px light highlight dropped just below the glyphs
|
|
238
|
+
* so a label reads as pressed INTO the surface. Deliberate, for section eyebrows
|
|
239
|
+
* on a matte plane — not body copy. Pair with a tertiary/dim text colour.
|
|
240
|
+
*/
|
|
241
|
+
export const debossLabel: TextStyle = {
|
|
242
|
+
textShadowColor: 'rgba(255,255,255,0.11)',
|
|
243
|
+
textShadowOffset: { width: 0, height: 1 },
|
|
244
|
+
textShadowRadius: 0,
|
|
245
|
+
}
|
|
@@ -37,7 +37,7 @@ const colorTree = tailwindConfig.theme?.extend?.colors ?? {}
|
|
|
37
37
|
// `brand-primary` class).
|
|
38
38
|
function flattenColors(
|
|
39
39
|
node: Record<string, unknown>,
|
|
40
|
-
prefix: string[] = []
|
|
40
|
+
prefix: string[] = []
|
|
41
41
|
): Record<string, string> {
|
|
42
42
|
const out: Record<string, string> = {}
|
|
43
43
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -83,11 +83,11 @@ describe('native theming guard (no color token renders black on native)', () =>
|
|
|
83
83
|
}
|
|
84
84
|
expect(
|
|
85
85
|
undefinedDark,
|
|
86
|
-
'tailwind colors point at vars missing from darkThemeCSSVars (className renders black on native dark)'
|
|
86
|
+
'tailwind colors point at vars missing from darkThemeCSSVars (className renders black on native dark)'
|
|
87
87
|
).toEqual([])
|
|
88
88
|
expect(
|
|
89
89
|
undefinedLight,
|
|
90
|
-
'tailwind colors point at vars missing from lightThemeCSSVars (className renders black on native light)'
|
|
90
|
+
'tailwind colors point at vars missing from lightThemeCSSVars (className renders black on native light)'
|
|
91
91
|
).toEqual([])
|
|
92
92
|
})
|
|
93
93
|
|
|
@@ -106,11 +106,11 @@ describe('native theming guard (no color token renders black on native)', () =>
|
|
|
106
106
|
}
|
|
107
107
|
expect(
|
|
108
108
|
invalidValues,
|
|
109
|
-
`getSemanticColors('${mode}') has tokens with no valid color (renders black/unset on native)
|
|
109
|
+
`getSemanticColors('${mode}') has tokens with no valid color (renders black/unset on native)`
|
|
110
110
|
).toEqual([])
|
|
111
111
|
expect(
|
|
112
112
|
missingVars,
|
|
113
|
-
`resolveColor tokens missing a --color-<token> in ${mode} vars (renders unset on web)
|
|
113
|
+
`resolveColor tokens missing a --color-<token> in ${mode} vars (renders unset on web)`
|
|
114
114
|
).toEqual([])
|
|
115
115
|
}
|
|
116
116
|
})
|
|
@@ -36,6 +36,10 @@ function injectFontImport(url: string) {
|
|
|
36
36
|
document.head.appendChild(link)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated Use `ThemeProvider` with semantic tokens — removed once audiobook
|
|
41
|
+
* migrates off presets (AW-129).
|
|
42
|
+
*/
|
|
39
43
|
export function applyThemePreset(preset: ThemePreset): () => void {
|
|
40
44
|
if (typeof document === 'undefined') {
|
|
41
45
|
return () => {}
|
|
@@ -5,12 +5,15 @@ import type { ThemePreset } from './types'
|
|
|
5
5
|
*
|
|
6
6
|
* Warm copper/patina aesthetic inspired by vintage audio equipment
|
|
7
7
|
* and reading lounges. Dark-mode only design with editorial typography.
|
|
8
|
+
*
|
|
9
|
+
* @deprecated Use `ThemeProvider` with semantic tokens — removed once audiobook
|
|
10
|
+
* migrates off presets (AW-129).
|
|
8
11
|
*/
|
|
9
12
|
export const audiobookPreset: ThemePreset = {
|
|
10
13
|
name: 'audiobook',
|
|
11
14
|
description: 'Warm copper/patina theme with editorial typography',
|
|
12
15
|
fontImport:
|
|
13
|
-
|
|
16
|
+
'https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,400;0,600;0,700;1,400&family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap',
|
|
14
17
|
fonts: {
|
|
15
18
|
sans: "'Outfit', 'Avenir', 'Avenir Next', system-ui, sans-serif",
|
|
16
19
|
body: "'Outfit', 'Avenir', 'Avenir Next', system-ui, sans-serif",
|
|
@@ -77,7 +80,7 @@ export const audiobookPreset: ThemePreset = {
|
|
|
77
80
|
'interactive-active': 'rgba(255, 255, 255, 0.16)',
|
|
78
81
|
'interactive-selected': 'rgba(255, 255, 255, 0.08)',
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
divider: 'rgba(255, 255, 255, 0.06)',
|
|
81
84
|
|
|
82
85
|
// Glow RGB values for shadow utilities
|
|
83
86
|
'brand-primary-rgb': '212, 120, 42',
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ThemePreset } from './types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Use `ThemeProvider` with semantic tokens — removed once audiobook
|
|
5
|
+
* migrates off presets (AW-129).
|
|
6
|
+
*/
|
|
3
7
|
export const defaultPreset: ThemePreset = {
|
|
4
8
|
name: 'default',
|
|
5
9
|
description: 'Titan Design System default theme — orange accent, steel secondary',
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated The whole `theme/presets` barrel. Use `ThemeProvider` with
|
|
3
|
+
* semantic tokens — removed once audiobook migrates off presets (AW-129).
|
|
4
|
+
*/
|
|
5
|
+
export type {
|
|
6
|
+
ThemePreset,
|
|
7
|
+
ThemePresetColors,
|
|
8
|
+
ThemePresetFonts,
|
|
9
|
+
ThemePresetRadii,
|
|
10
|
+
ThemePresetShadows,
|
|
11
|
+
} from './types'
|
|
12
|
+
/** @deprecated Use `ThemeProvider` with semantic tokens (AW-129). */
|
|
2
13
|
export { applyThemePreset } from './apply'
|
|
14
|
+
/** @deprecated Use `ThemeProvider` with semantic tokens (AW-129). */
|
|
3
15
|
export { defaultPreset } from './default'
|
|
16
|
+
/** @deprecated Use `ThemeProvider` with semantic tokens (AW-129). */
|
|
4
17
|
export { audiobookPreset } from './audiobook'
|
|
@@ -54,7 +54,7 @@ describe('semantic text on the surface planes (dark)', () => {
|
|
|
54
54
|
const ratio = contrast(fg, greyRamp[plane])
|
|
55
55
|
expect(
|
|
56
56
|
ratio,
|
|
57
|
-
`${role} ${fg} on grey-${plane} ${greyRamp[plane]} is ${ratio.toFixed(2)}:1, needs ${min}
|
|
57
|
+
`${role} ${fg} on grey-${plane} ${greyRamp[plane]} is ${ratio.toFixed(2)}:1, needs ${min}`
|
|
58
58
|
).toBeGreaterThanOrEqual(min)
|
|
59
59
|
})
|
|
60
60
|
})
|
|
@@ -68,10 +68,19 @@ describe('semantic text on the surface planes (dark)', () => {
|
|
|
68
68
|
it('keeps primary > secondary > tertiary in contrast on every plane', () => {
|
|
69
69
|
for (const plane of PLANES) {
|
|
70
70
|
const [pri, sec, ter] = TEXT_ROLES.map(({ role }) =>
|
|
71
|
-
contrast(
|
|
71
|
+
contrast(
|
|
72
|
+
semanticColorsDark[role as keyof typeof semanticColorsDark] as string,
|
|
73
|
+
greyRamp[plane]
|
|
74
|
+
)
|
|
72
75
|
)
|
|
73
|
-
expect(
|
|
74
|
-
|
|
76
|
+
expect(
|
|
77
|
+
pri,
|
|
78
|
+
`on grey-${plane}: primary ${pri.toFixed(2)} vs secondary ${sec.toFixed(2)}`
|
|
79
|
+
).toBeGreaterThan(sec)
|
|
80
|
+
expect(
|
|
81
|
+
sec,
|
|
82
|
+
`on grey-${plane}: secondary ${sec.toFixed(2)} vs tertiary ${ter.toFixed(2)}`
|
|
83
|
+
).toBeGreaterThan(ter)
|
|
75
84
|
}
|
|
76
85
|
})
|
|
77
86
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status tokens that mean different things must LOOK different.
|
|
3
|
+
*
|
|
4
|
+
* `status-error-vivid` existed to separate "drop everything" from "serious",
|
|
5
|
+
* and it pointed at `ramp.red[600]` — the exact value of `status-error`. The two
|
|
6
|
+
* had been rendering as one colour for as long as the token had existed, in both
|
|
7
|
+
* themes. Three other places in the system already disagreed with it and said
|
|
8
|
+
* vivid was `#FF4757`: `--color-status-error-vivid-rgb`, the `-subtle` rgba, and
|
|
9
|
+
* the `glow` shadow in `Indicator`. Only the value anyone actually rendered was
|
|
10
|
+
* wrong.
|
|
11
|
+
*
|
|
12
|
+
* Nothing caught it because every existing token test asks whether a value is
|
|
13
|
+
* PRESENT and parity-matched across config.ts and global.css — a collapsed token
|
|
14
|
+
* is present and matches perfectly. Distinctness is a different question, so it
|
|
15
|
+
* gets its own gate.
|
|
16
|
+
*
|
|
17
|
+
* Found 2026-08-31 by measuring a rendered Storybook story, not by reading the
|
|
18
|
+
* tokens: `SeverityLabel`'s Critical and High dots came back the same RGB.
|
|
19
|
+
*/
|
|
20
|
+
import { describe, it, expect } from 'vitest'
|
|
21
|
+
import { semanticColorsDark, semanticColorsLight } from './tokens/semantic'
|
|
22
|
+
import { alertRedVivid } from './tokens/primitives'
|
|
23
|
+
|
|
24
|
+
type SemanticMap = Record<string, string>
|
|
25
|
+
|
|
26
|
+
const THEMES: [string, SemanticMap][] = [
|
|
27
|
+
['dark', semanticColorsDark as SemanticMap],
|
|
28
|
+
['light', semanticColorsLight as SemanticMap],
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Token pairs a viewer is expected to tell apart at a glance. Each pair carries
|
|
33
|
+
* the reason, so a future retune can weigh it rather than just delete the row.
|
|
34
|
+
*/
|
|
35
|
+
const MUST_DIFFER: [string, string, string][] = [
|
|
36
|
+
[
|
|
37
|
+
'status-error',
|
|
38
|
+
'status-error-vivid',
|
|
39
|
+
'severity Critical vs High, and DeviceRow/DeviceIndicator "lost" vs a plain error',
|
|
40
|
+
],
|
|
41
|
+
['status-error', 'status-warning', 'error vs warning is the most common status pairing'],
|
|
42
|
+
['status-warning', 'status-info', 'warning vs informational'],
|
|
43
|
+
['status-success', 'status-info', 'succeeded vs merely reporting'],
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
describe('semantically distinct status tokens render distinct values', () => {
|
|
47
|
+
for (const [theme, colors] of THEMES) {
|
|
48
|
+
for (const [a, b, why] of MUST_DIFFER) {
|
|
49
|
+
it(`${theme}: ${a} !== ${b} — ${why}`, () => {
|
|
50
|
+
// Both must exist, or the assertion passes vacuously on a typo.
|
|
51
|
+
expect(colors[a], `${a} missing from ${theme}`).toBeTruthy()
|
|
52
|
+
expect(colors[b], `${b} missing from ${theme}`).toBeTruthy()
|
|
53
|
+
expect(colors[a]!.toLowerCase()).not.toBe(colors[b]!.toLowerCase())
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('status-error-vivid is the vivid red the rest of the system already assumes', () => {
|
|
60
|
+
for (const [theme, colors] of THEMES) {
|
|
61
|
+
it(`${theme}: is the alertRedVivid primitive that -rgb, -subtle and Indicator's glow encode`, () => {
|
|
62
|
+
expect(colors['status-error-vivid']).toBe(alertRedVivid)
|
|
63
|
+
expect(alertRedVivid.toLowerCase()).toBe('#ff4757')
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { PLANE_ORDER, pressedLevel, raisedLevel, surfaceBackground } from './surface-planes'
|
|
3
|
+
import { getSemanticColors } from './tokens/semantic'
|
|
4
|
+
|
|
5
|
+
describe('surface planes', () => {
|
|
6
|
+
it('orders the six planes darkest first', () => {
|
|
7
|
+
expect(PLANE_ORDER).toEqual(['frame', 'background', 'base', 'elevated', 'raised', 'overlay'])
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('resolves each plane to its semantic token, literal hex', () => {
|
|
11
|
+
const dark = getSemanticColors('dark')
|
|
12
|
+
expect(surfaceBackground('frame', 'dark')).toBe(dark['background-frame'])
|
|
13
|
+
expect(surfaceBackground('base', 'dark')).toBe(dark['surface-base'])
|
|
14
|
+
expect(surfaceBackground('overlay', 'dark')).toMatch(/^#[0-9A-F]{6}$/i)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it.each([
|
|
18
|
+
['base', 1, 'elevated'],
|
|
19
|
+
['base', 2, 'raised'],
|
|
20
|
+
['base', 3, 'overlay'],
|
|
21
|
+
['elevated', 2, 'overlay'],
|
|
22
|
+
['raised', 3, 'overlay'],
|
|
23
|
+
['overlay', 1, 'overlay'],
|
|
24
|
+
] as const)('raisedLevel(%s, %i) → %s, clamped at overlay', (from, steps, expected) => {
|
|
25
|
+
expect(raisedLevel(from, steps)).toBe(expected)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('raisedLevel with zero steps stays put', () => {
|
|
29
|
+
expect(raisedLevel('raised', 0)).toBe('raised')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('pressedLevel steps down and clamps at the frame', () => {
|
|
33
|
+
expect(pressedLevel('base')).toBe('background')
|
|
34
|
+
expect(pressedLevel('background')).toBe('frame')
|
|
35
|
+
expect(pressedLevel('frame')).toBe('frame')
|
|
36
|
+
})
|
|
37
|
+
})
|