@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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View, type ViewProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { resolveColor } from '../../../theme/resolve-color'
|
|
5
|
+
|
|
6
|
+
/** Shortest bar drawn, so an all-but-empty series still reads as a series. */
|
|
7
|
+
const MIN_BAR_HEIGHT = 2
|
|
8
|
+
|
|
9
|
+
export interface SparkBarsProps extends Omit<ViewProps, 'accessibilityLabel'> {
|
|
10
|
+
/** The series, oldest first. Negative values draw in `negativeColor`. */
|
|
11
|
+
values: number[]
|
|
12
|
+
/** Overall bar-field height in px. */
|
|
13
|
+
height?: number
|
|
14
|
+
/** Width of a single bar in px. */
|
|
15
|
+
barWidth?: number
|
|
16
|
+
/** Gap between bars in px. */
|
|
17
|
+
gap?: number
|
|
18
|
+
/** Keep only the last N values — a sparkline is a recent-history glance, not an archive. */
|
|
19
|
+
maxBars?: number
|
|
20
|
+
/** Fill for non-negative bars. Defaults to `result-improve`. */
|
|
21
|
+
color?: string
|
|
22
|
+
/** Fill for negative bars. Defaults to `result-degrade`. */
|
|
23
|
+
negativeColor?: string
|
|
24
|
+
/** Screen-reader description. Defaults to a bar count. */
|
|
25
|
+
label?: string
|
|
26
|
+
className?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SparkBars — a tiny bar-mark sparkline for a signed series.
|
|
31
|
+
*
|
|
32
|
+
* The bar-mark counterpart to {@link Sparkline}: where a line reads a
|
|
33
|
+
* *trajectory*, bars read *discrete per-period magnitude*, which is what
|
|
34
|
+
* per-session activity and per-session net growth actually are. Bars are scaled
|
|
35
|
+
* by absolute value so a negative period is as tall as an equally large
|
|
36
|
+
* positive one, and tinted with `negativeColor` to carry the sign.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* <SparkBars values={[3, 8, -2, 14]} height={22} />
|
|
40
|
+
*/
|
|
41
|
+
export function SparkBars({
|
|
42
|
+
values,
|
|
43
|
+
height = 22,
|
|
44
|
+
barWidth = 3,
|
|
45
|
+
gap = 2,
|
|
46
|
+
maxBars = 24,
|
|
47
|
+
color,
|
|
48
|
+
negativeColor,
|
|
49
|
+
label,
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}: SparkBarsProps) {
|
|
53
|
+
const shown = values.slice(-maxBars)
|
|
54
|
+
// A per-period delta going up or down is `result-*` (a change in a measurement),
|
|
55
|
+
// not `status-*` (a thing being broken). See TOKENS.md §1.
|
|
56
|
+
const positiveFill = color ?? resolveColor('result-improve')
|
|
57
|
+
const negativeFill = negativeColor ?? resolveColor('result-degrade')
|
|
58
|
+
// Scaling on magnitude keeps a -100 as tall as a +100; sign is carried by fill.
|
|
59
|
+
const peak = Math.max(1, ...shown.map((v) => Math.abs(v)))
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View
|
|
63
|
+
style={{ height, gap }}
|
|
64
|
+
className={cn('flex-row items-end', className)}
|
|
65
|
+
accessibilityRole="image"
|
|
66
|
+
accessibilityLabel={label ?? `Bar sparkline with ${shown.length} values`}
|
|
67
|
+
testID="spark-bars"
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
{shown.map((v, i) => (
|
|
71
|
+
<View
|
|
72
|
+
key={i}
|
|
73
|
+
style={{
|
|
74
|
+
height: Math.max(MIN_BAR_HEIGHT, Math.round((Math.abs(v) / peak) * height)),
|
|
75
|
+
width: barWidth,
|
|
76
|
+
backgroundColor: v < 0 ? negativeFill : positiveFill,
|
|
77
|
+
}}
|
|
78
|
+
className="rounded-t-[1px]"
|
|
79
|
+
accessibilityElementsHidden
|
|
80
|
+
testID={`spark-bars-bar-${i}`}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</View>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -5,7 +5,16 @@ import { Stepper, Step, StepIndicator, StepLabel, StepContent } from './Stepper'
|
|
|
5
5
|
const meta: Meta<typeof Stepper> = {
|
|
6
6
|
title: 'Components/Molecules/Stepper',
|
|
7
7
|
component: Stepper,
|
|
8
|
-
tags: ['autodocs'],
|
|
8
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'Composes RN primitives only. The five parts share step index and orientation through ' +
|
|
14
|
+
'a context, the same shape as **Tabs**.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
9
18
|
argTypes: {
|
|
10
19
|
activeStep: {
|
|
11
20
|
control: { type: 'number', min: 0, max: 4 },
|
|
@@ -124,9 +133,7 @@ export const VerticalWithContent: Story = {
|
|
|
124
133
|
<StepIndicator />
|
|
125
134
|
<StepLabel>Create Account</StepLabel>
|
|
126
135
|
<StepContent>
|
|
127
|
-
<Text className="text-text-secondary text-sm">
|
|
128
|
-
Account creation is complete.
|
|
129
|
-
</Text>
|
|
136
|
+
<Text className="text-text-secondary text-sm">Account creation is complete.</Text>
|
|
130
137
|
</StepContent>
|
|
131
138
|
</Step>
|
|
132
139
|
<Step>
|
|
@@ -147,9 +154,7 @@ export const VerticalWithContent: Story = {
|
|
|
147
154
|
<StepIndicator />
|
|
148
155
|
<StepLabel>Configure Settings</StepLabel>
|
|
149
156
|
<StepContent>
|
|
150
|
-
<Text className="text-text-secondary text-sm">
|
|
151
|
-
Adjust your preferences.
|
|
152
|
-
</Text>
|
|
157
|
+
<Text className="text-text-secondary text-sm">Adjust your preferences.</Text>
|
|
153
158
|
</StepContent>
|
|
154
159
|
</Step>
|
|
155
160
|
</Stepper>
|
|
@@ -221,9 +226,7 @@ export const ActiveStepProgression: Story = {
|
|
|
221
226
|
<View style={{ gap: 24 }}>
|
|
222
227
|
{[0, 1, 2, 3].map((activeStep) => (
|
|
223
228
|
<View key={activeStep}>
|
|
224
|
-
<Text className="text-text-secondary text-xs mb-2">
|
|
225
|
-
Active step: {activeStep}
|
|
226
|
-
</Text>
|
|
229
|
+
<Text className="text-text-secondary text-xs mb-2">Active step: {activeStep}</Text>
|
|
227
230
|
<Stepper activeStep={activeStep} orientation="horizontal">
|
|
228
231
|
<Step>
|
|
229
232
|
<StepIndicator />
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import { render, screen } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
|
-
import {
|
|
5
|
-
Stepper,
|
|
6
|
-
Step,
|
|
7
|
-
StepIndicator,
|
|
8
|
-
StepLabel,
|
|
9
|
-
StepContent,
|
|
10
|
-
} from './Stepper'
|
|
4
|
+
import { Stepper, Step, StepIndicator, StepLabel, StepContent } from './Stepper'
|
|
11
5
|
|
|
12
6
|
function renderStepper(activeStep = 1) {
|
|
13
7
|
return render(
|
|
@@ -65,9 +65,7 @@ export function Stepper({
|
|
|
65
65
|
return (
|
|
66
66
|
<>
|
|
67
67
|
{React.cloneElement(child as React.ReactElement<any>, { index })}
|
|
68
|
-
{index < React.Children.count(children) - 1 &&
|
|
69
|
-
<StepConnector index={index} />
|
|
70
|
-
)}
|
|
68
|
+
{index < React.Children.count(children) - 1 && <StepConnector index={index} />}
|
|
71
69
|
</>
|
|
72
70
|
)
|
|
73
71
|
}
|
|
@@ -88,22 +86,12 @@ function StepConnector({ index }: StepConnectorProps) {
|
|
|
88
86
|
|
|
89
87
|
if (orientation === 'horizontal') {
|
|
90
88
|
return (
|
|
91
|
-
<View
|
|
92
|
-
className={cn(
|
|
93
|
-
'flex-1 h-0.5 mx-2',
|
|
94
|
-
isCompleted ? 'bg-brand-primary' : 'bg-hairline'
|
|
95
|
-
)}
|
|
96
|
-
/>
|
|
89
|
+
<View className={cn('flex-1 h-0.5 mx-2', isCompleted ? 'bg-brand-primary' : 'bg-hairline')} />
|
|
97
90
|
)
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
return (
|
|
101
|
-
<View
|
|
102
|
-
className={cn(
|
|
103
|
-
'w-0.5 h-8 ml-4 my-1',
|
|
104
|
-
isCompleted ? 'bg-brand-primary' : 'bg-hairline'
|
|
105
|
-
)}
|
|
106
|
-
/>
|
|
94
|
+
<View className={cn('w-0.5 h-8 ml-4 my-1', isCompleted ? 'bg-brand-primary' : 'bg-hairline')} />
|
|
107
95
|
)
|
|
108
96
|
}
|
|
109
97
|
|
|
@@ -123,10 +111,8 @@ export interface StepProps {
|
|
|
123
111
|
export function Step({ index = 0, status, className, children }: StepProps) {
|
|
124
112
|
const { activeStep, orientation } = useContext(StepperContext)
|
|
125
113
|
|
|
126
|
-
const derivedStatus: StepStatus =
|
|
127
|
-
index < activeStep ? 'completed' :
|
|
128
|
-
index === activeStep ? 'active' : 'upcoming'
|
|
129
|
-
)
|
|
114
|
+
const derivedStatus: StepStatus =
|
|
115
|
+
status || (index < activeStep ? 'completed' : index === activeStep ? 'active' : 'upcoming')
|
|
130
116
|
|
|
131
117
|
return (
|
|
132
118
|
<View
|
|
@@ -216,10 +202,13 @@ export function StepLabel({ status = 'upcoming', className, children }: StepLabe
|
|
|
216
202
|
className={cn(
|
|
217
203
|
'text-sm font-medium',
|
|
218
204
|
orientation === 'horizontal' ? 'mt-2' : 'ml-3',
|
|
219
|
-
status === 'active'
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
205
|
+
status === 'active'
|
|
206
|
+
? 'text-text-primary'
|
|
207
|
+
: status === 'completed'
|
|
208
|
+
? 'text-brand-primary'
|
|
209
|
+
: status === 'error'
|
|
210
|
+
? 'text-status-error'
|
|
211
|
+
: 'text-text-secondary',
|
|
223
212
|
className
|
|
224
213
|
)}
|
|
225
214
|
>
|
|
@@ -242,9 +231,5 @@ export interface StepContentProps {
|
|
|
242
231
|
export function StepContent({ status = 'upcoming', className, children }: StepContentProps) {
|
|
243
232
|
if (status !== 'active') return null
|
|
244
233
|
|
|
245
|
-
return (
|
|
246
|
-
<View className={cn('ml-11 mt-2 pb-4', className)}>
|
|
247
|
-
{children}
|
|
248
|
-
</View>
|
|
249
|
-
)
|
|
234
|
+
return <View className={cn('ml-11 mt-2 pb-4', className)}>{children}</View>
|
|
250
235
|
}
|
|
@@ -151,3 +151,54 @@ export function InfoIcon(props: IconProps) {
|
|
|
151
151
|
</SvgIcon>
|
|
152
152
|
)
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// AW-132 · App brand marks — the mark half of a `BrandLockup` for each app that
|
|
157
|
+
// is likely to mount the shell. Sized 14 by default, like `VoltrasMark`.
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
/** Headphones glyph (mirrors lucide-react `Headphones`). Audiobook brand mark. */
|
|
161
|
+
export function HeadphonesIcon({ size = 14, ...props }: IconProps) {
|
|
162
|
+
return (
|
|
163
|
+
<SvgIcon size={size} {...props}>
|
|
164
|
+
<path d="M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5a9 9 0 0 1 18 0v5a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3" />
|
|
165
|
+
</SvgIcon>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Kanban board glyph (mirrors lucide-react `SquareKanban`). Active-work brand mark. */
|
|
170
|
+
export function KanbanIcon({ size = 14, ...props }: IconProps) {
|
|
171
|
+
return (
|
|
172
|
+
<SvgIcon size={size} {...props}>
|
|
173
|
+
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
174
|
+
<path d="M8 7v7" />
|
|
175
|
+
<path d="M12 7v4" />
|
|
176
|
+
<path d="M16 7v9" />
|
|
177
|
+
</SvgIcon>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Robot glyph (mirrors lucide-react `Bot`). Agent-dashboard brand mark. */
|
|
182
|
+
export function BotIcon({ size = 14, ...props }: IconProps) {
|
|
183
|
+
return (
|
|
184
|
+
<SvgIcon size={size} {...props}>
|
|
185
|
+
<path d="M12 8V4H8" />
|
|
186
|
+
<rect width="16" height="12" x="4" y="8" rx="2" />
|
|
187
|
+
<path d="M2 14h2" />
|
|
188
|
+
<path d="M20 14h2" />
|
|
189
|
+
<path d="M15 13v2" />
|
|
190
|
+
<path d="M9 13v2" />
|
|
191
|
+
</SvgIcon>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Brain glyph (mirrors lucide-react `Brain`). Brain-app brand mark. */
|
|
196
|
+
export function BrainIcon({ size = 14, ...props }: IconProps) {
|
|
197
|
+
return (
|
|
198
|
+
<SvgIcon size={size} {...props}>
|
|
199
|
+
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" />
|
|
200
|
+
<path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z" />
|
|
201
|
+
<path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
|
|
202
|
+
</SvgIcon>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import { Surface } from '../ui/surface'
|
|
4
|
+
import { Pill } from '../ui/pill'
|
|
5
|
+
import { Typography } from '../custom/Typography'
|
|
6
|
+
import { BotIcon, BrainIcon, HeadphonesIcon, LayersIcon, StarIcon } from '../icons'
|
|
7
|
+
import { AppShell } from './AppShell'
|
|
8
|
+
import { brandKeys } from './brands'
|
|
9
|
+
import { type SideNavItem } from './SideNav'
|
|
10
|
+
|
|
11
|
+
const brainNav: SideNavItem[] = [
|
|
12
|
+
{ key: 'notes', label: 'Notes', icon: <LayersIcon size={20} color="currentColor" /> },
|
|
13
|
+
{ key: 'graph', label: 'Graph', icon: <BrainIcon size={20} color="currentColor" /> },
|
|
14
|
+
{ key: 'agents', label: 'Agents', icon: <BotIcon size={20} color="currentColor" /> },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
const audiobookNav: SideNavItem[] = [
|
|
18
|
+
{ key: 'library', label: 'Library', icon: <HeadphonesIcon size={20} color="currentColor" /> },
|
|
19
|
+
{ key: 'saved', label: 'Saved', icon: <StarIcon size={20} color="currentColor" /> },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
function Page({ title }: { title: string }) {
|
|
23
|
+
return (
|
|
24
|
+
<View className="flex-1 gap-1.5 p-7">
|
|
25
|
+
<Typography variant="h4" color="primary">
|
|
26
|
+
{title}
|
|
27
|
+
</Typography>
|
|
28
|
+
<Typography variant="body2" color="tertiary">
|
|
29
|
+
A page mounts its content here; the shell keeps the nav + top-bar chrome persistent.
|
|
30
|
+
</Typography>
|
|
31
|
+
</View>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `Pages/AppShell` — the generic dashboard chrome as one registered surface.
|
|
37
|
+
* The app supplies its brand, its nav categories and its own top-bar chrome.
|
|
38
|
+
*/
|
|
39
|
+
const meta: Meta<typeof AppShell> = {
|
|
40
|
+
title: 'Pages/AppShell',
|
|
41
|
+
component: AppShell,
|
|
42
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
43
|
+
parameters: {
|
|
44
|
+
layout: 'fullscreen',
|
|
45
|
+
docs: {
|
|
46
|
+
description: {
|
|
47
|
+
component:
|
|
48
|
+
'**Page shell (generic).** Composes ' +
|
|
49
|
+
'[SideNav](?path=/docs/shell-sidenav--docs) + ' +
|
|
50
|
+
'[TopBar](?path=/docs/shell-topbar--docs) over a `children` content slot.\n\n' +
|
|
51
|
+
'**Composition (AW-132).** It knows nothing about any one app. Chrome arrives ' +
|
|
52
|
+
'through `topBarTrailing`; categories through `navItems`; `topBar` and `nav` ' +
|
|
53
|
+
'replace those regions outright. An app builds its own shell by composing this one ' +
|
|
54
|
+
'— see [WorkoutShell](?path=/docs/pages-workoutshell--docs).',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
argTypes: {
|
|
59
|
+
brand: { control: 'select', options: brandKeys },
|
|
60
|
+
onNavigate: { control: false },
|
|
61
|
+
topBar: { control: false },
|
|
62
|
+
nav: { control: false },
|
|
63
|
+
topBarTrailing: { control: false },
|
|
64
|
+
navItems: { control: false },
|
|
65
|
+
},
|
|
66
|
+
decorators: [
|
|
67
|
+
(Story) => (
|
|
68
|
+
<Surface level="base" className="min-h-screen">
|
|
69
|
+
<Story />
|
|
70
|
+
</Surface>
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default meta
|
|
76
|
+
type Story = StoryObj<typeof AppShell>
|
|
77
|
+
|
|
78
|
+
/** No app chrome at all — the frame on its own. */
|
|
79
|
+
export const Default: Story = {
|
|
80
|
+
args: { brand: 'voltras', navItems: brainNav, activeKey: 'notes' },
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The brain app: its own brand, categories, top-bar chrome and page. */
|
|
84
|
+
export const BrainApp: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
brand: 'brain',
|
|
87
|
+
navItems: brainNav,
|
|
88
|
+
activeKey: 'graph',
|
|
89
|
+
topBarTrailing: [
|
|
90
|
+
<Pill key="index" tone="success" leading="dot" size="xs">
|
|
91
|
+
indexed
|
|
92
|
+
</Pill>,
|
|
93
|
+
<Pill key="vaults" tone="neutral" size="xs">
|
|
94
|
+
4 vaults
|
|
95
|
+
</Pill>,
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
render: (args) => (
|
|
99
|
+
<AppShell {...args}>
|
|
100
|
+
<Page title="Graph" />
|
|
101
|
+
</AppShell>
|
|
102
|
+
),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The audiobook app in the same frame — different brand, categories and chrome. */
|
|
106
|
+
export const AudiobookApp: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
brand: 'audiobook',
|
|
109
|
+
navItems: audiobookNav,
|
|
110
|
+
activeKey: 'library',
|
|
111
|
+
liveKey: 'saved',
|
|
112
|
+
topBarTrailing: (
|
|
113
|
+
<Pill tone="brand-secondary" leading="dot" size="xs">
|
|
114
|
+
3 downloading
|
|
115
|
+
</Pill>
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
render: (args) => (
|
|
119
|
+
<AppShell {...args}>
|
|
120
|
+
<Page title="Library" />
|
|
121
|
+
</AppShell>
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Both slots replaced: the app brings its own bar and its own rail. */
|
|
126
|
+
export const ReplacedRegions: Story = {
|
|
127
|
+
args: { navItems: brainNav, activeKey: 'notes' },
|
|
128
|
+
render: (args) => (
|
|
129
|
+
<AppShell
|
|
130
|
+
{...args}
|
|
131
|
+
topBar={
|
|
132
|
+
<Surface level="elevated" className="h-[46px] justify-center border-b border-hairline px-4">
|
|
133
|
+
<Text className="font-heading text-sm text-text-secondary">an app-owned bar</Text>
|
|
134
|
+
</Surface>
|
|
135
|
+
}
|
|
136
|
+
nav={
|
|
137
|
+
<Surface
|
|
138
|
+
level="base"
|
|
139
|
+
className="w-[60px] items-center justify-center border-r border-hairline"
|
|
140
|
+
>
|
|
141
|
+
<Typography variant="microLabel" color="tertiary">
|
|
142
|
+
rail
|
|
143
|
+
</Typography>
|
|
144
|
+
</Surface>
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
<Page title="Anything" />
|
|
148
|
+
</AppShell>
|
|
149
|
+
),
|
|
150
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { Text } from 'react-native'
|
|
4
|
+
import { axe } from 'jest-axe'
|
|
5
|
+
import { AppShell } from './AppShell'
|
|
6
|
+
import { type SideNavItem } from './SideNav'
|
|
7
|
+
|
|
8
|
+
const navItems: SideNavItem[] = [
|
|
9
|
+
{ key: 'notes', label: 'Notes', icon: null },
|
|
10
|
+
{ key: 'graph', label: 'Graph', icon: null },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
describe('AppShell', () => {
|
|
14
|
+
it('composes the SideNav rail from the supplied categories', () => {
|
|
15
|
+
render(<AppShell navItems={navItems} activeKey="notes" />)
|
|
16
|
+
expect(screen.getByRole('tablist')).toBeInTheDocument()
|
|
17
|
+
;['Notes', 'Graph'].forEach((name) => {
|
|
18
|
+
expect(screen.getByRole('tab', { name })).toBeInTheDocument()
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('renders the content-slot placeholder when no children are given', () => {
|
|
23
|
+
render(<AppShell navItems={navItems} activeKey="notes" />)
|
|
24
|
+
expect(screen.getByText('main content region')).toBeInTheDocument()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('mounts children in the content slot and drops the placeholder', () => {
|
|
28
|
+
render(
|
|
29
|
+
<AppShell navItems={navItems} activeKey="graph">
|
|
30
|
+
<Text>my page body</Text>
|
|
31
|
+
</AppShell>
|
|
32
|
+
)
|
|
33
|
+
expect(screen.getByText('my page body')).toBeInTheDocument()
|
|
34
|
+
expect(screen.queryByText('main content region')).not.toBeInTheDocument()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('forwards nav taps through onNavigate', () => {
|
|
38
|
+
const onNavigate = vi.fn()
|
|
39
|
+
render(<AppShell navItems={navItems} activeKey="notes" onNavigate={onNavigate} />)
|
|
40
|
+
fireEvent.click(screen.getByRole('tab', { name: 'Graph' }))
|
|
41
|
+
expect(onNavigate).toHaveBeenCalledWith('graph')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders app chrome in the top bar without knowing what it is', () => {
|
|
45
|
+
render(<AppShell navItems={navItems} activeKey="notes" topBarTrailing={<Text>3 jobs</Text>} />)
|
|
46
|
+
expect(screen.getByText('3 jobs')).toBeInTheDocument()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('takes a whole replacement top bar and nav', () => {
|
|
50
|
+
render(
|
|
51
|
+
<AppShell topBar={<Text>my own bar</Text>} nav={<Text>my own rail</Text>}>
|
|
52
|
+
<Text>body</Text>
|
|
53
|
+
</AppShell>
|
|
54
|
+
)
|
|
55
|
+
expect(screen.getByText('my own bar')).toBeInTheDocument()
|
|
56
|
+
expect(screen.getByText('my own rail')).toBeInTheDocument()
|
|
57
|
+
expect(screen.queryByText('VOLTRAS')).not.toBeInTheDocument()
|
|
58
|
+
expect(screen.queryByRole('tablist')).not.toBeInTheDocument()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('carries the brand through to the top bar', () => {
|
|
62
|
+
render(<AppShell brand="agents" navItems={navItems} activeKey="notes" />)
|
|
63
|
+
expect(screen.getByText('AGENTS')).toBeInTheDocument()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('has no a11y violations', async () => {
|
|
67
|
+
const { container } = render(<AppShell navItems={navItems} activeKey="notes" />)
|
|
68
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Surface } from '../ui/surface'
|
|
5
|
+
import { SideNav, type SideNavItem } from './SideNav'
|
|
6
|
+
import { TopBar } from './TopBar'
|
|
7
|
+
import { brandPresets, type BrandKey } from './brands'
|
|
8
|
+
|
|
9
|
+
export interface AppShellProps {
|
|
10
|
+
/** Which app identity the default {@link TopBar} and nav accent render. */
|
|
11
|
+
brand?: BrandKey
|
|
12
|
+
/** Brand subtitle on the default top bar. */
|
|
13
|
+
subtitle?: string
|
|
14
|
+
/** App chrome for the default top bar's right cluster (divider-separated). */
|
|
15
|
+
topBarTrailing?: ReactNode | ReactNode[]
|
|
16
|
+
/** Replace the whole top bar. Wins over `brand` / `subtitle` / `topBarTrailing`. */
|
|
17
|
+
topBar?: ReactNode
|
|
18
|
+
/** Nav categories, top → bottom. */
|
|
19
|
+
navItems?: SideNavItem[]
|
|
20
|
+
/** Active nav category key. */
|
|
21
|
+
activeKey?: string
|
|
22
|
+
/** A category with off-view live activity → a quiet cue on that item. */
|
|
23
|
+
liveKey?: string | null
|
|
24
|
+
onNavigate?: (key: string) => void
|
|
25
|
+
/** Replace the whole left rail. Wins over `navItems` / `activeKey` / `liveKey`. */
|
|
26
|
+
nav?: ReactNode
|
|
27
|
+
/** Main content region. A placeholder renders when omitted. */
|
|
28
|
+
children?: ReactNode
|
|
29
|
+
className?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ContentPlaceholder() {
|
|
33
|
+
return (
|
|
34
|
+
<View className="flex-1 items-center justify-center">
|
|
35
|
+
<Text className="font-heading text-sm text-text-tertiary">main content region</Text>
|
|
36
|
+
</View>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `AppShell` — the generic dashboard chrome: a {@link TopBar} band over a
|
|
42
|
+
* {@link SideNav} rail and a `children` content region. It knows nothing about
|
|
43
|
+
* any one app: chrome arrives through the `topBarTrailing` slot (or the whole
|
|
44
|
+
* `topBar` / `nav` slots), and categories through `navItems`. An app builds its
|
|
45
|
+
* own shell by composing this one — see `shell/workout/WorkoutShell`.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* <AppShell brand="brain" navItems={brainNavItems} activeKey={key} onNavigate={go}>
|
|
49
|
+
* <BrainPage />
|
|
50
|
+
* </AppShell>
|
|
51
|
+
*/
|
|
52
|
+
export function AppShell({
|
|
53
|
+
brand = 'voltras',
|
|
54
|
+
subtitle,
|
|
55
|
+
topBarTrailing,
|
|
56
|
+
topBar,
|
|
57
|
+
navItems = [],
|
|
58
|
+
activeKey = '',
|
|
59
|
+
liveKey = null,
|
|
60
|
+
onNavigate,
|
|
61
|
+
nav,
|
|
62
|
+
children,
|
|
63
|
+
className,
|
|
64
|
+
}: AppShellProps) {
|
|
65
|
+
const { accentClassName, accentBarClassName } = brandPresets[brand]
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
// Column: the TopBar spans the FULL width across the top, and the SideNav sits BELOW it
|
|
69
|
+
// in the content row (not a full-height left rail). This keeps the brand/status band
|
|
70
|
+
// unbroken edge-to-edge and lets the nav align under it. The shell is the outermost
|
|
71
|
+
// Surface — it owns the base plane and seeds the on-surface colour context (mode) for
|
|
72
|
+
// the whole dashboard tree.
|
|
73
|
+
<Surface level="base" className={cn('flex-1', className)}>
|
|
74
|
+
{topBar ?? <TopBar brand={brand} subtitle={subtitle} trailing={topBarTrailing} />}
|
|
75
|
+
<View className="flex-1 flex-row">
|
|
76
|
+
{nav ?? (
|
|
77
|
+
<SideNav
|
|
78
|
+
items={navItems}
|
|
79
|
+
activeKey={activeKey}
|
|
80
|
+
liveKey={liveKey}
|
|
81
|
+
accentClassName={accentClassName}
|
|
82
|
+
accentBarClassName={accentBarClassName}
|
|
83
|
+
onNavigate={onNavigate}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
<View className="flex-1">{children ?? <ContentPlaceholder />}</View>
|
|
87
|
+
</View>
|
|
88
|
+
</Surface>
|
|
89
|
+
)
|
|
90
|
+
}
|