@titan-design/react-ui 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -47
- package/dist/bodymap.js +768 -701
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +770 -703
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1320 -215
- package/dist/index.d.ts +1320 -215
- package/dist/index.js +5996 -4498
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5773 -4357
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-CvsFqNtx.d.mts → pages-CpW3OB3g.d.mts} +21 -3
- package/dist/{pages-B1hqnGIC.d.ts → pages-_3Ajpiqu.d.ts} +21 -3
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1435 -1428
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1443 -1436
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +202 -1042
- package/dist/theme/index.d.ts +202 -1042
- package/dist/theme/index.js +215 -300
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +202 -296
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +62 -5
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +62 -5
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +972 -0
- package/dist/theme/tokens.d.ts +972 -0
- package/dist/theme/tokens.js +904 -0
- package/dist/theme/tokens.js.map +1 -0
- package/dist/theme/tokens.mjs +880 -0
- package/dist/theme/tokens.mjs.map +1 -0
- package/docs/audits/2026-09-08-critique-addenda.md +51 -0
- package/docs/audits/2026-09-08-library-critique.md +212 -0
- package/docs/library-roadmap.md +99 -0
- package/package.json +7 -1
- package/src/arch/arch-graph.freshness.test.ts +52 -0
- package/src/arch/arch-graph.json +5021 -1617
- package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
- package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
- package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
- package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
- package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
- package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
- package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
- package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
- package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
- package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
- package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
- package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +181 -0
- package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
- package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
- package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
- package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
- package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
- package/src/components/custom/ActiveWork/README.md +305 -0
- package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
- package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
- package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
- package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
- package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
- package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
- package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
- package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
- package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
- package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
- package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
- package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
- package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
- package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
- package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
- package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
- package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
- package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
- package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
- package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
- package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
- package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
- package/src/components/custom/ActiveWork/format-time.ts +32 -0
- package/src/components/custom/ActiveWork/index.ts +19 -0
- package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
- package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
- package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
- package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +4 -3
- package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
- package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
- package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
- package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
- package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
- package/src/components/custom/Fatigue/README.md +40 -3
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
- package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
- package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
- package/src/components/custom/Fatigue/index.ts +17 -5
- package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
- package/src/components/custom/Fatigue/panel-layout.ts +185 -0
- package/src/components/custom/Gauge/Gauge.stories.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.tsx +4 -1
- package/src/components/custom/Metric/Metric.stories.tsx +10 -1
- package/src/components/custom/Metric/Metric.test.tsx +1 -3
- package/src/components/custom/Metric/Metric.tsx +5 -20
- package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
- package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
- package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
- package/src/components/custom/Prose/README.md +58 -0
- package/src/components/custom/Prose/index.ts +1 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +46 -8
- package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
- package/src/components/custom/Scatter/Scatter.tsx +116 -14
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +40 -7
- package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
- package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
- package/src/components/custom/Table/Table.stories.tsx +98 -15
- package/src/components/custom/Table/Table.test.tsx +273 -86
- package/src/components/custom/Table/Table.tsx +159 -67
- package/src/components/custom/Table/column-fit.test.ts +75 -0
- package/src/components/custom/Table/column-fit.ts +116 -0
- package/src/components/custom/Table/index.ts +9 -0
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
- package/src/components/custom/Treemap/Treemap.stories.tsx +2 -2
- package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +7 -2
- package/src/components/custom/Typography/Typography.stories.tsx +28 -1
- package/src/components/custom/Typography/Typography.test.tsx +34 -0
- package/src/components/custom/Typography/Typography.tsx +6 -0
- package/src/components/custom/Typography/index.ts +1 -8
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +9 -1
- package/src/components/custom/Workout/BaseBadge.stories.tsx +19 -7
- package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
- package/src/components/custom/Workout/BaseBadge.tsx +3 -0
- package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
- package/src/components/custom/Workout/BodyMap.tsx +3 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +16 -4
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +45 -9
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +10 -8
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
- package/src/components/custom/Workout/CapacityBandChart.tsx +139 -108
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
- package/src/components/custom/Workout/DeviationBar.tsx +21 -16
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +182 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +3 -2
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -7
- package/src/components/custom/Workout/ExerciseCard.test.tsx +13 -3
- package/src/components/custom/Workout/ExerciseCard.tsx +42 -51
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +7 -7
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseHeading.tsx +8 -5
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -4
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
- package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
- package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
- package/src/components/custom/Workout/IntensityBar.tsx +4 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
- package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
- package/src/components/custom/Workout/MesoCard.tsx +12 -9
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
- package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
- package/src/components/custom/Workout/MesoStatusCard.tsx +9 -23
- package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
- package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
- package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
- package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
- package/src/components/custom/Workout/PrBadge.tsx +13 -17
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +4 -1
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
- package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
- package/src/components/custom/Workout/README.md +12 -7
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
- package/src/components/custom/Workout/ReadinessCheck.tsx +19 -3
- package/src/components/custom/Workout/RestTimer.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
- package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SegmentedBar.tsx +6 -2
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SessionHeader.tsx +12 -12
- package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
- package/src/components/custom/Workout/SessionRail.tsx +7 -9
- package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
- package/src/components/custom/Workout/SetBar.test.tsx +49 -4
- package/src/components/custom/Workout/SetBar.tsx +38 -12
- package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
- package/src/components/custom/Workout/SetRow.test.tsx +16 -2
- package/src/components/custom/Workout/SetRow.tsx +61 -23
- package/src/components/custom/Workout/SetStrip.stories.tsx +60 -5
- package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
- package/src/components/custom/Workout/SetStrip.tsx +24 -4
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
- package/src/components/custom/Workout/SetsRepsLoad.tsx +3 -5
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
- package/src/components/custom/Workout/Sparkline.tsx +14 -18
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
- package/src/components/custom/Workout/StatusDot.tsx +93 -63
- package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
- package/src/components/custom/Workout/StatusPill.tsx +27 -38
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +9 -3
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
- package/src/components/custom/Workout/StrengthTrendChart.tsx +31 -30
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
- package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +22 -7
- package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +7 -4
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +3 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
- package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
- package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
- package/src/components/custom/Workout/WeekRow.tsx +23 -16
- package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
- package/src/components/custom/Workout/WeightBadge.tsx +28 -27
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
- package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
- package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +7 -6
- package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
- package/src/components/custom/Workout/index.ts +5 -0
- package/src/components/custom/Workout/metricText.stories.tsx +6 -5
- package/src/components/custom/Workout/metricText.tsx +4 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +207 -26
- package/src/components/custom/charts/README.md +61 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +30 -1
- package/src/components/custom/charts/SetBarChart.tsx +42 -4
- package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
- package/src/components/custom/charts/SparkBars.test.tsx +69 -0
- package/src/components/custom/charts/SparkBars.tsx +85 -0
- package/src/components/custom/charts/index.ts +3 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
- package/src/components/custom/stepper/Stepper.test.tsx +1 -7
- package/src/components/custom/stepper/Stepper.tsx +13 -28
- package/src/components/icons/icons.stories.tsx +1 -1
- package/src/components/icons/icons.tsx +51 -0
- package/src/components/shell/AppShell.stories.tsx +150 -0
- package/src/components/shell/AppShell.test.tsx +70 -0
- package/src/components/shell/AppShell.tsx +90 -0
- package/src/components/shell/BrandLockup.stories.tsx +66 -5
- package/src/components/shell/BrandLockup.test.tsx +34 -1
- package/src/components/shell/BrandLockup.tsx +27 -11
- package/src/components/shell/NavItem.stories.tsx +4 -4
- package/src/components/shell/NavItem.test.tsx +17 -0
- package/src/components/shell/NavItem.tsx +17 -5
- package/src/components/shell/README.md +90 -27
- package/src/components/shell/SideNav.stories.tsx +38 -7
- package/src/components/shell/SideNav.test.tsx +16 -8
- package/src/components/shell/SideNav.tsx +17 -17
- package/src/components/shell/TopBar.stories.tsx +44 -19
- package/src/components/shell/TopBar.test.tsx +34 -12
- package/src/components/shell/TopBar.tsx +61 -40
- package/src/components/shell/brands.tsx +66 -0
- package/src/components/shell/index.ts +9 -8
- package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
- package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
- package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
- package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
- package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
- package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
- package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
- package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
- package/src/components/shell/workout/WorkoutShell.tsx +77 -0
- package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
- package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
- package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
- package/src/components/shell/workout/index.ts +9 -0
- package/src/components/shell/workout/workoutNavItems.tsx +16 -0
- package/src/components/ui/README.md +110 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
- package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -10
- package/src/components/ui/badge/Badge.stories.tsx +83 -28
- package/src/components/ui/badge/Badge.test.tsx +19 -3
- package/src/components/ui/badge/Badge.tsx +48 -55
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
- package/src/components/ui/button/Button.stories.tsx +46 -12
- package/src/components/ui/button/Button.tsx +32 -25
- package/src/components/ui/card/Card.stories.tsx +90 -94
- package/src/components/ui/card/Card.test.tsx +80 -6
- package/src/components/ui/card/Card.tsx +150 -203
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +6 -26
- package/src/components/ui/chip/Chip.stories.tsx +37 -13
- package/src/components/ui/chip/Chip.test.tsx +10 -6
- package/src/components/ui/chip/Chip.tsx +76 -91
- package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
- package/src/components/ui/collapse/Collapse.tsx +6 -11
- package/src/components/ui/data-row/DataRow.stories.tsx +9 -2
- package/src/components/ui/data-row/DataRow.test.tsx +5 -15
- package/src/components/ui/data-row/DataRow.tsx +18 -7
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.tsx +1 -5
- package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
- package/src/components/ui/drawer/Drawer.tsx +13 -30
- package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
- package/src/components/ui/form-field/FormField.stories.tsx +6 -20
- package/src/components/ui/form-field/FormField.test.tsx +1 -5
- package/src/components/ui/form-field/FormField.tsx +10 -46
- package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
- package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
- package/src/components/ui/help-tip/HelpTip.tsx +21 -22
- package/src/components/ui/help-tip/index.ts +7 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
- package/src/components/ui/icon-box/IconBox.tsx +2 -12
- package/src/components/ui/indicator/Indicator.stories.tsx +1 -1
- package/src/components/ui/indicator/Indicator.tsx +20 -14
- package/src/components/ui/input/Input.stories.tsx +3 -11
- package/src/components/ui/input/Input.test.tsx +2 -10
- package/src/components/ui/input/Input.tsx +10 -25
- package/src/components/ui/input/index.ts +7 -1
- package/src/components/ui/link/Link.stories.tsx +19 -7
- package/src/components/ui/list-item/ListItem.stories.tsx +11 -2
- package/src/components/ui/list-item/ListItem.tsx +2 -9
- package/src/components/ui/menu/Menu.stories.tsx +21 -15
- package/src/components/ui/menu/Menu.test.tsx +2 -11
- package/src/components/ui/menu/Menu.tsx +12 -14
- package/src/components/ui/menu/index.ts +8 -1
- package/src/components/ui/modal/Modal.stories.tsx +2 -2
- package/src/components/ui/modal/Modal.tsx +29 -19
- package/src/components/ui/pill/Pill.stories.tsx +58 -23
- package/src/components/ui/pill/Pill.test.tsx +101 -3
- package/src/components/ui/pill/Pill.tsx +164 -49
- package/src/components/ui/pill/index.ts +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +5 -7
- package/src/components/ui/popover/Popover.test.tsx +1 -6
- package/src/components/ui/popover/Popover.tsx +20 -15
- package/src/components/ui/popover/index.ts +7 -1
- package/src/components/ui/progress/Progress.stories.tsx +3 -12
- package/src/components/ui/progress/Progress.test.tsx +1 -5
- package/src/components/ui/radio/Radio.stories.tsx +42 -21
- package/src/components/ui/radio/Radio.test.tsx +6 -2
- package/src/components/ui/radio/Radio.tsx +23 -22
- package/src/components/ui/section/Section.stories.tsx +5 -14
- package/src/components/ui/section/Section.test.tsx +1 -6
- package/src/components/ui/section/Section.tsx +1 -3
- package/src/components/ui/section/index.ts +1 -5
- package/src/components/ui/select/Select.stories.tsx +3 -13
- package/src/components/ui/select/Select.test.tsx +2 -11
- package/src/components/ui/select/Select.tsx +14 -14
- package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
- package/src/components/ui/skeleton/Skeleton.tsx +3 -14
- package/src/components/ui/skeleton/index.ts +1 -7
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +1 -4
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +17 -5
- package/src/components/ui/surface/Surface.test.tsx +88 -28
- package/src/components/ui/surface/Surface.tsx +37 -87
- package/src/components/ui/surface/SurfaceContext.ts +22 -62
- package/src/components/ui/surface/index.ts +8 -0
- package/src/components/ui/surface/resolveSurface.ts +98 -0
- package/src/components/ui/surface/surface.contract.test.ts +6 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.tsx +7 -6
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +7 -22
- package/src/components/ui/tabs/index.ts +1 -7
- package/src/components/ui/tile/Tile.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.tsx +3 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toast/Toast.test.tsx +2 -8
- package/src/components/ui/toast/Toast.tsx +43 -53
- package/src/components/ui/toast/index.ts +0 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +25 -43
- package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
- package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
- package/src/components/ui/tooltip/Tooltip.tsx +55 -36
- package/src/components/ui/tooltip/index.ts +1 -1
- package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
- package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
- package/src/components/ui/trigger/index.ts +2 -0
- package/src/stories/PresetShowcase.stories.tsx +100 -47
- package/src/test/no-device-internals.test.ts +3 -3
- package/src/theme/ChoosingTokens.stories.tsx +48 -0
- package/src/theme/ColorPalettes.stories.tsx +62 -45
- package/src/theme/ColorPrimitives.stories.tsx +35 -35
- package/src/theme/Depth.stories.tsx +132 -65
- package/src/theme/DepthCalibration.stories.tsx +24 -22
- package/src/theme/ThemeProvider.test.tsx +4 -4
- package/src/theme/barrel.ts +7 -6
- package/src/theme/color-stories.coverage.test.ts +1 -1
- package/src/theme/color-utils.ts +41 -29
- package/src/theme/config.completeness.test.ts +1 -1
- package/src/theme/config.ts +28 -0
- package/src/theme/elevation.test.ts +82 -56
- package/src/theme/elevation.ts +77 -561
- package/src/theme/global.css +30 -2
- package/src/theme/grey-ramp.test.ts +24 -8
- package/src/theme/lift.test.ts +39 -0
- package/src/theme/lift.ts +102 -0
- package/src/theme/materials.test.ts +5 -2
- package/src/theme/materials.ts +38 -4
- package/src/theme/native-theming-guard.test.ts +5 -5
- package/src/theme/presets/apply.ts +4 -0
- package/src/theme/presets/audiobook.ts +5 -2
- package/src/theme/presets/default.ts +4 -0
- package/src/theme/presets/index.ts +14 -1
- package/src/theme/presets/types.ts +1 -1
- package/src/theme/semantic-contrast.test.ts +13 -4
- package/src/theme/status-distinctness.test.ts +66 -0
- package/src/theme/surface-planes.test.ts +37 -0
- package/src/theme/surface-planes.ts +68 -0
- package/src/theme/token-export-guard.test.ts +102 -0
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +67 -35
- package/src/theme/tokens/semantic.ts +71 -37
- package/src/theme/wash-ladder.test.ts +58 -0
- package/src/theme/workout-tokens.ts +1 -7
- package/src/types/jest-axe.d.ts +2 -6
- package/src/types/raw-md.d.ts +5 -0
- package/src/utils/colors.ts +4 -4
- package/src/utils/form.ts +1 -3
- package/src/utils/index.ts +2 -7
- package/src/utils/number-format.ts +17 -0
- package/src/utils/workout-format.ts +15 -6
- package/src/web-jsx/jsx-runtime.ts +1 -5
- package/tailwind.config.js +5 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
- package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
- package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
- package/src/components/shell/DashboardShell.stories.tsx +0 -65
- package/src/components/shell/DashboardShell.tsx +0 -75
- package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
- package/src/theme/elevation.stories.tsx +0 -346
- package/src/utils/useTheme.ts +0 -72
- /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
- /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
- /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
- /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
|
@@ -33,7 +33,8 @@ export const RED = '#D14343' // red 600
|
|
|
33
33
|
export const ORANGE = '#FF7900' // orange 400
|
|
34
34
|
export const AMBER = '#F9B415' // amber 300
|
|
35
35
|
export const GREEN = '#2ED573' // green 300
|
|
36
|
-
export const velColor = (v: number): string =>
|
|
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 = `
|
|
@@ -42,7 +43,16 @@ export const PULSE_CSS = `
|
|
|
42
43
|
@keyframes s3pAmber{0%,100%{background:#FFD352}50%{background:#E08C00}}
|
|
43
44
|
@keyframes s3pGreen{0%,100%{background:#58F69E}50%{background:#21C05D}}`
|
|
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
|
|
|
@@ -50,9 +60,23 @@ export const pulseAnim = (c: string): string | undefined => {
|
|
|
50
60
|
const INTER = 'Inter, sans-serif'
|
|
51
61
|
const SRL_SEP = '#6B7280'
|
|
52
62
|
function SRLCell({ children, color = T_PRIMARY }: { children: ReactNode; color?: string }) {
|
|
53
|
-
return
|
|
63
|
+
return (
|
|
64
|
+
<span style={{ fontFamily: INTER, fontSize: 11, fontWeight: 600, letterSpacing: 1, color }}>
|
|
65
|
+
{children}
|
|
66
|
+
</span>
|
|
67
|
+
)
|
|
54
68
|
}
|
|
55
|
-
export function SetsRepsLoad({
|
|
69
|
+
export function SetsRepsLoad({
|
|
70
|
+
sets,
|
|
71
|
+
reps,
|
|
72
|
+
load,
|
|
73
|
+
unit = 'lb',
|
|
74
|
+
}: {
|
|
75
|
+
sets: number
|
|
76
|
+
reps: number | string
|
|
77
|
+
load: number
|
|
78
|
+
unit?: string
|
|
79
|
+
}) {
|
|
56
80
|
return (
|
|
57
81
|
<span style={{ display: 'inline-flex', alignItems: 'baseline', whiteSpace: 'nowrap' }}>
|
|
58
82
|
<SRLCell>{sets}</SRLCell>
|
|
@@ -66,7 +90,10 @@ export function SetsRepsLoad({ sets, reps, load, unit = 'lb' }: { sets: number;
|
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
// ---- per-set bar: one continuous bar, rep intensities as butted segments (no gaps); active segments pulse
|
|
69
|
-
export type SetState =
|
|
93
|
+
export type SetState =
|
|
94
|
+
| { status: 'done'; reps: number[] }
|
|
95
|
+
| { status: 'active'; reps: number[]; planned: number }
|
|
96
|
+
| { status: 'todo'; planned: number }
|
|
70
97
|
export function setColors(s: SetState): string[] {
|
|
71
98
|
if (s.status === 'todo') return [GREY]
|
|
72
99
|
if (s.status === 'done') return s.reps.map(velColor)
|
|
@@ -74,9 +101,26 @@ export function setColors(s: SetState): string[] {
|
|
|
74
101
|
}
|
|
75
102
|
function SetBar({ colors, h, pulse }: { colors: string[]; h: number; pulse?: boolean }) {
|
|
76
103
|
return (
|
|
77
|
-
<div
|
|
104
|
+
<div
|
|
105
|
+
style={{
|
|
106
|
+
display: 'flex',
|
|
107
|
+
flex: 1,
|
|
108
|
+
minWidth: 0,
|
|
109
|
+
height: h,
|
|
110
|
+
borderRadius: 2,
|
|
111
|
+
overflow: 'hidden',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
78
114
|
{colors.map((c, i) => (
|
|
79
|
-
<div
|
|
115
|
+
<div
|
|
116
|
+
key={i}
|
|
117
|
+
style={{
|
|
118
|
+
flex: 1,
|
|
119
|
+
height: '100%',
|
|
120
|
+
background: c,
|
|
121
|
+
animation: pulse && c !== GREY ? pulseAnim(c) : undefined,
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
80
124
|
))}
|
|
81
125
|
</div>
|
|
82
126
|
)
|
|
@@ -95,17 +139,48 @@ export function Indicator({ kind }: { kind: 'pr' | 'issue' | 'info' }) {
|
|
|
95
139
|
const map = { pr: ['★', ORANGE], issue: ['!', RED], info: ['i', '#2196F3'] } as const
|
|
96
140
|
const [glyph, color] = map[kind]
|
|
97
141
|
return (
|
|
98
|
-
<span
|
|
142
|
+
<span
|
|
143
|
+
style={{
|
|
144
|
+
width: 16,
|
|
145
|
+
height: 16,
|
|
146
|
+
borderRadius: 8,
|
|
147
|
+
border: `1px solid ${color}`,
|
|
148
|
+
color,
|
|
149
|
+
fontSize: 10,
|
|
150
|
+
fontWeight: 800,
|
|
151
|
+
display: 'inline-flex',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{glyph}
|
|
157
|
+
</span>
|
|
99
158
|
)
|
|
100
159
|
}
|
|
101
160
|
|
|
102
161
|
const labelStyle = { fontSize: 14, fontWeight: 800, color: T_PRIMARY } as const
|
|
103
162
|
|
|
104
163
|
// ---- the finalized heading unit
|
|
105
|
-
export type Exercise = {
|
|
164
|
+
export type Exercise = {
|
|
165
|
+
name: string
|
|
166
|
+
sets: number
|
|
167
|
+
reps: number | string
|
|
168
|
+
load: number
|
|
169
|
+
tempo: [number, number, number, number]
|
|
170
|
+
indicator?: 'pr' | 'issue' | 'info'
|
|
171
|
+
setStates: SetState[]
|
|
172
|
+
upcoming?: boolean
|
|
173
|
+
}
|
|
106
174
|
export function Heading({ ex, stripH }: { ex: Exercise; stripH: number }) {
|
|
107
175
|
return (
|
|
108
|
-
<div
|
|
176
|
+
<div
|
|
177
|
+
style={{
|
|
178
|
+
padding: '9px 12px',
|
|
179
|
+
display: 'flex',
|
|
180
|
+
flexDirection: 'column',
|
|
181
|
+
opacity: ex.upcoming ? 0.55 : 1,
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
109
184
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
110
185
|
<span style={labelStyle}>{ex.name}</span>
|
|
111
186
|
<div style={{ flex: 1 }} />
|
|
@@ -124,21 +199,80 @@ export function Heading({ ex, stripH }: { ex: Exercise; stripH: number }) {
|
|
|
124
199
|
}
|
|
125
200
|
|
|
126
201
|
// ---- 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[] =>
|
|
202
|
+
export const reps = (n: number, start: number, span = 0.4): number[] =>
|
|
203
|
+
Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
|
|
128
204
|
export const SESSION: Exercise[] = [
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
205
|
+
{
|
|
206
|
+
name: 'Seated Cable Row',
|
|
207
|
+
sets: 5,
|
|
208
|
+
reps: 8,
|
|
209
|
+
load: 145,
|
|
210
|
+
tempo: [3, 1, 2, 0],
|
|
211
|
+
indicator: 'pr',
|
|
212
|
+
setStates: [1.0, 0.9, 0.8, 0.7, 0.6].map((s) => ({ status: 'done', reps: reps(8, s) })),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'Incline DB Press',
|
|
216
|
+
sets: 3,
|
|
217
|
+
reps: 8,
|
|
218
|
+
load: 70,
|
|
219
|
+
tempo: [3, 0, 3, 0],
|
|
220
|
+
setStates: [0.72, 0.62, 0.54].map((s) => ({ status: 'done', reps: reps(8, s) })),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'Cable Chest Press',
|
|
224
|
+
sets: 3,
|
|
225
|
+
reps: 10,
|
|
226
|
+
load: 90,
|
|
227
|
+
tempo: [2, 1, 2, 0],
|
|
228
|
+
indicator: 'info',
|
|
229
|
+
setStates: [
|
|
230
|
+
{ status: 'done', reps: reps(10, 0.72) },
|
|
231
|
+
{ status: 'active', reps: reps(5, 0.62), planned: 10 },
|
|
232
|
+
{ status: 'todo', planned: 10 },
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'Standing Calf Raise',
|
|
237
|
+
sets: 5,
|
|
238
|
+
reps: 20,
|
|
239
|
+
load: 25,
|
|
240
|
+
tempo: [2, 1, 2, 0],
|
|
241
|
+
upcoming: true,
|
|
242
|
+
setStates: [1, 2, 3, 4, 5].map(() => ({ status: 'todo', planned: 20 })),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'Face Pull',
|
|
246
|
+
sets: 3,
|
|
247
|
+
reps: '15-20',
|
|
248
|
+
load: 40,
|
|
249
|
+
tempo: [2, 1, 2, 0],
|
|
250
|
+
upcoming: true,
|
|
251
|
+
setStates: [1, 2, 3].map(() => ({ status: 'todo', planned: 18 })),
|
|
252
|
+
},
|
|
134
253
|
]
|
|
135
254
|
|
|
136
255
|
// ---- shell chrome (neumorphic): flat raised nav + heading plane; sunk inset exercise list
|
|
137
256
|
export function NavStub() {
|
|
138
257
|
return (
|
|
139
|
-
<div
|
|
258
|
+
<div
|
|
259
|
+
style={{
|
|
260
|
+
width: 60,
|
|
261
|
+
alignSelf: 'stretch',
|
|
262
|
+
position: 'relative',
|
|
263
|
+
zIndex: 2,
|
|
264
|
+
background: RAISED,
|
|
265
|
+
display: 'flex',
|
|
266
|
+
flexDirection: 'column',
|
|
267
|
+
alignItems: 'center',
|
|
268
|
+
paddingTop: 12,
|
|
269
|
+
gap: 8,
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
140
272
|
{['Live', 'Plan', 'Body'].map((t) => (
|
|
141
|
-
<span key={t} style={{ fontSize: 8, color: T_TERTIARY, fontWeight: 700 }}>
|
|
273
|
+
<span key={t} style={{ fontSize: 8, color: T_TERTIARY, fontWeight: 700 }}>
|
|
274
|
+
{t}
|
|
275
|
+
</span>
|
|
142
276
|
))}
|
|
143
277
|
</div>
|
|
144
278
|
)
|
|
@@ -146,17 +280,39 @@ export function NavStub() {
|
|
|
146
280
|
export function Rail({ stripH, footer }: { stripH: number; footer?: ReactNode }) {
|
|
147
281
|
return (
|
|
148
282
|
<div style={{ width: 246, background: INSET, display: 'flex', flexDirection: 'column' }}>
|
|
149
|
-
<div
|
|
150
|
-
|
|
283
|
+
<div
|
|
284
|
+
style={{ position: 'relative', zIndex: 2, padding: '11px 12px 10px', background: RAISED }}
|
|
285
|
+
>
|
|
286
|
+
<div
|
|
287
|
+
style={{
|
|
288
|
+
fontSize: 9,
|
|
289
|
+
fontWeight: 800,
|
|
290
|
+
letterSpacing: 1,
|
|
291
|
+
textTransform: 'uppercase',
|
|
292
|
+
color: T_TERTIARY,
|
|
293
|
+
display: 'flex',
|
|
294
|
+
alignItems: 'center',
|
|
295
|
+
gap: 5,
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
151
298
|
<span style={{ width: 6, height: 6, borderRadius: 3, background: GREEN }} />
|
|
152
299
|
Live session
|
|
153
300
|
</div>
|
|
154
|
-
<div style={{ fontSize: 14, fontWeight: 800, marginTop: 3, color: T_PRIMARY }}>
|
|
155
|
-
|
|
301
|
+
<div style={{ fontSize: 14, fontWeight: 800, marginTop: 3, color: T_PRIMARY }}>
|
|
302
|
+
Pull A · Intensification
|
|
303
|
+
</div>
|
|
304
|
+
<div style={{ fontSize: 10, color: T_SECONDARY, marginTop: 2, fontFamily: 'monospace' }}>
|
|
305
|
+
ex 3/5 · set 2 live
|
|
306
|
+
</div>
|
|
156
307
|
</div>
|
|
157
308
|
<div style={{ flex: 1, background: INSET, boxShadow: INSET_SHADOW }}>
|
|
158
309
|
{SESSION.map((ex, i) => (
|
|
159
|
-
<div
|
|
310
|
+
<div
|
|
311
|
+
key={i}
|
|
312
|
+
style={{
|
|
313
|
+
borderBottom: i < SESSION.length - 1 ? `1px solid ${BORDER_SUBTLE}` : undefined,
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
160
316
|
<Heading ex={ex} stripH={stripH} />
|
|
161
317
|
</div>
|
|
162
318
|
))}
|
|
@@ -167,9 +323,28 @@ export function Rail({ stripH, footer }: { stripH: number; footer?: ReactNode })
|
|
|
167
323
|
}
|
|
168
324
|
|
|
169
325
|
// ---- page scaffold shared by the sheets
|
|
170
|
-
export function Page({
|
|
326
|
+
export function Page({
|
|
327
|
+
title,
|
|
328
|
+
note,
|
|
329
|
+
children,
|
|
330
|
+
}: {
|
|
331
|
+
title: string
|
|
332
|
+
note: string
|
|
333
|
+
children: ReactNode
|
|
334
|
+
}) {
|
|
171
335
|
return (
|
|
172
|
-
<div
|
|
336
|
+
<div
|
|
337
|
+
style={{
|
|
338
|
+
minHeight: '100vh',
|
|
339
|
+
background: '#0A0A0A',
|
|
340
|
+
color: T_PRIMARY,
|
|
341
|
+
padding: 28,
|
|
342
|
+
display: 'flex',
|
|
343
|
+
flexDirection: 'column',
|
|
344
|
+
gap: 22,
|
|
345
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
173
348
|
<style>{PULSE_CSS}</style>
|
|
174
349
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
175
350
|
<span style={{ fontSize: 20, fontWeight: 800 }}>{title}</span>
|
|
@@ -179,5 +354,11 @@ export function Page({ title, note, children }: { title: string; note: string; c
|
|
|
179
354
|
</div>
|
|
180
355
|
)
|
|
181
356
|
}
|
|
182
|
-
export const sectionTitle = {
|
|
357
|
+
export const sectionTitle = {
|
|
358
|
+
fontSize: 11,
|
|
359
|
+
fontWeight: 800,
|
|
360
|
+
letterSpacing: 1.2,
|
|
361
|
+
textTransform: 'uppercase',
|
|
362
|
+
color: ORANGE,
|
|
363
|
+
} as const
|
|
183
364
|
export const monoTag = { fontFamily: 'monospace', fontSize: 11, color: T_SECONDARY } as const
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
|
|
20
|
+
`SetBarChart` and `live-rep-growth` are deliberately absent from `index.ts`: they
|
|
21
|
+
are workout-internal and imported by path, so the public barrel stays one mark
|
|
22
|
+
wide. Adding them to the barrel is a decision, not a tidy-up.
|
|
23
|
+
|
|
24
|
+
## What each owns
|
|
25
|
+
|
|
26
|
+
**`SetBarChart`** owns the chart _geometry_ — value→height scaling, the shared
|
|
27
|
+
`scaleMax` override, plot-width-driven adaptive bar width and gap stepping, value
|
|
28
|
+
label thinning — plus the shared bar _language_: paper treatment, the
|
|
29
|
+
grow-from-bottom live rep, up/down orientation, the set-type slot vocabulary. Bar
|
|
30
|
+
colour and the reference overlay (velocity VL bands, ROM working/short lines) are
|
|
31
|
+
props. A spacing or entrance improvement therefore lands once for both consumers.
|
|
32
|
+
Value-height only: `VelocityStrip`'s flat 3 px `mini` strip stays its own mark.
|
|
33
|
+
|
|
34
|
+
**`SparkBars`** is a signed-series sparkline: bars over a baseline, negatives in
|
|
35
|
+
`result-degrade`, a `MIN_BAR_HEIGHT` floor so an almost-empty series still reads
|
|
36
|
+
as a series, and `maxBars` because a sparkline is a recent-history glance.
|
|
37
|
+
|
|
38
|
+
**`live-rep-growth`** is the newest-rep entrance, promoted out of `VelocityStrip`
|
|
39
|
+
so every value-height family animates the live rep identically. It carries the
|
|
40
|
+
12% `PEAK_OVERSHOOT` for a new-peak bar and honours `prefers-reduced-motion`.
|
|
41
|
+
|
|
42
|
+
## Reuse audit
|
|
43
|
+
|
|
44
|
+
| Leaf | Should compose | Status |
|
|
45
|
+
| --------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| Bar geometry | `SetBarChart` | **Closed** for Fatigue and VelocityStrip's expanded charts; `VelocityStrip` mini is a deliberate exception. |
|
|
47
|
+
| Live-rep entrance | `live-rep-growth` | **Closed.** Both consumers use the hook. |
|
|
48
|
+
| 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. |
|
|
49
|
+
| Surface-relative fill | `SurfaceContext` | **Closed.** `SetBarChart` reads `useSurface`/`useOnSurfaceColor` rather than assuming a plane. |
|
|
50
|
+
| Inline colour | `resolveColor(token)` | **Closed** for `SparkBars`; the family is in the token-pure eslint error block. |
|
|
51
|
+
|
|
52
|
+
## Watch list
|
|
53
|
+
|
|
54
|
+
- **`mixHex` duplication** (above) is the family's only open token debt. Until
|
|
55
|
+
decision 8 lands, do not add a third copy — import the one in `SetBarChart`.
|
|
56
|
+
- **`SetBarChart` has no story.** `SparkBars` does; the chart is only seen through
|
|
57
|
+
its consumers' stories (`Custom/Fatigue/*`, `Custom/Workout/VelocityStrip`).
|
|
58
|
+
That is why it reads `status:candidate` — see [`MATURITY.md`](../../../../MATURITY.md).
|
|
59
|
+
- **Both marks are `keep-internal`** in the arch graph: two in-repo consumers
|
|
60
|
+
each, zero external. Neither is public API yet; treat prop changes as cheap
|
|
61
|
+
while that holds.
|
|
@@ -1,7 +1,14 @@
|
|
|
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
|
+
type SetSlot,
|
|
11
|
+
} from './SetBarChart'
|
|
5
12
|
|
|
6
13
|
const reps = (values: number[]): SetSlot[] => values.map((v) => ({ kind: 'rep', value: v }))
|
|
7
14
|
const silver = () => '#C7CBD1'
|
|
@@ -18,6 +25,28 @@ describe('valueLabelFontSize', () => {
|
|
|
18
25
|
})
|
|
19
26
|
})
|
|
20
27
|
|
|
28
|
+
describe('shouldFlipEdgeLabel (TD-07.10)', () => {
|
|
29
|
+
it.each([
|
|
30
|
+
// [roomPx, labelWidthPx, expected]
|
|
31
|
+
[30, 20, false], // half-width (10) comfortably clears the room (30)
|
|
32
|
+
[30, 70, true], // half-width (35) exceeds the room (30)
|
|
33
|
+
[10, 20, false], // half-width (10) exactly equals the room — still a fit (not flipped)
|
|
34
|
+
[9, 20, true], // half-width (10) just exceeds the room (9)
|
|
35
|
+
])('roomPx=%d labelWidthPx=%d -> flip=%s', (roomPx, labelWidthPx, expected) => {
|
|
36
|
+
expect(shouldFlipEdgeLabel(roomPx, labelWidthPx)).toBe(expected)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('estimateValueLabelWidth', () => {
|
|
41
|
+
it('scales with both character count and font size', () => {
|
|
42
|
+
expect(estimateValueLabelWidth('1.02', 12)).toBeCloseTo(4 * 12 * 0.62)
|
|
43
|
+
expect(estimateValueLabelWidth('1.02', 8)).toBeLessThan(estimateValueLabelWidth('1.02', 12))
|
|
44
|
+
expect(estimateValueLabelWidth('10.02', 12)).toBeGreaterThan(
|
|
45
|
+
estimateValueLabelWidth('1.02', 12)
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
21
50
|
describe('sideLabelText', () => {
|
|
22
51
|
it('keeps the full name when the vertical extent has room', () => {
|
|
23
52
|
expect(sideLabelText('Left Arm', 200)).toBe('Left Arm')
|
|
@@ -133,6 +133,14 @@ export interface SetBarChartProps {
|
|
|
133
133
|
showValueLabels?: boolean
|
|
134
134
|
/** Format a rep value for its label. Default `String`. */
|
|
135
135
|
formatValue?: (value: number) => string
|
|
136
|
+
/**
|
|
137
|
+
* The LAST column has no sibling to its right to absorb an overflowing value label, so a wide
|
|
138
|
+
* label there can bleed past the plot's right edge and crowd the reference line that also runs
|
|
139
|
+
* flush to it (TD-07.10). Default false (unchanged for every existing consumer — the single
|
|
140
|
+
* hero and ROM chart). When true, that column's label flips from centered to right-anchored
|
|
141
|
+
* (grows left) once {@link shouldFlipEdgeLabel} says it doesn't fit.
|
|
142
|
+
*/
|
|
143
|
+
flipEdgeLabel?: boolean
|
|
136
144
|
/**
|
|
137
145
|
* To-do placeholder treatment. `solid` (default) — a solid surface-relative section (the plane
|
|
138
146
|
* blended toward the on-surface neutral). `dashed` — a dashed outline stub. Exists so ROM can keep
|
|
@@ -198,6 +206,25 @@ export function valueLabelFontSize(height: number): number {
|
|
|
198
206
|
return Math.round(Math.max(8, Math.min(12, height * 0.11)))
|
|
199
207
|
}
|
|
200
208
|
|
|
209
|
+
/** Average glyph width for the bold numeric value-label font, as a fraction of its font size — a
|
|
210
|
+
* measurement-free estimate (RN text can't be synchronously measured) good enough to catch overflow. */
|
|
211
|
+
const LABEL_GLYPH_WIDTH_RATIO = 0.62
|
|
212
|
+
|
|
213
|
+
/** Estimate a value label's rendered width (px) from its formatted text and font size. */
|
|
214
|
+
export function estimateValueLabelWidth(text: string, fontSize: number): number {
|
|
215
|
+
return text.length * fontSize * LABEL_GLYPH_WIDTH_RATIO
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Whether a value label needs to flip from its default centered anchor to a right-anchored
|
|
220
|
+
* (left-growing) placement to clear the plot's right edge (TD-07.10). `roomPx` is the space
|
|
221
|
+
* available to the right of the label's centered position; `labelWidthPx` is its measured or
|
|
222
|
+
* estimated rendered width.
|
|
223
|
+
*/
|
|
224
|
+
export function shouldFlipEdgeLabel(roomPx: number, labelWidthPx: number): boolean {
|
|
225
|
+
return labelWidthPx / 2 > roomPx
|
|
226
|
+
}
|
|
227
|
+
|
|
201
228
|
// --- Hero geometry -----------------------------------------------------------
|
|
202
229
|
/** Default plot height (px) — tall enough to read a set's shape across a room. */
|
|
203
230
|
export const SET_BAR_DEFAULT_HEIGHT = 220
|
|
@@ -281,6 +308,7 @@ export function SetBarChart({
|
|
|
281
308
|
flat = false,
|
|
282
309
|
showValueLabels: showValueLabelsProp = false,
|
|
283
310
|
formatValue = String,
|
|
311
|
+
flipEdgeLabel = false,
|
|
284
312
|
todoVariant = 'solid',
|
|
285
313
|
minColumns,
|
|
286
314
|
renderReference,
|
|
@@ -462,6 +490,17 @@ export function SetBarChart({
|
|
|
462
490
|
// duplicate them); its own label shows otherwise, fading in with `expandProgress`.
|
|
463
491
|
const ownLabel = showValueLabels && !renderBarOverlay && showBarLabel(repIndex)
|
|
464
492
|
const labelBottom = (expandProgress ? valueBarHeight(best) : barHeight(best)) + LABEL_GAP
|
|
493
|
+
// Only the LAST cell has no sibling to its right to absorb an overflowing label
|
|
494
|
+
// (TD-07.10) — flip it right-anchored (grows left) when it doesn't fit.
|
|
495
|
+
const isLastCell = i === cells.length - 1
|
|
496
|
+
const labelText = formatValue(value)
|
|
497
|
+
const flipLabel =
|
|
498
|
+
flipEdgeLabel &&
|
|
499
|
+
isLastCell &&
|
|
500
|
+
shouldFlipEdgeLabel(
|
|
501
|
+
barWidth / 2,
|
|
502
|
+
estimateValueLabelWidth(labelText, valueLabelFontSize(height))
|
|
503
|
+
)
|
|
465
504
|
return (
|
|
466
505
|
<View key={i} accessibilityElementsHidden={renderBarOverlay == null} style={column}>
|
|
467
506
|
{ownLabel && (
|
|
@@ -471,9 +510,8 @@ export function SetBarChart({
|
|
|
471
510
|
style={{
|
|
472
511
|
position: 'absolute',
|
|
473
512
|
bottom: labelBottom,
|
|
474
|
-
left: 0,
|
|
475
|
-
|
|
476
|
-
alignItems: 'center',
|
|
513
|
+
...(flipLabel ? { right: 0 } : { left: 0, right: 0 }),
|
|
514
|
+
alignItems: flipLabel ? 'flex-end' : 'center',
|
|
477
515
|
...(expandProgress ? { opacity: expandProgress } : null),
|
|
478
516
|
}}
|
|
479
517
|
pointerEvents="none"
|
|
@@ -483,7 +521,7 @@ export function SetBarChart({
|
|
|
483
521
|
style={[{ fontSize: valueLabelFontSize(height), fontWeight: '700' }, flipStyle]}
|
|
484
522
|
testID={`${testIDPrefix}-label-${repIndex}`}
|
|
485
523
|
>
|
|
486
|
-
{
|
|
524
|
+
{labelText}
|
|
487
525
|
</Text>
|
|
488
526
|
</Animated.View>
|
|
489
527
|
)}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { SparkBars } from './SparkBars'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof SparkBars> = {
|
|
5
|
+
title: 'Custom/Charts/SparkBars',
|
|
6
|
+
component: SparkBars,
|
|
7
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
'**Atom.** A tiny bar-mark sparkline for a signed series — the counterpart to ' +
|
|
13
|
+
'[Sparkline](?path=/docs/custom-workout-dataviz-sparkline--docs). Where a line reads a *trajectory*, ' +
|
|
14
|
+
'bars read *discrete per-period magnitude*. Bars scale on absolute value so a negative ' +
|
|
15
|
+
'period is as tall as an equally large positive one, with the sign carried by fill colour.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
values: [120, 340, -80, 910, 420, 0, 1580, 260, -310, 740, 1120, 90],
|
|
21
|
+
height: 22,
|
|
22
|
+
barWidth: 3,
|
|
23
|
+
gap: 2,
|
|
24
|
+
maxBars: 24,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
export default meta
|
|
28
|
+
type Story = StoryObj<typeof SparkBars>
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {}
|
|
31
|
+
|
|
32
|
+
/** All-positive — the plain growth case. */
|
|
33
|
+
export const AllPositive: Story = {
|
|
34
|
+
args: { values: [40, 120, 260, 310, 480, 520, 900] },
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** All-negative — every bar takes the error token. */
|
|
38
|
+
export const AllNegative: Story = {
|
|
39
|
+
args: { values: [-40, -120, -260, -310, -480] },
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Taller field, as used in the file-history growth block. */
|
|
43
|
+
export const Tall: Story = {
|
|
44
|
+
args: { height: 34, barWidth: 5, gap: 3 },
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** `maxBars` keeps only the most recent values — a sparkline is a glance, not an archive. */
|
|
48
|
+
export const Truncated: Story = {
|
|
49
|
+
args: { values: Array.from({ length: 60 }, (_, i) => (i % 7) * 30 - 40), maxBars: 12 },
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** An empty series renders an empty field rather than collapsing the layout. */
|
|
53
|
+
export const Empty: Story = {
|
|
54
|
+
args: { values: [] },
|
|
55
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SparkBars } from './SparkBars'
|
|
5
|
+
|
|
6
|
+
describe('SparkBars', () => {
|
|
7
|
+
it('renders one bar per value', () => {
|
|
8
|
+
render(<SparkBars values={[1, 2, 3, 4]} />)
|
|
9
|
+
expect(screen.getAllByTestId(/^spark-bars-bar-/)).toHaveLength(4)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('keeps only the last maxBars values', () => {
|
|
13
|
+
render(<SparkBars values={[1, 2, 3, 4, 5, 6]} maxBars={2} />)
|
|
14
|
+
expect(screen.getAllByTestId(/^spark-bars-bar-/)).toHaveLength(2)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('renders no bars for an empty series but keeps the field', () => {
|
|
18
|
+
render(<SparkBars values={[]} />)
|
|
19
|
+
expect(screen.queryByTestId(/^spark-bars-bar-/)).not.toBeInTheDocument()
|
|
20
|
+
expect(screen.getByTestId('spark-bars')).toBeInTheDocument()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('tints bars by sign with the result-* tokens', () => {
|
|
24
|
+
// Asserts the token, not its current value: resolveColor emits the CSS var on
|
|
25
|
+
// web, which is what keeps the chart theme-correct.
|
|
26
|
+
render(<SparkBars values={[10, -10]} />)
|
|
27
|
+
expect(screen.getByTestId('spark-bars-bar-0')).toHaveStyle({
|
|
28
|
+
backgroundColor: 'var(--color-result-improve)',
|
|
29
|
+
})
|
|
30
|
+
expect(screen.getByTestId('spark-bars-bar-1')).toHaveStyle({
|
|
31
|
+
backgroundColor: 'var(--color-result-degrade)',
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('honours explicit color overrides', () => {
|
|
36
|
+
render(<SparkBars values={[10, -10]} color="#00ff00" negativeColor="#ff00ff" />)
|
|
37
|
+
expect(screen.getByTestId('spark-bars-bar-0')).toHaveStyle({
|
|
38
|
+
backgroundColor: 'rgb(0, 255, 0)',
|
|
39
|
+
})
|
|
40
|
+
expect(screen.getByTestId('spark-bars-bar-1')).toHaveStyle({
|
|
41
|
+
backgroundColor: 'rgb(255, 0, 255)',
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('scales bars on magnitude, so equal-magnitude values are equally tall', () => {
|
|
46
|
+
render(<SparkBars values={[100, -100, 50]} height={40} />)
|
|
47
|
+
const [a, b, c] = ['0', '1', '2'].map((i) => screen.getByTestId(`spark-bars-bar-${i}`))
|
|
48
|
+
expect(a).toHaveStyle({ height: '40px' })
|
|
49
|
+
expect(b).toHaveStyle({ height: '40px' })
|
|
50
|
+
expect(c).toHaveStyle({ height: '20px' })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('gives a zero value the minimum bar height rather than collapsing it', () => {
|
|
54
|
+
render(<SparkBars values={[100, 0]} height={40} />)
|
|
55
|
+
expect(screen.getByTestId('spark-bars-bar-1')).toHaveStyle({ height: '2px' })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('exposes a default accessible label and accepts an override', () => {
|
|
59
|
+
const { rerender } = render(<SparkBars values={[1, 2]} />)
|
|
60
|
+
expect(screen.getByLabelText('Bar sparkline with 2 values')).toBeInTheDocument()
|
|
61
|
+
rerender(<SparkBars values={[1, 2]} label="Growth for open.ts" />)
|
|
62
|
+
expect(screen.getByLabelText('Growth for open.ts')).toBeInTheDocument()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('has no a11y violations', async () => {
|
|
66
|
+
const { container } = render(<SparkBars values={[3, -1, 4]} />)
|
|
67
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
68
|
+
})
|
|
69
|
+
})
|