@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
|
@@ -1,28 +1,34 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
1
|
+
import { Fragment, useState, type ReactNode } from 'react'
|
|
2
2
|
import { View, type LayoutChangeEvent } from 'react-native'
|
|
3
3
|
import { cn } from '../../utils/cn'
|
|
4
4
|
import { surfaceGradient } from '../../theme/gradients'
|
|
5
5
|
import { Divider } from '../ui/divider'
|
|
6
6
|
import { DateTime } from '../custom/DateTime'
|
|
7
7
|
import { BrandLockup } from './BrandLockup'
|
|
8
|
-
import {
|
|
9
|
-
import { DeviceMenu } from './DeviceMenu'
|
|
10
|
-
import { type Device } from './DeviceRow'
|
|
8
|
+
import { type BrandKey } from './brands'
|
|
11
9
|
|
|
12
10
|
export interface TopBarProps {
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
devices: Device[]
|
|
17
|
-
/** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
|
|
18
|
-
time?: number | Date
|
|
19
|
-
/** Brand subtitle. Default "wall dashboard". */
|
|
11
|
+
/** Which app identity the default {@link BrandLockup} renders. */
|
|
12
|
+
brand?: BrandKey
|
|
13
|
+
/** Brand subtitle. Defaults to the brand preset's own. */
|
|
20
14
|
subtitle?: string
|
|
21
15
|
/** Force the subtitle on/off; defaults to container-responsive (hidden below ~1024px). */
|
|
22
16
|
showSubtitle?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* Replace the whole brand region. Wins over `brand` / `subtitle`, and opts out
|
|
19
|
+
* of the responsive subtitle collapse — the node owns its own behaviour.
|
|
20
|
+
*/
|
|
21
|
+
leading?: ReactNode
|
|
22
|
+
/**
|
|
23
|
+
* App chrome for the right cluster, left → right. An array is rendered with the
|
|
24
|
+
* bar's own vertical dividers between the items, so an app supplies its
|
|
25
|
+
* controls and the shell keeps the divider rhythm.
|
|
26
|
+
*/
|
|
27
|
+
trailing?: ReactNode | ReactNode[]
|
|
28
|
+
/** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
|
|
29
|
+
time?: number | Date
|
|
23
30
|
/** Force the clock on/off; defaults to container-responsive (hidden below ~720px). */
|
|
24
31
|
showClock?: boolean
|
|
25
|
-
onSelectDevice?: (device: Device) => void
|
|
26
32
|
className?: string
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -30,20 +36,39 @@ export interface TopBarProps {
|
|
|
30
36
|
const SUBTITLE_MIN = 1024
|
|
31
37
|
const CLOCK_MIN = 720
|
|
32
38
|
|
|
39
|
+
function ClusterDivider() {
|
|
40
|
+
return <Divider orientation="vertical" className="h-4 bg-border-prominent" />
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Interleave the bar's divider between slot items, skipping empty slots. */
|
|
44
|
+
function dividedCluster(items: ReactNode[]) {
|
|
45
|
+
return items
|
|
46
|
+
.filter((item) => item !== null && item !== undefined && item !== false)
|
|
47
|
+
.map((item, i) => (
|
|
48
|
+
<Fragment key={i}>
|
|
49
|
+
{i > 0 ? <ClusterDivider /> : null}
|
|
50
|
+
{item}
|
|
51
|
+
</Fragment>
|
|
52
|
+
))
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
/**
|
|
34
|
-
* S1 · TopBar — the persistent shell chrome band
|
|
35
|
-
*
|
|
36
|
-
* stable as the
|
|
37
|
-
* container narrows.
|
|
56
|
+
* S1 · TopBar — the persistent shell chrome band, generic over the app. Brand
|
|
57
|
+
* (left) + a `trailing` cluster of app-supplied chrome and the wall clock (clock
|
|
58
|
+
* edge-pinned so spacing stays stable as the items' text changes). Collapses
|
|
59
|
+
* subtitle then clock as its container narrows.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* <TopBar brand="brain" trailing={[<IndexStatus />, <SearchScope />]} />
|
|
38
63
|
*/
|
|
39
64
|
export function TopBar({
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
time,
|
|
43
|
-
subtitle = 'wall dashboard',
|
|
65
|
+
brand = 'voltras',
|
|
66
|
+
subtitle,
|
|
44
67
|
showSubtitle,
|
|
68
|
+
leading,
|
|
69
|
+
trailing,
|
|
70
|
+
time,
|
|
45
71
|
showClock,
|
|
46
|
-
onSelectDevice,
|
|
47
72
|
className,
|
|
48
73
|
}: TopBarProps) {
|
|
49
74
|
const [width, setWidth] = useState(SUBTITLE_MIN)
|
|
@@ -52,6 +77,18 @@ export function TopBar({
|
|
|
52
77
|
const subtitleVisible = showSubtitle ?? width >= SUBTITLE_MIN
|
|
53
78
|
const clockVisible = showClock ?? width >= CLOCK_MIN
|
|
54
79
|
|
|
80
|
+
const clock = clockVisible ? (
|
|
81
|
+
<DateTime
|
|
82
|
+
value={time}
|
|
83
|
+
live={time == null}
|
|
84
|
+
format="time"
|
|
85
|
+
hour12={false}
|
|
86
|
+
variant="mono"
|
|
87
|
+
color="secondary"
|
|
88
|
+
className="text-[11px] min-w-[38px] text-right"
|
|
89
|
+
/>
|
|
90
|
+
) : null
|
|
91
|
+
|
|
55
92
|
return (
|
|
56
93
|
<View
|
|
57
94
|
onLayout={onLayout}
|
|
@@ -62,27 +99,11 @@ export function TopBar({
|
|
|
62
99
|
className
|
|
63
100
|
)}
|
|
64
101
|
>
|
|
65
|
-
<BrandLockup subtitle={subtitle} showSubtitle={subtitleVisible} />
|
|
102
|
+
{leading ?? <BrandLockup brand={brand} subtitle={subtitle} showSubtitle={subtitleVisible} />}
|
|
66
103
|
|
|
67
|
-
{/* right cluster —
|
|
104
|
+
{/* right cluster — app chrome then the clock, pinned to the edge */}
|
|
68
105
|
<View className="ml-auto flex-row items-center gap-[12px]">
|
|
69
|
-
|
|
70
|
-
<Divider orientation="vertical" className="h-4 bg-border-prominent" />
|
|
71
|
-
<DeviceMenu devices={devices} onSelectDevice={onSelectDevice} />
|
|
72
|
-
{clockVisible ? (
|
|
73
|
-
<>
|
|
74
|
-
<Divider orientation="vertical" className="h-4 bg-border-prominent" />
|
|
75
|
-
<DateTime
|
|
76
|
-
value={time}
|
|
77
|
-
live={time == null}
|
|
78
|
-
format="time"
|
|
79
|
-
hour12={false}
|
|
80
|
-
variant="mono"
|
|
81
|
-
color="secondary"
|
|
82
|
-
className="text-[11px] min-w-[38px] text-right"
|
|
83
|
-
/>
|
|
84
|
-
</>
|
|
85
|
-
) : null}
|
|
106
|
+
{dividedCluster([...(Array.isArray(trailing) ? trailing : [trailing]), clock])}
|
|
86
107
|
</View>
|
|
87
108
|
</View>
|
|
88
109
|
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { BotIcon, BrainIcon, HeadphonesIcon, KanbanIcon, VoltrasMark } from '../icons'
|
|
3
|
+
|
|
4
|
+
/** The apps that are expected to mount this shell. */
|
|
5
|
+
export type BrandKey = 'voltras' | 'audiobook' | 'active-work' | 'agents' | 'brain'
|
|
6
|
+
|
|
7
|
+
export interface BrandPreset {
|
|
8
|
+
/** The mark glyph, rendered at 14px through the accent token via `currentColor`. */
|
|
9
|
+
mark: ReactNode
|
|
10
|
+
/** Uppercase product wordmark. */
|
|
11
|
+
wordmark: string
|
|
12
|
+
/** Semantic text token that colours the mark. */
|
|
13
|
+
accentClassName: string
|
|
14
|
+
/**
|
|
15
|
+
* The same accent as a background token, for the nav's active bar. Declared as
|
|
16
|
+
* a literal alongside its `text-*` twin because Tailwind only emits classes it
|
|
17
|
+
* can see in the source — a name built at runtime is never generated.
|
|
18
|
+
*/
|
|
19
|
+
accentBarClassName: string
|
|
20
|
+
/** Default "/ subtitle" for that app's shell. */
|
|
21
|
+
subtitle: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Accents come from `data-*` rather than `status-*`: `data-*` is the library's
|
|
25
|
+
// palette of N distinct, CVD-checked hues with no semantic load, which is exactly
|
|
26
|
+
// what a per-app identity accent needs. Voltras keeps the real brand token.
|
|
27
|
+
export const brandPresets: Record<BrandKey, BrandPreset> = {
|
|
28
|
+
voltras: {
|
|
29
|
+
mark: <VoltrasMark size={14} color="currentColor" />,
|
|
30
|
+
wordmark: 'VOLTRAS',
|
|
31
|
+
accentClassName: 'text-brand-primary',
|
|
32
|
+
accentBarClassName: 'bg-brand-primary',
|
|
33
|
+
subtitle: 'wall dashboard',
|
|
34
|
+
},
|
|
35
|
+
audiobook: {
|
|
36
|
+
mark: <HeadphonesIcon size={14} color="currentColor" />,
|
|
37
|
+
wordmark: 'AUDIOBOOK',
|
|
38
|
+
accentClassName: 'text-data-5',
|
|
39
|
+
accentBarClassName: 'bg-data-5',
|
|
40
|
+
subtitle: 'library',
|
|
41
|
+
},
|
|
42
|
+
'active-work': {
|
|
43
|
+
mark: <KanbanIcon size={14} color="currentColor" />,
|
|
44
|
+
wordmark: 'ACTIVE WORK',
|
|
45
|
+
accentClassName: 'text-data-1',
|
|
46
|
+
accentBarClassName: 'bg-data-1',
|
|
47
|
+
subtitle: 'initiatives',
|
|
48
|
+
},
|
|
49
|
+
agents: {
|
|
50
|
+
mark: <BotIcon size={14} color="currentColor" />,
|
|
51
|
+
wordmark: 'AGENTS',
|
|
52
|
+
accentClassName: 'text-data-2',
|
|
53
|
+
accentBarClassName: 'bg-data-2',
|
|
54
|
+
subtitle: 'fleet',
|
|
55
|
+
},
|
|
56
|
+
brain: {
|
|
57
|
+
mark: <BrainIcon size={14} color="currentColor" />,
|
|
58
|
+
wordmark: 'BRAIN',
|
|
59
|
+
accentClassName: 'text-data-3',
|
|
60
|
+
accentBarClassName: 'bg-data-3',
|
|
61
|
+
subtitle: 'knowledge',
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Every brand key, in the order the stories present them. */
|
|
66
|
+
export const brandKeys = Object.keys(brandPresets) as BrandKey[]
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
// Shell chrome — persistent
|
|
2
|
-
//
|
|
1
|
+
// Shell chrome — the GENERIC persistent app frame (nav / top bar / brand).
|
|
2
|
+
// App-specific chrome composes this family; it never leaks back into it.
|
|
3
|
+
// (Icons live in the shared `components/icons` primitive.)
|
|
4
|
+
export * from './brands'
|
|
3
5
|
export * from './BrandLockup'
|
|
4
|
-
|
|
5
|
-
export * from './DeviceIndicator'
|
|
6
|
-
export * from './DeviceRow'
|
|
7
|
-
export * from './DeviceMenu'
|
|
6
|
+
// S1 · Top bar family.
|
|
8
7
|
export * from './TopBar'
|
|
9
8
|
// S2 · Side nav family.
|
|
10
9
|
export * from './NavItem'
|
|
11
10
|
export * from './SideNav'
|
|
12
|
-
// S3 ·
|
|
13
|
-
export * from './
|
|
11
|
+
// S3 · App shell — composes the nav + top-bar chrome over a content slot.
|
|
12
|
+
export * from './AppShell'
|
|
13
|
+
// AW-132 · Workout-app shell, composed over the generic family above.
|
|
14
|
+
export * from './workout'
|
package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx}
RENAMED
|
@@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { DeviceIndicator } from './DeviceIndicator'
|
|
3
3
|
|
|
4
4
|
const meta: Meta<typeof DeviceIndicator> = {
|
|
5
|
-
title: 'Shell/DeviceIndicator',
|
|
5
|
+
title: 'Shell/Workout/DeviceIndicator',
|
|
6
6
|
component: DeviceIndicator,
|
|
7
|
-
tags: ['autodocs'],
|
|
7
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
8
8
|
args: { status: 'connected' },
|
|
9
9
|
argTypes: {
|
|
10
10
|
status: { control: 'select', options: ['connected', 'degraded', 'lost'] },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { View, Pressable, type ViewProps } from 'react-native'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { BluetoothIcon } from '
|
|
2
|
+
import { cn } from '../../../utils/cn'
|
|
3
|
+
import { BluetoothIcon } from '../../icons'
|
|
4
4
|
|
|
5
5
|
export type DeviceConnState = 'connected' | 'degraded' | 'lost'
|
|
6
6
|
|
|
@@ -10,9 +10,9 @@ const DEVICES: Device[] = [
|
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
const meta: Meta<typeof DeviceMenu> = {
|
|
13
|
-
title: 'Shell/DeviceMenu',
|
|
13
|
+
title: 'Shell/Workout/DeviceMenu',
|
|
14
14
|
component: DeviceMenu,
|
|
15
|
-
tags: ['autodocs'],
|
|
15
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
16
16
|
args: { devices: DEVICES, isOpen: false },
|
|
17
17
|
argTypes: {
|
|
18
18
|
devices: { control: 'object' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { View } from 'react-native'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { Typography } from '
|
|
4
|
-
import { Popover, PopoverTrigger, PopoverContent } from '
|
|
2
|
+
import { cn } from '../../../utils/cn'
|
|
3
|
+
import { Typography } from '../../custom/Typography'
|
|
4
|
+
import { Popover, PopoverTrigger, PopoverContent } from '../../ui/popover'
|
|
5
5
|
import { DeviceIndicator, type DeviceConnState } from './DeviceIndicator'
|
|
6
6
|
import { DeviceRow, type Device } from './DeviceRow'
|
|
7
7
|
|
|
@@ -3,9 +3,9 @@ import { View } from 'react-native'
|
|
|
3
3
|
import { DeviceRow } from './DeviceRow'
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof DeviceRow> = {
|
|
6
|
-
title: 'Shell/DeviceRow',
|
|
6
|
+
title: 'Shell/Workout/DeviceRow',
|
|
7
7
|
component: DeviceRow,
|
|
8
|
-
tags: ['autodocs'],
|
|
8
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
9
9
|
args: {
|
|
10
10
|
device: { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
11
11
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { View, Pressable, type ViewProps } from 'react-native'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { Indicator, type IndicatorColor } from '
|
|
4
|
-
import { Typography } from '
|
|
2
|
+
import { cn } from '../../../utils/cn'
|
|
3
|
+
import { Indicator, type IndicatorColor } from '../../ui/indicator'
|
|
4
|
+
import { Typography } from '../../custom/Typography'
|
|
5
5
|
|
|
6
6
|
export type DeviceRowState = 'connected' | 'available' | 'degraded' | 'lost'
|
|
7
7
|
|
package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx}
RENAMED
|
@@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { SessionStatePill } from './SessionStatePill'
|
|
3
3
|
|
|
4
4
|
const meta: Meta<typeof SessionStatePill> = {
|
|
5
|
-
title: 'Shell/SessionStatePill',
|
|
5
|
+
title: 'Shell/Workout/SessionStatePill',
|
|
6
6
|
component: SessionStatePill,
|
|
7
|
-
tags: ['autodocs'],
|
|
7
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
8
8
|
args: { state: 'live' },
|
|
9
9
|
argTypes: {
|
|
10
10
|
state: { control: 'select', options: ['live', 'rest', 'idle'] },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { View, type ViewProps } from 'react-native'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { Indicator, type IndicatorColor, type IndicatorPulse } from '
|
|
4
|
-
import { Typography, type TypographyColor } from '
|
|
2
|
+
import { cn } from '../../../utils/cn'
|
|
3
|
+
import { Indicator, type IndicatorColor, type IndicatorPulse } from '../../ui/indicator'
|
|
4
|
+
import { Typography, type TypographyColor } from '../../custom/Typography'
|
|
5
5
|
|
|
6
6
|
export type SessionState = 'live' | 'rest' | 'idle'
|
|
7
7
|
|
|
@@ -32,6 +32,8 @@ const stateConfig: Record<
|
|
|
32
32
|
* The global session-state readout (dot + label). Shared: the top bar's status
|
|
33
33
|
* and the Live-view header both use it (this is the ledger's "StatusPill",
|
|
34
34
|
* Family C — built once). S1 · SessionStatePill.
|
|
35
|
+
*
|
|
36
|
+
* @deprecated Use `<Pill tone="…" leading="dot">` — removed after AW-127 consumer migration.
|
|
35
37
|
*/
|
|
36
38
|
export function SessionStatePill({ state, label, className, ...props }: SessionStatePillProps) {
|
|
37
39
|
const cfg = stateConfig[state]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { WorkoutShell } from './WorkoutShell'
|
|
4
|
+
import { Surface } from '../../ui/surface'
|
|
5
|
+
import { Typography } from '../../custom/Typography'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `Pages/WorkoutShell` — the workout wall-dashboard chrome as one registered surface.
|
|
9
|
+
* Composes the generic AppShell and fills its slots with the workout's own chrome, so
|
|
10
|
+
* the dashboard app mounts a single component.
|
|
11
|
+
*/
|
|
12
|
+
const meta: Meta<typeof WorkoutShell> = {
|
|
13
|
+
title: 'Pages/WorkoutShell',
|
|
14
|
+
component: WorkoutShell,
|
|
15
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'fullscreen',
|
|
18
|
+
docs: {
|
|
19
|
+
description: {
|
|
20
|
+
component:
|
|
21
|
+
'**Page shell (workout app).** Composes ' +
|
|
22
|
+
'[AppShell](?path=/docs/pages-appshell--docs) and fills its top-bar slot with ' +
|
|
23
|
+
'[WorkoutTopBar](?path=/docs/shell-workout-workouttopbar--docs) — which pulls in ' +
|
|
24
|
+
'DeviceMenu, DeviceRow, DeviceIndicator and SessionStatePill — and its nav slot with ' +
|
|
25
|
+
'the four workout categories. Mount this instead of re-assembling the chrome per ' +
|
|
26
|
+
'view.\n\nThis is the AW-132 shape: the generic shell knows nothing about devices or ' +
|
|
27
|
+
'session state; this component is the whole workout-specific surface.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
argTypes: {
|
|
32
|
+
state: { control: 'select', options: ['live', 'rest', 'idle'] },
|
|
33
|
+
activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
|
|
34
|
+
liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
|
|
35
|
+
onNavigate: { control: false },
|
|
36
|
+
onSelectDevice: { control: false },
|
|
37
|
+
},
|
|
38
|
+
decorators: [
|
|
39
|
+
(Story) => (
|
|
40
|
+
<Surface level="base" style={{ height: '100vh' as unknown as number }}>
|
|
41
|
+
<Story />
|
|
42
|
+
</Surface>
|
|
43
|
+
),
|
|
44
|
+
],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default meta
|
|
48
|
+
type Story = StoryObj<typeof WorkoutShell>
|
|
49
|
+
|
|
50
|
+
export const Default: Story = {
|
|
51
|
+
args: { activeKey: 'live', state: 'live' },
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const WithContent: Story = {
|
|
55
|
+
args: { activeKey: 'review', state: 'rest', liveKey: 'live' },
|
|
56
|
+
render: (args) => (
|
|
57
|
+
<WorkoutShell {...args}>
|
|
58
|
+
<View className="flex-1 gap-1.5 p-7">
|
|
59
|
+
<Typography variant="h4" color="primary">
|
|
60
|
+
Review
|
|
61
|
+
</Typography>
|
|
62
|
+
<Typography variant="body2" color="tertiary">
|
|
63
|
+
A page mounts its content here; the shell keeps the nav + top-bar chrome persistent.
|
|
64
|
+
</Typography>
|
|
65
|
+
</View>
|
|
66
|
+
</WorkoutShell>
|
|
67
|
+
),
|
|
68
|
+
parameters: {
|
|
69
|
+
docs: { description: { story: 'The shell with a page mounted in its content slot.' } },
|
|
70
|
+
},
|
|
71
|
+
}
|
|
@@ -2,11 +2,11 @@ import { describe, it, expect, vi } from 'vitest'
|
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
3
|
import { Text } from 'react-native'
|
|
4
4
|
import { axe } from 'jest-axe'
|
|
5
|
-
import { DashboardShell } from './
|
|
5
|
+
import { WorkoutShell, DashboardShell } from './WorkoutShell'
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('WorkoutShell', () => {
|
|
8
8
|
it('composes the SideNav rail and its categories', () => {
|
|
9
|
-
render(<
|
|
9
|
+
render(<WorkoutShell activeKey="live" />)
|
|
10
10
|
expect(screen.getByRole('tablist')).toBeInTheDocument()
|
|
11
11
|
;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
|
|
12
12
|
expect(screen.getByRole('tab', { name })).toBeInTheDocument()
|
|
@@ -14,15 +14,15 @@ describe('DashboardShell', () => {
|
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
it('renders the content-slot placeholder when no children are given', () => {
|
|
17
|
-
render(<
|
|
17
|
+
render(<WorkoutShell activeKey="live" />)
|
|
18
18
|
expect(screen.getByText('main content region')).toBeInTheDocument()
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
it('mounts children in the content slot and drops the placeholder', () => {
|
|
22
22
|
render(
|
|
23
|
-
<
|
|
23
|
+
<WorkoutShell activeKey="review">
|
|
24
24
|
<Text>my page body</Text>
|
|
25
|
-
</
|
|
25
|
+
</WorkoutShell>
|
|
26
26
|
)
|
|
27
27
|
expect(screen.getByText('my page body')).toBeInTheDocument()
|
|
28
28
|
expect(screen.queryByText('main content region')).not.toBeInTheDocument()
|
|
@@ -30,13 +30,24 @@ describe('DashboardShell', () => {
|
|
|
30
30
|
|
|
31
31
|
it('forwards nav taps through onNavigate', () => {
|
|
32
32
|
const onNavigate = vi.fn()
|
|
33
|
-
render(<
|
|
33
|
+
render(<WorkoutShell activeKey="live" onNavigate={onNavigate} />)
|
|
34
34
|
fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
|
|
35
35
|
expect(onNavigate).toHaveBeenCalledWith('body')
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
+
it('fills the generic top-bar slot with the workout chrome', () => {
|
|
39
|
+
render(<WorkoutShell activeKey="live" state="rest" />)
|
|
40
|
+
expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
|
|
41
|
+
expect(screen.getByText('REST')).toBeInTheDocument()
|
|
42
|
+
expect(screen.getByRole('button', { name: 'Devices' })).toBeInTheDocument()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('still exports the pre-AW-132 DashboardShell name', () => {
|
|
46
|
+
expect(DashboardShell).toBe(WorkoutShell)
|
|
47
|
+
})
|
|
48
|
+
|
|
38
49
|
it('has no a11y violations', async () => {
|
|
39
|
-
const { container } = render(<
|
|
50
|
+
const { container } = render(<WorkoutShell activeKey="live" />)
|
|
40
51
|
expect(await axe(container)).toHaveNoViolations()
|
|
41
52
|
})
|
|
42
53
|
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { AppShell } from '../AppShell'
|
|
3
|
+
import { type SideNavItem } from '../SideNav'
|
|
4
|
+
import { WorkoutTopBar } from './WorkoutTopBar'
|
|
5
|
+
import { workoutNavItems } from './workoutNavItems'
|
|
6
|
+
import { type Device } from './DeviceRow'
|
|
7
|
+
import { type SessionState } from './SessionStatePill'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `WorkoutShell` — the workout wall-dashboard chrome. Composes the generic
|
|
11
|
+
* {@link AppShell} and fills its top-bar slot with the workout's own
|
|
12
|
+
* {@link WorkoutTopBar} (session state + device menu) and its nav slot with the
|
|
13
|
+
* four workout categories. The generic shell knows none of this; this component
|
|
14
|
+
* is the whole workout-specific surface. Driven entirely by props.
|
|
15
|
+
*/
|
|
16
|
+
export interface WorkoutShellProps {
|
|
17
|
+
/** Active nav category key. */
|
|
18
|
+
activeKey?: string
|
|
19
|
+
/** Nav categories, top → bottom. Defaults to the four workout categories. */
|
|
20
|
+
navItems?: SideNavItem[]
|
|
21
|
+
/** A category with off-view live activity → a quiet cue on that item. */
|
|
22
|
+
liveKey?: string | null
|
|
23
|
+
/** Global session state → the TopBar status pill. */
|
|
24
|
+
state?: SessionState
|
|
25
|
+
/** Connected devices → the TopBar connection glyph + dropdown. */
|
|
26
|
+
devices?: Device[]
|
|
27
|
+
/** TopBar brand subtitle. */
|
|
28
|
+
subtitle?: string
|
|
29
|
+
onNavigate?: (key: string) => void
|
|
30
|
+
onSelectDevice?: (device: Device) => void
|
|
31
|
+
/** Main content region. A placeholder renders when omitted. */
|
|
32
|
+
children?: ReactNode
|
|
33
|
+
className?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEMO_DEVICES: Device[] = [
|
|
37
|
+
{ id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
38
|
+
{ id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
export function WorkoutShell({
|
|
42
|
+
activeKey = 'live',
|
|
43
|
+
navItems = workoutNavItems,
|
|
44
|
+
liveKey = null,
|
|
45
|
+
state = 'live',
|
|
46
|
+
devices = DEMO_DEVICES,
|
|
47
|
+
subtitle = 'wall dashboard',
|
|
48
|
+
onNavigate,
|
|
49
|
+
onSelectDevice,
|
|
50
|
+
children,
|
|
51
|
+
className,
|
|
52
|
+
}: WorkoutShellProps) {
|
|
53
|
+
return (
|
|
54
|
+
<AppShell
|
|
55
|
+
className={className}
|
|
56
|
+
navItems={navItems}
|
|
57
|
+
activeKey={activeKey}
|
|
58
|
+
liveKey={liveKey}
|
|
59
|
+
onNavigate={onNavigate}
|
|
60
|
+
topBar={
|
|
61
|
+
<WorkoutTopBar
|
|
62
|
+
state={state}
|
|
63
|
+
devices={devices}
|
|
64
|
+
subtitle={subtitle}
|
|
65
|
+
onSelectDevice={onSelectDevice}
|
|
66
|
+
/>
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</AppShell>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @deprecated Renamed to `WorkoutShell` — same props (AW-132). Removed after consumer migration. */
|
|
75
|
+
export const DashboardShell = WorkoutShell
|
|
76
|
+
/** @deprecated Renamed to `WorkoutShellProps` (AW-132). */
|
|
77
|
+
export type DashboardShellProps = WorkoutShellProps
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { WorkoutTopBar } from './WorkoutTopBar'
|
|
3
|
+
import { type Device } from './DeviceRow'
|
|
4
|
+
|
|
5
|
+
const DEVICES: Device[] = [
|
|
6
|
+
{ id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
7
|
+
{ id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
|
|
8
|
+
{ id: 'Voltra-77E0', nickname: 'Spare', slot: null, state: 'available' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof WorkoutTopBar> = {
|
|
12
|
+
title: 'Shell/Workout/WorkoutTopBar',
|
|
13
|
+
component: WorkoutTopBar,
|
|
14
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
15
|
+
args: { state: 'live', devices: DEVICES },
|
|
16
|
+
argTypes: {
|
|
17
|
+
state: { control: 'select', options: ['live', 'rest', 'idle'] },
|
|
18
|
+
devices: { control: 'object' },
|
|
19
|
+
subtitle: { control: 'text' },
|
|
20
|
+
showSubtitle: { control: 'boolean' },
|
|
21
|
+
showClock: { control: 'boolean' },
|
|
22
|
+
time: { control: false },
|
|
23
|
+
onSelectDevice: { control: false },
|
|
24
|
+
},
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: 'fullscreen',
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component:
|
|
30
|
+
'**Organism (workout app).** The generic ' +
|
|
31
|
+
"[TopBar](?path=/docs/shell-topbar--docs) with the workout's own chrome in its " +
|
|
32
|
+
'`trailing` slot: [SessionStatePill](?path=/docs/shell-workout-sessionstatepill--docs) ' +
|
|
33
|
+
'then [DeviceMenu](?path=/docs/shell-workout-devicemenu--docs). The bar adds the ' +
|
|
34
|
+
'dividers and the edge-pinned clock; this component only supplies the items ' +
|
|
35
|
+
'(AW-132).\n\n' +
|
|
36
|
+
'**Try it:** use the **Controls** to change `state`, edit `devices` (set one to ' +
|
|
37
|
+
'`lost` to see the fault), or toggle `showSubtitle` / `showClock`. **Resize the ' +
|
|
38
|
+
'canvas** to watch the container-responsive collapse (SIZE-D01). Click the device ' +
|
|
39
|
+
'glyph to open the menu.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
export default meta
|
|
45
|
+
type Story = StoryObj<typeof WorkoutTopBar>
|
|
46
|
+
|
|
47
|
+
export const Default: Story = {}
|
|
48
|
+
|
|
49
|
+
/** A lost device — the glyph alone carries the fault. */
|
|
50
|
+
export const DeviceLost: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
state: 'rest',
|
|
53
|
+
devices: [
|
|
54
|
+
{ id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
55
|
+
{ id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'lost' },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { WorkoutTopBar } from './WorkoutTopBar'
|
|
5
|
+
import { type Device } from './DeviceRow'
|
|
6
|
+
|
|
7
|
+
const devices: Device[] = [
|
|
8
|
+
{ id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
9
|
+
{ id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const AT_1612 = new Date(2024, 0, 1, 16, 12) // local 16:12
|
|
13
|
+
|
|
14
|
+
describe('WorkoutTopBar', () => {
|
|
15
|
+
it('renders brand, session state, the device glyph and a 24h clock', () => {
|
|
16
|
+
render(<WorkoutTopBar state="live" devices={devices} time={AT_1612} showClock />)
|
|
17
|
+
expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
|
|
18
|
+
expect(screen.getByText('LIVE')).toBeInTheDocument()
|
|
19
|
+
expect(screen.getByRole('button', { name: 'Devices' })).toBeInTheDocument()
|
|
20
|
+
expect(screen.getByText('16:12')).toBeInTheDocument()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('hides the clock when showClock is false', () => {
|
|
24
|
+
render(<WorkoutTopBar state="live" devices={devices} time={AT_1612} showClock={false} />)
|
|
25
|
+
expect(screen.queryByText('16:12')).not.toBeInTheDocument()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('hides the subtitle when showSubtitle is false', () => {
|
|
29
|
+
render(<WorkoutTopBar state="rest" devices={devices} showSubtitle={false} />)
|
|
30
|
+
expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('has no accessibility violations', async () => {
|
|
34
|
+
const { container } = render(
|
|
35
|
+
<WorkoutTopBar state="live" devices={devices} time={AT_1612} showClock />
|
|
36
|
+
)
|
|
37
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
38
|
+
})
|
|
39
|
+
})
|