@titan-design/react-ui 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -47
- package/dist/bodymap.js +856 -736
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +858 -738
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1431 -237
- package/dist/index.d.ts +1431 -237
- package/dist/index.js +6506 -4781
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6280 -4642
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
- package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1919 -1937
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1927 -1945
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +225 -140
- package/dist/theme/index.d.ts +225 -140
- package/dist/theme/index.js +298 -319
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +283 -315
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +145 -24
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +145 -24
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +128 -11
- package/dist/theme/tokens.d.ts +128 -11
- package/dist/theme/tokens.js +126 -26
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +124 -27
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/audits/2026-09-08-critique-addenda.md +51 -0
- package/docs/audits/2026-09-08-library-critique.md +212 -0
- package/docs/library-roadmap.md +99 -0
- package/docs/render-testing-pattern.md +130 -0
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +3 -1
- package/src/arch/arch-graph.freshness.test.ts +52 -0
- package/src/arch/arch-graph.json +4989 -1583
- package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
- package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
- package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
- package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
- package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
- package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
- package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
- package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
- package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
- package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
- package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
- package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +182 -0
- package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
- package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
- package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
- package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
- package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
- package/src/components/custom/ActiveWork/README.md +305 -0
- package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
- package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
- package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
- package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
- package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
- package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
- package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
- package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
- package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
- package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
- package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
- package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
- package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
- package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
- package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
- package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
- package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
- package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
- package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
- package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
- package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
- package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
- package/src/components/custom/ActiveWork/format-time.ts +32 -0
- package/src/components/custom/ActiveWork/index.ts +19 -0
- package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
- package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
- package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
- package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +12 -6
- package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
- package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
- package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
- package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
- package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
- package/src/components/custom/Fatigue/README.md +40 -3
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
- package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
- package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
- package/src/components/custom/Fatigue/index.ts +17 -5
- package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
- package/src/components/custom/Fatigue/panel-layout.ts +185 -0
- package/src/components/custom/Gauge/Gauge.stories.tsx +6 -3
- package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.tsx +7 -2
- package/src/components/custom/Metric/Metric.stories.tsx +10 -1
- package/src/components/custom/Metric/Metric.test.tsx +1 -3
- package/src/components/custom/Metric/Metric.tsx +5 -20
- package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
- package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
- package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
- package/src/components/custom/Prose/README.md +58 -0
- package/src/components/custom/Prose/index.ts +1 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +55 -14
- package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
- package/src/components/custom/Scatter/Scatter.tsx +122 -18
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
- package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
- package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
- package/src/components/custom/Table/Table.stories.tsx +99 -16
- package/src/components/custom/Table/Table.test.tsx +273 -86
- package/src/components/custom/Table/Table.tsx +159 -67
- package/src/components/custom/Table/column-fit.test.ts +75 -0
- package/src/components/custom/Table/column-fit.ts +116 -0
- package/src/components/custom/Table/index.ts +9 -0
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
- package/src/components/custom/Treemap/Treemap.stories.tsx +15 -12
- package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +11 -3
- package/src/components/custom/Typography/Typography.stories.tsx +28 -1
- package/src/components/custom/Typography/Typography.test.tsx +34 -0
- package/src/components/custom/Typography/Typography.tsx +6 -0
- package/src/components/custom/Typography/index.ts +1 -8
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +11 -2
- package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
- package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
- package/src/components/custom/Workout/BaseBadge.tsx +6 -2
- package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
- package/src/components/custom/Workout/BodyMap.tsx +8 -4
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
- package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
- package/src/components/custom/Workout/DeviationBar.tsx +24 -16
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
- package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
- package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
- package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
- package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
- package/src/components/custom/Workout/InputBar.tsx +4 -2
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
- package/src/components/custom/Workout/IntensityBar.tsx +8 -3
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
- package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
- package/src/components/custom/Workout/MesoCard.tsx +14 -10
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
- package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -33
- package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
- package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +9 -4
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
- package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
- package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
- package/src/components/custom/Workout/PrBadge.tsx +13 -17
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +16 -3
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
- package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
- package/src/components/custom/Workout/README.md +68 -17
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
- package/src/components/custom/Workout/ReadinessCheck.tsx +22 -4
- package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
- package/src/components/custom/Workout/RestTimer.tsx +4 -2
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
- package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +21 -17
- package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SessionHeader.tsx +12 -12
- package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
- package/src/components/custom/Workout/SessionRail.tsx +7 -9
- package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
- package/src/components/custom/Workout/SetBar.test.tsx +49 -4
- package/src/components/custom/Workout/SetBar.tsx +38 -12
- package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
- package/src/components/custom/Workout/SetRow.test.tsx +16 -2
- package/src/components/custom/Workout/SetRow.tsx +61 -23
- package/src/components/custom/Workout/SetStrip.stories.tsx +63 -7
- package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
- package/src/components/custom/Workout/SetStrip.tsx +24 -4
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
- package/src/components/custom/Workout/SetsRepsLoad.tsx +24 -12
- package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
- package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
- package/src/components/custom/Workout/Sparkline.tsx +14 -18
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
- package/src/components/custom/Workout/StatusDot.tsx +93 -63
- package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
- package/src/components/custom/Workout/StatusPill.tsx +27 -38
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +11 -4
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
- package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
- package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +26 -10
- package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
- package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +10 -6
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
- package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
- package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
- package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
- package/src/components/custom/Workout/WeekRow.tsx +23 -16
- package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
- package/src/components/custom/Workout/WeightBadge.tsx +28 -27
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
- package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
- package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +10 -7
- package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
- package/src/components/custom/Workout/exerciseRowState.ts +29 -0
- package/src/components/custom/Workout/index.ts +17 -2
- package/src/components/custom/Workout/metricText.stories.tsx +15 -13
- package/src/components/custom/Workout/metricText.tsx +8 -2
- package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
- package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
- package/src/components/custom/charts/README.md +66 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
- package/src/components/custom/charts/SetBarChart.tsx +56 -9
- package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
- package/src/components/custom/charts/SparkBars.test.tsx +69 -0
- package/src/components/custom/charts/SparkBars.tsx +85 -0
- package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
- package/src/components/custom/charts/flatBarGeometry.ts +32 -0
- package/src/components/custom/charts/index.ts +3 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
- package/src/components/custom/stepper/Stepper.test.tsx +1 -7
- package/src/components/custom/stepper/Stepper.tsx +21 -34
- package/src/components/icons/icons.stories.tsx +1 -1
- package/src/components/icons/icons.tsx +51 -0
- package/src/components/shell/AppShell.stories.tsx +150 -0
- package/src/components/shell/AppShell.test.tsx +70 -0
- package/src/components/shell/AppShell.tsx +90 -0
- package/src/components/shell/BrandLockup.stories.tsx +66 -5
- package/src/components/shell/BrandLockup.test.tsx +34 -1
- package/src/components/shell/BrandLockup.tsx +27 -11
- package/src/components/shell/NavItem.stories.tsx +4 -4
- package/src/components/shell/NavItem.test.tsx +17 -0
- package/src/components/shell/NavItem.tsx +17 -5
- package/src/components/shell/README.md +89 -26
- package/src/components/shell/SideNav.stories.tsx +38 -7
- package/src/components/shell/SideNav.test.tsx +16 -8
- package/src/components/shell/SideNav.tsx +17 -17
- package/src/components/shell/TopBar.stories.tsx +44 -19
- package/src/components/shell/TopBar.test.tsx +34 -12
- package/src/components/shell/TopBar.tsx +61 -40
- package/src/components/shell/brands.tsx +66 -0
- package/src/components/shell/index.ts +9 -8
- package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
- package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
- package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
- package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
- package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
- package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
- package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
- package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
- package/src/components/shell/workout/WorkoutShell.tsx +77 -0
- package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
- package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
- package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
- package/src/components/shell/workout/index.ts +9 -0
- package/src/components/shell/workout/workoutNavItems.tsx +16 -0
- package/src/components/ui/README.md +110 -0
- package/src/components/ui/alert/Alert.stories.tsx +15 -5
- package/src/components/ui/alert/Alert.tsx +24 -3
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
- package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -10
- package/src/components/ui/badge/Badge.stories.tsx +83 -28
- package/src/components/ui/badge/Badge.test.tsx +19 -3
- package/src/components/ui/badge/Badge.tsx +48 -55
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
- package/src/components/ui/button/Button.stories.tsx +46 -12
- package/src/components/ui/button/Button.tsx +44 -37
- package/src/components/ui/card/Card.stories.tsx +90 -94
- package/src/components/ui/card/Card.test.tsx +80 -6
- package/src/components/ui/card/Card.tsx +150 -203
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +7 -27
- package/src/components/ui/chip/Chip.stories.tsx +37 -13
- package/src/components/ui/chip/Chip.test.tsx +10 -6
- package/src/components/ui/chip/Chip.tsx +76 -91
- package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
- package/src/components/ui/collapse/Collapse.tsx +6 -11
- package/src/components/ui/data-row/DataRow.stories.tsx +14 -3
- package/src/components/ui/data-row/DataRow.test.tsx +5 -15
- package/src/components/ui/data-row/DataRow.tsx +18 -7
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.tsx +1 -5
- package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
- package/src/components/ui/drawer/Drawer.tsx +13 -27
- package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
- package/src/components/ui/form-field/FormField.stories.tsx +6 -20
- package/src/components/ui/form-field/FormField.test.tsx +1 -5
- package/src/components/ui/form-field/FormField.tsx +10 -46
- package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
- package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
- package/src/components/ui/help-tip/HelpTip.tsx +21 -22
- package/src/components/ui/help-tip/index.ts +7 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
- package/src/components/ui/icon-box/IconBox.tsx +10 -18
- package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
- package/src/components/ui/indicator/Indicator.tsx +20 -14
- package/src/components/ui/input/Input.stories.tsx +3 -11
- package/src/components/ui/input/Input.test.tsx +2 -10
- package/src/components/ui/input/Input.tsx +10 -25
- package/src/components/ui/input/index.ts +7 -1
- package/src/components/ui/link/Link.stories.tsx +19 -7
- package/src/components/ui/list-item/ListItem.stories.tsx +17 -5
- package/src/components/ui/list-item/ListItem.tsx +2 -9
- package/src/components/ui/menu/Menu.stories.tsx +21 -15
- package/src/components/ui/menu/Menu.test.tsx +2 -11
- package/src/components/ui/menu/Menu.tsx +12 -14
- package/src/components/ui/menu/index.ts +8 -1
- package/src/components/ui/modal/Modal.stories.tsx +2 -2
- package/src/components/ui/modal/Modal.tsx +31 -19
- package/src/components/ui/pill/Pill.stories.tsx +111 -23
- package/src/components/ui/pill/Pill.test.tsx +101 -3
- package/src/components/ui/pill/Pill.tsx +168 -49
- package/src/components/ui/pill/index.ts +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +6 -8
- package/src/components/ui/popover/Popover.test.tsx +1 -6
- package/src/components/ui/popover/Popover.tsx +20 -15
- package/src/components/ui/popover/index.ts +7 -1
- package/src/components/ui/progress/Progress.stories.tsx +3 -12
- package/src/components/ui/progress/Progress.test.tsx +1 -5
- package/src/components/ui/progress/Progress.tsx +9 -6
- package/src/components/ui/radio/Radio.stories.tsx +42 -21
- package/src/components/ui/radio/Radio.test.tsx +6 -2
- package/src/components/ui/radio/Radio.tsx +23 -22
- package/src/components/ui/section/Section.stories.tsx +12 -21
- package/src/components/ui/section/Section.test.tsx +1 -6
- package/src/components/ui/section/Section.tsx +1 -3
- package/src/components/ui/section/index.ts +1 -5
- package/src/components/ui/select/Select.stories.tsx +3 -13
- package/src/components/ui/select/Select.test.tsx +2 -11
- package/src/components/ui/select/Select.tsx +16 -15
- package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
- package/src/components/ui/skeleton/Skeleton.tsx +3 -14
- package/src/components/ui/skeleton/index.ts +1 -7
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +4 -5
- package/src/components/ui/stack/Stack.stories.tsx +8 -8
- package/src/components/ui/stack/Stack.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +92 -33
- package/src/components/ui/surface/Surface.test.tsx +88 -28
- package/src/components/ui/surface/Surface.tsx +37 -87
- package/src/components/ui/surface/SurfaceContext.ts +22 -62
- package/src/components/ui/surface/index.ts +8 -0
- package/src/components/ui/surface/resolveSurface.ts +98 -0
- package/src/components/ui/surface/surface.contract.test.ts +6 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.tsx +7 -6
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +8 -21
- package/src/components/ui/tabs/index.ts +1 -7
- package/src/components/ui/tile/Tile.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.tsx +3 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toast/Toast.test.tsx +2 -8
- package/src/components/ui/toast/Toast.tsx +49 -57
- package/src/components/ui/toast/index.ts +0 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +38 -49
- package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
- package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
- package/src/components/ui/tooltip/Tooltip.tsx +55 -36
- package/src/components/ui/tooltip/index.ts +1 -1
- package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
- package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
- package/src/components/ui/trigger/index.ts +2 -0
- package/src/stories/PresetShowcase.stories.tsx +101 -48
- package/src/test/no-device-internals.test.ts +3 -3
- package/src/test/no-raw-color.test.ts +39 -0
- package/src/theme/Alpha.stories.tsx +103 -0
- package/src/theme/ChoosingTokens.stories.tsx +48 -0
- package/src/theme/ColorPalettes.stories.tsx +62 -45
- package/src/theme/ColorPrimitives.stories.tsx +35 -35
- package/src/theme/Depth.stories.tsx +132 -65
- package/src/theme/DepthCalibration.stories.tsx +23 -21
- package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
- package/src/theme/ThemeProvider.test.tsx +4 -4
- package/src/theme/barrel.ts +7 -6
- package/src/theme/color-stories.coverage.test.ts +1 -1
- package/src/theme/color-utils.ts +41 -29
- package/src/theme/config.completeness.test.ts +1 -1
- package/src/theme/config.ts +40 -0
- package/src/theme/elevation.test.ts +82 -56
- package/src/theme/elevation.ts +77 -561
- package/src/theme/global.css +45 -5
- package/src/theme/grey-ramp.test.ts +24 -8
- package/src/theme/lift.test.ts +39 -0
- package/src/theme/lift.ts +102 -0
- package/src/theme/materials.test.ts +5 -2
- package/src/theme/materials.ts +38 -4
- package/src/theme/native-theming-guard.test.ts +5 -5
- package/src/theme/presets/apply.ts +4 -0
- package/src/theme/presets/audiobook.ts +5 -2
- package/src/theme/presets/default.ts +4 -0
- package/src/theme/presets/index.ts +14 -1
- package/src/theme/presets/types.ts +1 -1
- package/src/theme/semantic-contrast.test.ts +13 -4
- package/src/theme/status-distinctness.test.ts +66 -0
- package/src/theme/subtle-tone-weight.test.ts +163 -0
- package/src/theme/surface-planes.test.ts +37 -0
- package/src/theme/surface-planes.ts +68 -0
- package/src/theme/tailwind-var-opacity.test.ts +163 -0
- package/src/theme/token-export-guard.test.ts +1 -3
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +98 -33
- package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
- package/src/theme/tokens/semantic.ts +124 -54
- package/src/theme/wash-ladder.test.ts +58 -0
- package/src/theme/workout-tokens.ts +1 -7
- package/src/types/jest-axe.d.ts +2 -6
- package/src/types/raw-md.d.ts +5 -0
- package/src/utils/colors.alpha-adoption.test.ts +408 -0
- package/src/utils/colors.ts +4 -4
- package/src/utils/form.ts +1 -3
- package/src/utils/index.ts +2 -7
- package/src/utils/number-format.ts +17 -0
- package/src/utils/workout-format.ts +15 -6
- package/src/web-jsx/jsx-runtime.ts +1 -5
- package/tailwind.config.js +55 -9
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
- package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
- package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
- package/src/components/shell/DashboardShell.stories.tsx +0 -65
- package/src/components/shell/DashboardShell.tsx +0 -75
- package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
- package/src/theme/elevation.stories.tsx +0 -346
- package/src/utils/useTheme.ts +0 -72
- /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
- /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
- /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
- /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
|
@@ -0,0 +1,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
|
+
}
|
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { Surface } from '../ui/surface'
|
|
4
|
+
import { Typography } from '../custom/Typography'
|
|
5
|
+
import { StarIcon } from '../icons'
|
|
2
6
|
import { BrandLockup } from './BrandLockup'
|
|
7
|
+
import { brandKeys } from './brands'
|
|
3
8
|
|
|
4
9
|
const meta: Meta<typeof BrandLockup> = {
|
|
5
10
|
title: 'Shell/BrandLockup',
|
|
6
11
|
component: BrandLockup,
|
|
7
|
-
tags: ['autodocs'],
|
|
8
|
-
args: {
|
|
12
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
13
|
+
args: { brand: 'voltras', showSubtitle: true },
|
|
9
14
|
argTypes: {
|
|
15
|
+
brand: { control: 'select', options: brandKeys },
|
|
10
16
|
subtitle: { control: 'text' },
|
|
17
|
+
wordmark: { control: 'text' },
|
|
18
|
+
accentClassName: { control: 'text' },
|
|
11
19
|
showSubtitle: { control: 'boolean' },
|
|
20
|
+
mark: { control: false },
|
|
12
21
|
},
|
|
13
22
|
parameters: {
|
|
14
23
|
docs: {
|
|
15
24
|
description: {
|
|
16
25
|
component:
|
|
17
|
-
'**Molecule.** Composes [
|
|
18
|
-
'[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle)
|
|
19
|
-
'
|
|
26
|
+
'**Molecule.** Composes an [icon](?path=/docs/foundations-icons--docs) mark + ' +
|
|
27
|
+
'[Typography](?path=/docs/foundations-typography--docs) (wordmark + subtitle).\n\n' +
|
|
28
|
+
'**Generic over the app (AW-132).** Pick a `brand` preset, or override `mark` / ' +
|
|
29
|
+
'`wordmark` / `accentClassName` / `subtitle` for an app that has no preset yet. ' +
|
|
30
|
+
"Accents come from `data-*` rather than `status-*`: `data-*` is the library's set of " +
|
|
31
|
+
'distinct, CVD-checked hues with no semantic load, which is what a per-app identity ' +
|
|
32
|
+
'accent needs. Voltras keeps the real `brand-primary` token.\n\n' +
|
|
33
|
+
'**Try it:** switch `brand` in the **Controls**, or toggle `showSubtitle` to see the ' +
|
|
34
|
+
'responsive collapse.',
|
|
20
35
|
},
|
|
21
36
|
},
|
|
22
37
|
},
|
|
@@ -25,3 +40,49 @@ export default meta
|
|
|
25
40
|
type Story = StoryObj<typeof BrandLockup>
|
|
26
41
|
|
|
27
42
|
export const Default: Story = {}
|
|
43
|
+
|
|
44
|
+
/** Every app likely to mount this shell, in the chrome band's own surface. */
|
|
45
|
+
export const BrandVariants: Story = {
|
|
46
|
+
parameters: {
|
|
47
|
+
docs: {
|
|
48
|
+
description: {
|
|
49
|
+
story:
|
|
50
|
+
'The five branding presets. Each is a mark, a wordmark, a semantic accent token and a ' +
|
|
51
|
+
'default subtitle — nothing else in the shell changes between them.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
render: () => (
|
|
56
|
+
<Surface level="elevated" className="gap-4 p-5">
|
|
57
|
+
{brandKeys.map((brand) => (
|
|
58
|
+
<View key={brand} className="gap-1">
|
|
59
|
+
<Typography variant="microLabel" color="tertiary">
|
|
60
|
+
{brand}
|
|
61
|
+
</Typography>
|
|
62
|
+
<BrandLockup brand={brand} />
|
|
63
|
+
</View>
|
|
64
|
+
))}
|
|
65
|
+
</Surface>
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Collapsed subtitle — what every brand looks like below ~1024px. */
|
|
70
|
+
export const BrandVariantsCollapsed: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<Surface level="elevated" className="gap-4 p-5">
|
|
73
|
+
{brandKeys.map((brand) => (
|
|
74
|
+
<BrandLockup key={brand} brand={brand} showSubtitle={false} />
|
|
75
|
+
))}
|
|
76
|
+
</Surface>
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** An app with no preset: supply mark, wordmark, accent and subtitle directly. */
|
|
81
|
+
export const CustomBrand: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
mark: <StarIcon size={14} color="currentColor" />,
|
|
84
|
+
wordmark: 'HYPERFRAMES',
|
|
85
|
+
subtitle: 'renders',
|
|
86
|
+
accentClassName: 'text-data-6',
|
|
87
|
+
},
|
|
88
|
+
}
|
|
@@ -2,9 +2,10 @@ import { describe, it, expect } from 'vitest'
|
|
|
2
2
|
import { render, screen } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
4
|
import { BrandLockup } from './BrandLockup'
|
|
5
|
+
import { brandKeys, brandPresets } from './brands'
|
|
5
6
|
|
|
6
7
|
describe('BrandLockup', () => {
|
|
7
|
-
it('renders the wordmark', () => {
|
|
8
|
+
it('renders the voltras wordmark by default', () => {
|
|
8
9
|
render(<BrandLockup />)
|
|
9
10
|
expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
|
|
10
11
|
})
|
|
@@ -16,6 +17,38 @@ describe('BrandLockup', () => {
|
|
|
16
17
|
expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
|
|
17
18
|
})
|
|
18
19
|
|
|
20
|
+
it.each(brandKeys)('renders the %s preset wordmark and subtitle', (brand) => {
|
|
21
|
+
render(<BrandLockup brand={brand} />)
|
|
22
|
+
expect(screen.getByText(brandPresets[brand].wordmark)).toBeInTheDocument()
|
|
23
|
+
expect(screen.getByText(`/ ${brandPresets[brand].subtitle}`)).toBeInTheDocument()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('lets an app override the preset parts', () => {
|
|
27
|
+
render(<BrandLockup brand="brain" wordmark="HYPERFRAMES" subtitle="renders" />)
|
|
28
|
+
expect(screen.getByText('HYPERFRAMES')).toBeInTheDocument()
|
|
29
|
+
expect(screen.getByText('/ renders')).toBeInTheDocument()
|
|
30
|
+
expect(screen.queryByText('BRAIN')).not.toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// nativewind compiles className to style, so the rendered accent colour is not
|
|
34
|
+
// assertable in jsdom. Assert the token CHOICE instead: every preset picks a
|
|
35
|
+
// semantic `text-*` token, and no two apps share one.
|
|
36
|
+
it('gives every brand a distinct semantic accent token', () => {
|
|
37
|
+
const accents = brandKeys.map((brand) => brandPresets[brand].accentClassName)
|
|
38
|
+
accents.forEach((accent) => expect(accent).toMatch(/^text-(brand|data)-/))
|
|
39
|
+
expect(new Set(accents).size).toBe(accents.length)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// The nav's active bar needs the accent as a background, and a mismatched pair
|
|
43
|
+
// would render a lockup and an active nav item in two different hues.
|
|
44
|
+
it('pairs every accent with the same token as a background', () => {
|
|
45
|
+
brandKeys.forEach((brand) => {
|
|
46
|
+
const { accentClassName, accentBarClassName } = brandPresets[brand]
|
|
47
|
+
expect(accentBarClassName).toMatch(/^bg-(brand|data)-/)
|
|
48
|
+
expect(accentBarClassName).toBe(accentClassName.replace(/^text-/, 'bg-'))
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
19
52
|
it('has no accessibility violations', async () => {
|
|
20
53
|
const { container } = render(<BrandLockup />)
|
|
21
54
|
expect(await axe(container)).toHaveNoViolations()
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
1
2
|
import { View, type ViewProps } from 'react-native'
|
|
2
3
|
import { cn } from '../../utils/cn'
|
|
3
4
|
import { Typography } from '../custom/Typography'
|
|
4
|
-
import {
|
|
5
|
+
import { brandPresets, type BrandKey } from './brands'
|
|
5
6
|
|
|
6
7
|
export interface BrandLockupProps extends ViewProps {
|
|
7
|
-
/**
|
|
8
|
+
/** Which app's identity to render. Defaults to `voltras`. */
|
|
9
|
+
brand?: BrandKey
|
|
10
|
+
/** Replace the preset's mark glyph. */
|
|
11
|
+
mark?: ReactNode
|
|
12
|
+
/** Replace the preset's wordmark. */
|
|
13
|
+
wordmark?: string
|
|
14
|
+
/** Replace the preset's accent — a semantic `text-*` token class for the mark. */
|
|
15
|
+
accentClassName?: string
|
|
16
|
+
/** The "/ subtitle" that follows the wordmark. Defaults to the brand's own. */
|
|
8
17
|
subtitle?: string
|
|
9
18
|
/** Show the subtitle. Hidden at tablet width and below (S1-Q4 responsive). */
|
|
10
19
|
showSubtitle?: boolean
|
|
@@ -12,34 +21,41 @@ export interface BrandLockupProps extends ViewProps {
|
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
|
-
* Product identity lockup for the top bar:
|
|
16
|
-
*
|
|
24
|
+
* Product identity lockup for the top bar: mark + wordmark + optional subtitle.
|
|
25
|
+
* Generic over the app: pick a `brand` preset, or override `mark` / `wordmark` /
|
|
26
|
+
* `accentClassName` / `subtitle` piecemeal for an app that has no preset yet.
|
|
27
|
+
* Composes an icon primitive + Typography. S1 · BrandLockup.
|
|
17
28
|
*/
|
|
18
29
|
export function BrandLockup({
|
|
19
|
-
|
|
30
|
+
brand = 'voltras',
|
|
31
|
+
mark,
|
|
32
|
+
wordmark,
|
|
33
|
+
accentClassName,
|
|
34
|
+
subtitle,
|
|
20
35
|
showSubtitle = true,
|
|
21
36
|
className,
|
|
22
37
|
...props
|
|
23
38
|
}: BrandLockupProps) {
|
|
39
|
+
const preset = brandPresets[brand]
|
|
40
|
+
const resolvedSubtitle = subtitle ?? preset.subtitle
|
|
41
|
+
|
|
24
42
|
return (
|
|
25
43
|
<View className={cn('flex-row items-center gap-[7px]', className)} {...props}>
|
|
26
|
-
<View className=
|
|
27
|
-
<VoltrasMark size={14} color="currentColor" />
|
|
28
|
-
</View>
|
|
44
|
+
<View className={accentClassName ?? preset.accentClassName}>{mark ?? preset.mark}</View>
|
|
29
45
|
<Typography
|
|
30
46
|
variant="button"
|
|
31
47
|
color="primary"
|
|
32
48
|
className="text-[12px] font-extrabold tracking-[1.5px]"
|
|
33
49
|
>
|
|
34
|
-
|
|
50
|
+
{wordmark ?? preset.wordmark}
|
|
35
51
|
</Typography>
|
|
36
|
-
{showSubtitle &&
|
|
52
|
+
{showSubtitle && resolvedSubtitle ? (
|
|
37
53
|
<Typography
|
|
38
54
|
variant="button"
|
|
39
55
|
color="tertiary"
|
|
40
56
|
className="text-[10px] font-semibold tracking-[0.4px]"
|
|
41
57
|
>
|
|
42
|
-
/ {
|
|
58
|
+
/ {resolvedSubtitle}
|
|
43
59
|
</Typography>
|
|
44
60
|
) : null}
|
|
45
61
|
</View>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
-
import { View } from 'react-native'
|
|
3
2
|
import { NavItem } from './NavItem'
|
|
4
3
|
import { ActivityIcon } from '../icons'
|
|
4
|
+
import { Surface } from '../ui/surface'
|
|
5
5
|
|
|
6
6
|
const meta: Meta<typeof NavItem> = {
|
|
7
7
|
title: 'Shell/NavItem',
|
|
8
8
|
component: NavItem,
|
|
9
|
-
tags: ['autodocs'],
|
|
9
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
10
10
|
args: { label: 'Live', active: false, live: false },
|
|
11
11
|
argTypes: {
|
|
12
12
|
label: { control: 'text' },
|
|
@@ -17,9 +17,9 @@ const meta: Meta<typeof NavItem> = {
|
|
|
17
17
|
},
|
|
18
18
|
decorators: [
|
|
19
19
|
(Story) => (
|
|
20
|
-
<
|
|
20
|
+
<Surface level="background" className="w-[60px] items-center py-2">
|
|
21
21
|
<Story />
|
|
22
|
-
</
|
|
22
|
+
</Surface>
|
|
23
23
|
),
|
|
24
24
|
],
|
|
25
25
|
parameters: {
|
|
@@ -32,4 +32,21 @@ describe('NavItem', () => {
|
|
|
32
32
|
const { container } = render(<NavItem icon={icon} label="Live" live />)
|
|
33
33
|
expect(container.firstChild).toBeInTheDocument()
|
|
34
34
|
})
|
|
35
|
+
|
|
36
|
+
// nativewind compiles className to style, so jsdom cannot see the accent colour.
|
|
37
|
+
// Assert an app-supplied accent leaves the rest of the active state intact; the
|
|
38
|
+
// colour itself is guarded by the paired-token test in BrandLockup.test.
|
|
39
|
+
it('keeps the active state when an app supplies its own accent', () => {
|
|
40
|
+
render(
|
|
41
|
+
<NavItem
|
|
42
|
+
icon={icon}
|
|
43
|
+
label="Graph"
|
|
44
|
+
active
|
|
45
|
+
accentClassName="text-data-3"
|
|
46
|
+
accentBarClassName="bg-data-3"
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
expect(screen.getByTestId('nav-item-accent')).toBeInTheDocument()
|
|
50
|
+
expect(screen.getByRole('tab', { name: 'Graph' })).toBeInTheDocument()
|
|
51
|
+
})
|
|
35
52
|
})
|
|
@@ -8,13 +8,17 @@ export interface NavItemProps {
|
|
|
8
8
|
icon: ReactNode
|
|
9
9
|
/** Short label shown under the glyph (uppercased for display; used as the accessible name). */
|
|
10
10
|
label: string
|
|
11
|
-
/** Active category → left accent bar +
|
|
11
|
+
/** Active category → left accent bar + accent-colored glyph & label. */
|
|
12
12
|
active?: boolean
|
|
13
13
|
/**
|
|
14
14
|
* A set is running for this category while it is NOT the active view
|
|
15
15
|
* (live-elsewhere) → a quiet muted-green label tint. Ignored when `active`.
|
|
16
16
|
*/
|
|
17
17
|
live?: boolean
|
|
18
|
+
/** Semantic `text-*` token for the active glyph and label. Defaults to the Voltras brand. */
|
|
19
|
+
accentClassName?: string
|
|
20
|
+
/** Semantic `bg-*` token for the active bar. Pair it with `accentClassName`. */
|
|
21
|
+
accentBarClassName?: string
|
|
18
22
|
onPress?: () => void
|
|
19
23
|
className?: string
|
|
20
24
|
}
|
|
@@ -23,20 +27,25 @@ export interface NavItemProps {
|
|
|
23
27
|
* Shell S2 · NavItem — one category button in the {@link SideNav}: a 20px glyph
|
|
24
28
|
* over an uppercase micro-label in a 46×46 target. The button spans the full 60px
|
|
25
29
|
* rail so the active **left accent bar** sits flush to the rail's edge. States:
|
|
26
|
-
* active = accent bar +
|
|
30
|
+
* active = accent bar + the accent token; `live` (while not active) tints only the
|
|
27
31
|
* label `status-success-dark` (the glyph stays dim); otherwise dim `text-tertiary`.
|
|
32
|
+
*
|
|
33
|
+
* The accent follows the mounting app's brand, so a Brain shell reads yellow
|
|
34
|
+
* throughout rather than showing a Voltras-orange active item under its own lockup.
|
|
28
35
|
*/
|
|
29
36
|
export function NavItem({
|
|
30
37
|
icon,
|
|
31
38
|
label,
|
|
32
39
|
active = false,
|
|
33
40
|
live = false,
|
|
41
|
+
accentClassName = 'text-brand-primary',
|
|
42
|
+
accentBarClassName = 'bg-brand-primary',
|
|
34
43
|
onPress,
|
|
35
44
|
className,
|
|
36
45
|
}: NavItemProps) {
|
|
37
|
-
const glyphColor = active ?
|
|
46
|
+
const glyphColor = active ? accentClassName : 'text-text-tertiary'
|
|
38
47
|
const labelColor = active
|
|
39
|
-
?
|
|
48
|
+
? accentClassName
|
|
40
49
|
: live
|
|
41
50
|
? 'text-status-live-muted'
|
|
42
51
|
: 'text-text-tertiary'
|
|
@@ -52,7 +61,10 @@ export function NavItem({
|
|
|
52
61
|
{active ? (
|
|
53
62
|
<View
|
|
54
63
|
testID="nav-item-accent"
|
|
55
|
-
className=
|
|
64
|
+
className={cn(
|
|
65
|
+
'absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px]',
|
|
66
|
+
accentBarClassName
|
|
67
|
+
)}
|
|
56
68
|
/>
|
|
57
69
|
) : null}
|
|
58
70
|
<View
|