@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
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { Surface } from '../ui/surface'
|
|
4
|
+
import { Typography } from '../custom/Typography'
|
|
5
|
+
import { StarIcon } from '../icons'
|
|
2
6
|
import { BrandLockup } from './BrandLockup'
|
|
7
|
+
import { brandKeys } from './brands'
|
|
3
8
|
|
|
4
9
|
const meta: Meta<typeof BrandLockup> = {
|
|
5
10
|
title: 'Shell/BrandLockup',
|
|
6
11
|
component: BrandLockup,
|
|
7
|
-
tags: ['autodocs'],
|
|
8
|
-
args: {
|
|
12
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
13
|
+
args: { brand: 'voltras', showSubtitle: true },
|
|
9
14
|
argTypes: {
|
|
15
|
+
brand: { control: 'select', options: brandKeys },
|
|
10
16
|
subtitle: { control: 'text' },
|
|
17
|
+
wordmark: { control: 'text' },
|
|
18
|
+
accentClassName: { control: 'text' },
|
|
11
19
|
showSubtitle: { control: 'boolean' },
|
|
20
|
+
mark: { control: false },
|
|
12
21
|
},
|
|
13
22
|
parameters: {
|
|
14
23
|
docs: {
|
|
15
24
|
description: {
|
|
16
25
|
component:
|
|
17
|
-
'**Molecule.** Composes [
|
|
18
|
-
'[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle)
|
|
19
|
-
'
|
|
26
|
+
'**Molecule.** Composes an [icon](?path=/docs/foundations-icons--docs) mark + ' +
|
|
27
|
+
'[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle).\n\n' +
|
|
28
|
+
'**Generic over the app (AW-132).** Pick a `brand` preset, or override `mark` / ' +
|
|
29
|
+
'`wordmark` / `accentClassName` / `subtitle` for an app that has no preset yet. ' +
|
|
30
|
+
"Accents come from `data-*` rather than `status-*`: `data-*` is the library's set of " +
|
|
31
|
+
'distinct, CVD-checked hues with no semantic load, which is what a per-app identity ' +
|
|
32
|
+
'accent needs. Voltras keeps the real `brand-primary` token.\n\n' +
|
|
33
|
+
'**Try it:** switch `brand` in the **Controls**, or toggle `showSubtitle` to see the ' +
|
|
34
|
+
'responsive collapse.',
|
|
20
35
|
},
|
|
21
36
|
},
|
|
22
37
|
},
|
|
@@ -25,3 +40,49 @@ export default meta
|
|
|
25
40
|
type Story = StoryObj<typeof BrandLockup>
|
|
26
41
|
|
|
27
42
|
export const Default: Story = {}
|
|
43
|
+
|
|
44
|
+
/** Every app likely to mount this shell, in the chrome band's own surface. */
|
|
45
|
+
export const BrandVariants: Story = {
|
|
46
|
+
parameters: {
|
|
47
|
+
docs: {
|
|
48
|
+
description: {
|
|
49
|
+
story:
|
|
50
|
+
'The five branding presets. Each is a mark, a wordmark, a semantic accent token and a ' +
|
|
51
|
+
'default subtitle — nothing else in the shell changes between them.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
render: () => (
|
|
56
|
+
<Surface level="elevated" className="gap-4 p-5">
|
|
57
|
+
{brandKeys.map((brand) => (
|
|
58
|
+
<View key={brand} className="gap-1">
|
|
59
|
+
<Typography variant="microLabel" color="tertiary">
|
|
60
|
+
{brand}
|
|
61
|
+
</Typography>
|
|
62
|
+
<BrandLockup brand={brand} />
|
|
63
|
+
</View>
|
|
64
|
+
))}
|
|
65
|
+
</Surface>
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Collapsed subtitle — what every brand looks like below ~1024px. */
|
|
70
|
+
export const BrandVariantsCollapsed: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<Surface level="elevated" className="gap-4 p-5">
|
|
73
|
+
{brandKeys.map((brand) => (
|
|
74
|
+
<BrandLockup key={brand} brand={brand} showSubtitle={false} />
|
|
75
|
+
))}
|
|
76
|
+
</Surface>
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** An app with no preset: supply mark, wordmark, accent and subtitle directly. */
|
|
81
|
+
export const CustomBrand: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
mark: <StarIcon size={14} color="currentColor" />,
|
|
84
|
+
wordmark: 'HYPERFRAMES',
|
|
85
|
+
subtitle: 'renders',
|
|
86
|
+
accentClassName: 'text-data-6',
|
|
87
|
+
},
|
|
88
|
+
}
|
|
@@ -2,9 +2,10 @@ import { describe, it, expect } from 'vitest'
|
|
|
2
2
|
import { render, screen } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
4
|
import { BrandLockup } from './BrandLockup'
|
|
5
|
+
import { brandKeys, brandPresets } from './brands'
|
|
5
6
|
|
|
6
7
|
describe('BrandLockup', () => {
|
|
7
|
-
it('renders the wordmark', () => {
|
|
8
|
+
it('renders the voltras wordmark by default', () => {
|
|
8
9
|
render(<BrandLockup />)
|
|
9
10
|
expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
|
|
10
11
|
})
|
|
@@ -16,6 +17,38 @@ describe('BrandLockup', () => {
|
|
|
16
17
|
expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
|
|
17
18
|
})
|
|
18
19
|
|
|
20
|
+
it.each(brandKeys)('renders the %s preset wordmark and subtitle', (brand) => {
|
|
21
|
+
render(<BrandLockup brand={brand} />)
|
|
22
|
+
expect(screen.getByText(brandPresets[brand].wordmark)).toBeInTheDocument()
|
|
23
|
+
expect(screen.getByText(`/ ${brandPresets[brand].subtitle}`)).toBeInTheDocument()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('lets an app override the preset parts', () => {
|
|
27
|
+
render(<BrandLockup brand="brain" wordmark="HYPERFRAMES" subtitle="renders" />)
|
|
28
|
+
expect(screen.getByText('HYPERFRAMES')).toBeInTheDocument()
|
|
29
|
+
expect(screen.getByText('/ renders')).toBeInTheDocument()
|
|
30
|
+
expect(screen.queryByText('BRAIN')).not.toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// nativewind compiles className to style, so the rendered accent colour is not
|
|
34
|
+
// assertable in jsdom. Assert the token CHOICE instead: every preset picks a
|
|
35
|
+
// semantic `text-*` token, and no two apps share one.
|
|
36
|
+
it('gives every brand a distinct semantic accent token', () => {
|
|
37
|
+
const accents = brandKeys.map((brand) => brandPresets[brand].accentClassName)
|
|
38
|
+
accents.forEach((accent) => expect(accent).toMatch(/^text-(brand|data)-/))
|
|
39
|
+
expect(new Set(accents).size).toBe(accents.length)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// The nav's active bar needs the accent as a background, and a mismatched pair
|
|
43
|
+
// would render a lockup and an active nav item in two different hues.
|
|
44
|
+
it('pairs every accent with the same token as a background', () => {
|
|
45
|
+
brandKeys.forEach((brand) => {
|
|
46
|
+
const { accentClassName, accentBarClassName } = brandPresets[brand]
|
|
47
|
+
expect(accentBarClassName).toMatch(/^bg-(brand|data)-/)
|
|
48
|
+
expect(accentBarClassName).toBe(accentClassName.replace(/^text-/, 'bg-'))
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
19
52
|
it('has no accessibility violations', async () => {
|
|
20
53
|
const { container } = render(<BrandLockup />)
|
|
21
54
|
expect(await axe(container)).toHaveNoViolations()
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
1
2
|
import { View, type ViewProps } from 'react-native'
|
|
2
3
|
import { cn } from '../../utils/cn'
|
|
3
4
|
import { Typography } from '../custom/Typography'
|
|
4
|
-
import {
|
|
5
|
+
import { brandPresets, type BrandKey } from './brands'
|
|
5
6
|
|
|
6
7
|
export interface BrandLockupProps extends ViewProps {
|
|
7
|
-
/**
|
|
8
|
+
/** Which app's identity to render. Defaults to `voltras`. */
|
|
9
|
+
brand?: BrandKey
|
|
10
|
+
/** Replace the preset's mark glyph. */
|
|
11
|
+
mark?: ReactNode
|
|
12
|
+
/** Replace the preset's wordmark. */
|
|
13
|
+
wordmark?: string
|
|
14
|
+
/** Replace the preset's accent — a semantic `text-*` token class for the mark. */
|
|
15
|
+
accentClassName?: string
|
|
16
|
+
/** The "/ subtitle" that follows the wordmark. Defaults to the brand's own. */
|
|
8
17
|
subtitle?: string
|
|
9
18
|
/** Show the subtitle. Hidden at tablet width and below (S1-Q4 responsive). */
|
|
10
19
|
showSubtitle?: boolean
|
|
@@ -12,34 +21,41 @@ export interface BrandLockupProps extends ViewProps {
|
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
|
-
* Product identity lockup for the top bar:
|
|
16
|
-
*
|
|
24
|
+
* Product identity lockup for the top bar: mark + wordmark + optional subtitle.
|
|
25
|
+
* Generic over the app: pick a `brand` preset, or override `mark` / `wordmark` /
|
|
26
|
+
* `accentClassName` / `subtitle` piecemeal for an app that has no preset yet.
|
|
27
|
+
* Composes an icon primitive + Typography. S1 · BrandLockup.
|
|
17
28
|
*/
|
|
18
29
|
export function BrandLockup({
|
|
19
|
-
|
|
30
|
+
brand = 'voltras',
|
|
31
|
+
mark,
|
|
32
|
+
wordmark,
|
|
33
|
+
accentClassName,
|
|
34
|
+
subtitle,
|
|
20
35
|
showSubtitle = true,
|
|
21
36
|
className,
|
|
22
37
|
...props
|
|
23
38
|
}: BrandLockupProps) {
|
|
39
|
+
const preset = brandPresets[brand]
|
|
40
|
+
const resolvedSubtitle = subtitle ?? preset.subtitle
|
|
41
|
+
|
|
24
42
|
return (
|
|
25
43
|
<View className={cn('flex-row items-center gap-[7px]', className)} {...props}>
|
|
26
|
-
<View className=
|
|
27
|
-
<VoltrasMark size={14} color="currentColor" />
|
|
28
|
-
</View>
|
|
44
|
+
<View className={accentClassName ?? preset.accentClassName}>{mark ?? preset.mark}</View>
|
|
29
45
|
<Typography
|
|
30
46
|
variant="button"
|
|
31
47
|
color="primary"
|
|
32
48
|
className="text-[12px] font-extrabold tracking-[1.5px]"
|
|
33
49
|
>
|
|
34
|
-
|
|
50
|
+
{wordmark ?? preset.wordmark}
|
|
35
51
|
</Typography>
|
|
36
|
-
{showSubtitle &&
|
|
52
|
+
{showSubtitle && resolvedSubtitle ? (
|
|
37
53
|
<Typography
|
|
38
54
|
variant="button"
|
|
39
55
|
color="tertiary"
|
|
40
56
|
className="text-[10px] font-semibold tracking-[0.4px]"
|
|
41
57
|
>
|
|
42
|
-
/ {
|
|
58
|
+
/ {resolvedSubtitle}
|
|
43
59
|
</Typography>
|
|
44
60
|
) : null}
|
|
45
61
|
</View>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
-
import { View } from 'react-native'
|
|
3
2
|
import { NavItem } from './NavItem'
|
|
4
3
|
import { ActivityIcon } from '../icons'
|
|
4
|
+
import { Surface } from '../ui/surface'
|
|
5
5
|
|
|
6
6
|
const meta: Meta<typeof NavItem> = {
|
|
7
7
|
title: 'Shell/NavItem',
|
|
8
8
|
component: NavItem,
|
|
9
|
-
tags: ['autodocs'],
|
|
9
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
10
10
|
args: { label: 'Live', active: false, live: false },
|
|
11
11
|
argTypes: {
|
|
12
12
|
label: { control: 'text' },
|
|
@@ -17,9 +17,9 @@ const meta: Meta<typeof NavItem> = {
|
|
|
17
17
|
},
|
|
18
18
|
decorators: [
|
|
19
19
|
(Story) => (
|
|
20
|
-
<
|
|
20
|
+
<Surface level="background" className="w-[60px] items-center py-2">
|
|
21
21
|
<Story />
|
|
22
|
-
</
|
|
22
|
+
</Surface>
|
|
23
23
|
),
|
|
24
24
|
],
|
|
25
25
|
parameters: {
|
|
@@ -32,4 +32,21 @@ describe('NavItem', () => {
|
|
|
32
32
|
const { container } = render(<NavItem icon={icon} label="Live" live />)
|
|
33
33
|
expect(container.firstChild).toBeInTheDocument()
|
|
34
34
|
})
|
|
35
|
+
|
|
36
|
+
// nativewind compiles className to style, so jsdom cannot see the accent colour.
|
|
37
|
+
// Assert an app-supplied accent leaves the rest of the active state intact; the
|
|
38
|
+
// colour itself is guarded by the paired-token test in BrandLockup.test.
|
|
39
|
+
it('keeps the active state when an app supplies its own accent', () => {
|
|
40
|
+
render(
|
|
41
|
+
<NavItem
|
|
42
|
+
icon={icon}
|
|
43
|
+
label="Graph"
|
|
44
|
+
active
|
|
45
|
+
accentClassName="text-data-3"
|
|
46
|
+
accentBarClassName="bg-data-3"
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
expect(screen.getByTestId('nav-item-accent')).toBeInTheDocument()
|
|
50
|
+
expect(screen.getByRole('tab', { name: 'Graph' })).toBeInTheDocument()
|
|
51
|
+
})
|
|
35
52
|
})
|
|
@@ -8,13 +8,17 @@ export interface NavItemProps {
|
|
|
8
8
|
icon: ReactNode
|
|
9
9
|
/** Short label shown under the glyph (uppercased for display; used as the accessible name). */
|
|
10
10
|
label: string
|
|
11
|
-
/** Active category → left accent bar +
|
|
11
|
+
/** Active category → left accent bar + accent-colored glyph & label. */
|
|
12
12
|
active?: boolean
|
|
13
13
|
/**
|
|
14
14
|
* A set is running for this category while it is NOT the active view
|
|
15
15
|
* (live-elsewhere) → a quiet muted-green label tint. Ignored when `active`.
|
|
16
16
|
*/
|
|
17
17
|
live?: boolean
|
|
18
|
+
/** Semantic `text-*` token for the active glyph and label. Defaults to the Voltras brand. */
|
|
19
|
+
accentClassName?: string
|
|
20
|
+
/** Semantic `bg-*` token for the active bar. Pair it with `accentClassName`. */
|
|
21
|
+
accentBarClassName?: string
|
|
18
22
|
onPress?: () => void
|
|
19
23
|
className?: string
|
|
20
24
|
}
|
|
@@ -23,20 +27,25 @@ export interface NavItemProps {
|
|
|
23
27
|
* Shell S2 · NavItem — one category button in the {@link SideNav}: a 20px glyph
|
|
24
28
|
* over an uppercase micro-label in a 46×46 target. The button spans the full 60px
|
|
25
29
|
* rail so the active **left accent bar** sits flush to the rail's edge. States:
|
|
26
|
-
* active = accent bar +
|
|
30
|
+
* active = accent bar + the accent token; `live` (while not active) tints only the
|
|
27
31
|
* label `status-success-dark` (the glyph stays dim); otherwise dim `text-tertiary`.
|
|
32
|
+
*
|
|
33
|
+
* The accent follows the mounting app's brand, so a Brain shell reads yellow
|
|
34
|
+
* throughout rather than showing a Voltras-orange active item under its own lockup.
|
|
28
35
|
*/
|
|
29
36
|
export function NavItem({
|
|
30
37
|
icon,
|
|
31
38
|
label,
|
|
32
39
|
active = false,
|
|
33
40
|
live = false,
|
|
41
|
+
accentClassName = 'text-brand-primary',
|
|
42
|
+
accentBarClassName = 'bg-brand-primary',
|
|
34
43
|
onPress,
|
|
35
44
|
className,
|
|
36
45
|
}: NavItemProps) {
|
|
37
|
-
const glyphColor = active ?
|
|
46
|
+
const glyphColor = active ? accentClassName : 'text-text-tertiary'
|
|
38
47
|
const labelColor = active
|
|
39
|
-
?
|
|
48
|
+
? accentClassName
|
|
40
49
|
: live
|
|
41
50
|
? 'text-status-live-muted'
|
|
42
51
|
: 'text-text-tertiary'
|
|
@@ -52,7 +61,10 @@ export function NavItem({
|
|
|
52
61
|
{active ? (
|
|
53
62
|
<View
|
|
54
63
|
testID="nav-item-accent"
|
|
55
|
-
className=
|
|
64
|
+
className={cn(
|
|
65
|
+
'absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px]',
|
|
66
|
+
accentBarClassName
|
|
67
|
+
)}
|
|
56
68
|
/>
|
|
57
69
|
) : null}
|
|
58
70
|
<View
|
|
@@ -1,22 +1,54 @@
|
|
|
1
1
|
# Shell chrome — component family
|
|
2
2
|
|
|
3
|
-
The persistent dashboard frame (top bar, and — as the decomposition proceeds —
|
|
4
|
-
rail, drawers, pinned strip).
|
|
5
|
-
|
|
3
|
+
The persistent dashboard frame (top bar, side nav, and — as the decomposition proceeds — session
|
|
4
|
+
rail, drawers, pinned strip). The tiering below is a documentation contract, not a directory
|
|
5
|
+
layout, so promoting/composing is pure import churn.
|
|
6
6
|
|
|
7
7
|
This README is the **index**: it maps each component's dependencies (**composes ↓**) and its consumers
|
|
8
8
|
(**used-by ↑**) so you can navigate the tree in both directions and spot where a hand-rolled element
|
|
9
9
|
should be reaching for an existing primitive instead. Storybook mirrors this tree under `Shell/TopBar/…`,
|
|
10
10
|
and every component's autodocs page repeats its "Composes" links.
|
|
11
11
|
|
|
12
|
+
## The generic/app split (AW-132)
|
|
13
|
+
|
|
14
|
+
`shell/` is **generic**. It knows about a band, a rail, a brand lockup and a content region, and
|
|
15
|
+
nothing about any one product. `shell/workout/` is the **workout app's** chrome, and it composes
|
|
16
|
+
the generic family. The dependency arrow points one way only: nothing in `shell/` may import from
|
|
17
|
+
`shell/workout/`, and a second app (audiobook, active-work, agent dashboards, brain) gets its own
|
|
18
|
+
sibling directory rather than a new prop on `AppShell`.
|
|
19
|
+
|
|
20
|
+
**The composition mechanism is the library's existing slot idiom** — named `ReactNode` props with a
|
|
21
|
+
built-in default, the same shape as `Pill`'s `leading`/`trailing` and `SectionHeader`'s `trailing`.
|
|
22
|
+
No second mechanism was introduced.
|
|
23
|
+
|
|
24
|
+
| Slot | On | Default | An app uses it to… |
|
|
25
|
+
| ------------------------ | ----------- | -------------------------------- | ---------------------------------------- |
|
|
26
|
+
| `TopBar.trailing` | `TopBar` | just the clock | put its own chrome in the right cluster |
|
|
27
|
+
| `TopBar.leading` | `TopBar` | `<BrandLockup brand=…>` | replace the brand region outright |
|
|
28
|
+
| `AppShell.topBarTrailing` | `AppShell` | — | pass chrome through to the default bar |
|
|
29
|
+
| `AppShell.topBar` | `AppShell` | `<TopBar brand=…>` | swap the whole band |
|
|
30
|
+
| `AppShell.nav` | `AppShell` | `<SideNav items=…>` | swap the whole rail |
|
|
31
|
+
| `AppShell.children` | `AppShell` | a placeholder | mount its page |
|
|
32
|
+
|
|
33
|
+
`TopBar.trailing` takes an array as well as a node. **The bar interleaves its own vertical dividers
|
|
34
|
+
between the items**, so an app supplies the controls and the shell keeps the divider rhythm from the
|
|
35
|
+
S1 lock — the app never hand-places a `Divider`, and can't drift from it.
|
|
36
|
+
|
|
37
|
+
Two things stopped being generic defaults and became the workout app's data:
|
|
38
|
+
|
|
39
|
+
- `SideNav.items` is **required**. The four Live · Review · Plan · Body categories are
|
|
40
|
+
`workoutNavItems` in `shell/workout/`.
|
|
41
|
+
- `BrandLockup` is generic over `brand` (five presets in `brands.tsx`), rather than hard-coding
|
|
42
|
+
the Voltras mark and wordmark.
|
|
43
|
+
|
|
12
44
|
## Specimen: `SideNav` (the second shell unit, S2)
|
|
13
45
|
|
|
14
46
|
```
|
|
15
47
|
SideNav ...................... organism — the 60px left rail (Shell/SideNav)
|
|
16
|
-
├─ NavItem ................... molecule ×
|
|
17
|
-
│ ├─ icon ................... icons (shared primitive) —
|
|
48
|
+
├─ NavItem ................... molecule × `items` (the app's own categories)
|
|
49
|
+
│ ├─ icon ................... icons (shared primitive) — supplied per item
|
|
18
50
|
│ └─ Typography ............. titan · button variant (uppercase micro-label)
|
|
19
|
-
└─ (accent bar) ..............
|
|
51
|
+
└─ (accent bar) .............. edge bar on the active item, in the app's brand accent
|
|
20
52
|
```
|
|
21
53
|
|
|
22
54
|
Decisions locked 2026-07-08 (specimen `sources/design/shell/S2-sidenav/`): lucide glyphs
|
|
@@ -24,41 +56,58 @@ Decisions locked 2026-07-08 (specimen `sources/design/shell/S2-sidenav/`): lucid
|
|
|
24
56
|
bar** (short, centered on the icon+label) · **60px** icon+micro-label · live cue = **muted-green label**
|
|
25
57
|
(`status-live-muted`) on the Live item while a set runs off-Live · four items, no footer, no top-nav. Fixed
|
|
26
58
|
60px at every width (labels sit under the glyph). `SideNav` is presentational — `activeKey` / `onNavigate` /
|
|
27
|
-
`liveKey`; the app owns routing + which key is live.
|
|
59
|
+
`liveKey`; the app owns routing + which key is live. Those four categories are now
|
|
60
|
+
`workoutNavItems` in `shell/workout/` (AW-132); the rail itself has no built-in set.
|
|
28
61
|
|
|
29
62
|
## Specimen: `TopBar` (the first shell unit, S1)
|
|
30
63
|
|
|
31
64
|
```
|
|
32
65
|
TopBar ....................... organism — the chrome band (Shell/TopBar)
|
|
33
|
-
├─ BrandLockup ............... molecule
|
|
34
|
-
│ ├─
|
|
66
|
+
├─ BrandLockup ............... molecule — default `leading`
|
|
67
|
+
│ ├─ brand mark ............. icons (shared primitive) — per `brands.tsx` preset
|
|
35
68
|
│ └─ Typography ............. titan · button variant
|
|
36
|
-
├─
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
├─ Divider ................... titan · bg-border-prominent
|
|
40
|
-
├─ DeviceMenu ................ organism
|
|
41
|
-
│ ├─ Popover ................ titan
|
|
42
|
-
│ ├─ DeviceIndicator ........ molecule → BluetoothIcon (icons primitive)
|
|
43
|
-
│ ├─ DeviceRow .............. molecule → Indicator + Typography (body2 / mono)
|
|
44
|
-
│ └─ Typography ............. titan · monoLabel (header)
|
|
45
|
-
├─ DateTime .................. titan · mono variant, live 24h clock
|
|
69
|
+
├─ trailing[] ................ SLOT — app chrome, divider-interleaved by the bar
|
|
70
|
+
├─ Divider ................... titan · bg-border-prominent (one per slot gap)
|
|
71
|
+
├─ DateTime .................. titan · mono variant, live 24h clock (edge-pinned)
|
|
46
72
|
└─ surfaceGradient.chrome .... theme · gradient primitive
|
|
47
73
|
```
|
|
48
74
|
|
|
49
|
-
##
|
|
75
|
+
## Specimen: `WorkoutTopBar` (the same band, filled by the workout app)
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
WorkoutTopBar ................ organism — Shell/Workout/WorkoutTopBar
|
|
79
|
+
└─ TopBar .................... generic band, brand="voltras"
|
|
80
|
+
└─ trailing[] ............. supplied by the workout app:
|
|
81
|
+
├─ SessionStatePill .... molecule → Indicator + Typography (monoLabel)
|
|
82
|
+
└─ DeviceMenu .......... organism
|
|
83
|
+
├─ Popover .......... titan
|
|
84
|
+
├─ DeviceIndicator .. molecule → BluetoothIcon (icons primitive)
|
|
85
|
+
├─ DeviceRow ........ molecule → Indicator + Typography (body2 / mono)
|
|
86
|
+
└─ Typography ....... titan · monoLabel (header)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Dependency map — generic (`shell/`)
|
|
50
90
|
|
|
51
91
|
| Component | Tier | Composes ↓ | Used-by ↑ |
|
|
52
92
|
|---|---|---|---|
|
|
53
|
-
| `
|
|
93
|
+
| `AppShell` | page shell | TopBar, SideNav, Surface | WorkoutShell, any app root |
|
|
94
|
+
| `SideNav` | organism | NavItem × `items` | AppShell |
|
|
54
95
|
| `NavItem` | molecule | icon, Typography | SideNav |
|
|
55
|
-
| `TopBar` | organism | BrandLockup,
|
|
56
|
-
| `BrandLockup` | molecule |
|
|
57
|
-
| `
|
|
58
|
-
|
|
96
|
+
| `TopBar` | organism | BrandLockup, Divider, DateTime, `surfaceGradient.chrome` | AppShell, WorkoutTopBar |
|
|
97
|
+
| `BrandLockup` | molecule | a brand mark icon, Typography | TopBar |
|
|
98
|
+
| `brands.tsx` | data | VoltrasMark / Headphones / Kanban / Bot / Brain icons | BrandLockup, TopBar, AppShell |
|
|
99
|
+
|
|
100
|
+
## Dependency map — workout app (`shell/workout/`)
|
|
101
|
+
|
|
102
|
+
| Component | Tier | Composes ↓ | Used-by ↑ |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `WorkoutShell` | page shell | AppShell, WorkoutTopBar, workoutNavItems | dashboard app root |
|
|
105
|
+
| `WorkoutTopBar` | organism | TopBar, SessionStatePill, DeviceMenu | WorkoutShell |
|
|
106
|
+
| `SessionStatePill` | molecule | Indicator, Typography | WorkoutTopBar, **Live-view header** *(planned reuse)* |
|
|
107
|
+
| `DeviceMenu` | organism | Popover, DeviceIndicator, DeviceRow, Typography | WorkoutTopBar |
|
|
59
108
|
| `DeviceIndicator` | molecule | BluetoothIcon | DeviceMenu |
|
|
60
109
|
| `DeviceRow` | molecule | Indicator, Typography | DeviceMenu |
|
|
61
|
-
| `
|
|
110
|
+
| `workoutNavItems` | data | Activity/History/Layers/PersonStanding icons | WorkoutShell |
|
|
62
111
|
|
|
63
112
|
## Shared substrates introduced here (reusable beyond the shell)
|
|
64
113
|
|
|
@@ -98,6 +147,20 @@ Every leaf now composes a primitive rather than hand-rolling it:
|
|
|
98
147
|
- **Lint guardrails** — components may not inline `linear-gradient` (use `surfaceGradient`); shell + icons may
|
|
99
148
|
not use raw hex (use tokens).
|
|
100
149
|
|
|
150
|
+
**AW-132 shared substrate:** four brand marks added to `components/icons` (`HeadphonesIcon`,
|
|
151
|
+
`KanbanIcon`, `BotIcon`, `BrainIcon`), so `brands.tsx` can carry a preset for every app expected to
|
|
152
|
+
mount this shell. Brand accents come from `data-*` rather than `status-*`: `data-*` is the library's
|
|
153
|
+
set of distinct, CVD-checked hues with no semantic load, which is what a per-app identity accent
|
|
154
|
+
needs. Voltras keeps the real `brand-primary` token.
|
|
155
|
+
|
|
156
|
+
**The nav accent is brand-level, not shell-level** (operator, Gate 2 2026-09-11). `SideNav`'s active
|
|
157
|
+
item and its edge bar take the mounting app's accent, so a Brain shell reads yellow throughout
|
|
158
|
+
rather than showing a Voltras-orange active item under a yellow lockup. `AppShell` threads it down
|
|
159
|
+
from `brand`; a bare `SideNav` takes `accentClassName` / `accentBarClassName`. Each preset declares
|
|
160
|
+
both as literals (`text-data-3` + `bg-data-3`) because Tailwind only emits classes it can see in the
|
|
161
|
+
source — a name built at runtime is never generated — and a test asserts the pair always matches.
|
|
162
|
+
Workout is unaffected: its accent *is* `brand-primary`.
|
|
163
|
+
|
|
101
164
|
**S2 shared substrate:** four nav glyphs added to `components/icons` (`ActivityIcon`, `HistoryIcon`,
|
|
102
165
|
`LayersIcon`, `PersonStandingIcon` — lucide-mirrored, like Dumbbell/Star), available system-wide.
|
|
103
166
|
|
|
@@ -3,15 +3,20 @@ import { useArgs } from 'storybook/preview-api'
|
|
|
3
3
|
import { View } from 'react-native'
|
|
4
4
|
import { SideNav } from './SideNav'
|
|
5
5
|
import { Typography } from '../custom/Typography'
|
|
6
|
+
import { Surface } from '../ui/surface'
|
|
7
|
+
import { BotIcon, BrainIcon, LayersIcon } from '../icons'
|
|
8
|
+
import { workoutNavItems } from './workout'
|
|
9
|
+
import { brandPresets } from './brands'
|
|
6
10
|
|
|
7
11
|
const meta: Meta<typeof SideNav> = {
|
|
8
12
|
title: 'Shell/SideNav',
|
|
9
13
|
component: SideNav,
|
|
10
|
-
tags: ['autodocs'],
|
|
11
|
-
args: { activeKey: 'live', liveKey: null },
|
|
14
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
15
|
+
args: { items: workoutNavItems, activeKey: 'live', liveKey: null },
|
|
12
16
|
argTypes: {
|
|
13
17
|
activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
|
|
14
18
|
liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
|
|
19
|
+
items: { control: false },
|
|
15
20
|
onNavigate: { action: 'navigate' },
|
|
16
21
|
},
|
|
17
22
|
// Interactive AND Controls-synced: clicking a category writes `activeKey` back to the
|
|
@@ -35,14 +40,14 @@ const meta: Meta<typeof SideNav> = {
|
|
|
35
40
|
// stretches with the viewport (resize the canvas height to see it hold).
|
|
36
41
|
decorators: [
|
|
37
42
|
(Story) => (
|
|
38
|
-
<
|
|
43
|
+
<Surface level="base" className="min-h-screen flex-row">
|
|
39
44
|
<Story />
|
|
40
45
|
<View className="flex-1 items-center justify-center">
|
|
41
46
|
<Typography variant="body2" color="tertiary">
|
|
42
47
|
main viewport
|
|
43
48
|
</Typography>
|
|
44
49
|
</View>
|
|
45
|
-
</
|
|
50
|
+
</Surface>
|
|
46
51
|
),
|
|
47
52
|
],
|
|
48
53
|
parameters: {
|
|
@@ -50,9 +55,11 @@ const meta: Meta<typeof SideNav> = {
|
|
|
50
55
|
docs: {
|
|
51
56
|
description: {
|
|
52
57
|
component:
|
|
53
|
-
'**Organism** (shell region). The persistent 60px left rail
|
|
54
|
-
'
|
|
55
|
-
'shared [icon set](?path=/docs/foundations-icons--docs).
|
|
58
|
+
'**Organism** (shell region). The persistent 60px left rail that switches the main viewport ' +
|
|
59
|
+
"between an app's categories. Composes [NavItem](?path=/docs/shell-navitem--docs) × `items` + the " +
|
|
60
|
+
'shared [icon set](?path=/docs/foundations-icons--docs). It has no built-in categories: `items` is ' +
|
|
61
|
+
'required, and each app supplies its own (the workout set is `workoutNavItems`). ' +
|
|
62
|
+
'Presentational — drive it with `activeKey` / ' +
|
|
56
63
|
'`onNavigate` / `liveKey`. Active = left accent bar; `liveKey` (when not active) tints that ' +
|
|
57
64
|
'label a muted green.\n\n' +
|
|
58
65
|
'**Try it:** stories render in a full-height shell frame — the rail pins to the left edge and ' +
|
|
@@ -79,3 +86,27 @@ export const LiveElsewhere: Story = {
|
|
|
79
86
|
},
|
|
80
87
|
},
|
|
81
88
|
}
|
|
89
|
+
|
|
90
|
+
/** A different app's categories and accent in the same rail (AW-132). */
|
|
91
|
+
export const AnotherApp: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
items: [
|
|
94
|
+
{ key: 'notes', label: 'Notes', icon: <LayersIcon size={20} color="currentColor" /> },
|
|
95
|
+
{ key: 'graph', label: 'Graph', icon: <BrainIcon size={20} color="currentColor" /> },
|
|
96
|
+
{ key: 'agents', label: 'Agents', icon: <BotIcon size={20} color="currentColor" /> },
|
|
97
|
+
],
|
|
98
|
+
activeKey: 'graph',
|
|
99
|
+
accentClassName: brandPresets.brain.accentClassName,
|
|
100
|
+
accentBarClassName: brandPresets.brain.accentBarClassName,
|
|
101
|
+
},
|
|
102
|
+
parameters: {
|
|
103
|
+
docs: {
|
|
104
|
+
description: {
|
|
105
|
+
story:
|
|
106
|
+
"The brain app's three categories in its own accent. Same rail, same states, different " +
|
|
107
|
+
'`items` and accent. `AppShell` passes the accent down from its `brand`; drive the bare ' +
|
|
108
|
+
'rail with `accentClassName` / `accentBarClassName`.',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
2
|
import { render, screen, fireEvent, within } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
|
-
import { SideNav } from './SideNav'
|
|
4
|
+
import { SideNav, type SideNavItem } from './SideNav'
|
|
5
|
+
|
|
6
|
+
// A generic four-category set — the rail has no built-in categories any more.
|
|
7
|
+
const items: SideNavItem[] = [
|
|
8
|
+
{ key: 'live', label: 'Live', icon: null },
|
|
9
|
+
{ key: 'review', label: 'Review', icon: null },
|
|
10
|
+
{ key: 'program', label: 'Plan', icon: null },
|
|
11
|
+
{ key: 'body', label: 'Body', icon: null },
|
|
12
|
+
]
|
|
5
13
|
|
|
6
14
|
describe('SideNav', () => {
|
|
7
|
-
it('renders the
|
|
8
|
-
render(<SideNav activeKey="live" />)
|
|
15
|
+
it('renders the supplied categories as tabs in a tablist', () => {
|
|
16
|
+
render(<SideNav items={items} activeKey="live" />)
|
|
9
17
|
expect(screen.getByRole('tablist')).toBeInTheDocument()
|
|
10
18
|
;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
|
|
11
19
|
expect(screen.getByRole('tab', { name })).toBeInTheDocument()
|
|
@@ -13,7 +21,7 @@ describe('SideNav', () => {
|
|
|
13
21
|
})
|
|
14
22
|
|
|
15
23
|
it('shows the accent bar only on the active category', () => {
|
|
16
|
-
render(<SideNav activeKey="program" />)
|
|
24
|
+
render(<SideNav items={items} activeKey="program" />)
|
|
17
25
|
expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
|
|
18
26
|
expect(
|
|
19
27
|
within(screen.getByRole('tab', { name: 'Plan' })).getByTestId('nav-item-accent')
|
|
@@ -25,13 +33,13 @@ describe('SideNav', () => {
|
|
|
25
33
|
|
|
26
34
|
it('reports the tapped key via onNavigate', () => {
|
|
27
35
|
const onNavigate = vi.fn()
|
|
28
|
-
render(<SideNav activeKey="live" onNavigate={onNavigate} />)
|
|
36
|
+
render(<SideNav items={items} activeKey="live" onNavigate={onNavigate} />)
|
|
29
37
|
fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
|
|
30
38
|
expect(onNavigate).toHaveBeenCalledWith('body')
|
|
31
39
|
})
|
|
32
40
|
|
|
33
41
|
it('keeps the active item as active even when it is also the liveKey (no double-state)', () => {
|
|
34
|
-
render(<SideNav activeKey="live" liveKey="live" />)
|
|
42
|
+
render(<SideNav items={items} activeKey="live" liveKey="live" />)
|
|
35
43
|
// active wins: exactly one accent bar, on Live (the live cue never applies to the active view)
|
|
36
44
|
expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
|
|
37
45
|
expect(
|
|
@@ -39,13 +47,13 @@ describe('SideNav', () => {
|
|
|
39
47
|
).toBeInTheDocument()
|
|
40
48
|
})
|
|
41
49
|
|
|
42
|
-
it('accepts
|
|
50
|
+
it('accepts an app-specific item set', () => {
|
|
43
51
|
render(<SideNav activeKey="a" items={[{ key: 'a', label: 'Alpha', icon: null }]} />)
|
|
44
52
|
expect(screen.getByRole('tab', { name: 'Alpha' })).toBeInTheDocument()
|
|
45
53
|
})
|
|
46
54
|
|
|
47
55
|
it('has no accessibility violations', async () => {
|
|
48
|
-
const { container } = render(<SideNav activeKey="live" />)
|
|
56
|
+
const { container } = render(<SideNav items={items} activeKey="live" />)
|
|
49
57
|
expect(await axe(container)).toHaveNoViolations()
|
|
50
58
|
})
|
|
51
59
|
})
|