@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState } from 'react'
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
3
|
import { View, Text } from 'react-native'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
TableSelectCell,
|
|
16
16
|
useTable,
|
|
17
17
|
} from './Table'
|
|
18
|
+
import { useColumnFit, type TableColumnFit } from './column-fit'
|
|
18
19
|
import { Button, ButtonText } from '../../ui/button/Button'
|
|
19
20
|
|
|
20
21
|
interface User {
|
|
@@ -44,7 +45,10 @@ const manyUsers: User[] = Array.from({ length: 50 }, (_, i) => ({
|
|
|
44
45
|
const meta: Meta<typeof Table> = {
|
|
45
46
|
title: 'Components/Organisms/Table',
|
|
46
47
|
component: Table,
|
|
47
|
-
|
|
48
|
+
parameters: {
|
|
49
|
+
docs: { description: { component: 'Composes **Tooltip**.' } },
|
|
50
|
+
},
|
|
51
|
+
tags: ['autodocs', 'status:candidate', '!status:review'],
|
|
48
52
|
argTypes: {
|
|
49
53
|
isLoading: {
|
|
50
54
|
control: 'boolean',
|
|
@@ -114,9 +118,7 @@ export const WithSorting: Story = {
|
|
|
114
118
|
|
|
115
119
|
const handleSort = (column: string) => {
|
|
116
120
|
if (sortColumn === column) {
|
|
117
|
-
setSortDirection((prev) =>
|
|
118
|
-
prev === 'asc' ? 'desc' : prev === 'desc' ? null : 'asc'
|
|
119
|
-
)
|
|
121
|
+
setSortDirection((prev) => (prev === 'asc' ? 'desc' : prev === 'desc' ? null : 'asc'))
|
|
120
122
|
} else {
|
|
121
123
|
setSortColumn(column)
|
|
122
124
|
setSortDirection('asc')
|
|
@@ -313,11 +315,17 @@ export const WithColumnWidths: Story = {
|
|
|
313
315
|
<Table>
|
|
314
316
|
<TableHeader>
|
|
315
317
|
<TableRow isHoverable={false}>
|
|
316
|
-
<TableHeaderCell width={60} align="center"
|
|
318
|
+
<TableHeaderCell width={60} align="center">
|
|
319
|
+
#
|
|
320
|
+
</TableHeaderCell>
|
|
317
321
|
<TableHeaderCell width={200}>Name</TableHeaderCell>
|
|
318
322
|
<TableHeaderCell>Email</TableHeaderCell>
|
|
319
|
-
<TableHeaderCell width={120} align="center">
|
|
320
|
-
|
|
323
|
+
<TableHeaderCell width={120} align="center">
|
|
324
|
+
Role
|
|
325
|
+
</TableHeaderCell>
|
|
326
|
+
<TableHeaderCell width={100} align="right">
|
|
327
|
+
Status
|
|
328
|
+
</TableHeaderCell>
|
|
321
329
|
</TableRow>
|
|
322
330
|
</TableHeader>
|
|
323
331
|
<TableBody>
|
|
@@ -330,8 +338,12 @@ export const WithColumnWidths: Story = {
|
|
|
330
338
|
<Text className="text-sm text-text-primary font-medium">{user.name}</Text>
|
|
331
339
|
</TableCell>
|
|
332
340
|
<TableCell>{user.email}</TableCell>
|
|
333
|
-
<TableCell width={120} align="center">
|
|
334
|
-
|
|
341
|
+
<TableCell width={120} align="center">
|
|
342
|
+
{user.role}
|
|
343
|
+
</TableCell>
|
|
344
|
+
<TableCell width={100} align="right">
|
|
345
|
+
{user.status}
|
|
346
|
+
</TableCell>
|
|
335
347
|
</TableRow>
|
|
336
348
|
))}
|
|
337
349
|
</TableBody>
|
|
@@ -339,6 +351,79 @@ export const WithColumnWidths: Story = {
|
|
|
339
351
|
),
|
|
340
352
|
}
|
|
341
353
|
|
|
354
|
+
/**
|
|
355
|
+
* A column's terms for the fit: what it needs, and when it leaves. `name` has no
|
|
356
|
+
* `dropPriority`, so it never drops — which is what puts a floor under the
|
|
357
|
+
* dropping and hands the overflow to horizontal scroll instead.
|
|
358
|
+
*/
|
|
359
|
+
const FIT_COLUMNS: (TableColumnFit & { label: string })[] = [
|
|
360
|
+
{ key: 'name', label: 'Name', minWidth: 200 },
|
|
361
|
+
{ key: 'email', label: 'Email', minWidth: 220, dropPriority: 3 },
|
|
362
|
+
{ key: 'role', label: 'Role', minWidth: 120, dropPriority: 2 },
|
|
363
|
+
{ key: 'status', label: 'Status', minWidth: 100, dropPriority: 1 },
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
/** The hairline frame, which the outer width includes but the columns cannot use. */
|
|
367
|
+
const FRAME_INSET = 2
|
|
368
|
+
|
|
369
|
+
/** The same table at a pinned width, so one story shows every step of the order at once. */
|
|
370
|
+
function FittedTable({ available }: { available: number }) {
|
|
371
|
+
const fit = useColumnFit(FIT_COLUMNS, available - FRAME_INSET)
|
|
372
|
+
const columns = FIT_COLUMNS.filter((column) => fit.isVisible(column.key))
|
|
373
|
+
const width = (key: string) =>
|
|
374
|
+
key === 'name' ? undefined : FIT_COLUMNS.find((c) => c.key === key)!.minWidth
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<View className="gap-1" style={{ width: available }}>
|
|
378
|
+
<Text className="text-xs text-text-secondary">
|
|
379
|
+
{`${available}px · ${columns.length} columns${fit.isScrolling ? ' · scrolls' : ''}`}
|
|
380
|
+
</Text>
|
|
381
|
+
<View className="overflow-hidden rounded-lg border border-hairline">
|
|
382
|
+
<Table contentMinWidth={fit.contentMinWidth}>
|
|
383
|
+
<TableHeader>
|
|
384
|
+
<TableRow isHoverable={false}>
|
|
385
|
+
{columns.map((column) => (
|
|
386
|
+
<TableHeaderCell key={column.key} width={width(column.key)}>
|
|
387
|
+
{column.label}
|
|
388
|
+
</TableHeaderCell>
|
|
389
|
+
))}
|
|
390
|
+
</TableRow>
|
|
391
|
+
</TableHeader>
|
|
392
|
+
<TableBody>
|
|
393
|
+
{sampleUsers.slice(0, 3).map((user) => (
|
|
394
|
+
<TableRow key={user.id}>
|
|
395
|
+
{columns.map((column) => (
|
|
396
|
+
<TableCell key={column.key} width={width(column.key)}>
|
|
397
|
+
{user[column.key as keyof User]}
|
|
398
|
+
</TableCell>
|
|
399
|
+
))}
|
|
400
|
+
</TableRow>
|
|
401
|
+
))}
|
|
402
|
+
</TableBody>
|
|
403
|
+
</Table>
|
|
404
|
+
</View>
|
|
405
|
+
</View>
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* **Column drop order.** When the measured width cannot hold every column, they
|
|
411
|
+
* drop one at a time in the order the caller declared — `status`, then `role`,
|
|
412
|
+
* then `email`. `name` declares no `dropPriority`, so dropping stops there and
|
|
413
|
+
* the table scrolls horizontally instead: scroll is the floor, not the first
|
|
414
|
+
* resort. Measure with `useMeasuredWidth`, decide with `useColumnFit`, and hand
|
|
415
|
+
* `contentMinWidth` to `Table` so it knows what to scroll to.
|
|
416
|
+
*/
|
|
417
|
+
export const ColumnDropOrder: Story = {
|
|
418
|
+
render: () => (
|
|
419
|
+
<View className="gap-6">
|
|
420
|
+
{[660, 560, 440, 300, 150].map((available) => (
|
|
421
|
+
<FittedTable key={available} available={available} />
|
|
422
|
+
))}
|
|
423
|
+
</View>
|
|
424
|
+
),
|
|
425
|
+
}
|
|
426
|
+
|
|
342
427
|
export const UseTableHook: Story = {
|
|
343
428
|
render: function Render() {
|
|
344
429
|
const {
|
|
@@ -361,8 +446,8 @@ export const UseTableHook: Story = {
|
|
|
361
446
|
return (
|
|
362
447
|
<View style={{ gap: 8 }}>
|
|
363
448
|
<Text className="text-sm text-text-secondary">
|
|
364
|
-
Showing {paginatedData.length} of {totalItems} users, sorted by{
|
|
365
|
-
{
|
|
449
|
+
Showing {paginatedData.length} of {totalItems} users, sorted by {sortColumn ?? 'none'} (
|
|
450
|
+
{sortDirection ?? 'none'})
|
|
366
451
|
</Text>
|
|
367
452
|
<Table sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort}>
|
|
368
453
|
<TableHeader>
|
|
@@ -434,9 +519,7 @@ export const SortingAndSelection: Story = {
|
|
|
434
519
|
|
|
435
520
|
return (
|
|
436
521
|
<View style={{ gap: 8 }}>
|
|
437
|
-
<Text className="text-sm text-text-secondary">
|
|
438
|
-
Selected: {selectedRows.size} rows
|
|
439
|
-
</Text>
|
|
522
|
+
<Text className="text-sm text-text-secondary">Selected: {selectedRows.size} rows</Text>
|
|
440
523
|
<Table
|
|
441
524
|
sortColumn={sortColumn}
|
|
442
525
|
sortDirection={sortDirection}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { render, screen, fireEvent, renderHook, act } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
4
|
import {
|
|
5
5
|
Table,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
TableEmptyState,
|
|
13
13
|
TableSelectAllCell,
|
|
14
14
|
TableSelectCell,
|
|
15
|
+
useTable,
|
|
15
16
|
} from './Table'
|
|
16
17
|
|
|
17
18
|
function renderBasicTable() {
|
|
@@ -321,29 +322,142 @@ describe('Table', () => {
|
|
|
321
322
|
})
|
|
322
323
|
})
|
|
323
324
|
|
|
324
|
-
describe('
|
|
325
|
-
|
|
325
|
+
describe('sort cycle end state', () => {
|
|
326
|
+
// Regression: after asc -> desc -> unsorted the column is still the sort
|
|
327
|
+
// column, and the header kept showing the descending arrow.
|
|
328
|
+
it('shows the neutral glyph once the cycle returns to unsorted', () => {
|
|
326
329
|
render(
|
|
327
|
-
<
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
<Table sortColumn="name" sortDirection={null} onSort={vi.fn()}>
|
|
331
|
+
<TableHeader>
|
|
332
|
+
<TableRow>
|
|
333
|
+
<TableHeaderCell sortKey="name">Name</TableHeaderCell>
|
|
334
|
+
</TableRow>
|
|
335
|
+
</TableHeader>
|
|
336
|
+
</Table>
|
|
337
|
+
)
|
|
338
|
+
expect(screen.getByText('↕')).toBeInTheDocument()
|
|
339
|
+
expect(screen.getByRole('columnheader')).toHaveAttribute('aria-sort', 'none')
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('idle sort glyph', () => {
|
|
344
|
+
it('hides the neutral glyph until the header is hovered, and keeps an active arrow visible', () => {
|
|
345
|
+
render(
|
|
346
|
+
<Table sortColumn="name" sortDirection="asc" onSort={vi.fn()}>
|
|
347
|
+
<TableHeader>
|
|
348
|
+
<TableRow>
|
|
349
|
+
<TableHeaderCell sortKey="name">Name</TableHeaderCell>
|
|
350
|
+
<TableHeaderCell sortKey="email">Email</TableHeaderCell>
|
|
351
|
+
</TableRow>
|
|
352
|
+
</TableHeader>
|
|
353
|
+
</Table>
|
|
354
|
+
)
|
|
355
|
+
expect(screen.getByText('↑')).toHaveStyle({ opacity: '1' })
|
|
356
|
+
const idle = screen.getByText('↕')
|
|
357
|
+
expect(idle).toHaveStyle({ opacity: '0' })
|
|
358
|
+
fireEvent.mouseEnter(screen.getByRole('button', { name: 'Sort by Email' }))
|
|
359
|
+
expect(idle).toHaveStyle({ opacity: '1' })
|
|
360
|
+
fireEvent.mouseLeave(screen.getByRole('button', { name: 'Sort by Email' }))
|
|
361
|
+
expect(idle).toHaveStyle({ opacity: '0' })
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
describe('header tooltip', () => {
|
|
366
|
+
it('uses the full column name as the accessible sort name and shows it on hover', () => {
|
|
367
|
+
render(
|
|
368
|
+
<Table onSort={vi.fn()}>
|
|
369
|
+
<TableHeader>
|
|
370
|
+
<TableRow>
|
|
371
|
+
<TableHeaderCell sortKey="priority" tooltip="Priority">
|
|
372
|
+
Pri
|
|
373
|
+
</TableHeaderCell>
|
|
374
|
+
</TableRow>
|
|
375
|
+
</TableHeader>
|
|
376
|
+
</Table>
|
|
377
|
+
)
|
|
378
|
+
const sortButton = screen.getByRole('button', { name: 'Sort by Priority' })
|
|
379
|
+
fireEvent.mouseEnter(sortButton.parentElement!)
|
|
380
|
+
expect(screen.getByText('Priority')).toBeInTheDocument()
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('still sorts when the tooltip-wrapped header is pressed', () => {
|
|
384
|
+
const onSort = vi.fn()
|
|
385
|
+
render(
|
|
386
|
+
<Table onSort={onSort}>
|
|
387
|
+
<TableHeader>
|
|
388
|
+
<TableRow>
|
|
389
|
+
<TableHeaderCell sortKey="priority" tooltip="Priority">
|
|
390
|
+
Pri
|
|
391
|
+
</TableHeaderCell>
|
|
392
|
+
</TableRow>
|
|
393
|
+
</TableHeader>
|
|
394
|
+
</Table>
|
|
333
395
|
)
|
|
396
|
+
fireEvent.click(screen.getByText('Pri'))
|
|
397
|
+
expect(onSort).toHaveBeenCalledWith('priority')
|
|
398
|
+
})
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
describe('flexible column layout', () => {
|
|
402
|
+
// Regression: a long nowrap title used to size the horizontal scroller to
|
|
403
|
+
// max-content, so the flexible column never shrank and the last fixed
|
|
404
|
+
// column was clipped inside the table's card (AW-22 T2 Gate 2 finding).
|
|
405
|
+
it('fills the scroll viewport instead of sizing to its longest cell', () => {
|
|
406
|
+
renderBasicTable()
|
|
407
|
+
const scrollContent = screen.getByRole('table').parentElement
|
|
408
|
+
expect(scrollContent).toHaveStyle({ width: '100%' })
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('lets a cell without a width shrink below its content', () => {
|
|
412
|
+
render(
|
|
413
|
+
<Table>
|
|
414
|
+
<TableBody>
|
|
415
|
+
<TableRow>
|
|
416
|
+
<TableCell width={80}>fixed</TableCell>
|
|
417
|
+
<TableCell>flexible</TableCell>
|
|
418
|
+
</TableRow>
|
|
419
|
+
</TableBody>
|
|
420
|
+
</Table>
|
|
421
|
+
)
|
|
422
|
+
expect(screen.getByText('fixed').closest('[role="cell"]')).toHaveStyle({ width: '80px' })
|
|
423
|
+
expect(screen.getByText('flexible').closest('[role="cell"]')).toHaveStyle({
|
|
424
|
+
flexGrow: '1',
|
|
425
|
+
minWidth: '0px',
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('floors the content at the width the columns need, so a too-narrow table scrolls', () => {
|
|
430
|
+
render(
|
|
431
|
+
<Table contentMinWidth={420}>
|
|
432
|
+
<TableBody>
|
|
433
|
+
<TableRow>
|
|
434
|
+
<TableCell>only</TableCell>
|
|
435
|
+
</TableRow>
|
|
436
|
+
</TableBody>
|
|
437
|
+
</Table>
|
|
438
|
+
)
|
|
439
|
+
// width 100% + the floor resolves to max(container, floor): fill, then scroll.
|
|
440
|
+
expect(screen.getByRole('table').parentElement).toHaveStyle({
|
|
441
|
+
width: '100%',
|
|
442
|
+
minWidth: '420px',
|
|
443
|
+
})
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('sets no floor when the caller has not measured one', () => {
|
|
447
|
+
renderBasicTable()
|
|
448
|
+
expect(screen.getByRole('table').parentElement).not.toHaveStyle({ minWidth: '420px' })
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('TablePagination', () => {
|
|
453
|
+
it('renders pagination info', () => {
|
|
454
|
+
render(<TablePagination page={0} pageSize={10} totalItems={50} onPageChange={vi.fn()} />)
|
|
334
455
|
expect(screen.getByText('1-10 of 50')).toBeInTheDocument()
|
|
335
456
|
})
|
|
336
457
|
|
|
337
458
|
it('calls onPageChange when next is clicked', () => {
|
|
338
459
|
const onPageChange = vi.fn()
|
|
339
|
-
render(
|
|
340
|
-
<TablePagination
|
|
341
|
-
page={0}
|
|
342
|
-
pageSize={10}
|
|
343
|
-
totalItems={50}
|
|
344
|
-
onPageChange={onPageChange}
|
|
345
|
-
/>
|
|
346
|
-
)
|
|
460
|
+
render(<TablePagination page={0} pageSize={10} totalItems={50} onPageChange={onPageChange} />)
|
|
347
461
|
|
|
348
462
|
fireEvent.click(screen.getByLabelText('Next page'))
|
|
349
463
|
expect(onPageChange).toHaveBeenCalledWith(1)
|
|
@@ -351,41 +465,20 @@ describe('Table', () => {
|
|
|
351
465
|
|
|
352
466
|
it('calls onPageChange when previous is clicked', () => {
|
|
353
467
|
const onPageChange = vi.fn()
|
|
354
|
-
render(
|
|
355
|
-
<TablePagination
|
|
356
|
-
page={2}
|
|
357
|
-
pageSize={10}
|
|
358
|
-
totalItems={50}
|
|
359
|
-
onPageChange={onPageChange}
|
|
360
|
-
/>
|
|
361
|
-
)
|
|
468
|
+
render(<TablePagination page={2} pageSize={10} totalItems={50} onPageChange={onPageChange} />)
|
|
362
469
|
|
|
363
470
|
fireEvent.click(screen.getByLabelText('Previous page'))
|
|
364
471
|
expect(onPageChange).toHaveBeenCalledWith(1)
|
|
365
472
|
})
|
|
366
473
|
|
|
367
474
|
it('disables previous button on first page', () => {
|
|
368
|
-
render(
|
|
369
|
-
<TablePagination
|
|
370
|
-
page={0}
|
|
371
|
-
pageSize={10}
|
|
372
|
-
totalItems={50}
|
|
373
|
-
onPageChange={vi.fn()}
|
|
374
|
-
/>
|
|
375
|
-
)
|
|
475
|
+
render(<TablePagination page={0} pageSize={10} totalItems={50} onPageChange={vi.fn()} />)
|
|
376
476
|
const prevButton = screen.getByLabelText('Previous page')
|
|
377
477
|
expect(prevButton).toHaveAttribute('aria-disabled', 'true')
|
|
378
478
|
})
|
|
379
479
|
|
|
380
480
|
it('disables next button on last page', () => {
|
|
381
|
-
render(
|
|
382
|
-
<TablePagination
|
|
383
|
-
page={4}
|
|
384
|
-
pageSize={10}
|
|
385
|
-
totalItems={50}
|
|
386
|
-
onPageChange={vi.fn()}
|
|
387
|
-
/>
|
|
388
|
-
)
|
|
481
|
+
render(<TablePagination page={4} pageSize={10} totalItems={50} onPageChange={vi.fn()} />)
|
|
389
482
|
const nextButton = screen.getByLabelText('Next page')
|
|
390
483
|
expect(nextButton).toHaveAttribute('aria-disabled', 'true')
|
|
391
484
|
})
|
|
@@ -422,26 +515,12 @@ describe('Table', () => {
|
|
|
422
515
|
})
|
|
423
516
|
|
|
424
517
|
it('shows correct range for middle pages', () => {
|
|
425
|
-
render(
|
|
426
|
-
<TablePagination
|
|
427
|
-
page={2}
|
|
428
|
-
pageSize={10}
|
|
429
|
-
totalItems={50}
|
|
430
|
-
onPageChange={vi.fn()}
|
|
431
|
-
/>
|
|
432
|
-
)
|
|
518
|
+
render(<TablePagination page={2} pageSize={10} totalItems={50} onPageChange={vi.fn()} />)
|
|
433
519
|
expect(screen.getByText('21-30 of 50')).toBeInTheDocument()
|
|
434
520
|
})
|
|
435
521
|
|
|
436
522
|
it('shows correct range for last partial page', () => {
|
|
437
|
-
render(
|
|
438
|
-
<TablePagination
|
|
439
|
-
page={2}
|
|
440
|
-
pageSize={10}
|
|
441
|
-
totalItems={25}
|
|
442
|
-
onPageChange={vi.fn()}
|
|
443
|
-
/>
|
|
444
|
-
)
|
|
523
|
+
render(<TablePagination page={2} pageSize={10} totalItems={25} onPageChange={vi.fn()} />)
|
|
445
524
|
expect(screen.getByText('21-25 of 25')).toBeInTheDocument()
|
|
446
525
|
})
|
|
447
526
|
})
|
|
@@ -454,30 +533,18 @@ describe('Table', () => {
|
|
|
454
533
|
})
|
|
455
534
|
|
|
456
535
|
it('renders with custom title and description', () => {
|
|
457
|
-
render(
|
|
458
|
-
<TableEmptyState title="No users" description="Add users to see them here." />
|
|
459
|
-
)
|
|
536
|
+
render(<TableEmptyState title="No users" description="Add users to see them here." />)
|
|
460
537
|
expect(screen.getByText('No users')).toBeInTheDocument()
|
|
461
538
|
expect(screen.getByText('Add users to see them here.')).toBeInTheDocument()
|
|
462
539
|
})
|
|
463
540
|
|
|
464
541
|
it('renders with action element', () => {
|
|
465
|
-
render(
|
|
466
|
-
<TableEmptyState
|
|
467
|
-
title="No data"
|
|
468
|
-
action={<button>Add Item</button>}
|
|
469
|
-
/>
|
|
470
|
-
)
|
|
542
|
+
render(<TableEmptyState title="No data" action={<button>Add Item</button>} />)
|
|
471
543
|
expect(screen.getByText('Add Item')).toBeInTheDocument()
|
|
472
544
|
})
|
|
473
545
|
|
|
474
546
|
it('renders with custom icon', () => {
|
|
475
|
-
render(
|
|
476
|
-
<TableEmptyState
|
|
477
|
-
title="No data"
|
|
478
|
-
icon={<span data-testid="empty-icon">E</span>}
|
|
479
|
-
/>
|
|
480
|
-
)
|
|
547
|
+
render(<TableEmptyState title="No data" icon={<span data-testid="empty-icon">E</span>} />)
|
|
481
548
|
expect(screen.getByTestId('empty-icon')).toBeInTheDocument()
|
|
482
549
|
})
|
|
483
550
|
})
|
|
@@ -491,12 +558,7 @@ describe('Table', () => {
|
|
|
491
558
|
|
|
492
559
|
it('has no accessibility violations for pagination', async () => {
|
|
493
560
|
const { container } = render(
|
|
494
|
-
<TablePagination
|
|
495
|
-
page={0}
|
|
496
|
-
pageSize={10}
|
|
497
|
-
totalItems={50}
|
|
498
|
-
onPageChange={vi.fn()}
|
|
499
|
-
/>
|
|
561
|
+
<TablePagination page={0} pageSize={10} totalItems={50} onPageChange={vi.fn()} />
|
|
500
562
|
)
|
|
501
563
|
const results = await axe(container)
|
|
502
564
|
expect(results).toHaveNoViolations()
|
|
@@ -543,16 +605,141 @@ describe('Table', () => {
|
|
|
543
605
|
})
|
|
544
606
|
|
|
545
607
|
it('pagination buttons have accessible labels', () => {
|
|
546
|
-
render(
|
|
547
|
-
<TablePagination
|
|
548
|
-
page={1}
|
|
549
|
-
pageSize={10}
|
|
550
|
-
totalItems={50}
|
|
551
|
-
onPageChange={vi.fn()}
|
|
552
|
-
/>
|
|
553
|
-
)
|
|
608
|
+
render(<TablePagination page={1} pageSize={10} totalItems={50} onPageChange={vi.fn()} />)
|
|
554
609
|
expect(screen.getByLabelText('Previous page')).toBeInTheDocument()
|
|
555
610
|
expect(screen.getByLabelText('Next page')).toBeInTheDocument()
|
|
556
611
|
})
|
|
557
612
|
})
|
|
558
613
|
})
|
|
614
|
+
|
|
615
|
+
describe('Table density', () => {
|
|
616
|
+
it('defaults to comfortable and accepts dense without changing structure', () => {
|
|
617
|
+
const { rerender } = render(
|
|
618
|
+
<Table>
|
|
619
|
+
<TableBody>
|
|
620
|
+
<TableRow>
|
|
621
|
+
<TableCell>Alice</TableCell>
|
|
622
|
+
</TableRow>
|
|
623
|
+
</TableBody>
|
|
624
|
+
</Table>
|
|
625
|
+
)
|
|
626
|
+
expect(screen.getByRole('cell')).toBeInTheDocument()
|
|
627
|
+
|
|
628
|
+
rerender(
|
|
629
|
+
<Table density="dense">
|
|
630
|
+
<TableBody>
|
|
631
|
+
<TableRow>
|
|
632
|
+
<TableCell>Alice</TableCell>
|
|
633
|
+
</TableRow>
|
|
634
|
+
</TableBody>
|
|
635
|
+
</Table>
|
|
636
|
+
)
|
|
637
|
+
// nativewind compiles className to style, so density is asserted through
|
|
638
|
+
// structure + a11y surviving rather than through class names.
|
|
639
|
+
expect(screen.getByRole('cell')).toBeInTheDocument()
|
|
640
|
+
expect(screen.getByText('Alice')).toBeInTheDocument()
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
it('keeps header cells sortable at dense density', () => {
|
|
644
|
+
const onSort = vi.fn()
|
|
645
|
+
render(
|
|
646
|
+
<Table density="dense" sortColumn="name" sortDirection="asc" onSort={onSort}>
|
|
647
|
+
<TableHeader>
|
|
648
|
+
<TableRow>
|
|
649
|
+
<TableHeaderCell sortKey="name">Name</TableHeaderCell>
|
|
650
|
+
</TableRow>
|
|
651
|
+
</TableHeader>
|
|
652
|
+
</Table>
|
|
653
|
+
)
|
|
654
|
+
fireEvent.click(screen.getByRole('button', { name: 'Sort by Name' }))
|
|
655
|
+
expect(onSort).toHaveBeenCalledWith('name')
|
|
656
|
+
expect(screen.getByRole('columnheader')).toHaveAttribute('aria-sort', 'ascending')
|
|
657
|
+
})
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
describe('useTable', () => {
|
|
661
|
+
// A type alias, not an interface: `useTable` constrains T to an index-signature
|
|
662
|
+
// shape, and only aliases get the implicit index signature that satisfies it.
|
|
663
|
+
type Row = {
|
|
664
|
+
id: string
|
|
665
|
+
rank: number
|
|
666
|
+
size?: number
|
|
667
|
+
}
|
|
668
|
+
const rows: Row[] = [
|
|
669
|
+
{ id: 'b', rank: 2, size: 5 },
|
|
670
|
+
{ id: 'a', rank: 3 },
|
|
671
|
+
{ id: 'c', rank: 1, size: 2 },
|
|
672
|
+
]
|
|
673
|
+
|
|
674
|
+
const ids = (data: Row[]) => data.map((r) => r.id)
|
|
675
|
+
|
|
676
|
+
it('returns data untouched until a direction is set', () => {
|
|
677
|
+
const { result } = renderHook(() => useTable<Row>({ data: rows, defaultSortColumn: 'rank' }))
|
|
678
|
+
expect(ids(result.current.sortedData)).toEqual(['b', 'a', 'c'])
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
it('sorts by raw field value when no comparator is supplied', () => {
|
|
682
|
+
const { result } = renderHook(() =>
|
|
683
|
+
useTable<Row>({ data: rows, defaultSortColumn: 'rank', defaultSortDirection: 'asc' })
|
|
684
|
+
)
|
|
685
|
+
expect(ids(result.current.sortedData)).toEqual(['c', 'b', 'a'])
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
it('ranks blank values last in BOTH directions', () => {
|
|
689
|
+
const asc = renderHook(() =>
|
|
690
|
+
useTable<Row>({ data: rows, defaultSortColumn: 'size', defaultSortDirection: 'asc' })
|
|
691
|
+
)
|
|
692
|
+
const desc = renderHook(() =>
|
|
693
|
+
useTable<Row>({ data: rows, defaultSortColumn: 'size', defaultSortDirection: 'desc' })
|
|
694
|
+
)
|
|
695
|
+
// `a` has no size; flipping direction must not promote it to the top.
|
|
696
|
+
expect(ids(asc.result.current.sortedData).at(-1)).toBe('a')
|
|
697
|
+
expect(ids(desc.result.current.sortedData).at(-1)).toBe('a')
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('uses a per-column comparator when one is supplied', () => {
|
|
701
|
+
const { result } = renderHook(() =>
|
|
702
|
+
useTable<Row>({
|
|
703
|
+
data: rows,
|
|
704
|
+
defaultSortColumn: 'id',
|
|
705
|
+
defaultSortDirection: 'asc',
|
|
706
|
+
// Reverse-alphabetical, to prove the comparator beats the field compare.
|
|
707
|
+
comparators: { id: (x, y) => y.id.localeCompare(x.id) },
|
|
708
|
+
})
|
|
709
|
+
)
|
|
710
|
+
expect(ids(result.current.sortedData)).toEqual(['c', 'b', 'a'])
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
it('inverts a comparator for desc rather than reversing the array', () => {
|
|
714
|
+
const { result } = renderHook(() =>
|
|
715
|
+
useTable<Row>({
|
|
716
|
+
data: rows,
|
|
717
|
+
defaultSortColumn: 'id',
|
|
718
|
+
defaultSortDirection: 'desc',
|
|
719
|
+
comparators: { id: (x, y) => y.id.localeCompare(x.id) },
|
|
720
|
+
})
|
|
721
|
+
)
|
|
722
|
+
expect(ids(result.current.sortedData)).toEqual(['a', 'b', 'c'])
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
it('leaves columns without a comparator on the default compare', () => {
|
|
726
|
+
const { result } = renderHook(() =>
|
|
727
|
+
useTable<Row>({
|
|
728
|
+
data: rows,
|
|
729
|
+
defaultSortColumn: 'rank',
|
|
730
|
+
defaultSortDirection: 'asc',
|
|
731
|
+
comparators: { id: (x, y) => y.id.localeCompare(x.id) },
|
|
732
|
+
})
|
|
733
|
+
)
|
|
734
|
+
expect(ids(result.current.sortedData)).toEqual(['c', 'b', 'a'])
|
|
735
|
+
})
|
|
736
|
+
|
|
737
|
+
it('toggles direction through handleSort on the same column', () => {
|
|
738
|
+
const { result } = renderHook(() =>
|
|
739
|
+
useTable<Row>({ data: rows, defaultSortColumn: 'rank', defaultSortDirection: 'asc' })
|
|
740
|
+
)
|
|
741
|
+
act(() => result.current.handleSort('rank'))
|
|
742
|
+
expect(result.current.sortDirection).toBe('desc')
|
|
743
|
+
expect(ids(result.current.sortedData)).toEqual(['a', 'b', 'c'])
|
|
744
|
+
})
|
|
745
|
+
})
|