@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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { useMemo, type ReactNode } from 'react'
|
|
3
|
+
import { Pressable, View } from 'react-native'
|
|
4
|
+
import { Tooltip, useHoverState } from '../../ui/tooltip'
|
|
5
|
+
import { DateTime } from '../DateTime'
|
|
6
|
+
import { Typography } from '../Typography'
|
|
7
|
+
import { formatSessionDuration, formatTaskAge } from './format-time'
|
|
8
|
+
import { extractTaskRefs } from './session-linkers'
|
|
9
|
+
|
|
10
|
+
/** One recorded session, as the session reader consumes it. */
|
|
11
|
+
export interface SessionSummary {
|
|
12
|
+
id: string
|
|
13
|
+
/** The session file's name, when the host has it. */
|
|
14
|
+
filename?: string
|
|
15
|
+
/** ISO timestamps. */
|
|
16
|
+
started: string
|
|
17
|
+
ended: string
|
|
18
|
+
/** `canonical` is the mainline thread; anything else (`sidecar`, `adhoc`) is folded or parallel work. */
|
|
19
|
+
track: string
|
|
20
|
+
/** The log's first heading, without the leading `#`. */
|
|
21
|
+
title: string
|
|
22
|
+
/** The session log's markdown body. */
|
|
23
|
+
body: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SessionListItemProps {
|
|
27
|
+
session: SessionSummary
|
|
28
|
+
/** Reference instant for the age label, injected so renders are deterministic. */
|
|
29
|
+
now: number
|
|
30
|
+
/** Renders the selected treatment (raised fill + leading accent bar). */
|
|
31
|
+
selected?: boolean
|
|
32
|
+
onSelect?: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The row's footer as one string: age, wall-clock length, distinct tasks touched, and the track. */
|
|
36
|
+
export function sessionRowMeta(session: SessionSummary, now: number, taskCount: number): string {
|
|
37
|
+
const parts = [formatTaskAge(session.ended, now)]
|
|
38
|
+
const duration = formatSessionDuration(session.started, session.ended)
|
|
39
|
+
if (duration) parts.push(duration)
|
|
40
|
+
if (taskCount > 0) parts.push(`${taskCount} ${taskCount === 1 ? 'task' : 'tasks'}`)
|
|
41
|
+
parts.push(session.track)
|
|
42
|
+
return parts.join(' · ')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function MetaText({ children }: { children: ReactNode }) {
|
|
46
|
+
return (
|
|
47
|
+
<Typography variant="caption" className="leading-none text-text-tertiary">
|
|
48
|
+
{children}
|
|
49
|
+
</Typography>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A footer segment with a hover detail. The Tooltip is controlled and renders
|
|
55
|
+
* no Pressable, so the row underneath keeps its press.
|
|
56
|
+
*/
|
|
57
|
+
function MetaHover({
|
|
58
|
+
tip,
|
|
59
|
+
testID,
|
|
60
|
+
children,
|
|
61
|
+
}: {
|
|
62
|
+
tip: ReactNode
|
|
63
|
+
testID: string
|
|
64
|
+
children: string
|
|
65
|
+
}) {
|
|
66
|
+
const { hovered, hoverProps } = useHoverState()
|
|
67
|
+
return (
|
|
68
|
+
<Tooltip usePortal isOpen={hovered} content={tip}>
|
|
69
|
+
<View {...hoverProps} testID={testID}>
|
|
70
|
+
<MetaText>{children}</MetaText>
|
|
71
|
+
</View>
|
|
72
|
+
</Tooltip>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The exact end time behind the relative age. */
|
|
77
|
+
export function ExactTime({ session }: { session: SessionSummary }) {
|
|
78
|
+
return (
|
|
79
|
+
<DateTime value={session.ended} format="datetime" isUTC variant="caption" color="primary" />
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The task ids behind the count. */
|
|
84
|
+
export function TaskIds({ refs }: { refs: string[] }) {
|
|
85
|
+
return (
|
|
86
|
+
<Typography variant="mono" className="text-text-primary">
|
|
87
|
+
{refs.join(' ')}
|
|
88
|
+
</Typography>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function RowMeta({ session, now, refs }: { session: SessionSummary; now: number; refs: string[] }) {
|
|
93
|
+
const duration = formatSessionDuration(session.started, session.ended)
|
|
94
|
+
const count = refs.length
|
|
95
|
+
return (
|
|
96
|
+
<View className="flex-row flex-wrap items-center">
|
|
97
|
+
<MetaHover tip={<ExactTime session={session} />} testID="session-age">
|
|
98
|
+
{formatTaskAge(session.ended, now)}
|
|
99
|
+
</MetaHover>
|
|
100
|
+
{duration ? <MetaText>{` · ${duration}`}</MetaText> : null}
|
|
101
|
+
{count > 0 ? (
|
|
102
|
+
<>
|
|
103
|
+
<MetaText>{' · '}</MetaText>
|
|
104
|
+
<MetaHover tip={<TaskIds refs={refs} />} testID="session-task-count">
|
|
105
|
+
{`${count} ${count === 1 ? 'task' : 'tasks'}`}
|
|
106
|
+
</MetaHover>
|
|
107
|
+
</>
|
|
108
|
+
) : null}
|
|
109
|
+
<MetaText>{` · ${session.track}`}</MetaText>
|
|
110
|
+
</View>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* SessionListItem — one session in the reader's list, led by its title, with
|
|
116
|
+
* a footer of age · duration · tasks touched · track. Hovering the age shows
|
|
117
|
+
* the exact end time; hovering the task count lists the task ids.
|
|
118
|
+
*
|
|
119
|
+
* Composes {@link Tooltip} (controlled, so the row keeps its press),
|
|
120
|
+
* {@link DateTime} and {@link Typography}. Used by {@link SessionList}.
|
|
121
|
+
*/
|
|
122
|
+
export function SessionListItem({
|
|
123
|
+
session,
|
|
124
|
+
now,
|
|
125
|
+
selected = false,
|
|
126
|
+
onSelect,
|
|
127
|
+
}: SessionListItemProps) {
|
|
128
|
+
const refs = useMemo(() => extractTaskRefs(session.body), [session.body])
|
|
129
|
+
return (
|
|
130
|
+
<Pressable
|
|
131
|
+
onPress={onSelect}
|
|
132
|
+
// Raw `role`/`aria-selected` rather than `accessibilityState={{ selected }}`:
|
|
133
|
+
// RNW silently drops the latter, so selection would never reach AT.
|
|
134
|
+
role="option"
|
|
135
|
+
aria-selected={selected}
|
|
136
|
+
accessibilityLabel={`${session.title}, ${sessionRowMeta(session, now, refs.length)}`}
|
|
137
|
+
testID="session-list-item"
|
|
138
|
+
className={`relative gap-1 rounded-md px-3 py-2 ${selected ? 'bg-surface-raised' : ''}`}
|
|
139
|
+
>
|
|
140
|
+
{selected ? (
|
|
141
|
+
<View
|
|
142
|
+
testID="session-list-item-accent"
|
|
143
|
+
className="absolute bottom-2 left-0 top-2 w-[3px] rounded-r-[3px] bg-brand-primary"
|
|
144
|
+
/>
|
|
145
|
+
) : null}
|
|
146
|
+
<Typography variant="body2" numberOfLines={2} className="font-medium text-text-primary">
|
|
147
|
+
{session.title}
|
|
148
|
+
</Typography>
|
|
149
|
+
<RowMeta session={session} now={now} refs={refs} />
|
|
150
|
+
</Pressable>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { View } from 'react-native'
|
|
4
|
+
import { Surface } from '../../ui/surface'
|
|
5
|
+
import { SessionDetail } from './SessionDetail'
|
|
6
|
+
import { SessionList } from './SessionList'
|
|
7
|
+
import type { SessionSummary } from './SessionListItem'
|
|
8
|
+
import { SESSION_FIXTURE, SESSION_NOW, SESSION_TASK_FIXTURE } from './session-fixture'
|
|
9
|
+
import type { TaskListItem } from './TaskRow'
|
|
10
|
+
|
|
11
|
+
interface ReaderArgs {
|
|
12
|
+
sessions: SessionSummary[]
|
|
13
|
+
/** The host's task store; the detail looks its touched ids up here. */
|
|
14
|
+
tasks: TaskListItem[]
|
|
15
|
+
now: number
|
|
16
|
+
onPressTask: (id: string) => void
|
|
17
|
+
onPressLink: (name: string) => void
|
|
18
|
+
onPressPr: (number: number) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The two halves composed the way a host would: the host owns `selectedId`
|
|
23
|
+
* and lays the list beside the detail. There is deliberately no
|
|
24
|
+
* `SessionReader` organism; this story is the reference composition.
|
|
25
|
+
*/
|
|
26
|
+
function SessionReader({ sessions, tasks, now, onPressTask, onPressLink, onPressPr }: ReaderArgs) {
|
|
27
|
+
const [selectedId, setSelectedId] = useState(sessions[0]?.id)
|
|
28
|
+
const selected = sessions.find((s) => s.id === selectedId) ?? sessions[0]
|
|
29
|
+
return (
|
|
30
|
+
<View className="flex-row gap-4">
|
|
31
|
+
<SessionList
|
|
32
|
+
className="w-72"
|
|
33
|
+
sessions={sessions}
|
|
34
|
+
now={now}
|
|
35
|
+
selectedId={selected?.id}
|
|
36
|
+
onSelect={(s) => setSelectedId(s.id)}
|
|
37
|
+
/>
|
|
38
|
+
{selected ? (
|
|
39
|
+
<SessionDetail
|
|
40
|
+
className="flex-1"
|
|
41
|
+
session={selected}
|
|
42
|
+
tasks={tasks}
|
|
43
|
+
now={now}
|
|
44
|
+
onPressTask={onPressTask}
|
|
45
|
+
onPressLink={onPressLink}
|
|
46
|
+
onPressPr={onPressPr}
|
|
47
|
+
/>
|
|
48
|
+
) : null}
|
|
49
|
+
</View>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* **M1 · Session reader** — a selectable list of session logs beside the
|
|
55
|
+
* selected log, rendered as prose with task ids, `[[name]]` links and PR
|
|
56
|
+
* numbers auto-linked.
|
|
57
|
+
*
|
|
58
|
+
* Composes `SessionList` (→ `SessionListItem`) and `SessionDetail`
|
|
59
|
+
* (→ `MarkdownProse`). Selection lives in the host.
|
|
60
|
+
*/
|
|
61
|
+
const meta: Meta<ReaderArgs> = {
|
|
62
|
+
title: 'Custom/ActiveWork/SessionReader',
|
|
63
|
+
component: SessionReader,
|
|
64
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
65
|
+
args: {
|
|
66
|
+
sessions: SESSION_FIXTURE,
|
|
67
|
+
tasks: SESSION_TASK_FIXTURE,
|
|
68
|
+
now: SESSION_NOW,
|
|
69
|
+
},
|
|
70
|
+
argTypes: {
|
|
71
|
+
sessions: { table: { disable: true } },
|
|
72
|
+
tasks: { table: { disable: true } },
|
|
73
|
+
now: { table: { disable: true } },
|
|
74
|
+
onPressTask: { action: 'onPressTask' },
|
|
75
|
+
onPressLink: { action: 'onPressLink' },
|
|
76
|
+
onPressPr: { action: 'onPressPr' },
|
|
77
|
+
},
|
|
78
|
+
decorators: [
|
|
79
|
+
(Story) => (
|
|
80
|
+
<Surface level="base" className="min-h-screen p-6" testID="page-surface">
|
|
81
|
+
<View className="w-full max-w-[1100px]">
|
|
82
|
+
<Story />
|
|
83
|
+
</View>
|
|
84
|
+
</Surface>
|
|
85
|
+
),
|
|
86
|
+
],
|
|
87
|
+
parameters: {
|
|
88
|
+
// Fullscreen: the page Surface is the host plane the detail card lifts off, so it owns the frame.
|
|
89
|
+
layout: 'fullscreen',
|
|
90
|
+
docs: {
|
|
91
|
+
description: {
|
|
92
|
+
component:
|
|
93
|
+
'Composes **SessionList** (→ SessionListItem) · **SessionDetail** (→ MarkdownProse, Card, Divider, Pill, DateTime). The host owns selection.',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
export default meta
|
|
99
|
+
|
|
100
|
+
type Story = StoryObj<ReaderArgs>
|
|
101
|
+
|
|
102
|
+
/** Six real sessions, newest first; the newest is selected. */
|
|
103
|
+
export const Default: Story = {}
|
|
104
|
+
|
|
105
|
+
/** A single session, so the list and detail read without neighbours. */
|
|
106
|
+
export const OneSession: Story = {
|
|
107
|
+
args: { sessions: SESSION_FIXTURE.slice(0, 1) },
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** No task store: the tasks-touched strip stays pills with no table toggle. */
|
|
111
|
+
export const WithoutTaskStore: Story = {
|
|
112
|
+
args: { tasks: [] },
|
|
113
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { SeverityLabel, SEVERITY_ORDER } from './SeverityLabel'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* **SeverityLabel** — a task's severity as a coloured dot plus its label.
|
|
7
|
+
* Composes `Indicator`; the family's single owner of the severity vocabulary
|
|
8
|
+
* (type, order, rank, dot and bar colours).
|
|
9
|
+
*/
|
|
10
|
+
const meta: Meta<typeof SeverityLabel> = {
|
|
11
|
+
title: 'Custom/ActiveWork/SeverityLabel',
|
|
12
|
+
component: SeverityLabel,
|
|
13
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
14
|
+
args: { severity: 'high' },
|
|
15
|
+
argTypes: {
|
|
16
|
+
severity: { control: 'select', options: [...SEVERITY_ORDER, undefined] },
|
|
17
|
+
dotOnly: { control: 'boolean' },
|
|
18
|
+
},
|
|
19
|
+
parameters: {
|
|
20
|
+
docs: { description: { component: 'Composes **Indicator** · **Typography**.' } },
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
export default meta
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof SeverityLabel>
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {}
|
|
28
|
+
|
|
29
|
+
/** All four severities, worst-first — the canonical `SEVERITY_ORDER`. */
|
|
30
|
+
export const AllSeverities: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<View className="gap-2">
|
|
33
|
+
{SEVERITY_ORDER.map((severity) => (
|
|
34
|
+
<SeverityLabel key={severity} severity={severity} />
|
|
35
|
+
))}
|
|
36
|
+
</View>
|
|
37
|
+
),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Dot only, for a legend key or a very tight column. */
|
|
41
|
+
export const DotOnly: Story = {
|
|
42
|
+
args: { dotOnly: true },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Unset severity renders an em-dash rather than nothing, so a table column keeps
|
|
47
|
+
* its alignment instead of collapsing.
|
|
48
|
+
*/
|
|
49
|
+
export const Unset: Story = {
|
|
50
|
+
args: { severity: undefined },
|
|
51
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import {
|
|
5
|
+
SeverityLabel,
|
|
6
|
+
SEVERITY_ORDER,
|
|
7
|
+
SEVERITY_RANK,
|
|
8
|
+
SEVERITY_BAR_COLOR,
|
|
9
|
+
severityRank,
|
|
10
|
+
} from './SeverityLabel'
|
|
11
|
+
|
|
12
|
+
describe('SeverityLabel', () => {
|
|
13
|
+
it('renders the dot and the label for a severity', () => {
|
|
14
|
+
render(<SeverityLabel severity="critical" />)
|
|
15
|
+
expect(screen.getByText('Critical')).toBeInTheDocument()
|
|
16
|
+
expect(screen.getByTestId('severity-dot-critical')).toBeInTheDocument()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('renders every severity with its own label', () => {
|
|
20
|
+
for (const severity of SEVERITY_ORDER) {
|
|
21
|
+
const { unmount } = render(<SeverityLabel severity={severity} />)
|
|
22
|
+
expect(screen.getByTestId(`severity-dot-${severity}`)).toBeInTheDocument()
|
|
23
|
+
unmount()
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('drops the label but keeps the dot when dotOnly', () => {
|
|
28
|
+
render(<SeverityLabel severity="high" dotOnly />)
|
|
29
|
+
expect(screen.getByTestId('severity-dot-high')).toBeInTheDocument()
|
|
30
|
+
expect(screen.queryByText('High')).not.toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('renders an em-dash and no dot when severity is unset', () => {
|
|
34
|
+
render(<SeverityLabel />)
|
|
35
|
+
expect(screen.getByText('—')).toBeInTheDocument()
|
|
36
|
+
expect(screen.queryByTestId(/^severity-dot-/)).not.toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('has no a11y violations', async () => {
|
|
40
|
+
const { container } = render(<SeverityLabel severity="medium" />)
|
|
41
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('severity vocabulary', () => {
|
|
46
|
+
it('ranks worst-first, matching SEVERITY_ORDER', () => {
|
|
47
|
+
const byRank = [...SEVERITY_ORDER].sort((a, b) => SEVERITY_RANK[a] - SEVERITY_RANK[b])
|
|
48
|
+
expect(byRank).toEqual(SEVERITY_ORDER)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('ranks an unset severity after every real one', () => {
|
|
52
|
+
for (const severity of SEVERITY_ORDER) {
|
|
53
|
+
expect(severityRank(severity)).toBeLessThan(severityRank(undefined))
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('does not rank alphabetically — low is last, not second', () => {
|
|
58
|
+
// The bug this guards: 'critical' < 'high' < 'low' < 'medium' as strings,
|
|
59
|
+
// which reads as "low is more urgent than medium".
|
|
60
|
+
expect(severityRank('low')).toBeGreaterThan(severityRank('medium'))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('covers every severity in the bar palette', () => {
|
|
64
|
+
for (const severity of SEVERITY_ORDER) {
|
|
65
|
+
expect(SEVERITY_BAR_COLOR[severity]).toBeTruthy()
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Font mapping: font-heading=Space Grotesk, font-body=Nunito Sans (UI), font-sans=Inter (body)
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { resolveColor } from '../../../theme/resolve-color'
|
|
5
|
+
import { Indicator } from '../../ui/indicator'
|
|
6
|
+
import { Typography } from '../Typography'
|
|
7
|
+
|
|
8
|
+
/** How much a task hurts if left undone. Ordered worst-first by {@link SEVERITY_RANK}. */
|
|
9
|
+
export type TaskSeverity = 'critical' | 'high' | 'medium' | 'low'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sort weight per severity, worst-first. Exported so a table can rank a severity
|
|
13
|
+
* column by meaning rather than alphabetically ("critical" < "high" < "low" as
|
|
14
|
+
* strings puts low in the middle).
|
|
15
|
+
*/
|
|
16
|
+
export const SEVERITY_RANK: Record<TaskSeverity, number> = {
|
|
17
|
+
critical: 0,
|
|
18
|
+
high: 1,
|
|
19
|
+
medium: 2,
|
|
20
|
+
low: 3,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Severities worst-first — the canonical order for legends and pickers. */
|
|
24
|
+
export const SEVERITY_ORDER: TaskSeverity[] = ['critical', 'high', 'medium', 'low']
|
|
25
|
+
|
|
26
|
+
/** Rank for a possibly-absent severity; unset sorts after every real value. */
|
|
27
|
+
export function severityRank(severity?: TaskSeverity): number {
|
|
28
|
+
return severity ? SEVERITY_RANK[severity] : SEVERITY_ORDER.length
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Severity to its display label and {@link Indicator} colour. Critical and high
|
|
33
|
+
* both read as error; the vivid step separates "drop everything" from "serious".
|
|
34
|
+
*/
|
|
35
|
+
export const SEVERITY_META: Record<
|
|
36
|
+
TaskSeverity,
|
|
37
|
+
{ label: string; dot: React.ComponentProps<typeof Indicator>['color'] }
|
|
38
|
+
> = {
|
|
39
|
+
critical: { label: 'Critical', dot: 'error-vivid' },
|
|
40
|
+
high: { label: 'High', dot: 'error' },
|
|
41
|
+
medium: { label: 'Medium', dot: 'warning' },
|
|
42
|
+
low: { label: 'Low', dot: 'info' },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Severity as a fill for an area mark (a {@link SegmentedBar} segment), where
|
|
47
|
+
* {@link SEVERITY_META} gives the point-mark dot colour.
|
|
48
|
+
*
|
|
49
|
+
* `low` diverges deliberately: as a dot among four it needs to stay legible, but
|
|
50
|
+
* as a bar segment it should recede, so it takes `text-tertiary` rather than
|
|
51
|
+
* `status-info`. Both live here so the severity vocabulary has one owner.
|
|
52
|
+
*/
|
|
53
|
+
export const SEVERITY_BAR_COLOR: Record<TaskSeverity, string> = {
|
|
54
|
+
critical: resolveColor('status-error-vivid'),
|
|
55
|
+
high: resolveColor('status-error'),
|
|
56
|
+
medium: resolveColor('status-warning'),
|
|
57
|
+
low: resolveColor('text-tertiary'),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SeverityLabelProps {
|
|
61
|
+
/** Omitted renders the em-dash placeholder rather than nothing, so columns stay aligned. */
|
|
62
|
+
severity?: TaskSeverity
|
|
63
|
+
/** Hides the text, leaving only the dot — for a legend key or a very tight column. */
|
|
64
|
+
dotOnly?: boolean
|
|
65
|
+
className?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* SeverityLabel — a task's severity as a coloured dot plus its label.
|
|
70
|
+
*
|
|
71
|
+
* Composes {@link Indicator}; never hand-roll a status dot (there are already two
|
|
72
|
+
* dot primitives in the system, and this is not a third). Used by
|
|
73
|
+
* {@link TaskRow} and {@link TaskTable}'s legend.
|
|
74
|
+
*
|
|
75
|
+
* @deprecated Use `<Pill tone="…" leading="dot">` — removed after AW-127 consumer migration.
|
|
76
|
+
*/
|
|
77
|
+
export function SeverityLabel({ severity, dotOnly = false, className }: SeverityLabelProps) {
|
|
78
|
+
if (!severity) {
|
|
79
|
+
return (
|
|
80
|
+
<Typography variant="caption" className={cn('text-text-tertiary', className)}>
|
|
81
|
+
—
|
|
82
|
+
</Typography>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { label, dot } = SEVERITY_META[severity]
|
|
87
|
+
return (
|
|
88
|
+
<View className={cn('flex-row items-center gap-1.5', className)}>
|
|
89
|
+
<Indicator size="sm" color={dot} testID={`severity-dot-${severity}`} />
|
|
90
|
+
{dotOnly ? null : (
|
|
91
|
+
<Typography variant="caption" color="inherit" className="text-text-secondary">
|
|
92
|
+
{label}
|
|
93
|
+
</Typography>
|
|
94
|
+
)}
|
|
95
|
+
</View>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { Surface } from '../../ui/surface'
|
|
3
|
+
import { Table, TableBody } from '../Table'
|
|
4
|
+
import { TaskRow } from './TaskRow'
|
|
5
|
+
import { TASK_LIST_FIXTURE } from './task-list-fixture'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* **TaskRow** — one task as a dense grid row. Composes `TableRow`/`TableCell`,
|
|
9
|
+
* so it must render inside a `Table` to pick up row semantics and density.
|
|
10
|
+
*/
|
|
11
|
+
const meta: Meta<typeof TaskRow> = {
|
|
12
|
+
title: 'Custom/ActiveWork/TaskRow',
|
|
13
|
+
component: TaskRow,
|
|
14
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
15
|
+
args: {
|
|
16
|
+
task: TASK_LIST_FIXTURE[0],
|
|
17
|
+
ageLabel: '2d ago',
|
|
18
|
+
},
|
|
19
|
+
decorators: [
|
|
20
|
+
(Story) => (
|
|
21
|
+
<Surface
|
|
22
|
+
level="base"
|
|
23
|
+
className="min-h-screen w-full max-w-[1100px] p-6"
|
|
24
|
+
testID="page-surface"
|
|
25
|
+
>
|
|
26
|
+
<Table density="dense">
|
|
27
|
+
<TableBody>
|
|
28
|
+
<Story />
|
|
29
|
+
</TableBody>
|
|
30
|
+
</Table>
|
|
31
|
+
</Surface>
|
|
32
|
+
),
|
|
33
|
+
],
|
|
34
|
+
parameters: {
|
|
35
|
+
layout: 'fullscreen',
|
|
36
|
+
docs: {
|
|
37
|
+
description: {
|
|
38
|
+
component:
|
|
39
|
+
'Composes **TableRow** · **TableCell** · **SeverityLabel** · **Pill** · **Typography**.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
export default meta
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<typeof TaskRow>
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {}
|
|
49
|
+
|
|
50
|
+
/** A critical task carrying more tags than the row shows — the rest collapse to a `+n`. */
|
|
51
|
+
export const CriticalWithOverflowingTags: Story = {
|
|
52
|
+
args: { task: TASK_LIST_FIXTURE[2], ageLabel: 'today' },
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** No severity and no estimate: both columns hold an em-dash so the grid stays aligned. */
|
|
56
|
+
export const MissingSeverityAndEstimate: Story = {
|
|
57
|
+
args: { task: TASK_LIST_FIXTURE[5], ageLabel: '29d ago' },
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A title long enough to clip to one line rather than wrapping the row taller. */
|
|
61
|
+
export const LongTitle: Story = {
|
|
62
|
+
args: { task: TASK_LIST_FIXTURE[0], ageLabel: '1d ago' },
|
|
63
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { Table, TableBody } from '../Table'
|
|
5
|
+
import { TaskRow } from './TaskRow'
|
|
6
|
+
import type { TaskListItem } from './TaskRow'
|
|
7
|
+
|
|
8
|
+
const task: TaskListItem = {
|
|
9
|
+
slug: 'active-work',
|
|
10
|
+
id: 'AW-22',
|
|
11
|
+
title: 'Read-only dashboard prototypes',
|
|
12
|
+
severity: 'high',
|
|
13
|
+
priority: 20,
|
|
14
|
+
estimate: 8,
|
|
15
|
+
tags: ['dashboard', 'titan', 'specimen', 'extra'],
|
|
16
|
+
updated: '2026-08-29T10:00:00Z',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** TaskRow composes TableRow/TableCell, so it needs a Table for row semantics. */
|
|
20
|
+
function renderRow(
|
|
21
|
+
overrides: Partial<TaskListItem> = {},
|
|
22
|
+
ageLabel = '1d ago',
|
|
23
|
+
severityDotOnly = false
|
|
24
|
+
) {
|
|
25
|
+
return render(
|
|
26
|
+
<Table density="dense">
|
|
27
|
+
<TableBody>
|
|
28
|
+
<TaskRow
|
|
29
|
+
task={{ ...task, ...overrides }}
|
|
30
|
+
ageLabel={ageLabel}
|
|
31
|
+
severityDotOnly={severityDotOnly}
|
|
32
|
+
/>
|
|
33
|
+
</TableBody>
|
|
34
|
+
</Table>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('TaskRow', () => {
|
|
39
|
+
it('shows only the severity dot when collapsed, with the word on hover', () => {
|
|
40
|
+
renderRow({}, '1d ago', true)
|
|
41
|
+
expect(screen.queryByText('High')).not.toBeInTheDocument()
|
|
42
|
+
fireEvent.mouseEnter(screen.getByTestId('severity-dot-high').parentElement!.parentElement!)
|
|
43
|
+
expect(screen.getByText('High')).toBeInTheDocument()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('reveals the elided tags when the overflow count is hovered', () => {
|
|
47
|
+
renderRow()
|
|
48
|
+
expect(screen.queryByText('specimen')).not.toBeInTheDocument()
|
|
49
|
+
fireEvent.mouseEnter(screen.getByText('+2').parentElement!)
|
|
50
|
+
expect(screen.getByText('specimen')).toBeInTheDocument()
|
|
51
|
+
expect(screen.getByText('extra')).toBeInTheDocument()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('shows the exact date behind the age label on hover', () => {
|
|
55
|
+
renderRow()
|
|
56
|
+
fireEvent.mouseEnter(screen.getByText('1d ago').parentElement!)
|
|
57
|
+
expect(screen.getByText('Aug 29, 2026')).toBeInTheDocument()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('renders every column of a fully-populated task', () => {
|
|
61
|
+
renderRow()
|
|
62
|
+
expect(screen.getByText('active-work')).toBeInTheDocument()
|
|
63
|
+
expect(screen.getByText('AW-22')).toBeInTheDocument()
|
|
64
|
+
expect(screen.getByText('Read-only dashboard prototypes')).toBeInTheDocument()
|
|
65
|
+
expect(screen.getByText('High')).toBeInTheDocument()
|
|
66
|
+
expect(screen.getByText('20')).toBeInTheDocument()
|
|
67
|
+
expect(screen.getByText('8')).toBeInTheDocument()
|
|
68
|
+
expect(screen.getByText('1d ago')).toBeInTheDocument()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('shows the first two tags and collapses the rest into a count', () => {
|
|
72
|
+
renderRow()
|
|
73
|
+
expect(screen.getByText('dashboard')).toBeInTheDocument()
|
|
74
|
+
expect(screen.getByText('titan')).toBeInTheDocument()
|
|
75
|
+
expect(screen.queryByText('specimen')).not.toBeInTheDocument()
|
|
76
|
+
expect(screen.getByText('+2')).toBeInTheDocument()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('omits the overflow count when the tags all fit', () => {
|
|
80
|
+
renderRow({ tags: ['one'] })
|
|
81
|
+
expect(screen.queryByText(/^\+\d+$/)).not.toBeInTheDocument()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('handles a task with no tags at all', () => {
|
|
85
|
+
renderRow({ tags: undefined })
|
|
86
|
+
expect(screen.queryByText(/^\+\d+$/)).not.toBeInTheDocument()
|
|
87
|
+
expect(screen.getByText('AW-22')).toBeInTheDocument()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('renders an em-dash for an absent estimate rather than a zero', () => {
|
|
91
|
+
renderRow({ estimate: undefined })
|
|
92
|
+
expect(screen.getByText('—')).toBeInTheDocument()
|
|
93
|
+
expect(screen.queryByText('0')).not.toBeInTheDocument()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('renders an em-dash in the severity column when severity is unset', () => {
|
|
97
|
+
renderRow({ severity: undefined, estimate: 8 })
|
|
98
|
+
expect(screen.getByText('—')).toBeInTheDocument()
|
|
99
|
+
expect(screen.queryByTestId(/^severity-dot-/)).not.toBeInTheDocument()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('exposes one row with eight cells to assistive tech', () => {
|
|
103
|
+
renderRow()
|
|
104
|
+
expect(screen.getByRole('row')).toBeInTheDocument()
|
|
105
|
+
expect(screen.getAllByRole('cell')).toHaveLength(8)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('has no a11y violations', async () => {
|
|
109
|
+
const { container } = renderRow()
|
|
110
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
111
|
+
})
|
|
112
|
+
})
|