@titan-design/react-ui 0.12.1 → 0.14.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 +60 -47
- package/dist/bodymap.js +856 -736
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +858 -738
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1431 -237
- package/dist/index.d.ts +1431 -237
- package/dist/index.js +6506 -4781
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6280 -4642
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
- package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1919 -1937
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1927 -1945
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +225 -140
- package/dist/theme/index.d.ts +225 -140
- package/dist/theme/index.js +298 -319
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +283 -315
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +145 -24
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +145 -24
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +128 -11
- package/dist/theme/tokens.d.ts +128 -11
- package/dist/theme/tokens.js +126 -26
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +124 -27
- 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/docs/render-testing-pattern.md +130 -0
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +3 -1
- package/src/arch/arch-graph.freshness.test.ts +52 -0
- package/src/arch/arch-graph.json +4989 -1583
- 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 +182 -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 +12 -6
- 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 +6 -3
- package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.tsx +7 -2
- 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 +55 -14
- package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
- package/src/components/custom/Scatter/Scatter.tsx +122 -18
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
- 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 +99 -16
- 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 +15 -12
- package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +11 -3
- 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 +11 -2
- package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
- package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
- package/src/components/custom/Workout/BaseBadge.tsx +6 -2
- package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
- package/src/components/custom/Workout/BodyMap.tsx +8 -4
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
- package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
- package/src/components/custom/Workout/DeviationBar.tsx +24 -16
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
- package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
- package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
- 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/InputBar.tsx +4 -2
- 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 +8 -3
- 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 +14 -10
- 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 +20 -33
- 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 +9 -4
- 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 +16 -3
- 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 +68 -17
- 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 +22 -4
- package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
- package/src/components/custom/Workout/RestTimer.tsx +4 -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 +21 -17
- package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
- 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 +63 -7
- 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 +24 -12
- package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
- 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 +11 -4
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
- package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
- 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 +26 -10
- package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
- package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
- 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 +10 -6
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
- package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
- 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 +10 -7
- package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
- package/src/components/custom/Workout/exerciseRowState.ts +29 -0
- package/src/components/custom/Workout/index.ts +17 -2
- package/src/components/custom/Workout/metricText.stories.tsx +15 -13
- package/src/components/custom/Workout/metricText.tsx +8 -2
- package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
- package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
- package/src/components/custom/charts/README.md +66 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
- package/src/components/custom/charts/SetBarChart.tsx +56 -9
- 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/flatBarGeometry.test.ts +41 -0
- package/src/components/custom/charts/flatBarGeometry.ts +32 -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 +21 -34
- 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 +15 -5
- package/src/components/ui/alert/Alert.tsx +24 -3
- 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 +44 -37
- 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 +7 -27
- 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 +14 -3
- 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 -27
- 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 +10 -18
- package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
- 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 +17 -5
- 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 +31 -19
- package/src/components/ui/pill/Pill.stories.tsx +111 -23
- package/src/components/ui/pill/Pill.test.tsx +101 -3
- package/src/components/ui/pill/Pill.tsx +168 -49
- package/src/components/ui/pill/index.ts +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +6 -8
- 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/progress/Progress.tsx +9 -6
- 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 +12 -21
- 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 +16 -15
- 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 +4 -5
- package/src/components/ui/stack/Stack.stories.tsx +8 -8
- package/src/components/ui/stack/Stack.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +92 -33
- 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 +8 -21
- 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 +49 -57
- 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 +38 -49
- 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 +101 -48
- package/src/test/no-device-internals.test.ts +3 -3
- package/src/test/no-raw-color.test.ts +39 -0
- package/src/theme/Alpha.stories.tsx +103 -0
- 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/ProposedTokensVW82.stories.tsx +414 -0
- 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 +40 -0
- package/src/theme/elevation.test.ts +82 -56
- package/src/theme/elevation.ts +77 -561
- package/src/theme/global.css +45 -5
- 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/subtle-tone-weight.test.ts +163 -0
- package/src/theme/surface-planes.test.ts +37 -0
- package/src/theme/surface-planes.ts +68 -0
- package/src/theme/tailwind-var-opacity.test.ts +163 -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 +98 -33
- package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
- package/src/theme/tokens/semantic.ts +124 -54
- 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.alpha-adoption.test.ts +408 -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 +55 -9
- 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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AW-133 — the `-subtle` fill and the text on it, as a gate.
|
|
3
|
+
*
|
|
4
|
+
* The status family had one token doing two incompatible jobs. `status-error`
|
|
5
|
+
* and `brand-secondary` sit at ramp rung 600 so a white label reads on them as a
|
|
6
|
+
* solid fill; that same depth made them unreadable as TEXT on a dark plane, and a
|
|
7
|
+
* subtle Pill in those tones measured 2.9-3.1 against its own fill. The four
|
|
8
|
+
* lighter tones had the mirror problem. Splitting the roles fixed it: `on-*-subtle`
|
|
9
|
+
* is text on the subtle fill, levelled at rung 300.
|
|
10
|
+
*
|
|
11
|
+
* The numbers run in CI so a future retune cannot quietly reopen it.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from 'vitest'
|
|
14
|
+
import { greyRamp } from './tokens/primitives'
|
|
15
|
+
import { semanticColorsDark } from './tokens/semantic'
|
|
16
|
+
|
|
17
|
+
const hex2rgb = (h: string): [number, number, number] => {
|
|
18
|
+
const n = parseInt(h.slice(1), 16)
|
|
19
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
|
|
20
|
+
}
|
|
21
|
+
const srgb2lin = (c: number) => {
|
|
22
|
+
const s = c / 255
|
|
23
|
+
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
|
|
24
|
+
}
|
|
25
|
+
const relLum = (rgb: number[]) => {
|
|
26
|
+
const [r, g, b] = rgb.map(srgb2lin)
|
|
27
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
28
|
+
}
|
|
29
|
+
const contrast = (a: number[], b: number[]) => {
|
|
30
|
+
const [hi, lo] = [relLum(a), relLum(b)].sort((x, y) => y - x)
|
|
31
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
32
|
+
}
|
|
33
|
+
/** OKLab lightness — the perceptual axis the ramps were generated on. */
|
|
34
|
+
const oklabL = (rgb: number[]) => {
|
|
35
|
+
const [r, g, b] = rgb.map(srgb2lin)
|
|
36
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
|
|
37
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
|
|
38
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
|
|
39
|
+
return 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s
|
|
40
|
+
}
|
|
41
|
+
/** OKLCH chroma — how saturated, independent of how light. */
|
|
42
|
+
const oklabChroma = (rgb: number[]) => {
|
|
43
|
+
const [r, g, b] = rgb.map(srgb2lin)
|
|
44
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
|
|
45
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
|
|
46
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
|
|
47
|
+
const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s
|
|
48
|
+
const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
49
|
+
return Math.sqrt(a * a + bb * bb)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parseRgba = (v: string): { rgb: number[]; alpha: number } => {
|
|
53
|
+
const m = v.match(/rgba?\(([^)]+)\)/)
|
|
54
|
+
if (!m) throw new Error(`expected an rgba() subtle fill, got "${v}"`)
|
|
55
|
+
const parts = m[1].split(',').map((n) => parseFloat(n.trim()))
|
|
56
|
+
return { rgb: parts.slice(0, 3), alpha: parts[3] ?? 1 }
|
|
57
|
+
}
|
|
58
|
+
const composite = (fill: string, plane: string) => {
|
|
59
|
+
const { rgb, alpha } = parseRgba(fill)
|
|
60
|
+
const bg = hex2rgb(plane)
|
|
61
|
+
return rgb.map((c, i) => Math.round(c * alpha + bg[i] * (1 - alpha)))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The two planes a subtle pill actually lands on. */
|
|
65
|
+
const PAGE = greyRamp[925]
|
|
66
|
+
const CARD = greyRamp[875]
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Four tones level at ramp rung 300. Two are deliberate exceptions, both operator
|
|
70
|
+
* calls during the AW-133 review, and both are held here rather than quietly
|
|
71
|
+
* widening the band:
|
|
72
|
+
*
|
|
73
|
+
* - `brand` keeps the exact `orange[400]` brand hue instead of levelling, so the
|
|
74
|
+
* Voltras tone never drifts. It is the darkest label and the lowest contrast.
|
|
75
|
+
* - `error` sits at `red[400]`, a rung darker than its siblings. red[300] levelled
|
|
76
|
+
* perfectly but read PINK: a red that light can only hold ~0.121 OKLCH chroma.
|
|
77
|
+
* red[400] carries ~0.165 and reads red. Its fill is thinned to 0.08 to buy back
|
|
78
|
+
* the contrast that darkness costs, which is why it still beats brand's floor.
|
|
79
|
+
*/
|
|
80
|
+
const TONES = [
|
|
81
|
+
{
|
|
82
|
+
name: 'brand',
|
|
83
|
+
fill: 'brand-primary-subtle',
|
|
84
|
+
text: 'on-brand-primary-subtle',
|
|
85
|
+
min: 4.0,
|
|
86
|
+
levelled: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'accent',
|
|
90
|
+
fill: 'brand-secondary-subtle',
|
|
91
|
+
text: 'on-brand-secondary-subtle',
|
|
92
|
+
min: 4.5,
|
|
93
|
+
levelled: true,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'success',
|
|
97
|
+
fill: 'status-success-subtle',
|
|
98
|
+
text: 'on-status-success-subtle',
|
|
99
|
+
min: 4.5,
|
|
100
|
+
levelled: true,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'warning',
|
|
104
|
+
fill: 'status-warning-subtle',
|
|
105
|
+
text: 'on-status-warning-subtle',
|
|
106
|
+
min: 4.5,
|
|
107
|
+
levelled: true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'error',
|
|
111
|
+
fill: 'status-error-subtle',
|
|
112
|
+
text: 'on-status-error-subtle',
|
|
113
|
+
min: 4.2,
|
|
114
|
+
levelled: false,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'info',
|
|
118
|
+
fill: 'status-info-subtle',
|
|
119
|
+
text: 'on-status-info-subtle',
|
|
120
|
+
min: 4.5,
|
|
121
|
+
levelled: true,
|
|
122
|
+
},
|
|
123
|
+
] as const
|
|
124
|
+
|
|
125
|
+
describe('subtle tone weight (dark)', () => {
|
|
126
|
+
it.each(TONES)('$name text clears its floor on both planes', ({ fill, text, min }) => {
|
|
127
|
+
const label = hex2rgb(semanticColorsDark[text])
|
|
128
|
+
for (const plane of [PAGE, CARD]) {
|
|
129
|
+
const composited = composite(semanticColorsDark[fill], plane)
|
|
130
|
+
expect(contrast(label, composited)).toBeGreaterThanOrEqual(min)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// The original defect was a WEIGHT mismatch, not only a contrast one: the family
|
|
135
|
+
// spanned OKLCH L 0.550-0.813, so accent and error read as a different rung of
|
|
136
|
+
// the system. Hold the levelled tones in a tight band; the two exceptions are
|
|
137
|
+
// measured by their own floors above.
|
|
138
|
+
it('levels the unexceptional tones to one weight', () => {
|
|
139
|
+
const ls = TONES.filter((t) => t.levelled).map((t) =>
|
|
140
|
+
oklabL(hex2rgb(semanticColorsDark[t.text]))
|
|
141
|
+
)
|
|
142
|
+
expect(Math.max(...ls) - Math.min(...ls)).toBeLessThanOrEqual(0.06)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Guards the operator's actual complaint. Levelling error back onto rung 300 would
|
|
146
|
+
// satisfy every other assertion in this file — tighter band, higher contrast — and
|
|
147
|
+
// reintroduce the pink. Chroma is the thing that made it wrong, so chroma is what
|
|
148
|
+
// gets asserted.
|
|
149
|
+
it('keeps error saturated enough to read red rather than pink', () => {
|
|
150
|
+
const c = oklabChroma(hex2rgb(semanticColorsDark['on-status-error-subtle']))
|
|
151
|
+
expect(c).toBeGreaterThanOrEqual(0.15)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// A fill whose hue drifts from its label's re-opens the split this fixed.
|
|
155
|
+
it('draws each fill from the same hue family as its label', () => {
|
|
156
|
+
for (const { fill, text, name } of TONES) {
|
|
157
|
+
if (name === 'brand') continue
|
|
158
|
+
const fillRgb = parseRgba(semanticColorsDark[fill]).rgb
|
|
159
|
+
const labelRgb = hex2rgb(semanticColorsDark[text])
|
|
160
|
+
expect(fillRgb).toEqual(labelRgb)
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The surface planes: the grey-ramp steps a container can sit on, darkest first.
|
|
3
|
+
*
|
|
4
|
+
* Every background a `<Surface>` or `<Card>` paints resolves through this list,
|
|
5
|
+
* so a container can only ever land ON the ramp. Stepping up (a lifted card) or
|
|
6
|
+
* down (a pressed well) is indexing `PLANE_ORDER`, clamped at both ends.
|
|
7
|
+
*
|
|
8
|
+
* `frame` is the bezel the ramp sits inside and the floor a pressed surface
|
|
9
|
+
* clamps at. It replaced a separate `inset` level in TD-07.14: at ΔE 1.10 from
|
|
10
|
+
* the frame that level was an imperceptible duplicate.
|
|
11
|
+
*/
|
|
12
|
+
import { getSemanticColors, type ThemeMode } from './tokens/semantic'
|
|
13
|
+
|
|
14
|
+
type ColorToken = keyof ReturnType<typeof getSemanticColors>
|
|
15
|
+
|
|
16
|
+
export type SurfaceLevel = 'frame' | 'background' | 'base' | 'elevated' | 'raised' | 'overlay'
|
|
17
|
+
|
|
18
|
+
// Which semantic token backs each plane. Values live in `semantic.ts` and stay
|
|
19
|
+
// in sync with the grey ramp; this map never carries literal hexes.
|
|
20
|
+
export const SURFACE_LEVEL_TOKEN = {
|
|
21
|
+
frame: 'background-frame',
|
|
22
|
+
background: 'background-base',
|
|
23
|
+
base: 'surface-base',
|
|
24
|
+
elevated: 'surface-elevated',
|
|
25
|
+
raised: 'surface-raised',
|
|
26
|
+
overlay: 'surface-overlay',
|
|
27
|
+
} as const satisfies Record<SurfaceLevel, ColorToken>
|
|
28
|
+
|
|
29
|
+
/** Darkest → lightest. Kept in lockstep with the grey ramp and surface.contract.test. */
|
|
30
|
+
export const PLANE_ORDER = [
|
|
31
|
+
'frame',
|
|
32
|
+
'background',
|
|
33
|
+
'base',
|
|
34
|
+
'elevated',
|
|
35
|
+
'raised',
|
|
36
|
+
'overlay',
|
|
37
|
+
] as const satisfies readonly SurfaceLevel[]
|
|
38
|
+
|
|
39
|
+
/** The background hex for a plane under a theme mode (literal hex, never a var()). */
|
|
40
|
+
export function surfaceBackground(level: SurfaceLevel, mode: ThemeMode): string {
|
|
41
|
+
return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stepPlane(from: SurfaceLevel, delta: number): SurfaceLevel {
|
|
45
|
+
const index = PLANE_ORDER.indexOf(from)
|
|
46
|
+
const clamped = Math.min(PLANE_ORDER.length - 1, Math.max(0, index + delta))
|
|
47
|
+
return PLANE_ORDER[clamped]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The plane a `<Surface pressed>` resolves to: its parent's plane stepped one
|
|
52
|
+
* index DOWN the ramp, clamped at the `frame` floor.
|
|
53
|
+
* overlay→raised, raised→elevated, elevated→base, base→background,
|
|
54
|
+
* background→frame, frame→frame (clamped).
|
|
55
|
+
*/
|
|
56
|
+
export function pressedLevel(parent: SurfaceLevel): SurfaceLevel {
|
|
57
|
+
return stepPlane(parent, -1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The plane a lifted surface resolves to: its parent's plane stepped `steps`
|
|
62
|
+
* UP the ramp, clamped at `overlay`. The clamp is what makes raised-on-raised
|
|
63
|
+
* unrepresentable: a card inside an overlay stays on the overlay and relies on
|
|
64
|
+
* its lift treatment, never on a lighter grey that is not on the ramp.
|
|
65
|
+
*/
|
|
66
|
+
export function raisedLevel(parent: SurfaceLevel, steps: number): SurfaceLevel {
|
|
67
|
+
return stepPlane(parent, Math.max(0, steps))
|
|
68
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { readdirSync, readFileSync } from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tailwind opacity modifiers on var()-backed colours (VW-308).
|
|
9
|
+
*
|
|
10
|
+
* Tailwind v3 cannot apply `/<n>` to a colour whose value is a `var(--…)`
|
|
11
|
+
* reference. It does not warn and does not fall back: it emits NO RULE, and the
|
|
12
|
+
* class is dead CSS on web. Every colour in `tailwind.config.js` is var()-backed
|
|
13
|
+
* (that is what makes light/dark switching work), so `bg-brand-primary/10` never
|
|
14
|
+
* painted anything — while the literal-valued `text-white/70` compiles fine.
|
|
15
|
+
* That asymmetry is why it sat unnoticed in four components.
|
|
16
|
+
*
|
|
17
|
+
* This test compiles the REAL `tailwind.config.js` against a fixture, which is
|
|
18
|
+
* how the finding was reproduced. It pins three things:
|
|
19
|
+
* 1. the defect itself, so nobody "fixes" a call site back to a modifier;
|
|
20
|
+
* 2. a POSITIVE CONTROL — `text-white/70` and `bg-black/50` still compile, so
|
|
21
|
+
* an empty stylesheet from a broken harness can't pass as proof;
|
|
22
|
+
* 3. that no var()-backed opacity modifier survives anywhere in `src/`.
|
|
23
|
+
*
|
|
24
|
+
* The chosen fix is (3), not making the modifier work: the `<alpha-value>`
|
|
25
|
+
* pattern needs channel-triplet CSS vars, a hand-aligned rewrite of the whole
|
|
26
|
+
* token layer, and `color-mix()` — the config-only alternative — is not a value
|
|
27
|
+
* React Native understands. The wash ladder (`-subtle`/`-muted`/`-strong`)
|
|
28
|
+
* already expresses translucency on both platforms.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url)
|
|
32
|
+
const uiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
33
|
+
|
|
34
|
+
// postcss is a declared dependency of tailwindcss, not of this package; resolve
|
|
35
|
+
// it from there rather than relying on hoisting.
|
|
36
|
+
const tailwindEntry = require.resolve('tailwindcss', { paths: [uiRoot] })
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
const tailwind = require(tailwindEntry) as any
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
const postcss = createRequire(tailwindEntry)('postcss') as any
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
const tailwindConfig = require(path.join(uiRoot, 'tailwind.config.js')) as any
|
|
43
|
+
|
|
44
|
+
/** `brand.primary.DEFAULT` -> the `brand-primary` class suffix. */
|
|
45
|
+
function flattenColors(
|
|
46
|
+
node: Record<string, unknown>,
|
|
47
|
+
prefix: string[] = []
|
|
48
|
+
): Record<string, string> {
|
|
49
|
+
const out: Record<string, string> = {}
|
|
50
|
+
for (const [key, value] of Object.entries(node)) {
|
|
51
|
+
const nextPrefix = key === 'DEFAULT' ? prefix : [...prefix, key]
|
|
52
|
+
if (value && typeof value === 'object') {
|
|
53
|
+
Object.assign(out, flattenColors(value as Record<string, unknown>, nextPrefix))
|
|
54
|
+
} else if (typeof value === 'string') {
|
|
55
|
+
out[nextPrefix.join('-')] = value
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const varBackedTokens = new Set(
|
|
62
|
+
Object.entries(flattenColors(tailwindConfig.theme?.extend?.colors ?? {}))
|
|
63
|
+
.filter(([, value]) => /^var\(--color-/.test(value))
|
|
64
|
+
.map(([name]) => name)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const PROBE_CLASSES = [
|
|
68
|
+
// The finding, verbatim.
|
|
69
|
+
'bg-brand-primary/10',
|
|
70
|
+
'text-on-status-success/70',
|
|
71
|
+
// The four call sites' original classes.
|
|
72
|
+
'bg-status-success/10',
|
|
73
|
+
'bg-brand-primary/20',
|
|
74
|
+
'text-text-tertiary/40',
|
|
75
|
+
// Positive control: literal-valued colours, which DO support the modifier.
|
|
76
|
+
'text-white/70',
|
|
77
|
+
'bg-black/50',
|
|
78
|
+
// The replacements the call sites now use.
|
|
79
|
+
'bg-brand-primary-subtle',
|
|
80
|
+
'bg-status-success-subtle',
|
|
81
|
+
'bg-brand-primary-muted',
|
|
82
|
+
'bg-status-info-muted',
|
|
83
|
+
'text-text-disabled',
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
let emitted: Set<string>
|
|
87
|
+
|
|
88
|
+
beforeAll(async () => {
|
|
89
|
+
const raw = `<div class="${PROBE_CLASSES.join(' ')}"></div>`
|
|
90
|
+
const result = await postcss([
|
|
91
|
+
tailwind({ ...tailwindConfig, content: { files: [{ raw, extension: 'html' }] } }),
|
|
92
|
+
]).process('@tailwind utilities;', { from: undefined })
|
|
93
|
+
|
|
94
|
+
emitted = new Set(
|
|
95
|
+
[...(result.css as string).matchAll(/^\.([^\s{]+)\s*\{/gm)].map((m) => m[1].replace(/\\/g, ''))
|
|
96
|
+
)
|
|
97
|
+
}, 30_000)
|
|
98
|
+
|
|
99
|
+
describe('tailwind opacity modifiers on var()-backed colours (VW-308)', () => {
|
|
100
|
+
it('compiles the real config against the fixture at all', () => {
|
|
101
|
+
// Guards the harness: without this, every "emits no rule" assertion below
|
|
102
|
+
// would also pass against a stylesheet that failed to build.
|
|
103
|
+
expect(emitted.size).toBeGreaterThan(0)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// POSITIVE CONTROL. These are literal colours in tailwind's own palette, so
|
|
107
|
+
// the modifier works. If one of these ever stops compiling, the harness is
|
|
108
|
+
// broken, not the design system.
|
|
109
|
+
it.each(['text-white/70', 'bg-black/50'])('%s compiles (positive control)', (className) => {
|
|
110
|
+
expect(emitted).toContain(className)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it.each([
|
|
114
|
+
'bg-brand-primary/10',
|
|
115
|
+
'text-on-status-success/70',
|
|
116
|
+
'bg-status-success/10',
|
|
117
|
+
'bg-brand-primary/20',
|
|
118
|
+
'text-text-tertiary/40',
|
|
119
|
+
])('%s emits NO rule — this is the defect, do not use it', (className) => {
|
|
120
|
+
expect(emitted).not.toContain(className)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it.each([
|
|
124
|
+
'bg-brand-primary-subtle',
|
|
125
|
+
'bg-status-success-subtle',
|
|
126
|
+
'bg-brand-primary-muted',
|
|
127
|
+
'bg-status-info-muted',
|
|
128
|
+
'text-text-disabled',
|
|
129
|
+
])('%s compiles — the replacement the call sites use', (className) => {
|
|
130
|
+
expect(emitted).toContain(className)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const OPACITY_UTILITY =
|
|
135
|
+
/\b(?:[a-z][a-z0-9-]*:)*(?:bg|text|border|ring|fill|stroke|from|to|via|shadow|outline|decoration|divide|accent|caret|placeholder)-([a-z][a-zA-Z0-9-]*)\/(?:\[[^\]]+\]|\d+)/g
|
|
136
|
+
|
|
137
|
+
function sourceFiles(dir: string, found: string[] = []): string[] {
|
|
138
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
139
|
+
const full = path.join(dir, entry.name)
|
|
140
|
+
if (entry.isDirectory()) sourceFiles(full, found)
|
|
141
|
+
else if (/\.(ts|tsx)$/.test(entry.name)) found.push(full)
|
|
142
|
+
}
|
|
143
|
+
return found
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe('no var()-backed opacity modifier survives in src/', () => {
|
|
147
|
+
it('finds no dead colour class', () => {
|
|
148
|
+
const srcRoot = path.join(uiRoot, 'src')
|
|
149
|
+
const violations: string[] = []
|
|
150
|
+
|
|
151
|
+
for (const file of sourceFiles(srcRoot)) {
|
|
152
|
+
// This file quotes the dead classes on purpose, as the probe list above.
|
|
153
|
+
if (file === fileURLToPath(import.meta.url)) continue
|
|
154
|
+
for (const match of readFileSync(file, 'utf8').matchAll(OPACITY_UTILITY)) {
|
|
155
|
+
if (varBackedTokens.has(match[1])) {
|
|
156
|
+
violations.push(`${path.relative(uiRoot, file)}: ${match[0]}`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
expect(violations, `dead colour classes (VW-308):\n${violations.join('\n')}`).toEqual([])
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -82,9 +82,7 @@ describe('token-only subpath export (TP-16)', () => {
|
|
|
82
82
|
|
|
83
83
|
it('reaches no react / react-native / nativewind module in its import graph', () => {
|
|
84
84
|
const { bare } = collectGraph(TOKEN_ENTRY)
|
|
85
|
-
const leaked = bare.filter((s) =>
|
|
86
|
-
FORBIDDEN.some((f) => s === f || s.startsWith(`${f}/`)),
|
|
87
|
-
)
|
|
85
|
+
const leaked = bare.filter((s) => FORBIDDEN.some((f) => s === f || s.startsWith(`${f}/`)))
|
|
88
86
|
expect(leaked).toEqual([])
|
|
89
87
|
})
|
|
90
88
|
|
|
@@ -34,15 +34,17 @@ const mul = (M: number[], v: number[]) => [
|
|
|
34
34
|
// Machado-2009 dichromacy simulation matrices, severity 1.0. The palette solver
|
|
35
35
|
// optimizes worst-case deuteranopia/protanopia (red-green is the binding case) —
|
|
36
36
|
// match that 2-way metric here, not a stricter 3-way including tritan.
|
|
37
|
-
const DEUT = [
|
|
38
|
-
|
|
37
|
+
const DEUT = [
|
|
38
|
+
0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881,
|
|
39
|
+
]
|
|
40
|
+
const PROT = [
|
|
41
|
+
0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998,
|
|
42
|
+
]
|
|
39
43
|
const dist = (a: number[], b: number[]) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]) * 100
|
|
40
44
|
// worst-case perceived ΔE across deuteranopia and protanopia
|
|
41
45
|
const cvdDelta = (x: string, y: string) => {
|
|
42
46
|
const sim = (M: number[], hex: string) => toOklab(mul(M, hexToRgb(hex).map(lin)))
|
|
43
|
-
return Math.min(
|
|
44
|
-
...[DEUT, PROT].map((M) => dist(sim(M, x), sim(M, y))),
|
|
45
|
-
)
|
|
47
|
+
return Math.min(...[DEUT, PROT].map((M) => dist(sim(M, x), sim(M, y))))
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
const HEX6 = /^#[0-9A-F]{6}$/
|
|
@@ -115,7 +117,10 @@ describe('categoricalPalette', () => {
|
|
|
115
117
|
worst = Math.min(worst, cvdDelta(colors[i], colors[j]))
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
|
-
expect(
|
|
120
|
+
expect(
|
|
121
|
+
worst,
|
|
122
|
+
`${variant} min CVD deltaE (first ${CATEGORICAL_CVD_SAFE_MAX})`
|
|
123
|
+
).toBeGreaterThanOrEqual(8)
|
|
119
124
|
}
|
|
120
125
|
})
|
|
121
126
|
|
|
@@ -212,7 +217,10 @@ describe('sequentialEffort', () => {
|
|
|
212
217
|
|
|
213
218
|
it('keeps adjacent stops distinguishable', () => {
|
|
214
219
|
for (let i = 0; i < sequentialEffort.length - 1; i++) {
|
|
215
|
-
expect(
|
|
220
|
+
expect(
|
|
221
|
+
cvdDelta(sequentialEffort[i], sequentialEffort[i + 1]),
|
|
222
|
+
`stop ${i}->${i + 1}`
|
|
223
|
+
).toBeGreaterThanOrEqual(4.5)
|
|
216
224
|
}
|
|
217
225
|
})
|
|
218
226
|
|
|
@@ -19,6 +19,51 @@ export const primitiveColors = {
|
|
|
19
19
|
transparent: 'transparent',
|
|
20
20
|
} as const
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The vivid alert red: the one chromatic anchor outside the OKLCH ramps.
|
|
24
|
+
*
|
|
25
|
+
* It survived the TD-07.14 fold of the old `redVivid` scale only as the
|
|
26
|
+
* `-rgb`, `-subtle` and glow encodings of `status-error-vivid`, while the token
|
|
27
|
+
* itself was pinned to `red[600]` and collapsed onto `status-error`. Owning it
|
|
28
|
+
* here means the semantic layer references a primitive instead of carrying a
|
|
29
|
+
* literal. The red ramp has no step this saturated, and adding one means
|
|
30
|
+
* regenerating the ramp, so it stays a pin.
|
|
31
|
+
*/
|
|
32
|
+
export const alertRedVivid = '#FF4757' as const
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Result/outcome palette anchors (VW-83): Material-style green/red/grey chosen
|
|
36
|
+
* for the result-improve/-degrade/-inconclusive tokens. No `primitiveRamps` step
|
|
37
|
+
* matches any of these exactly (the ramps are OKLCH-generated with a different
|
|
38
|
+
* hue/chroma arc), so they are pinned by name here rather than left as raw hex
|
|
39
|
+
* in semantic.ts — same pattern as {@link alertRedVivid}.
|
|
40
|
+
*/
|
|
41
|
+
export const resultPaletteColors = {
|
|
42
|
+
improve: '#4caf50',
|
|
43
|
+
improveDark: '#248a24',
|
|
44
|
+
degrade: '#ef5350',
|
|
45
|
+
degradeDark: '#b30000',
|
|
46
|
+
inconclusive: '#9E9A97',
|
|
47
|
+
} as const
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One-off semantic literals (VW-83) with no exact `greyRamp`/`primitiveRamps`
|
|
51
|
+
* match, pinned by name instead of left as raw hex in semantic.ts.
|
|
52
|
+
*/
|
|
53
|
+
export const semanticPins = {
|
|
54
|
+
// Cool navy-black; darker and more blue-shifted than any warm greyRamp step.
|
|
55
|
+
textPrimaryLight: '#121828',
|
|
56
|
+
// Cool blue-grey; outside greyRamp's warm (R>=G>=B) scale.
|
|
57
|
+
textSecondaryLight: '#65748B',
|
|
58
|
+
// Pure neutral grey (R=G=B); no warm greyRamp step lands on it exactly.
|
|
59
|
+
backgroundBaseLight: '#EBEBEB',
|
|
60
|
+
// Near-neutral grey, kept one step off backgroundBaseLight by design.
|
|
61
|
+
dividerLight: '#E8E9EB',
|
|
62
|
+
// Periwinkle/indigo; more purple than any primitiveRamps.blue step. Shared by
|
|
63
|
+
// dark-mode text-link, border-focus and border-input-focus.
|
|
64
|
+
focusIndigoDark: '#828DF8',
|
|
65
|
+
} as const
|
|
66
|
+
|
|
22
67
|
/**
|
|
23
68
|
* Unified warm-neutral grey ramp (TD-07.14)
|
|
24
69
|
*
|
|
@@ -244,7 +289,10 @@ export type CategoricalVariant = keyof typeof categoricalPalette
|
|
|
244
289
|
* @param variant - `default` (black-text / light surfaces) or `dark` (white text)
|
|
245
290
|
* @returns The hex color string
|
|
246
291
|
*/
|
|
247
|
-
export function getCategoricalColor(
|
|
292
|
+
export function getCategoricalColor(
|
|
293
|
+
index: number,
|
|
294
|
+
variant: CategoricalVariant = 'default'
|
|
295
|
+
): string {
|
|
248
296
|
const palette = categoricalPalette[variant]
|
|
249
297
|
index = Math.max(0, Math.floor(index))
|
|
250
298
|
return palette[index % palette.length]
|
|
@@ -387,21 +435,38 @@ export function getDiscreteRainbowColor(index: number, size: number): string {
|
|
|
387
435
|
|
|
388
436
|
export const primitiveTypography = {
|
|
389
437
|
fontFamilies: {
|
|
390
|
-
sans: [
|
|
438
|
+
sans: [
|
|
439
|
+
'Inter',
|
|
440
|
+
'-apple-system',
|
|
441
|
+
'BlinkMacSystemFont',
|
|
442
|
+
'Segoe UI',
|
|
443
|
+
'Helvetica',
|
|
444
|
+
'Arial',
|
|
445
|
+
'sans-serif',
|
|
446
|
+
],
|
|
391
447
|
body: ['Nunito Sans', 'sans-serif'],
|
|
392
448
|
heading: ['Space Grotesk', 'sans-serif'],
|
|
393
|
-
mono: [
|
|
449
|
+
mono: [
|
|
450
|
+
'SF Mono',
|
|
451
|
+
'Monaco',
|
|
452
|
+
'Inconsolata',
|
|
453
|
+
'Fira Mono',
|
|
454
|
+
'Droid Sans Mono',
|
|
455
|
+
'Source Code Pro',
|
|
456
|
+
'monospace',
|
|
457
|
+
],
|
|
394
458
|
},
|
|
395
459
|
fontSizes: {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
460
|
+
'2xs': '0.625rem', // 10px
|
|
461
|
+
xs: '0.75rem', // 12px
|
|
462
|
+
sm: '0.875rem', // 14px
|
|
463
|
+
base: '1rem', // 16px
|
|
464
|
+
lg: '1.125rem', // 18px
|
|
465
|
+
xl: '1.5rem', // 24px
|
|
466
|
+
'2xl': '2rem', // 32px
|
|
402
467
|
'3xl': '2.25rem', // 36px
|
|
403
|
-
'4xl': '3rem',
|
|
404
|
-
'5xl': '3.5rem',
|
|
468
|
+
'4xl': '3rem', // 48px
|
|
469
|
+
'5xl': '3.5rem', // 56px
|
|
405
470
|
},
|
|
406
471
|
fontWeights: {
|
|
407
472
|
normal: 400,
|
|
@@ -430,28 +495,28 @@ export const primitiveTypography = {
|
|
|
430
495
|
export const primitiveSpacing = {
|
|
431
496
|
0: '0',
|
|
432
497
|
px: '1px',
|
|
433
|
-
0.5: '0.125rem',
|
|
434
|
-
1: '0.25rem',
|
|
435
|
-
1.5: '0.375rem',
|
|
436
|
-
2: '0.5rem',
|
|
437
|
-
2.5: '0.625rem',
|
|
438
|
-
3: '0.75rem',
|
|
439
|
-
3.5: '0.875rem',
|
|
440
|
-
4: '1rem',
|
|
441
|
-
5: '1.25rem',
|
|
442
|
-
6: '1.5rem',
|
|
443
|
-
7: '1.75rem',
|
|
444
|
-
8: '2rem',
|
|
445
|
-
9: '2.25rem',
|
|
446
|
-
10: '2.5rem',
|
|
447
|
-
11: '2.75rem',
|
|
448
|
-
12: '3rem',
|
|
449
|
-
14: '3.5rem',
|
|
450
|
-
16: '4rem',
|
|
451
|
-
20: '5rem',
|
|
452
|
-
24: '6rem',
|
|
453
|
-
28: '7rem',
|
|
454
|
-
32: '8rem',
|
|
498
|
+
0.5: '0.125rem', // 2px
|
|
499
|
+
1: '0.25rem', // 4px
|
|
500
|
+
1.5: '0.375rem', // 6px
|
|
501
|
+
2: '0.5rem', // 8px
|
|
502
|
+
2.5: '0.625rem', // 10px
|
|
503
|
+
3: '0.75rem', // 12px
|
|
504
|
+
3.5: '0.875rem', // 14px
|
|
505
|
+
4: '1rem', // 16px
|
|
506
|
+
5: '1.25rem', // 20px
|
|
507
|
+
6: '1.5rem', // 24px
|
|
508
|
+
7: '1.75rem', // 28px
|
|
509
|
+
8: '2rem', // 32px
|
|
510
|
+
9: '2.25rem', // 36px
|
|
511
|
+
10: '2.5rem', // 40px
|
|
512
|
+
11: '2.75rem', // 44px
|
|
513
|
+
12: '3rem', // 48px
|
|
514
|
+
14: '3.5rem', // 56px
|
|
515
|
+
16: '4rem', // 64px
|
|
516
|
+
20: '5rem', // 80px
|
|
517
|
+
24: '6rem', // 96px
|
|
518
|
+
28: '7rem', // 112px
|
|
519
|
+
32: '8rem', // 128px
|
|
455
520
|
} as const
|
|
456
521
|
|
|
457
522
|
export const primitiveBorderRadius = {
|