@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ReactNode } from 'react'
|
|
7
7
|
import { TempoDisplay } from './TempoDisplay'
|
|
8
|
-
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
8
|
+
import { greyRamp, primitiveRamps } from '../../../theme/tokens/primitives'
|
|
9
9
|
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
10
10
|
|
|
11
11
|
// ---- geometry
|
|
@@ -29,30 +29,55 @@ export const INSET_SHADOW =
|
|
|
29
29
|
'inset 0 9px 12px -6px rgba(0,0,0,0.85), inset 7px 0 10px -6px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.02)'
|
|
30
30
|
|
|
31
31
|
// ---- zone colors = real titan ramp pins (primitiveRamps)
|
|
32
|
-
export const RED =
|
|
33
|
-
export const ORANGE =
|
|
34
|
-
export const AMBER =
|
|
35
|
-
export const GREEN =
|
|
36
|
-
export const velColor = (v: number): string =>
|
|
32
|
+
export const RED = primitiveRamps.red[600]
|
|
33
|
+
export const ORANGE = primitiveRamps.orange[400]
|
|
34
|
+
export const AMBER = primitiveRamps.amber[300]
|
|
35
|
+
export const GREEN = primitiveRamps.green[300]
|
|
36
|
+
export const velColor = (v: number): string =>
|
|
37
|
+
v < 0.5 ? RED : v < 0.75 ? ORANGE : v < 1.0 ? AMBER : GREEN
|
|
37
38
|
|
|
38
39
|
// active-set pulse: each segment eases along its own ramp (adjacent lighter/darker steps) through the pin.
|
|
39
40
|
export const PULSE_CSS = `
|
|
40
|
-
@keyframes s3pRed{0%,100%{background
|
|
41
|
-
@keyframes s3pOrange{0%,100%{background
|
|
42
|
-
@keyframes s3pAmber{0%,100%{background
|
|
43
|
-
@keyframes s3pGreen{0%,100%{background
|
|
41
|
+
@keyframes s3pRed{0%,100%{background:${primitiveRamps.red[500]}}50%{background:${primitiveRamps.red[700]}}}
|
|
42
|
+
@keyframes s3pOrange{0%,100%{background:${primitiveRamps.orange[300]}}50%{background:${primitiveRamps.orange[500]}}}
|
|
43
|
+
@keyframes s3pAmber{0%,100%{background:${primitiveRamps.amber[200]}}50%{background:${primitiveRamps.amber[400]}}}
|
|
44
|
+
@keyframes s3pGreen{0%,100%{background:${primitiveRamps.green[200]}}50%{background:${primitiveRamps.green[400]}}}`
|
|
44
45
|
export const pulseAnim = (c: string): string | undefined => {
|
|
45
|
-
const name =
|
|
46
|
+
const name =
|
|
47
|
+
c === RED
|
|
48
|
+
? 's3pRed'
|
|
49
|
+
: c === ORANGE
|
|
50
|
+
? 's3pOrange'
|
|
51
|
+
: c === AMBER
|
|
52
|
+
? 's3pAmber'
|
|
53
|
+
: c === GREEN
|
|
54
|
+
? 's3pGreen'
|
|
55
|
+
: null
|
|
46
56
|
return name ? `${name} 1.9s ease-in-out infinite` : undefined
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
// ---- sets/reps/load in the TempoDisplay visual language (Inter · 600 · letter-spacing 1 · gray separators)
|
|
50
60
|
const INTER = 'Inter, sans-serif'
|
|
61
|
+
// VW-82: Tailwind gray-500; no ramp step matches. Left raw, proposed in the PR.
|
|
51
62
|
const SRL_SEP = '#6B7280'
|
|
52
63
|
function SRLCell({ children, color = T_PRIMARY }: { children: ReactNode; color?: string }) {
|
|
53
|
-
return
|
|
64
|
+
return (
|
|
65
|
+
<span style={{ fontFamily: INTER, fontSize: 11, fontWeight: 600, letterSpacing: 1, color }}>
|
|
66
|
+
{children}
|
|
67
|
+
</span>
|
|
68
|
+
)
|
|
54
69
|
}
|
|
55
|
-
export function SetsRepsLoad({
|
|
70
|
+
export function SetsRepsLoad({
|
|
71
|
+
sets,
|
|
72
|
+
reps,
|
|
73
|
+
load,
|
|
74
|
+
unit = 'lb',
|
|
75
|
+
}: {
|
|
76
|
+
sets: number
|
|
77
|
+
reps: number | string
|
|
78
|
+
load: number
|
|
79
|
+
unit?: string
|
|
80
|
+
}) {
|
|
56
81
|
return (
|
|
57
82
|
<span style={{ display: 'inline-flex', alignItems: 'baseline', whiteSpace: 'nowrap' }}>
|
|
58
83
|
<SRLCell>{sets}</SRLCell>
|
|
@@ -66,7 +91,10 @@ export function SetsRepsLoad({ sets, reps, load, unit = 'lb' }: { sets: number;
|
|
|
66
91
|
}
|
|
67
92
|
|
|
68
93
|
// ---- per-set bar: one continuous bar, rep intensities as butted segments (no gaps); active segments pulse
|
|
69
|
-
export type SetState =
|
|
94
|
+
export type SetState =
|
|
95
|
+
| { status: 'done'; reps: number[] }
|
|
96
|
+
| { status: 'active'; reps: number[]; planned: number }
|
|
97
|
+
| { status: 'todo'; planned: number }
|
|
70
98
|
export function setColors(s: SetState): string[] {
|
|
71
99
|
if (s.status === 'todo') return [GREY]
|
|
72
100
|
if (s.status === 'done') return s.reps.map(velColor)
|
|
@@ -74,9 +102,26 @@ export function setColors(s: SetState): string[] {
|
|
|
74
102
|
}
|
|
75
103
|
function SetBar({ colors, h, pulse }: { colors: string[]; h: number; pulse?: boolean }) {
|
|
76
104
|
return (
|
|
77
|
-
<div
|
|
105
|
+
<div
|
|
106
|
+
style={{
|
|
107
|
+
display: 'flex',
|
|
108
|
+
flex: 1,
|
|
109
|
+
minWidth: 0,
|
|
110
|
+
height: h,
|
|
111
|
+
borderRadius: 2,
|
|
112
|
+
overflow: 'hidden',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
78
115
|
{colors.map((c, i) => (
|
|
79
|
-
<div
|
|
116
|
+
<div
|
|
117
|
+
key={i}
|
|
118
|
+
style={{
|
|
119
|
+
flex: 1,
|
|
120
|
+
height: '100%',
|
|
121
|
+
background: c,
|
|
122
|
+
animation: pulse && c !== GREY ? pulseAnim(c) : undefined,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
80
125
|
))}
|
|
81
126
|
</div>
|
|
82
127
|
)
|
|
@@ -92,20 +137,51 @@ export function SetStrip({ sets, h }: { sets: SetState[]; h: number }) {
|
|
|
92
137
|
}
|
|
93
138
|
|
|
94
139
|
export function Indicator({ kind }: { kind: 'pr' | 'issue' | 'info' }) {
|
|
95
|
-
const map = { pr: ['★', ORANGE], issue: ['!', RED], info: ['i', '
|
|
140
|
+
const map = { pr: ['★', ORANGE], issue: ['!', RED], info: ['i', T['status-info']] } as const
|
|
96
141
|
const [glyph, color] = map[kind]
|
|
97
142
|
return (
|
|
98
|
-
<span
|
|
143
|
+
<span
|
|
144
|
+
style={{
|
|
145
|
+
width: 16,
|
|
146
|
+
height: 16,
|
|
147
|
+
borderRadius: 8,
|
|
148
|
+
border: `1px solid ${color}`,
|
|
149
|
+
color,
|
|
150
|
+
fontSize: 10,
|
|
151
|
+
fontWeight: 800,
|
|
152
|
+
display: 'inline-flex',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center',
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{glyph}
|
|
158
|
+
</span>
|
|
99
159
|
)
|
|
100
160
|
}
|
|
101
161
|
|
|
102
162
|
const labelStyle = { fontSize: 14, fontWeight: 800, color: T_PRIMARY } as const
|
|
103
163
|
|
|
104
164
|
// ---- the finalized heading unit
|
|
105
|
-
export type Exercise = {
|
|
165
|
+
export type Exercise = {
|
|
166
|
+
name: string
|
|
167
|
+
sets: number
|
|
168
|
+
reps: number | string
|
|
169
|
+
load: number
|
|
170
|
+
tempo: [number, number, number, number]
|
|
171
|
+
indicator?: 'pr' | 'issue' | 'info'
|
|
172
|
+
setStates: SetState[]
|
|
173
|
+
upcoming?: boolean
|
|
174
|
+
}
|
|
106
175
|
export function Heading({ ex, stripH }: { ex: Exercise; stripH: number }) {
|
|
107
176
|
return (
|
|
108
|
-
<div
|
|
177
|
+
<div
|
|
178
|
+
style={{
|
|
179
|
+
padding: '9px 12px',
|
|
180
|
+
display: 'flex',
|
|
181
|
+
flexDirection: 'column',
|
|
182
|
+
opacity: ex.upcoming ? 0.55 : 1,
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
109
185
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
110
186
|
<span style={labelStyle}>{ex.name}</span>
|
|
111
187
|
<div style={{ flex: 1 }} />
|
|
@@ -124,21 +200,80 @@ export function Heading({ ex, stripH }: { ex: Exercise; stripH: number }) {
|
|
|
124
200
|
}
|
|
125
201
|
|
|
126
202
|
// ---- sample session (per-rep mean velocities; sets fatigue across the workout, reps decay in-set)
|
|
127
|
-
export const reps = (n: number, start: number, span = 0.4): number[] =>
|
|
203
|
+
export const reps = (n: number, start: number, span = 0.4): number[] =>
|
|
204
|
+
Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
|
|
128
205
|
export const SESSION: Exercise[] = [
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
206
|
+
{
|
|
207
|
+
name: 'Seated Cable Row',
|
|
208
|
+
sets: 5,
|
|
209
|
+
reps: 8,
|
|
210
|
+
load: 145,
|
|
211
|
+
tempo: [3, 1, 2, 0],
|
|
212
|
+
indicator: 'pr',
|
|
213
|
+
setStates: [1.0, 0.9, 0.8, 0.7, 0.6].map((s) => ({ status: 'done', reps: reps(8, s) })),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'Incline DB Press',
|
|
217
|
+
sets: 3,
|
|
218
|
+
reps: 8,
|
|
219
|
+
load: 70,
|
|
220
|
+
tempo: [3, 0, 3, 0],
|
|
221
|
+
setStates: [0.72, 0.62, 0.54].map((s) => ({ status: 'done', reps: reps(8, s) })),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'Cable Chest Press',
|
|
225
|
+
sets: 3,
|
|
226
|
+
reps: 10,
|
|
227
|
+
load: 90,
|
|
228
|
+
tempo: [2, 1, 2, 0],
|
|
229
|
+
indicator: 'info',
|
|
230
|
+
setStates: [
|
|
231
|
+
{ status: 'done', reps: reps(10, 0.72) },
|
|
232
|
+
{ status: 'active', reps: reps(5, 0.62), planned: 10 },
|
|
233
|
+
{ status: 'todo', planned: 10 },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'Standing Calf Raise',
|
|
238
|
+
sets: 5,
|
|
239
|
+
reps: 20,
|
|
240
|
+
load: 25,
|
|
241
|
+
tempo: [2, 1, 2, 0],
|
|
242
|
+
upcoming: true,
|
|
243
|
+
setStates: [1, 2, 3, 4, 5].map(() => ({ status: 'todo', planned: 20 })),
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'Face Pull',
|
|
247
|
+
sets: 3,
|
|
248
|
+
reps: '15-20',
|
|
249
|
+
load: 40,
|
|
250
|
+
tempo: [2, 1, 2, 0],
|
|
251
|
+
upcoming: true,
|
|
252
|
+
setStates: [1, 2, 3].map(() => ({ status: 'todo', planned: 18 })),
|
|
253
|
+
},
|
|
134
254
|
]
|
|
135
255
|
|
|
136
256
|
// ---- shell chrome (neumorphic): flat raised nav + heading plane; sunk inset exercise list
|
|
137
257
|
export function NavStub() {
|
|
138
258
|
return (
|
|
139
|
-
<div
|
|
259
|
+
<div
|
|
260
|
+
style={{
|
|
261
|
+
width: 60,
|
|
262
|
+
alignSelf: 'stretch',
|
|
263
|
+
position: 'relative',
|
|
264
|
+
zIndex: 2,
|
|
265
|
+
background: RAISED,
|
|
266
|
+
display: 'flex',
|
|
267
|
+
flexDirection: 'column',
|
|
268
|
+
alignItems: 'center',
|
|
269
|
+
paddingTop: 12,
|
|
270
|
+
gap: 8,
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
140
273
|
{['Live', 'Plan', 'Body'].map((t) => (
|
|
141
|
-
<span key={t} style={{ fontSize: 8, color: T_TERTIARY, fontWeight: 700 }}>
|
|
274
|
+
<span key={t} style={{ fontSize: 8, color: T_TERTIARY, fontWeight: 700 }}>
|
|
275
|
+
{t}
|
|
276
|
+
</span>
|
|
142
277
|
))}
|
|
143
278
|
</div>
|
|
144
279
|
)
|
|
@@ -146,17 +281,39 @@ export function NavStub() {
|
|
|
146
281
|
export function Rail({ stripH, footer }: { stripH: number; footer?: ReactNode }) {
|
|
147
282
|
return (
|
|
148
283
|
<div style={{ width: 246, background: INSET, display: 'flex', flexDirection: 'column' }}>
|
|
149
|
-
<div
|
|
150
|
-
|
|
284
|
+
<div
|
|
285
|
+
style={{ position: 'relative', zIndex: 2, padding: '11px 12px 10px', background: RAISED }}
|
|
286
|
+
>
|
|
287
|
+
<div
|
|
288
|
+
style={{
|
|
289
|
+
fontSize: 9,
|
|
290
|
+
fontWeight: 800,
|
|
291
|
+
letterSpacing: 1,
|
|
292
|
+
textTransform: 'uppercase',
|
|
293
|
+
color: T_TERTIARY,
|
|
294
|
+
display: 'flex',
|
|
295
|
+
alignItems: 'center',
|
|
296
|
+
gap: 5,
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
151
299
|
<span style={{ width: 6, height: 6, borderRadius: 3, background: GREEN }} />
|
|
152
300
|
Live session
|
|
153
301
|
</div>
|
|
154
|
-
<div style={{ fontSize: 14, fontWeight: 800, marginTop: 3, color: T_PRIMARY }}>
|
|
155
|
-
|
|
302
|
+
<div style={{ fontSize: 14, fontWeight: 800, marginTop: 3, color: T_PRIMARY }}>
|
|
303
|
+
Pull A · Intensification
|
|
304
|
+
</div>
|
|
305
|
+
<div style={{ fontSize: 10, color: T_SECONDARY, marginTop: 2, fontFamily: 'monospace' }}>
|
|
306
|
+
ex 3/5 · set 2 live
|
|
307
|
+
</div>
|
|
156
308
|
</div>
|
|
157
309
|
<div style={{ flex: 1, background: INSET, boxShadow: INSET_SHADOW }}>
|
|
158
310
|
{SESSION.map((ex, i) => (
|
|
159
|
-
<div
|
|
311
|
+
<div
|
|
312
|
+
key={i}
|
|
313
|
+
style={{
|
|
314
|
+
borderBottom: i < SESSION.length - 1 ? `1px solid ${BORDER_SUBTLE}` : undefined,
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
160
317
|
<Heading ex={ex} stripH={stripH} />
|
|
161
318
|
</div>
|
|
162
319
|
))}
|
|
@@ -167,9 +324,29 @@ export function Rail({ stripH, footer }: { stripH: number; footer?: ReactNode })
|
|
|
167
324
|
}
|
|
168
325
|
|
|
169
326
|
// ---- page scaffold shared by the sheets
|
|
170
|
-
export function Page({
|
|
327
|
+
export function Page({
|
|
328
|
+
title,
|
|
329
|
+
note,
|
|
330
|
+
children,
|
|
331
|
+
}: {
|
|
332
|
+
title: string
|
|
333
|
+
note: string
|
|
334
|
+
children: ReactNode
|
|
335
|
+
}) {
|
|
171
336
|
return (
|
|
172
|
-
<div
|
|
337
|
+
<div
|
|
338
|
+
style={{
|
|
339
|
+
minHeight: '100vh',
|
|
340
|
+
// VW-82: no token at this value (nearest background-frame #100D0A).
|
|
341
|
+
background: '#0A0A0A',
|
|
342
|
+
color: T_PRIMARY,
|
|
343
|
+
padding: 28,
|
|
344
|
+
display: 'flex',
|
|
345
|
+
flexDirection: 'column',
|
|
346
|
+
gap: 22,
|
|
347
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
173
350
|
<style>{PULSE_CSS}</style>
|
|
174
351
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
175
352
|
<span style={{ fontSize: 20, fontWeight: 800 }}>{title}</span>
|
|
@@ -179,5 +356,11 @@ export function Page({ title, note, children }: { title: string; note: string; c
|
|
|
179
356
|
</div>
|
|
180
357
|
)
|
|
181
358
|
}
|
|
182
|
-
export const sectionTitle = {
|
|
359
|
+
export const sectionTitle = {
|
|
360
|
+
fontSize: 11,
|
|
361
|
+
fontWeight: 800,
|
|
362
|
+
letterSpacing: 1.2,
|
|
363
|
+
textTransform: 'uppercase',
|
|
364
|
+
color: ORANGE,
|
|
365
|
+
} as const
|
|
183
366
|
export const monoTag = { fontFamily: 'monospace', fontSize: 11, color: T_SECONDARY } as const
|
|
@@ -116,8 +116,8 @@ export const VIEW_HEIGHT: Record<StripView, { single: number; dual: number }> =
|
|
|
116
116
|
|
|
117
117
|
export type StripView = 'compact' | 'expanded' | 'hero'
|
|
118
118
|
|
|
119
|
-
/** The dual renderer for a view. `expanded`'s dual is the lean `
|
|
120
|
-
export const dualVariantFor = (view: StripView) => (view === 'expanded' ? '
|
|
119
|
+
/** The dual renderer for a view. `expanded`'s dual is the lean `dual-expanded` renderer. */
|
|
120
|
+
export const dualVariantFor = (view: StripView) => (view === 'expanded' ? 'dual-expanded' : view)
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* One scenario, rendered single above dual on the same dataset.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# `custom/charts` — shared chart substrate
|
|
2
|
+
|
|
3
|
+
Two marks and one hook, extracted so every value-height bar family in the library
|
|
4
|
+
draws the same bar rather than each re-rolling geometry and entrance animation.
|
|
5
|
+
This is a substrate family: it holds no domain knowledge, and colour, reference
|
|
6
|
+
overlays and labels are passed in by the consumer.
|
|
7
|
+
|
|
8
|
+
This README is the **index**: **composes ↓** and **used-by ↑** for each member,
|
|
9
|
+
so the tree navigates both ways and a hand-rolled bar shows up as a gap. Counts
|
|
10
|
+
come from [`src/arch/arch-graph.json`](../../../arch/arch-graph.json).
|
|
11
|
+
|
|
12
|
+
## Dependency map
|
|
13
|
+
|
|
14
|
+
| Member | Kind | Composes ↓ | Used-by ↑ | Exported |
|
|
15
|
+
| ----------------- | ---- | ----------------------------------------------- | ------------------------------------------------------ | ------------ |
|
|
16
|
+
| `SparkBars` | atom | `resolveColor`, `cn` | FileActivityDetail, FileActivityRow (ActiveWork) | yes |
|
|
17
|
+
| `SetBarChart` | atom | `barPaper`, `SurfaceContext`, `live-rep-growth` | RomProgressionChart (Fatigue), VelocityStrip (Workout) | no — by path |
|
|
18
|
+
| `live-rep-growth` | hook | `Animated`, `Easing` | SetBarChart, VelocityStrip | no — by path |
|
|
19
|
+
| `flatBarGeometry` | module | — | SegmentedBar (Workout), SetBarChart | no — by path |
|
|
20
|
+
|
|
21
|
+
`SetBarChart` and `live-rep-growth` are deliberately absent from `index.ts`: they
|
|
22
|
+
are workout-internal and imported by path, so the public barrel stays one mark
|
|
23
|
+
wide. Adding them to the barrel is a decision, not a tidy-up.
|
|
24
|
+
|
|
25
|
+
## What each owns
|
|
26
|
+
|
|
27
|
+
**`SetBarChart`** owns the chart _geometry_ — value→height scaling, the shared
|
|
28
|
+
`scaleMax` override, plot-width-driven adaptive bar width and gap stepping, value
|
|
29
|
+
label thinning — plus the shared bar _language_: paper treatment, the
|
|
30
|
+
grow-from-bottom live rep, up/down orientation, the set-type slot vocabulary. Bar
|
|
31
|
+
colour and the reference overlay (velocity VL bands, ROM working/short lines) are
|
|
32
|
+
props. A spacing or entrance improvement therefore lands once for both consumers.
|
|
33
|
+
Value-height only: `VelocityStrip`'s flat 3 px `mini` strip stays its own mark.
|
|
34
|
+
|
|
35
|
+
**`SparkBars`** is a signed-series sparkline: bars over a baseline, negatives in
|
|
36
|
+
`result-degrade`, a `MIN_BAR_HEIGHT` floor so an almost-empty series still reads
|
|
37
|
+
as a series, and `maxBars` because a sparkline is a recent-history glance.
|
|
38
|
+
|
|
39
|
+
**`live-rep-growth`** is the newest-rep entrance, promoted out of `VelocityStrip`
|
|
40
|
+
so every value-height family animates the live rep identically. It carries the
|
|
41
|
+
12% `PEAK_OVERSHOOT` for a new-peak bar and honours `prefers-reduced-motion`.
|
|
42
|
+
|
|
43
|
+
**`flatBarGeometry`** is the single source for the default height/gap/radius
|
|
44
|
+
`SegmentedBar` (set-level) and `SetBarChart` (rep-level) each declare, documented
|
|
45
|
+
as intentionally different sizes rather than left to drift apart unnoticed (VW-86).
|
|
46
|
+
|
|
47
|
+
## Reuse audit
|
|
48
|
+
|
|
49
|
+
| Leaf | Should compose | Status |
|
|
50
|
+
| --------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| Bar geometry | `SetBarChart` | **Closed** for Fatigue and VelocityStrip's expanded charts; `VelocityStrip` mini is a deliberate exception. |
|
|
52
|
+
| Live-rep entrance | `live-rep-growth` | **Closed.** Both consumers use the hook. |
|
|
53
|
+
| Colour blending | `theme/color-math` | **Open.** `SetBarChart.tsx` carries a local `mixHex`. Roadmap decision 8 collapses the four copies into one module; this is one of them. |
|
|
54
|
+
| Surface-relative fill | `SurfaceContext` | **Closed.** `SetBarChart` reads `useSurface`/`useOnSurfaceColor` rather than assuming a plane. |
|
|
55
|
+
| Inline colour | `resolveColor(token)` | **Closed** for `SparkBars`; the family is in the token-pure eslint error block. |
|
|
56
|
+
|
|
57
|
+
## Watch list
|
|
58
|
+
|
|
59
|
+
- **`mixHex` duplication** (above) is the family's only open token debt. Until
|
|
60
|
+
decision 8 lands, do not add a third copy — import the one in `SetBarChart`.
|
|
61
|
+
- **`SetBarChart` has no story.** `SparkBars` does; the chart is only seen through
|
|
62
|
+
its consumers' stories (`Custom/Fatigue/*`, `Custom/Workout/VelocityStrip`).
|
|
63
|
+
That is why it reads `status:candidate` — see [`MATURITY.md`](../../../../MATURITY.md).
|
|
64
|
+
- **Both marks are `keep-internal`** in the arch graph: two in-repo consumers
|
|
65
|
+
each, zero external. Neither is public API yet; treat prop changes as cheap
|
|
66
|
+
while that holds.
|
|
@@ -1,11 +1,34 @@
|
|
|
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 {
|
|
4
|
+
import {
|
|
5
|
+
SetBarChart,
|
|
6
|
+
sideLabelText,
|
|
7
|
+
valueLabelFontSize,
|
|
8
|
+
estimateValueLabelWidth,
|
|
9
|
+
shouldFlipEdgeLabel,
|
|
10
|
+
scaleDenominator,
|
|
11
|
+
FIXED_MAX_VALUE,
|
|
12
|
+
PEAK_HEADROOM,
|
|
13
|
+
type SetSlot,
|
|
14
|
+
} from './SetBarChart'
|
|
5
15
|
|
|
6
16
|
const reps = (values: number[]): SetSlot[] => values.map((v) => ({ kind: 'rep', value: v }))
|
|
7
17
|
const silver = () => '#C7CBD1'
|
|
8
18
|
|
|
19
|
+
describe('scaleDenominator', () => {
|
|
20
|
+
it('gives `fixed` the same headroom band above its ceiling as `peak` gets above its max', () => {
|
|
21
|
+
// Pinned: without this headroom, a rep AT the fixed ceiling fills the plot with zero margin
|
|
22
|
+
// above it, unlike an equally-tall `peak` bar (which always keeps PEAK_HEADROOM's margin).
|
|
23
|
+
expect(scaleDenominator('fixed', 0)).toBeCloseTo(FIXED_MAX_VALUE * PEAK_HEADROOM)
|
|
24
|
+
expect(scaleDenominator('peak', 1)).toBeCloseTo(1 * PEAK_HEADROOM)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('ignores the performed max for `fixed` — the ceiling stays constant across sets', () => {
|
|
28
|
+
expect(scaleDenominator('fixed', 0.5)).toBe(scaleDenominator('fixed', 100))
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
9
32
|
describe('valueLabelFontSize', () => {
|
|
10
33
|
it('reads the full 12px at the standard single-hero (220) and dual-wing (110) scales', () => {
|
|
11
34
|
expect(valueLabelFontSize(220)).toBe(12)
|
|
@@ -18,6 +41,28 @@ describe('valueLabelFontSize', () => {
|
|
|
18
41
|
})
|
|
19
42
|
})
|
|
20
43
|
|
|
44
|
+
describe('shouldFlipEdgeLabel (TD-07.10)', () => {
|
|
45
|
+
it.each([
|
|
46
|
+
// [roomPx, labelWidthPx, expected]
|
|
47
|
+
[30, 20, false], // half-width (10) comfortably clears the room (30)
|
|
48
|
+
[30, 70, true], // half-width (35) exceeds the room (30)
|
|
49
|
+
[10, 20, false], // half-width (10) exactly equals the room — still a fit (not flipped)
|
|
50
|
+
[9, 20, true], // half-width (10) just exceeds the room (9)
|
|
51
|
+
])('roomPx=%d labelWidthPx=%d -> flip=%s', (roomPx, labelWidthPx, expected) => {
|
|
52
|
+
expect(shouldFlipEdgeLabel(roomPx, labelWidthPx)).toBe(expected)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('estimateValueLabelWidth', () => {
|
|
57
|
+
it('scales with both character count and font size', () => {
|
|
58
|
+
expect(estimateValueLabelWidth('1.02', 12)).toBeCloseTo(4 * 12 * 0.62)
|
|
59
|
+
expect(estimateValueLabelWidth('1.02', 8)).toBeLessThan(estimateValueLabelWidth('1.02', 12))
|
|
60
|
+
expect(estimateValueLabelWidth('10.02', 12)).toBeGreaterThan(
|
|
61
|
+
estimateValueLabelWidth('1.02', 12)
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
21
66
|
describe('sideLabelText', () => {
|
|
22
67
|
it('keeps the full name when the vertical extent has room', () => {
|
|
23
68
|
expect(sideLabelText('Left Arm', 200)).toBe('Left Arm')
|
|
@@ -23,6 +23,7 @@ import { primitiveRamps } from '../../../theme/tokens/primitives'
|
|
|
23
23
|
import { barPaper } from '../../../theme/materials'
|
|
24
24
|
import { useOnSurfaceColor, useSurface, surfaceBackground } from '../../ui/surface/SurfaceContext'
|
|
25
25
|
import { useLiveRepGrowth } from './live-rep-growth'
|
|
26
|
+
import { REP_LEVEL_FLAT_BAR } from './flatBarGeometry'
|
|
26
27
|
|
|
27
28
|
/** Linear-blend two #RRGGBB hexes (`t`=0 → a, 1 → b) — the surface-relative solid to-do tone. */
|
|
28
29
|
function mixHex(a: string, b: string, t: number): string {
|
|
@@ -133,6 +134,14 @@ export interface SetBarChartProps {
|
|
|
133
134
|
showValueLabels?: boolean
|
|
134
135
|
/** Format a rep value for its label. Default `String`. */
|
|
135
136
|
formatValue?: (value: number) => string
|
|
137
|
+
/**
|
|
138
|
+
* The LAST column has no sibling to its right to absorb an overflowing value label, so a wide
|
|
139
|
+
* label there can bleed past the plot's right edge and crowd the reference line that also runs
|
|
140
|
+
* flush to it (TD-07.10). Default false (unchanged for every existing consumer — the single
|
|
141
|
+
* hero and ROM chart). When true, that column's label flips from centered to right-anchored
|
|
142
|
+
* (grows left) once {@link shouldFlipEdgeLabel} says it doesn't fit.
|
|
143
|
+
*/
|
|
144
|
+
flipEdgeLabel?: boolean
|
|
136
145
|
/**
|
|
137
146
|
* To-do placeholder treatment. `solid` (default) — a solid surface-relative section (the plane
|
|
138
147
|
* blended toward the on-surface neutral). `dashed` — a dashed outline stub. Exists so ROM can keep
|
|
@@ -180,13 +189,21 @@ export interface SetBarChartProps {
|
|
|
180
189
|
}
|
|
181
190
|
|
|
182
191
|
/** The `scale="fixed"` velocity ceiling (m/s) — a bar height reads the same absolute value across sets. */
|
|
183
|
-
const FIXED_MAX_VALUE = 1.15
|
|
184
|
-
/**
|
|
192
|
+
export const FIXED_MAX_VALUE = 1.15
|
|
193
|
+
/**
|
|
194
|
+
* Headroom above the scaling ceiling: just enough to seat the peak bar's value label without a big
|
|
195
|
+
* empty band on top. Applied to BOTH scales — `peak` already multiplies the performed max by this,
|
|
196
|
+
* but `fixed` used the bare ceiling with none: a rep AT or PAST `FIXED_MAX_VALUE` (a fast lift on a
|
|
197
|
+
* live wall, not just a theoretical edge) filled the plot to its very top with no room left for that
|
|
198
|
+
* bar's own label, while an equally-tall `peak` bar always kept this margin. Multiplying `fixed`'s
|
|
199
|
+
* denominator by the same constant gives both scales the identical breathing room above their tallest
|
|
200
|
+
* bar, so a lift at the ceiling reads the same on either scale.
|
|
201
|
+
*/
|
|
185
202
|
export const PEAK_HEADROOM = 1.03
|
|
186
203
|
|
|
187
204
|
/** The height-scaling denominator for `scale` at the given performed max (guarded ≥ 0 by callers). */
|
|
188
205
|
export function scaleDenominator(scale: 'peak' | 'fixed', maxValue: number): number {
|
|
189
|
-
return scale === 'fixed' ? FIXED_MAX_VALUE : maxValue * PEAK_HEADROOM
|
|
206
|
+
return (scale === 'fixed' ? FIXED_MAX_VALUE : maxValue) * PEAK_HEADROOM
|
|
190
207
|
}
|
|
191
208
|
|
|
192
209
|
/**
|
|
@@ -198,6 +215,25 @@ export function valueLabelFontSize(height: number): number {
|
|
|
198
215
|
return Math.round(Math.max(8, Math.min(12, height * 0.11)))
|
|
199
216
|
}
|
|
200
217
|
|
|
218
|
+
/** Average glyph width for the bold numeric value-label font, as a fraction of its font size — a
|
|
219
|
+
* measurement-free estimate (RN text can't be synchronously measured) good enough to catch overflow. */
|
|
220
|
+
const LABEL_GLYPH_WIDTH_RATIO = 0.62
|
|
221
|
+
|
|
222
|
+
/** Estimate a value label's rendered width (px) from its formatted text and font size. */
|
|
223
|
+
export function estimateValueLabelWidth(text: string, fontSize: number): number {
|
|
224
|
+
return text.length * fontSize * LABEL_GLYPH_WIDTH_RATIO
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether a value label needs to flip from its default centered anchor to a right-anchored
|
|
229
|
+
* (left-growing) placement to clear the plot's right edge (TD-07.10). `roomPx` is the space
|
|
230
|
+
* available to the right of the label's centered position; `labelWidthPx` is its measured or
|
|
231
|
+
* estimated rendered width.
|
|
232
|
+
*/
|
|
233
|
+
export function shouldFlipEdgeLabel(roomPx: number, labelWidthPx: number): boolean {
|
|
234
|
+
return labelWidthPx / 2 > roomPx
|
|
235
|
+
}
|
|
236
|
+
|
|
201
237
|
// --- Hero geometry -----------------------------------------------------------
|
|
202
238
|
/** Default plot height (px) — tall enough to read a set's shape across a room. */
|
|
203
239
|
export const SET_BAR_DEFAULT_HEIGHT = 220
|
|
@@ -208,7 +244,7 @@ const LABEL_GAP = 3
|
|
|
208
244
|
/** Inter-bar gap as a fraction of bar width — the locked dense default (near expanded density). */
|
|
209
245
|
export const GAP_RATIO = 0.08
|
|
210
246
|
/** Floor on the proportional inter-bar gap (px) — narrow plots tighten to ~expanded density. */
|
|
211
|
-
const MIN_BAR_GAP =
|
|
247
|
+
const MIN_BAR_GAP = REP_LEVEL_FLAT_BAR.gapFloor
|
|
212
248
|
/**
|
|
213
249
|
* The EXTRA margin at a chunk boundary (drop sub-load / myo cluster / cluster intra-rest), as a
|
|
214
250
|
* fraction of bar width — proportional so it scales with the dense spacing and reads as a clear
|
|
@@ -223,7 +259,7 @@ export const BAR_MAX_WIDTH = 120
|
|
|
223
259
|
/** Below this per-bar width the value labels collide, so all but the peak + live rep are dropped. */
|
|
224
260
|
const LABEL_MIN_BAR_WIDTH = 30
|
|
225
261
|
/** Default top-corner radius on bars (px). */
|
|
226
|
-
const DEFAULT_BAR_RADIUS =
|
|
262
|
+
const DEFAULT_BAR_RADIUS = REP_LEVEL_FLAT_BAR.radius
|
|
227
263
|
/** Minimum drawn height of a performed bar (px) — a near-zero rep still reads as a rep. */
|
|
228
264
|
const MIN_BAR_HEIGHT = 4
|
|
229
265
|
/** In `flat` (compact) mode every rep bar is this fraction of the plot height — a uniform short bar. */
|
|
@@ -281,6 +317,7 @@ export function SetBarChart({
|
|
|
281
317
|
flat = false,
|
|
282
318
|
showValueLabels: showValueLabelsProp = false,
|
|
283
319
|
formatValue = String,
|
|
320
|
+
flipEdgeLabel = false,
|
|
284
321
|
todoVariant = 'solid',
|
|
285
322
|
minColumns,
|
|
286
323
|
renderReference,
|
|
@@ -462,6 +499,17 @@ export function SetBarChart({
|
|
|
462
499
|
// duplicate them); its own label shows otherwise, fading in with `expandProgress`.
|
|
463
500
|
const ownLabel = showValueLabels && !renderBarOverlay && showBarLabel(repIndex)
|
|
464
501
|
const labelBottom = (expandProgress ? valueBarHeight(best) : barHeight(best)) + LABEL_GAP
|
|
502
|
+
// Only the LAST cell has no sibling to its right to absorb an overflowing label
|
|
503
|
+
// (TD-07.10) — flip it right-anchored (grows left) when it doesn't fit.
|
|
504
|
+
const isLastCell = i === cells.length - 1
|
|
505
|
+
const labelText = formatValue(value)
|
|
506
|
+
const flipLabel =
|
|
507
|
+
flipEdgeLabel &&
|
|
508
|
+
isLastCell &&
|
|
509
|
+
shouldFlipEdgeLabel(
|
|
510
|
+
barWidth / 2,
|
|
511
|
+
estimateValueLabelWidth(labelText, valueLabelFontSize(height))
|
|
512
|
+
)
|
|
465
513
|
return (
|
|
466
514
|
<View key={i} accessibilityElementsHidden={renderBarOverlay == null} style={column}>
|
|
467
515
|
{ownLabel && (
|
|
@@ -471,9 +519,8 @@ export function SetBarChart({
|
|
|
471
519
|
style={{
|
|
472
520
|
position: 'absolute',
|
|
473
521
|
bottom: labelBottom,
|
|
474
|
-
left: 0,
|
|
475
|
-
|
|
476
|
-
alignItems: 'center',
|
|
522
|
+
...(flipLabel ? { right: 0 } : { left: 0, right: 0 }),
|
|
523
|
+
alignItems: flipLabel ? 'flex-end' : 'center',
|
|
477
524
|
...(expandProgress ? { opacity: expandProgress } : null),
|
|
478
525
|
}}
|
|
479
526
|
pointerEvents="none"
|
|
@@ -483,7 +530,7 @@ export function SetBarChart({
|
|
|
483
530
|
style={[{ fontSize: valueLabelFontSize(height), fontWeight: '700' }, flipStyle]}
|
|
484
531
|
testID={`${testIDPrefix}-label-${repIndex}`}
|
|
485
532
|
>
|
|
486
|
-
{
|
|
533
|
+
{labelText}
|
|
487
534
|
</Text>
|
|
488
535
|
</Animated.View>
|
|
489
536
|
)}
|