@titan-design/react-ui 0.12.1 → 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 +1319 -216
- package/dist/index.d.ts +1319 -216
- 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 +195 -134
- package/dist/theme/index.d.ts +195 -134
- 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 +73 -5
- package/dist/theme/tokens.d.ts +73 -5
- package/dist/theme/tokens.js +55 -7
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +55 -8
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/audits/2026-09-08-critique-addenda.md +51 -0
- package/docs/audits/2026-09-08-library-critique.md +212 -0
- package/docs/library-roadmap.md +99 -0
- package/package.json +1 -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 +11 -6
- 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 +89 -26
- package/src/components/shell/SideNav.stories.tsx +38 -7
- package/src/components/shell/SideNav.test.tsx +16 -8
- package/src/components/shell/SideNav.tsx +17 -17
- package/src/components/shell/TopBar.stories.tsx +44 -19
- package/src/components/shell/TopBar.test.tsx +34 -12
- package/src/components/shell/TopBar.tsx +61 -40
- package/src/components/shell/brands.tsx +66 -0
- package/src/components/shell/index.ts +9 -8
- package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
- package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
- package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
- package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
- package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
- package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
- package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
- package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
- package/src/components/shell/workout/WorkoutShell.tsx +77 -0
- package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
- package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
- package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
- package/src/components/shell/workout/index.ts +9 -0
- package/src/components/shell/workout/workoutNavItems.tsx +16 -0
- package/src/components/ui/README.md +110 -0
- package/src/components/ui/alert/Alert.stories.tsx +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 +23 -21
- 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 +1 -3
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +65 -33
- 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
|
@@ -11,12 +11,7 @@ describe('ToolbarButton', () => {
|
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
it('renders icon when provided', () => {
|
|
14
|
-
render(
|
|
15
|
-
<ToolbarButton
|
|
16
|
-
label="Settings"
|
|
17
|
-
icon={<span data-testid="icon">gear</span>}
|
|
18
|
-
/>
|
|
19
|
-
)
|
|
14
|
+
render(<ToolbarButton label="Settings" icon={<span data-testid="icon">gear</span>} />)
|
|
20
15
|
expect(screen.getByTestId('icon')).toBeInTheDocument()
|
|
21
16
|
})
|
|
22
17
|
|
|
@@ -108,24 +103,14 @@ describe('ToolbarButton', () => {
|
|
|
108
103
|
|
|
109
104
|
describe('menu content', () => {
|
|
110
105
|
it('shows menu when pressed with menuContent', () => {
|
|
111
|
-
render(
|
|
112
|
-
<ToolbarButton
|
|
113
|
-
label="Options"
|
|
114
|
-
menuContent={<span>Menu item</span>}
|
|
115
|
-
/>
|
|
116
|
-
)
|
|
106
|
+
render(<ToolbarButton label="Options" menuContent={<span>Menu item</span>} />)
|
|
117
107
|
|
|
118
108
|
fireEvent.click(screen.getByRole('button', { name: 'Options' }))
|
|
119
109
|
expect(screen.getByText('Menu item')).toBeInTheDocument()
|
|
120
110
|
})
|
|
121
111
|
|
|
122
112
|
it('hides menu when pressed again', () => {
|
|
123
|
-
render(
|
|
124
|
-
<ToolbarButton
|
|
125
|
-
label="Options"
|
|
126
|
-
menuContent={<span>Menu item</span>}
|
|
127
|
-
/>
|
|
128
|
-
)
|
|
113
|
+
render(<ToolbarButton label="Options" menuContent={<span>Menu item</span>} />)
|
|
129
114
|
|
|
130
115
|
const button = screen.getByRole('button', { name: 'Options' })
|
|
131
116
|
fireEvent.click(button)
|
|
@@ -3,8 +3,10 @@ import { View, Text, Pressable, type ViewProps, StyleSheet, Platform } from 'rea
|
|
|
3
3
|
import { cn } from '../../../utils/cn'
|
|
4
4
|
import { getHoverColors } from '../../../theme'
|
|
5
5
|
import { greyRamp } from '../../../theme/tokens/primitives'
|
|
6
|
-
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
7
6
|
import { resolveColor } from '../../../theme/resolve-color'
|
|
7
|
+
import { getPressedRecessShadow } from '../../../theme/elevation'
|
|
8
|
+
import { liftStyle } from '../../../theme/lift'
|
|
9
|
+
import { Surface, useSurfaceMode } from '../surface'
|
|
8
10
|
|
|
9
11
|
export type ToolbarButtonVariant = 'default' | 'raised'
|
|
10
12
|
export type ToolbarButtonSize = 'sm' | 'md' | 'lg'
|
|
@@ -24,7 +26,7 @@ export interface ToolbarButtonProps extends ViewProps {
|
|
|
24
26
|
label: string
|
|
25
27
|
/** Icon component */
|
|
26
28
|
icon?: React.ReactNode
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
28
30
|
* Whether the button is in active/selected state.
|
|
29
31
|
* - `true`: pressed/active appearance (darker, white text, orange icon)
|
|
30
32
|
* - `undefined`: pressed appearance (darker, white text/icon)
|
|
@@ -49,19 +51,10 @@ export interface ToolbarButtonProps extends ViewProps {
|
|
|
49
51
|
className?: string
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
const SEMANTIC = getSemanticColors('dark')
|
|
53
|
-
|
|
54
54
|
// Base button colours. These were raw `#3C3C3C`/`#2C2C2C` literals — old cold
|
|
55
55
|
// charcoal steps that survived the grey migration only because they were plain
|
|
56
56
|
// strings rather than scale references.
|
|
57
57
|
const BUTTON_BG = greyRamp[800]
|
|
58
|
-
const MENU_BG = greyRamp[900]
|
|
59
|
-
|
|
60
|
-
// Inline depth is a HAIRLINE, not a shadow (TD-07.16). On a ~10% lightness
|
|
61
|
-
// surface a dual-opposing neumorphic shadow has no room below to read; alpha
|
|
62
|
-
// composites by the same amount over any plane, so the edge holds everywhere.
|
|
63
|
-
const EDGE = { borderWidth: 1, borderColor: SEMANTIC['hairline-default'] } as const
|
|
64
|
-
const EDGE_PRESSED = { borderWidth: 1, borderColor: SEMANTIC['hairline-strong'] } as const
|
|
65
58
|
|
|
66
59
|
// Calculate hover colors using color math
|
|
67
60
|
const hoverColors = getHoverColors(BUTTON_BG, 'medium')
|
|
@@ -83,11 +76,11 @@ const textSizeStyles: Record<ToolbarButtonSize, string> = {
|
|
|
83
76
|
* ToolbarButton component for toolbar actions with toggle state support.
|
|
84
77
|
*
|
|
85
78
|
* Features:
|
|
86
|
-
* -
|
|
79
|
+
* - Depth from the fill plus the lift (raised) or the inset recess (active)
|
|
87
80
|
* - Active (pressed) state is the default visual appearance
|
|
88
81
|
* - Set isActive={false} explicitly for the raised/inactive appearance
|
|
89
82
|
* - Orange accent color on icon when isActive={true}
|
|
90
|
-
* - Hover states that lighten background
|
|
83
|
+
* - Hover states that lighten the background
|
|
91
84
|
*
|
|
92
85
|
* @example
|
|
93
86
|
* // Toggle button - orange icon when active
|
|
@@ -123,6 +116,7 @@ export function ToolbarButton({
|
|
|
123
116
|
}: ToolbarButtonProps) {
|
|
124
117
|
const [isOpen, setIsOpen] = useState(false)
|
|
125
118
|
const [isHovered, setIsHovered] = useState(false)
|
|
119
|
+
const mode = useSurfaceMode()
|
|
126
120
|
|
|
127
121
|
const handlePress = useCallback(() => {
|
|
128
122
|
if (isDisabled) return
|
|
@@ -146,28 +140,21 @@ export function ToolbarButton({
|
|
|
146
140
|
return showActive ? '#FFFFFF' : '#D1D1D1'
|
|
147
141
|
}
|
|
148
142
|
|
|
149
|
-
// Get shadow and background styles for neumorphic variant
|
|
150
143
|
/**
|
|
151
|
-
* Raised/pressed treatment
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* for the dark half to have somewhere to fall, and these buttons sit at ~10%
|
|
156
|
-
* lightness.
|
|
144
|
+
* Raised/pressed treatment on the depth model: the raised face is a plane one
|
|
145
|
+
* step above the toolbar and wears the lift, the active face is pressed into
|
|
146
|
+
* it and wears the recess. The hairline ring both used to carry is an edge,
|
|
147
|
+
* not depth, and is gone with the rest of the rings on this pass.
|
|
157
148
|
*/
|
|
158
149
|
const getRaisedStyle = () => {
|
|
159
|
-
if (isDisabled)
|
|
160
|
-
return styles.disabledBg
|
|
161
|
-
}
|
|
150
|
+
if (isDisabled) return styles.disabledBg
|
|
162
151
|
if (showActive) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
...EDGE_PRESSED,
|
|
166
|
-
}
|
|
152
|
+
const fill = isHovered ? hoverColors.pressed : greyRamp[900]
|
|
153
|
+
return { backgroundColor: fill, ...getPressedRecessShadow(fill, mode) }
|
|
167
154
|
}
|
|
168
155
|
return {
|
|
169
156
|
backgroundColor: isHovered ? hoverColors.raised : BUTTON_BG,
|
|
170
|
-
...
|
|
157
|
+
...liftStyle(1, mode),
|
|
171
158
|
}
|
|
172
159
|
}
|
|
173
160
|
|
|
@@ -202,20 +189,19 @@ export function ToolbarButton({
|
|
|
202
189
|
>
|
|
203
190
|
{icon && (
|
|
204
191
|
<View className="w-5 h-5 items-center justify-center">
|
|
205
|
-
{React.isValidElement(icon)
|
|
192
|
+
{React.isValidElement(icon)
|
|
206
193
|
? React.cloneElement(icon as React.ReactElement<any>, {
|
|
207
194
|
size: 20,
|
|
208
195
|
width: 20,
|
|
209
196
|
height: 20,
|
|
210
197
|
color: getIconColor(),
|
|
211
|
-
style: {
|
|
198
|
+
style: {
|
|
212
199
|
color: getIconColor(),
|
|
213
200
|
width: 20,
|
|
214
201
|
height: 20,
|
|
215
202
|
},
|
|
216
203
|
})
|
|
217
|
-
: icon
|
|
218
|
-
}
|
|
204
|
+
: icon}
|
|
219
205
|
</View>
|
|
220
206
|
)}
|
|
221
207
|
{showLabel && (
|
|
@@ -237,22 +223,18 @@ export function ToolbarButton({
|
|
|
237
223
|
{menuContent && isOpen && (
|
|
238
224
|
<>
|
|
239
225
|
{/* Backdrop */}
|
|
240
|
-
<Pressable
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
{/* Menu Content */}
|
|
246
|
-
<View
|
|
226
|
+
<Pressable onPress={handleClose} style={StyleSheet.absoluteFill} className="z-40" />
|
|
227
|
+
{/* Menu Content — floating: overlay plane + lift, no ring. */}
|
|
228
|
+
<Surface
|
|
229
|
+
elevation={4}
|
|
230
|
+
rounded={false}
|
|
247
231
|
className={cn(
|
|
248
232
|
'absolute z-50 top-full left-0 mt-1',
|
|
249
|
-
'rounded-lg
|
|
250
|
-
'min-w-[150px] overflow-hidden',
|
|
251
|
-
`bg-[${MENU_BG}]`
|
|
233
|
+
'rounded-lg min-w-[150px] overflow-hidden'
|
|
252
234
|
)}
|
|
253
235
|
>
|
|
254
236
|
{menuContent}
|
|
255
|
-
</
|
|
237
|
+
</Surface>
|
|
256
238
|
</>
|
|
257
239
|
)}
|
|
258
240
|
</View>
|
|
@@ -10,7 +10,16 @@ const meta: Meta<typeof Tooltip> = {
|
|
|
10
10
|
argTypes: {
|
|
11
11
|
placement: {
|
|
12
12
|
control: 'select',
|
|
13
|
-
options: [
|
|
13
|
+
options: [
|
|
14
|
+
'top',
|
|
15
|
+
'top-start',
|
|
16
|
+
'top-end',
|
|
17
|
+
'bottom',
|
|
18
|
+
'bottom-start',
|
|
19
|
+
'bottom-end',
|
|
20
|
+
'left',
|
|
21
|
+
'right',
|
|
22
|
+
],
|
|
14
23
|
},
|
|
15
24
|
hasArrow: { control: 'boolean' },
|
|
16
25
|
isDisabled: { control: 'boolean' },
|
|
@@ -118,8 +127,8 @@ export const OnText: Story = {
|
|
|
118
127
|
Hover over the{' '}
|
|
119
128
|
<Tooltip label="This is helpful information">
|
|
120
129
|
<Text className="text-brand-primary underline">underlined text</Text>
|
|
121
|
-
</Tooltip>
|
|
122
|
-
|
|
130
|
+
</Tooltip>{' '}
|
|
131
|
+
to see a tooltip.
|
|
123
132
|
</Text>
|
|
124
133
|
</View>
|
|
125
134
|
),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { Text } from 'react-native'
|
|
3
4
|
import { axe } from 'jest-axe'
|
|
4
5
|
import { Tooltip } from './Tooltip'
|
|
5
6
|
|
|
@@ -202,9 +203,7 @@ describe('Tooltip', () => {
|
|
|
202
203
|
hoverTrigger('Hover me')
|
|
203
204
|
expect(screen.getByText('Portal tooltip')).toBeInTheDocument()
|
|
204
205
|
expect(screen.getByTestId('tooltip-portal')).toBeInTheDocument()
|
|
205
|
-
expect(screen.getByTestId('tooltip-portal').parentElement).toBe(
|
|
206
|
-
document.body
|
|
207
|
-
)
|
|
206
|
+
expect(screen.getByTestId('tooltip-portal').parentElement).toBe(document.body)
|
|
208
207
|
})
|
|
209
208
|
|
|
210
209
|
it('applies fixed positioning and z-index to portal tooltip', () => {
|
|
@@ -260,3 +259,22 @@ describe('Tooltip', () => {
|
|
|
260
259
|
})
|
|
261
260
|
})
|
|
262
261
|
})
|
|
262
|
+
|
|
263
|
+
describe('controlled visibility', () => {
|
|
264
|
+
it('follows isOpen instead of its own hover, and renders no pressable wrapper', () => {
|
|
265
|
+
const { rerender } = render(
|
|
266
|
+
<Tooltip label="Controlled" isOpen={false}>
|
|
267
|
+
<Text>Trigger</Text>
|
|
268
|
+
</Tooltip>
|
|
269
|
+
)
|
|
270
|
+
hoverTrigger('Trigger')
|
|
271
|
+
expect(screen.queryByText('Controlled')).not.toBeInTheDocument()
|
|
272
|
+
rerender(
|
|
273
|
+
<Tooltip label="Controlled" isOpen>
|
|
274
|
+
<Text>Trigger</Text>
|
|
275
|
+
</Tooltip>
|
|
276
|
+
)
|
|
277
|
+
expect(screen.getByText('Controlled')).toBeInTheDocument()
|
|
278
|
+
expect(screen.getByText('Trigger').parentElement).not.toHaveAttribute('tabindex')
|
|
279
|
+
})
|
|
280
|
+
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react'
|
|
2
|
-
import { View, Text,
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
|
2
|
+
import { View, Text, Platform, type ViewProps } from 'react-native'
|
|
3
3
|
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { Surface } from '../surface'
|
|
5
|
+
import { TriggerSurface } from '../trigger'
|
|
4
6
|
|
|
5
7
|
let createPortal: typeof import('react-dom').createPortal | undefined
|
|
6
8
|
if (Platform.OS === 'web') {
|
|
@@ -45,6 +47,21 @@ export interface TooltipProps extends ViewProps {
|
|
|
45
47
|
className?: string
|
|
46
48
|
/** Render tooltip via portal to escape overflow:hidden ancestors (web only) */
|
|
47
49
|
usePortal?: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Controlled visibility. In this mode the tooltip renders no Pressable of its
|
|
52
|
+
* own, so it can sit inside a pressable row without taking its press; the
|
|
53
|
+
* caller owns hover (see {@link useHoverState}). Needed because RNW ends a
|
|
54
|
+
* wrapper's hover the moment a nested Pressable claims the pointer.
|
|
55
|
+
*/
|
|
56
|
+
isOpen?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Hover state from pointer enter/leave on any View, for driving a controlled Tooltip. */
|
|
60
|
+
export function useHoverState() {
|
|
61
|
+
const [hovered, setHovered] = useState(false)
|
|
62
|
+
const onPointerEnter = useCallback(() => setHovered(true), [])
|
|
63
|
+
const onPointerLeave = useCallback(() => setHovered(false), [])
|
|
64
|
+
return { hovered, hoverProps: { onPointerEnter, onPointerLeave } }
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
/**
|
|
@@ -74,9 +91,11 @@ export function Tooltip({
|
|
|
74
91
|
isDisabled = false,
|
|
75
92
|
className,
|
|
76
93
|
usePortal: usePortalProp = false,
|
|
94
|
+
isOpen,
|
|
77
95
|
...props
|
|
78
96
|
}: TooltipProps) {
|
|
79
|
-
const [
|
|
97
|
+
const [hovered, setHovered] = useState(false)
|
|
98
|
+
const isVisible = isOpen ?? hovered
|
|
80
99
|
const [portalPos, setPortalPos] = useState<PortalPosition | null>(null)
|
|
81
100
|
const openTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
82
101
|
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
@@ -129,31 +148,37 @@ export function Tooltip({
|
|
|
129
148
|
if (isDisabled) return
|
|
130
149
|
clearTimeouts()
|
|
131
150
|
if (openDelay > 0) {
|
|
132
|
-
openTimeoutRef.current = setTimeout(() =>
|
|
151
|
+
openTimeoutRef.current = setTimeout(() => setHovered(true), openDelay)
|
|
133
152
|
} else {
|
|
134
|
-
|
|
153
|
+
setHovered(true)
|
|
135
154
|
}
|
|
136
155
|
}
|
|
137
156
|
|
|
138
157
|
const hide = () => {
|
|
139
158
|
clearTimeouts()
|
|
140
159
|
if (closeDelay > 0) {
|
|
141
|
-
closeTimeoutRef.current = setTimeout(() =>
|
|
160
|
+
closeTimeoutRef.current = setTimeout(() => setHovered(false), closeDelay)
|
|
142
161
|
} else {
|
|
143
|
-
|
|
162
|
+
setHovered(false)
|
|
144
163
|
}
|
|
145
164
|
}
|
|
146
165
|
|
|
147
|
-
// Arrow positioning
|
|
166
|
+
// Arrow positioning. The arrow is a border triangle, so it takes the same surface token as the box.
|
|
148
167
|
const arrowStyles = {
|
|
149
|
-
top: 'bottom-0 left-1/2 -translate-x-1/2 translate-y-full border-l-transparent border-r-transparent border-b-transparent border-t-
|
|
150
|
-
'top-start':
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
168
|
+
top: 'bottom-0 left-1/2 -translate-x-1/2 translate-y-full border-l-transparent border-r-transparent border-b-transparent border-t-surface-overlay',
|
|
169
|
+
'top-start':
|
|
170
|
+
'bottom-0 left-4 translate-y-full border-l-transparent border-r-transparent border-b-transparent border-t-surface-overlay',
|
|
171
|
+
'top-end':
|
|
172
|
+
'bottom-0 right-4 translate-y-full border-l-transparent border-r-transparent border-b-transparent border-t-surface-overlay',
|
|
173
|
+
bottom:
|
|
174
|
+
'top-0 left-1/2 -translate-x-1/2 -translate-y-full border-l-transparent border-r-transparent border-t-transparent border-b-surface-overlay',
|
|
175
|
+
'bottom-start':
|
|
176
|
+
'top-0 left-4 -translate-y-full border-l-transparent border-r-transparent border-t-transparent border-b-surface-overlay',
|
|
177
|
+
'bottom-end':
|
|
178
|
+
'top-0 right-4 -translate-y-full border-l-transparent border-r-transparent border-t-transparent border-b-surface-overlay',
|
|
179
|
+
left: 'right-0 top-1/2 translate-x-full -translate-y-1/2 border-t-transparent border-b-transparent border-r-transparent border-l-surface-overlay',
|
|
180
|
+
right:
|
|
181
|
+
'left-0 top-1/2 -translate-x-full -translate-y-1/2 border-t-transparent border-b-transparent border-l-transparent border-r-surface-overlay',
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
// Tooltip positioning
|
|
@@ -168,18 +193,12 @@ export function Tooltip({
|
|
|
168
193
|
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
|
169
194
|
}
|
|
170
195
|
|
|
196
|
+
// Floating: overlay plane + lift, no ring.
|
|
171
197
|
const tooltipContent = (
|
|
172
|
-
<
|
|
173
|
-
{content ?? <Text className="text-
|
|
174
|
-
{hasArrow && (
|
|
175
|
-
|
|
176
|
-
className={cn(
|
|
177
|
-
'absolute w-0 h-0 border-4',
|
|
178
|
-
arrowStyles[placement]
|
|
179
|
-
)}
|
|
180
|
-
/>
|
|
181
|
-
)}
|
|
182
|
-
</View>
|
|
198
|
+
<Surface elevation={4} rounded={false} className="px-3 py-2 rounded-md max-w-xs">
|
|
199
|
+
{content ?? <Text className="text-text-primary text-sm">{label}</Text>}
|
|
200
|
+
{hasArrow && <View className={cn('absolute w-0 h-0 border-4', arrowStyles[placement])} />}
|
|
201
|
+
</Surface>
|
|
183
202
|
)
|
|
184
203
|
|
|
185
204
|
const renderPortalTooltip = () => {
|
|
@@ -205,15 +224,15 @@ export function Tooltip({
|
|
|
205
224
|
|
|
206
225
|
return (
|
|
207
226
|
<View className="relative" ref={triggerRef} {...props}>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
{isOpen === undefined ? (
|
|
228
|
+
<TriggerSurface
|
|
229
|
+
handlers={{ onHoverIn: show, onHoverOut: hide, onLongPress: show, onPressOut: hide }}
|
|
230
|
+
>
|
|
231
|
+
{children}
|
|
232
|
+
</TriggerSurface>
|
|
233
|
+
) : (
|
|
234
|
+
children
|
|
235
|
+
)}
|
|
217
236
|
|
|
218
237
|
{isPortalMode
|
|
219
238
|
? renderPortalTooltip()
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Tooltip } from './Tooltip'
|
|
1
|
+
export { Tooltip, useHoverState } from './Tooltip'
|
|
2
2
|
export type { TooltipProps, TooltipPlacement } from './Tooltip'
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { View, Text } from 'react-native'
|
|
5
|
+
import { TriggerSurface } from './TriggerSurface'
|
|
6
|
+
import { Button, ButtonText } from '../button'
|
|
7
|
+
import { Menu, MenuTrigger, MenuList, MenuItem } from '../menu'
|
|
8
|
+
import { Popover, PopoverTrigger, PopoverContent } from '../popover'
|
|
9
|
+
import { Tooltip } from '../tooltip'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A real pointer press, not a bare synthetic click.
|
|
13
|
+
*
|
|
14
|
+
* The bug (AW-130) only shows when the whole gesture reaches the innermost
|
|
15
|
+
* element: React Native Web's PressResponder calls `stopPropagation()` on the
|
|
16
|
+
* click it handles, so an outer Pressable wrapping a Pressable child never runs
|
|
17
|
+
* its handler. `fireEvent.click` on the outer element sidesteps that entirely.
|
|
18
|
+
*/
|
|
19
|
+
function pressWithPointer(element: Element) {
|
|
20
|
+
fireEvent.mouseDown(element, { button: 0, detail: 1 })
|
|
21
|
+
fireEvent.mouseUp(element, { button: 0, detail: 1 })
|
|
22
|
+
fireEvent.click(element, { button: 0, detail: 1 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hover the element itself. Enter events do not bubble, and RNW's `useHover`
|
|
27
|
+
* contains hover to the innermost node, so a wrapper never learns about a hover
|
|
28
|
+
* that lands on a Pressable child.
|
|
29
|
+
*/
|
|
30
|
+
function hoverWithPointer(element: Element) {
|
|
31
|
+
fireEvent.pointerEnter(element)
|
|
32
|
+
fireEvent.mouseEnter(element)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('TriggerSurface', () => {
|
|
36
|
+
it('fires when the child is a Pressable component that claims the gesture', () => {
|
|
37
|
+
const onPress = vi.fn()
|
|
38
|
+
render(
|
|
39
|
+
<TriggerSurface handlers={{ onPress }} accessibilityRole="button">
|
|
40
|
+
<Button>
|
|
41
|
+
<ButtonText>Options</ButtonText>
|
|
42
|
+
</Button>
|
|
43
|
+
</TriggerSurface>
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
pressWithPointer(screen.getByText('Options'))
|
|
47
|
+
expect(onPress).toHaveBeenCalledTimes(1)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('fires exactly once, not once per nested pressable', () => {
|
|
51
|
+
const onPress = vi.fn()
|
|
52
|
+
render(
|
|
53
|
+
<TriggerSurface handlers={{ onPress }} accessibilityRole="button">
|
|
54
|
+
<Button>
|
|
55
|
+
<ButtonText>Options</ButtonText>
|
|
56
|
+
</Button>
|
|
57
|
+
</TriggerSurface>
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
pressWithPointer(screen.getByText('Options'))
|
|
61
|
+
pressWithPointer(screen.getByText('Options'))
|
|
62
|
+
expect(onPress).toHaveBeenCalledTimes(2)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('keeps the child own onPress alongside the trigger handler', () => {
|
|
66
|
+
const onPress = vi.fn()
|
|
67
|
+
const childPress = vi.fn()
|
|
68
|
+
render(
|
|
69
|
+
<TriggerSurface handlers={{ onPress }} accessibilityRole="button">
|
|
70
|
+
<Button onPress={childPress}>
|
|
71
|
+
<ButtonText>Options</ButtonText>
|
|
72
|
+
</Button>
|
|
73
|
+
</TriggerSurface>
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
pressWithPointer(screen.getByText('Options'))
|
|
77
|
+
expect(childPress).toHaveBeenCalledTimes(1)
|
|
78
|
+
expect(onPress).toHaveBeenCalledTimes(1)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('fires when the child is a host element that cannot take onPress', () => {
|
|
82
|
+
const onPress = vi.fn()
|
|
83
|
+
render(
|
|
84
|
+
<TriggerSurface handlers={{ onPress }} accessibilityRole="button">
|
|
85
|
+
<span>Options</span>
|
|
86
|
+
</TriggerSurface>
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
pressWithPointer(screen.getByText('Options'))
|
|
90
|
+
expect(onPress).toHaveBeenCalledTimes(1)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('fires when the child is a component with no press handling of its own', () => {
|
|
94
|
+
const onPress = vi.fn()
|
|
95
|
+
render(
|
|
96
|
+
<TriggerSurface handlers={{ onPress }} accessibilityRole="button">
|
|
97
|
+
<View>
|
|
98
|
+
<Text>Options</Text>
|
|
99
|
+
</View>
|
|
100
|
+
</TriggerSurface>
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
pressWithPointer(screen.getByText('Options'))
|
|
104
|
+
expect(onPress).toHaveBeenCalledTimes(1)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('leaves a single button role rather than nesting two', () => {
|
|
108
|
+
const { container } = render(
|
|
109
|
+
<TriggerSurface
|
|
110
|
+
handlers={{ onPress: () => {} }}
|
|
111
|
+
accessibilityRole="button"
|
|
112
|
+
accessibilityState={{ expanded: false }}
|
|
113
|
+
>
|
|
114
|
+
<Button>
|
|
115
|
+
<ButtonText>Options</ButtonText>
|
|
116
|
+
</Button>
|
|
117
|
+
</TriggerSurface>
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
expect(container.querySelectorAll('[role="button"]')).toHaveLength(1)
|
|
121
|
+
expect(container.querySelector('button button')).toBeNull()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('keeps the wrapper out of the tab order when the child owns the press', () => {
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<TriggerSurface handlers={{ onPress: () => {} }} accessibilityRole="button">
|
|
127
|
+
<Button>
|
|
128
|
+
<ButtonText>Options</ButtonText>
|
|
129
|
+
</Button>
|
|
130
|
+
</TriggerSurface>
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
expect(container.querySelectorAll('[tabindex="0"]')).toHaveLength(1)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('has no accessibility violations', async () => {
|
|
137
|
+
const { container } = render(
|
|
138
|
+
<TriggerSurface
|
|
139
|
+
handlers={{ onPress: () => {} }}
|
|
140
|
+
accessibilityRole="button"
|
|
141
|
+
accessibilityState={{ expanded: false }}
|
|
142
|
+
>
|
|
143
|
+
<Button>
|
|
144
|
+
<ButtonText>Options</ButtonText>
|
|
145
|
+
</Button>
|
|
146
|
+
</TriggerSurface>
|
|
147
|
+
)
|
|
148
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('trigger families under a real pointer press', () => {
|
|
153
|
+
it('Menu opens when its Button trigger is pressed', () => {
|
|
154
|
+
render(
|
|
155
|
+
<Menu>
|
|
156
|
+
<MenuTrigger>
|
|
157
|
+
<Button>
|
|
158
|
+
<ButtonText>Options</ButtonText>
|
|
159
|
+
</Button>
|
|
160
|
+
</MenuTrigger>
|
|
161
|
+
<MenuList>
|
|
162
|
+
<MenuItem>Edit</MenuItem>
|
|
163
|
+
</MenuList>
|
|
164
|
+
</Menu>
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
pressWithPointer(screen.getByText('Options'))
|
|
168
|
+
expect(screen.getByText('Edit')).toBeInTheDocument()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('Popover opens when its Button trigger is pressed', () => {
|
|
172
|
+
render(
|
|
173
|
+
<Popover>
|
|
174
|
+
<PopoverTrigger>
|
|
175
|
+
<Button>
|
|
176
|
+
<ButtonText>Open</ButtonText>
|
|
177
|
+
</Button>
|
|
178
|
+
</PopoverTrigger>
|
|
179
|
+
<PopoverContent>
|
|
180
|
+
<Text>Details</Text>
|
|
181
|
+
</PopoverContent>
|
|
182
|
+
</Popover>
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
pressWithPointer(screen.getByText('Open'))
|
|
186
|
+
expect(screen.getByText('Details')).toBeInTheDocument()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('Tooltip shows when its Button child is hovered', () => {
|
|
190
|
+
render(
|
|
191
|
+
<Tooltip label="Helpful hint">
|
|
192
|
+
<Button>
|
|
193
|
+
<ButtonText>Hover me</ButtonText>
|
|
194
|
+
</Button>
|
|
195
|
+
</Tooltip>
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
hoverWithPointer(screen.getByRole('button'))
|
|
199
|
+
expect(screen.getByText('Helpful hint')).toBeInTheDocument()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('Tooltip leaves its Button child clickable', () => {
|
|
203
|
+
const onPress = vi.fn()
|
|
204
|
+
render(
|
|
205
|
+
<Tooltip label="Helpful hint">
|
|
206
|
+
<Button onPress={onPress}>
|
|
207
|
+
<ButtonText>Hover me</ButtonText>
|
|
208
|
+
</Button>
|
|
209
|
+
</Tooltip>
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
pressWithPointer(screen.getByText('Hover me'))
|
|
213
|
+
expect(onPress).toHaveBeenCalledTimes(1)
|
|
214
|
+
})
|
|
215
|
+
})
|