@titan-design/react-ui 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -47
- package/dist/bodymap.js +856 -736
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +858 -738
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1431 -237
- package/dist/index.d.ts +1431 -237
- package/dist/index.js +6506 -4781
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6280 -4642
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
- package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1919 -1937
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1927 -1945
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +225 -140
- package/dist/theme/index.d.ts +225 -140
- package/dist/theme/index.js +298 -319
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +283 -315
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +145 -24
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +145 -24
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/dist/theme/tokens.d.mts +128 -11
- package/dist/theme/tokens.d.ts +128 -11
- package/dist/theme/tokens.js +126 -26
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/tokens.mjs +124 -27
- package/dist/theme/tokens.mjs.map +1 -1
- package/docs/audits/2026-09-08-critique-addenda.md +51 -0
- package/docs/audits/2026-09-08-library-critique.md +212 -0
- package/docs/library-roadmap.md +99 -0
- package/docs/render-testing-pattern.md +130 -0
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +3 -1
- package/src/arch/arch-graph.freshness.test.ts +52 -0
- package/src/arch/arch-graph.json +4989 -1583
- package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
- package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
- package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
- package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
- package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
- package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
- package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
- package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
- package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
- package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
- package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
- package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
- package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
- package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
- package/src/components/custom/ActiveWork/InitiativeBrief.tsx +182 -0
- package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
- package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
- package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
- package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
- package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
- package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
- package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
- package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
- package/src/components/custom/ActiveWork/README.md +305 -0
- package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
- package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
- package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
- package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
- package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
- package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
- package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
- package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
- package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
- package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
- package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
- package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
- package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
- package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
- package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
- package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
- package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
- package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
- package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
- package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
- package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
- package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
- package/src/components/custom/ActiveWork/format-time.ts +32 -0
- package/src/components/custom/ActiveWork/index.ts +19 -0
- package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
- package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
- package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
- package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +12 -6
- package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
- package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
- package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
- package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
- package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
- package/src/components/custom/Fatigue/README.md +40 -3
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
- package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
- package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
- package/src/components/custom/Fatigue/index.ts +17 -5
- package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
- package/src/components/custom/Fatigue/panel-layout.ts +185 -0
- package/src/components/custom/Gauge/Gauge.stories.tsx +6 -3
- package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.tsx +7 -2
- package/src/components/custom/Metric/Metric.stories.tsx +10 -1
- package/src/components/custom/Metric/Metric.test.tsx +1 -3
- package/src/components/custom/Metric/Metric.tsx +5 -20
- package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
- package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
- package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
- package/src/components/custom/Prose/README.md +58 -0
- package/src/components/custom/Prose/index.ts +1 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +55 -14
- package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
- package/src/components/custom/Scatter/Scatter.tsx +122 -18
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
- package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
- package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
- package/src/components/custom/Table/Table.stories.tsx +99 -16
- package/src/components/custom/Table/Table.test.tsx +273 -86
- package/src/components/custom/Table/Table.tsx +159 -67
- package/src/components/custom/Table/column-fit.test.ts +75 -0
- package/src/components/custom/Table/column-fit.ts +116 -0
- package/src/components/custom/Table/index.ts +9 -0
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
- package/src/components/custom/Treemap/Treemap.stories.tsx +15 -12
- package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +11 -3
- package/src/components/custom/Typography/Typography.stories.tsx +28 -1
- package/src/components/custom/Typography/Typography.test.tsx +34 -0
- package/src/components/custom/Typography/Typography.tsx +6 -0
- package/src/components/custom/Typography/index.ts +1 -8
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +11 -2
- package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
- package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
- package/src/components/custom/Workout/BaseBadge.tsx +6 -2
- package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
- package/src/components/custom/Workout/BodyMap.tsx +8 -4
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
- package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
- package/src/components/custom/Workout/DeviationBar.tsx +24 -16
- package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
- package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
- package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
- package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
- package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
- package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
- package/src/components/custom/Workout/InputBar.tsx +4 -2
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
- package/src/components/custom/Workout/IntensityBar.tsx +8 -3
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
- package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
- package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
- package/src/components/custom/Workout/MesoCard.tsx +14 -10
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
- package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -33
- package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
- package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +9 -4
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
- package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
- package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
- package/src/components/custom/Workout/PrBadge.tsx +13 -17
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +16 -3
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
- package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
- package/src/components/custom/Workout/README.md +68 -17
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
- package/src/components/custom/Workout/ReadinessCheck.tsx +22 -4
- package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
- package/src/components/custom/Workout/RestTimer.tsx +4 -2
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
- package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +21 -17
- package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
- package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SessionHeader.tsx +12 -12
- package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
- package/src/components/custom/Workout/SessionRail.tsx +7 -9
- package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
- package/src/components/custom/Workout/SetBar.test.tsx +49 -4
- package/src/components/custom/Workout/SetBar.tsx +38 -12
- package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
- package/src/components/custom/Workout/SetRow.test.tsx +16 -2
- package/src/components/custom/Workout/SetRow.tsx +61 -23
- package/src/components/custom/Workout/SetStrip.stories.tsx +63 -7
- package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
- package/src/components/custom/Workout/SetStrip.tsx +24 -4
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
- package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
- package/src/components/custom/Workout/SetsRepsLoad.tsx +24 -12
- package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
- package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
- package/src/components/custom/Workout/Sparkline.tsx +14 -18
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
- package/src/components/custom/Workout/StatusDot.tsx +93 -63
- package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
- package/src/components/custom/Workout/StatusPill.tsx +27 -38
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +11 -4
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
- package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
- package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +26 -10
- package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
- package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +10 -6
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
- package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
- package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
- package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
- package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
- package/src/components/custom/Workout/WeekRow.tsx +23 -16
- package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
- package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
- package/src/components/custom/Workout/WeightBadge.tsx +28 -27
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
- package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
- package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +10 -7
- package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
- package/src/components/custom/Workout/exerciseRowState.ts +29 -0
- package/src/components/custom/Workout/index.ts +17 -2
- package/src/components/custom/Workout/metricText.stories.tsx +15 -13
- package/src/components/custom/Workout/metricText.tsx +8 -2
- package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
- package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
- package/src/components/custom/charts/README.md +66 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
- package/src/components/custom/charts/SetBarChart.tsx +56 -9
- package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
- package/src/components/custom/charts/SparkBars.test.tsx +69 -0
- package/src/components/custom/charts/SparkBars.tsx +85 -0
- package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
- package/src/components/custom/charts/flatBarGeometry.ts +32 -0
- package/src/components/custom/charts/index.ts +3 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
- package/src/components/custom/stepper/Stepper.test.tsx +1 -7
- package/src/components/custom/stepper/Stepper.tsx +21 -34
- package/src/components/icons/icons.stories.tsx +1 -1
- package/src/components/icons/icons.tsx +51 -0
- package/src/components/shell/AppShell.stories.tsx +150 -0
- package/src/components/shell/AppShell.test.tsx +70 -0
- package/src/components/shell/AppShell.tsx +90 -0
- package/src/components/shell/BrandLockup.stories.tsx +66 -5
- package/src/components/shell/BrandLockup.test.tsx +34 -1
- package/src/components/shell/BrandLockup.tsx +27 -11
- package/src/components/shell/NavItem.stories.tsx +4 -4
- package/src/components/shell/NavItem.test.tsx +17 -0
- package/src/components/shell/NavItem.tsx +17 -5
- package/src/components/shell/README.md +89 -26
- package/src/components/shell/SideNav.stories.tsx +38 -7
- package/src/components/shell/SideNav.test.tsx +16 -8
- package/src/components/shell/SideNav.tsx +17 -17
- package/src/components/shell/TopBar.stories.tsx +44 -19
- package/src/components/shell/TopBar.test.tsx +34 -12
- package/src/components/shell/TopBar.tsx +61 -40
- package/src/components/shell/brands.tsx +66 -0
- package/src/components/shell/index.ts +9 -8
- package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
- package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
- package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
- package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
- package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
- package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
- package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
- package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
- package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
- package/src/components/shell/workout/WorkoutShell.tsx +77 -0
- package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
- package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
- package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
- package/src/components/shell/workout/index.ts +9 -0
- package/src/components/shell/workout/workoutNavItems.tsx +16 -0
- package/src/components/ui/README.md +110 -0
- package/src/components/ui/alert/Alert.stories.tsx +15 -5
- package/src/components/ui/alert/Alert.tsx +24 -3
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
- package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -10
- package/src/components/ui/badge/Badge.stories.tsx +83 -28
- package/src/components/ui/badge/Badge.test.tsx +19 -3
- package/src/components/ui/badge/Badge.tsx +48 -55
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
- package/src/components/ui/button/Button.stories.tsx +46 -12
- package/src/components/ui/button/Button.tsx +44 -37
- package/src/components/ui/card/Card.stories.tsx +90 -94
- package/src/components/ui/card/Card.test.tsx +80 -6
- package/src/components/ui/card/Card.tsx +150 -203
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +7 -27
- package/src/components/ui/chip/Chip.stories.tsx +37 -13
- package/src/components/ui/chip/Chip.test.tsx +10 -6
- package/src/components/ui/chip/Chip.tsx +76 -91
- package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
- package/src/components/ui/collapse/Collapse.tsx +6 -11
- package/src/components/ui/data-row/DataRow.stories.tsx +14 -3
- package/src/components/ui/data-row/DataRow.test.tsx +5 -15
- package/src/components/ui/data-row/DataRow.tsx +18 -7
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.tsx +1 -5
- package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
- package/src/components/ui/drawer/Drawer.tsx +13 -27
- package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
- package/src/components/ui/form-field/FormField.stories.tsx +6 -20
- package/src/components/ui/form-field/FormField.test.tsx +1 -5
- package/src/components/ui/form-field/FormField.tsx +10 -46
- package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
- package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
- package/src/components/ui/help-tip/HelpTip.tsx +21 -22
- package/src/components/ui/help-tip/index.ts +7 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
- package/src/components/ui/icon-box/IconBox.tsx +10 -18
- package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
- package/src/components/ui/indicator/Indicator.tsx +20 -14
- package/src/components/ui/input/Input.stories.tsx +3 -11
- package/src/components/ui/input/Input.test.tsx +2 -10
- package/src/components/ui/input/Input.tsx +10 -25
- package/src/components/ui/input/index.ts +7 -1
- package/src/components/ui/link/Link.stories.tsx +19 -7
- package/src/components/ui/list-item/ListItem.stories.tsx +17 -5
- package/src/components/ui/list-item/ListItem.tsx +2 -9
- package/src/components/ui/menu/Menu.stories.tsx +21 -15
- package/src/components/ui/menu/Menu.test.tsx +2 -11
- package/src/components/ui/menu/Menu.tsx +12 -14
- package/src/components/ui/menu/index.ts +8 -1
- package/src/components/ui/modal/Modal.stories.tsx +2 -2
- package/src/components/ui/modal/Modal.tsx +31 -19
- package/src/components/ui/pill/Pill.stories.tsx +111 -23
- package/src/components/ui/pill/Pill.test.tsx +101 -3
- package/src/components/ui/pill/Pill.tsx +168 -49
- package/src/components/ui/pill/index.ts +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +6 -8
- package/src/components/ui/popover/Popover.test.tsx +1 -6
- package/src/components/ui/popover/Popover.tsx +20 -15
- package/src/components/ui/popover/index.ts +7 -1
- package/src/components/ui/progress/Progress.stories.tsx +3 -12
- package/src/components/ui/progress/Progress.test.tsx +1 -5
- package/src/components/ui/progress/Progress.tsx +9 -6
- package/src/components/ui/radio/Radio.stories.tsx +42 -21
- package/src/components/ui/radio/Radio.test.tsx +6 -2
- package/src/components/ui/radio/Radio.tsx +23 -22
- package/src/components/ui/section/Section.stories.tsx +12 -21
- package/src/components/ui/section/Section.test.tsx +1 -6
- package/src/components/ui/section/Section.tsx +1 -3
- package/src/components/ui/section/index.ts +1 -5
- package/src/components/ui/select/Select.stories.tsx +3 -13
- package/src/components/ui/select/Select.test.tsx +2 -11
- package/src/components/ui/select/Select.tsx +16 -15
- package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
- package/src/components/ui/skeleton/Skeleton.tsx +3 -14
- package/src/components/ui/skeleton/index.ts +1 -7
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +4 -5
- package/src/components/ui/stack/Stack.stories.tsx +8 -8
- package/src/components/ui/stack/Stack.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +92 -33
- package/src/components/ui/surface/Surface.test.tsx +88 -28
- package/src/components/ui/surface/Surface.tsx +37 -87
- package/src/components/ui/surface/SurfaceContext.ts +22 -62
- package/src/components/ui/surface/index.ts +8 -0
- package/src/components/ui/surface/resolveSurface.ts +98 -0
- package/src/components/ui/surface/surface.contract.test.ts +6 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.tsx +7 -6
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +8 -21
- package/src/components/ui/tabs/index.ts +1 -7
- package/src/components/ui/tile/Tile.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.tsx +3 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toast/Toast.test.tsx +2 -8
- package/src/components/ui/toast/Toast.tsx +49 -57
- package/src/components/ui/toast/index.ts +0 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +38 -49
- package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
- package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
- package/src/components/ui/tooltip/Tooltip.tsx +55 -36
- package/src/components/ui/tooltip/index.ts +1 -1
- package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
- package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
- package/src/components/ui/trigger/index.ts +2 -0
- package/src/stories/PresetShowcase.stories.tsx +101 -48
- package/src/test/no-device-internals.test.ts +3 -3
- package/src/test/no-raw-color.test.ts +39 -0
- package/src/theme/Alpha.stories.tsx +103 -0
- package/src/theme/ChoosingTokens.stories.tsx +48 -0
- package/src/theme/ColorPalettes.stories.tsx +62 -45
- package/src/theme/ColorPrimitives.stories.tsx +35 -35
- package/src/theme/Depth.stories.tsx +132 -65
- package/src/theme/DepthCalibration.stories.tsx +23 -21
- package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
- package/src/theme/ThemeProvider.test.tsx +4 -4
- package/src/theme/barrel.ts +7 -6
- package/src/theme/color-stories.coverage.test.ts +1 -1
- package/src/theme/color-utils.ts +41 -29
- package/src/theme/config.completeness.test.ts +1 -1
- package/src/theme/config.ts +40 -0
- package/src/theme/elevation.test.ts +82 -56
- package/src/theme/elevation.ts +77 -561
- package/src/theme/global.css +45 -5
- package/src/theme/grey-ramp.test.ts +24 -8
- package/src/theme/lift.test.ts +39 -0
- package/src/theme/lift.ts +102 -0
- package/src/theme/materials.test.ts +5 -2
- package/src/theme/materials.ts +38 -4
- package/src/theme/native-theming-guard.test.ts +5 -5
- package/src/theme/presets/apply.ts +4 -0
- package/src/theme/presets/audiobook.ts +5 -2
- package/src/theme/presets/default.ts +4 -0
- package/src/theme/presets/index.ts +14 -1
- package/src/theme/presets/types.ts +1 -1
- package/src/theme/semantic-contrast.test.ts +13 -4
- package/src/theme/status-distinctness.test.ts +66 -0
- package/src/theme/subtle-tone-weight.test.ts +163 -0
- package/src/theme/surface-planes.test.ts +37 -0
- package/src/theme/surface-planes.ts +68 -0
- package/src/theme/tailwind-var-opacity.test.ts +163 -0
- package/src/theme/token-export-guard.test.ts +1 -3
- package/src/theme/tokens/primitives.test.ts +15 -7
- package/src/theme/tokens/primitives.ts +98 -33
- package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
- package/src/theme/tokens/semantic.ts +124 -54
- package/src/theme/wash-ladder.test.ts +58 -0
- package/src/theme/workout-tokens.ts +1 -7
- package/src/types/jest-axe.d.ts +2 -6
- package/src/types/raw-md.d.ts +5 -0
- package/src/utils/colors.alpha-adoption.test.ts +408 -0
- package/src/utils/colors.ts +4 -4
- package/src/utils/form.ts +1 -3
- package/src/utils/index.ts +2 -7
- package/src/utils/number-format.ts +17 -0
- package/src/utils/workout-format.ts +15 -6
- package/src/web-jsx/jsx-runtime.ts +1 -5
- package/tailwind.config.js +55 -9
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
- package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
- package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
- package/src/components/shell/DashboardShell.stories.tsx +0 -65
- package/src/components/shell/DashboardShell.tsx +0 -75
- package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
- package/src/theme/elevation.stories.tsx +0 -346
- package/src/utils/useTheme.ts +0 -72
- /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
- /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
- /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
- /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
package/src/theme/elevation.ts
CHANGED
|
@@ -1,600 +1,116 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Elevation
|
|
3
|
-
*
|
|
4
|
-
* A
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
2
|
+
* Elevation — numeric depth that resolves to the grey ramp.
|
|
3
|
+
*
|
|
4
|
+
* A level is a plane on the surface ramp plus the treatment that plane wears:
|
|
5
|
+
*
|
|
6
|
+
* -2 frame recessed (inset well, two planes down)
|
|
7
|
+
* -1 background recessed (inset well, one plane down)
|
|
8
|
+
* 0 base flat, no treatment
|
|
9
|
+
* 1 elevated lifted (tone + rim-light + ambient shadow)
|
|
10
|
+
* 2 raised lifted
|
|
11
|
+
* 3 overlay lifted
|
|
12
|
+
* 4 overlay floating (same recipe, larger shadow)
|
|
13
|
+
* 5 overlay floating
|
|
14
|
+
*
|
|
15
|
+
* These are ABSOLUTE readings from the page plane. `<Surface>` and `<Card>`
|
|
16
|
+
* apply the same numbers RELATIVE to the plane they are nested in; see
|
|
17
|
+
* `resolveSurfaceDepth`.
|
|
18
|
+
*
|
|
19
|
+
* No level can produce a colour off the ramp. The previous ladder lightened a
|
|
20
|
+
* base colour in HSV by 2.5% per level and put five of its six stops between
|
|
21
|
+
* ramp steps, drifting warmer as it rose while the ramp is designed to cool.
|
|
22
|
+
* Floating levels share the overlay plane on purpose: past the top of the ramp,
|
|
23
|
+
* separation is the shadow's job, not a lighter grey.
|
|
24
|
+
*
|
|
25
|
+
* Depth is TONE + LIFT by default. The earlier rule that levels 1–3 separate by
|
|
26
|
+
* tone alone (TD-07.16) was reversed on 2026-09-08: the tight upper steps of the
|
|
27
|
+
* ramp (ΔL* 2.5–3) need the second channel, and a soft ambient shadow on a
|
|
28
|
+
* near-black plane is not inert once the rim-light gives it an edge to fall
|
|
29
|
+
* from. See Foundations/Depth.
|
|
22
30
|
*/
|
|
23
|
-
|
|
24
31
|
import { Platform, type ViewStyle } from 'react-native'
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
32
|
+
import { hexToRgb } from './color-utils'
|
|
33
|
+
import { insetWell } from './materials'
|
|
34
|
+
import { FLOATING_LIFT_MIN, liftStyle, type LiftOptions, type LiftStep } from './lift'
|
|
35
|
+
import { surfaceBackground, type SurfaceLevel } from './surface-planes'
|
|
36
|
+
import type { ThemeMode } from './tokens/semantic'
|
|
27
37
|
|
|
28
38
|
export type ElevationLevel = -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5
|
|
29
39
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/** Description of use case */
|
|
40
|
-
description: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const elevationSystem: Record<ElevationLevel, ElevationConfig> = {
|
|
44
|
-
// Deep inset (recessed input fields, deeply pressed buttons)
|
|
45
|
-
[-2]: {
|
|
46
|
-
name: 'deep-inset',
|
|
47
|
-
colorAdjustment: 0,
|
|
48
|
-
shadowIntensity: 'strong',
|
|
49
|
-
shadowStyle: 'pressed',
|
|
50
|
-
description: 'Deeply recessed elements (e.g., input fields, deeply pressed buttons)',
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
// Shallow inset (pressed buttons, sunken panels)
|
|
54
|
-
[-1]: {
|
|
55
|
-
name: 'inset',
|
|
56
|
-
colorAdjustment: 0,
|
|
57
|
-
shadowIntensity: 'medium',
|
|
58
|
-
shadowStyle: 'pressed',
|
|
59
|
-
description: 'Shallow inset elements (e.g., pressed buttons, sunken panels within cards)',
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
// Base level (page background, default surface)
|
|
63
|
-
[0]: {
|
|
64
|
-
name: 'base',
|
|
65
|
-
colorAdjustment: 0,
|
|
66
|
-
shadowIntensity: 'subtle',
|
|
67
|
-
shadowStyle: 'raised',
|
|
68
|
-
description: 'Base surface level (page background, default containers)',
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
// Level 1: Subtle elevation (outline cards, subtle containers)
|
|
72
|
-
[1]: {
|
|
73
|
-
name: 'subtle',
|
|
74
|
-
// The whole ladder was re-spaced in TD-07.16. It used to run
|
|
75
|
-
// 0.01/0.02/0.04/0.06/0.08, where the first step was ~2 RGB values — under
|
|
76
|
-
// the perceptual floor, so level 1 existed in the type system and nowhere on
|
|
77
|
-
// screen. With drop-shadow now gated off below FLOATING_ELEVATION_MIN, TONE
|
|
78
|
-
// is the only thing separating 0-3, so every step has to actually land.
|
|
79
|
-
// Even 0.025 spacing, verified in elevation.test.ts as ΔL* > 1 per step.
|
|
80
|
-
colorAdjustment: 0.025,
|
|
81
|
-
shadowIntensity: 'subtle',
|
|
82
|
-
shadowStyle: 'raised',
|
|
83
|
-
description: 'Subtle elevation (outline cards, subtle containers)',
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
// Level 2: Standard elevation (default cards, buttons)
|
|
87
|
-
[2]: {
|
|
88
|
-
name: 'standard',
|
|
89
|
-
colorAdjustment: 0.05,
|
|
90
|
-
shadowIntensity: 'medium',
|
|
91
|
-
shadowStyle: 'raised',
|
|
92
|
-
description: 'Standard elevation (default cards, raised buttons)',
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
// Level 3: Prominent elevation (important cards, modals)
|
|
96
|
-
[3]: {
|
|
97
|
-
name: 'prominent',
|
|
98
|
-
colorAdjustment: 0.075,
|
|
99
|
-
shadowIntensity: 'strong',
|
|
100
|
-
shadowStyle: 'raised',
|
|
101
|
-
description: 'Prominent elevation (important cards, modals, dropdowns)',
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
// Level 4: High elevation (overlays, popovers)
|
|
105
|
-
[4]: {
|
|
106
|
-
name: 'high',
|
|
107
|
-
colorAdjustment: 0.10,
|
|
108
|
-
shadowIntensity: 'strong',
|
|
109
|
-
shadowStyle: 'raised',
|
|
110
|
-
description: 'High elevation (overlays, popovers, tooltips)',
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
// Level 5: Floating (toasts, floating action buttons)
|
|
114
|
-
[5]: {
|
|
115
|
-
name: 'floating',
|
|
116
|
-
colorAdjustment: 0.13,
|
|
117
|
-
shadowIntensity: 'strong',
|
|
118
|
-
shadowStyle: 'raised',
|
|
119
|
-
description: 'Floating elevation (toasts, floating action buttons, highest z-index)',
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Calculate rim light color from the BASE color.
|
|
125
|
-
* Creates a consistent bright highlight with opacity that scales with elevation.
|
|
126
|
-
*
|
|
127
|
-
* The rim light represents light hitting the edge of raised elements from a
|
|
128
|
-
* consistent light source. Uses ABSOLUTE HSV value (not relative lightening)
|
|
129
|
-
* for consistent intensity across all surface colors.
|
|
130
|
-
*
|
|
131
|
-
* For colored surfaces: preserves hue/saturation, sets value to fixed bright level
|
|
132
|
-
* For gray surfaces: uses white with appropriate opacity
|
|
133
|
-
*
|
|
134
|
-
* @param baseColor - The base surface color (hex)
|
|
135
|
-
* @param level - Elevation level (used to scale opacity)
|
|
136
|
-
*/
|
|
137
|
-
function calculateRimLightColor(baseColor: string, level: ElevationLevel): string {
|
|
138
|
-
const rgb = hexToRgb(baseColor)
|
|
139
|
-
if (!rgb) {
|
|
140
|
-
return 'rgba(255, 255, 255, 0.5)'
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
|
|
144
|
-
const isLight = hsv.v > 0.6
|
|
145
|
-
const isSaturated = hsv.s > 0.3 // Colored (not gray)
|
|
146
|
-
|
|
147
|
-
// Opacity scales with elevation to compensate for lighter surfaces at higher levels
|
|
148
|
-
// Range: +0% at level 1 to +20% at level 5
|
|
149
|
-
const absLevel = Math.abs(level)
|
|
150
|
-
const opacityBoost = Math.max(0, (absLevel - 1) * 0.05) // +5% per level above 1 (0% to 20%)
|
|
151
|
-
|
|
152
|
-
if (isSaturated) {
|
|
153
|
-
// For colored surfaces, preserve hue and saturation but set value to fixed bright level
|
|
154
|
-
// This ensures consistent rim light intensity across all colored surfaces
|
|
155
|
-
const rimLightValue = 0.95 // Fixed bright value for rim light
|
|
156
|
-
const rimLightRgb = hsvToRgb(hsv.h, hsv.s * 0.3, rimLightValue) // Reduce saturation for brighter appearance
|
|
157
|
-
|
|
158
|
-
// Base opacity + elevation boost, capped at 1.0
|
|
159
|
-
const baseOpacity = isLight ? 0.9 : 0.5
|
|
160
|
-
const finalOpacity = Math.min(1, baseOpacity + opacityBoost)
|
|
161
|
-
return `rgba(${rimLightRgb.r}, ${rimLightRgb.g}, ${rimLightRgb.b}, ${finalOpacity.toFixed(2)})`
|
|
162
|
-
} else {
|
|
163
|
-
// For gray/neutral surfaces, use white with appropriate opacity
|
|
164
|
-
const baseOpacity = isLight ? 0.9 : 0.12
|
|
165
|
-
const finalOpacity = Math.min(1, baseOpacity + opacityBoost)
|
|
166
|
-
return `rgba(255, 255, 255, ${finalOpacity.toFixed(2)})`
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Calculate dark shadow colors from the surface color.
|
|
172
|
-
* The dark shadow is cast BY the element, so it should match the surface color.
|
|
173
|
-
*
|
|
174
|
-
* Uses ABSOLUTE HSV value (not relative darkening) for consistent shadow intensity
|
|
175
|
-
* across all surface colors - simulating a consistent light source.
|
|
176
|
-
*
|
|
177
|
-
* For colored surfaces: preserves hue/saturation, sets value to fixed dark level
|
|
178
|
-
* For gray surfaces: uses black with appropriate opacity
|
|
179
|
-
*/
|
|
180
|
-
function calculateDarkShadowColors(surfaceColor: string): {
|
|
181
|
-
dark: string
|
|
182
|
-
darker: string
|
|
183
|
-
} {
|
|
184
|
-
const rgb = hexToRgb(surfaceColor)
|
|
185
|
-
if (!rgb) {
|
|
186
|
-
return {
|
|
187
|
-
dark: 'rgba(0, 0, 0, 0.3)',
|
|
188
|
-
darker: 'rgba(0, 0, 0, 0.4)',
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
|
|
193
|
-
const isLight = hsv.v > 0.6
|
|
194
|
-
const isSaturated = hsv.s > 0.3
|
|
195
|
-
|
|
196
|
-
if (isSaturated) {
|
|
197
|
-
// For colored surfaces, preserve hue and saturation but set value to fixed dark level
|
|
198
|
-
// This ensures consistent shadow intensity across all colored surfaces
|
|
199
|
-
const darkShadowValue = 0.15 // Fixed dark value for shadows
|
|
200
|
-
const darkShadowRgb = hsvToRgb(hsv.h, hsv.s, darkShadowValue)
|
|
201
|
-
|
|
202
|
-
if (isLight) {
|
|
203
|
-
return {
|
|
204
|
-
dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.3)`,
|
|
205
|
-
darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.4)`,
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
return {
|
|
209
|
-
dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.5)`,
|
|
210
|
-
darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.6)`,
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
// For gray surfaces, use standard black shadows
|
|
215
|
-
if (isLight) {
|
|
216
|
-
return {
|
|
217
|
-
dark: 'rgba(0, 0, 0, 0.2)',
|
|
218
|
-
darker: 'rgba(0, 0, 0, 0.3)',
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
return {
|
|
222
|
-
dark: 'rgba(0, 0, 0, 0.5)',
|
|
223
|
-
darker: 'rgba(0, 0, 0, 0.6)',
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
40
|
+
export const ELEVATION_PLANE: Record<ElevationLevel, SurfaceLevel> = {
|
|
41
|
+
[-2]: 'frame',
|
|
42
|
+
[-1]: 'background',
|
|
43
|
+
[0]: 'base',
|
|
44
|
+
[1]: 'elevated',
|
|
45
|
+
[2]: 'raised',
|
|
46
|
+
[3]: 'overlay',
|
|
47
|
+
[4]: 'overlay',
|
|
48
|
+
[5]: 'overlay',
|
|
227
49
|
}
|
|
228
50
|
|
|
229
|
-
/**
|
|
230
|
-
|
|
231
|
-
* Fixed 1px offset and blur for a consistent, crisp rim highlight.
|
|
232
|
-
* Opacity scaling (in calculateRimLightColor) handles elevation differences.
|
|
233
|
-
*/
|
|
234
|
-
function getRimLightConfig(): { offset: number; blur: number } {
|
|
235
|
-
return {
|
|
236
|
-
offset: 1, // Fixed 1px for crisp rim
|
|
237
|
-
blur: 1, // Tight blur for crisp edge
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get elevation configuration for a given level
|
|
243
|
-
*/
|
|
244
|
-
export function getElevationConfig(level: ElevationLevel): ElevationConfig {
|
|
245
|
-
return elevationSystem[level]
|
|
246
|
-
}
|
|
51
|
+
/** The lowest elevation that is floating (menu, popover, toast) rather than content. */
|
|
52
|
+
export const FLOATING_ELEVATION_MIN: ElevationLevel = FLOATING_LIFT_MIN
|
|
247
53
|
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const MAX_CACHE_SIZE = 100
|
|
54
|
+
/** Elevation backing a `<Surface pressed>` recess: one plane down. */
|
|
55
|
+
export const PRESSED_ELEVATION_LEVEL: ElevationLevel = -1
|
|
251
56
|
|
|
252
|
-
|
|
253
|
-
|
|
57
|
+
/** The ramp plane an absolute elevation sits on. */
|
|
58
|
+
export function elevationPlane(level: ElevationLevel): SurfaceLevel {
|
|
59
|
+
return ELEVATION_PLANE[level]
|
|
254
60
|
}
|
|
255
61
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
level
|
|
259
|
-
theme: 'light' | 'dark'
|
|
260
|
-
): string {
|
|
261
|
-
const config = elevationSystem[level]
|
|
262
|
-
|
|
263
|
-
if (config.colorAdjustment === 0) {
|
|
264
|
-
return baseColor
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// For raised elevations (positive), always lighten (consistent lighting model)
|
|
268
|
-
// Light source is from upper-right, so raised elements are always lighter
|
|
269
|
-
// For inset elevations (negative), we keep base color (shadow creates depth)
|
|
270
|
-
if (level < 0) {
|
|
271
|
-
return baseColor // Inset elements use base color
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Always lighten for raised elements (consistent across themes)
|
|
275
|
-
// Base colors must be chosen to allow sufficient lightening in dark mode
|
|
276
|
-
return lighten(baseColor, config.colorAdjustment)
|
|
62
|
+
/** The background hex for an absolute elevation: always a ramp step. */
|
|
63
|
+
export function getElevationSurface(level: ElevationLevel, mode: ThemeMode = 'dark'): string {
|
|
64
|
+
return surfaceBackground(ELEVATION_PLANE[level], mode)
|
|
277
65
|
}
|
|
278
66
|
|
|
279
67
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* @param baseColor - Base surface color (hex)
|
|
284
|
-
* @param level - Elevation level
|
|
285
|
-
* @param theme - Current theme ('light' or 'dark')
|
|
286
|
-
* @returns Calculated surface color (hex)
|
|
68
|
+
* Inner-shadow recess for a pressed (sunken) surface: the `insetWell` material's
|
|
69
|
+
* cut, which the wall calibrated in VW-99. Web only; on native the recess reads
|
|
70
|
+
* from the darker fill alone, which is why the fill is never optional.
|
|
287
71
|
*/
|
|
288
|
-
export function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// Check cache
|
|
296
|
-
if (elevationSurfaceCache.has(cacheKey)) {
|
|
297
|
-
return elevationSurfaceCache.get(cacheKey)!
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Calculate
|
|
301
|
-
const result = calculateElevationSurface(baseColor, level, theme)
|
|
302
|
-
|
|
303
|
-
// Cache result (with size limit to prevent memory leaks)
|
|
304
|
-
if (elevationSurfaceCache.size >= MAX_CACHE_SIZE) {
|
|
305
|
-
// Remove oldest entry (simple FIFO)
|
|
306
|
-
const firstKey = elevationSurfaceCache.keys().next().value!
|
|
307
|
-
elevationSurfaceCache.delete(firstKey)
|
|
308
|
-
}
|
|
309
|
-
elevationSurfaceCache.set(cacheKey, result)
|
|
310
|
-
|
|
311
|
-
return result
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Cache for shadow styles (less frequently used, smaller cache)
|
|
315
|
-
const elevationShadowCache = new Map<string, ViewStyle>()
|
|
316
|
-
const MAX_SHADOW_CACHE_SIZE = 50
|
|
317
|
-
|
|
318
|
-
function getShadowCacheKey(
|
|
319
|
-
baseColor: string,
|
|
320
|
-
level: ElevationLevel,
|
|
321
|
-
theme: 'light' | 'dark',
|
|
322
|
-
isHovered: boolean
|
|
323
|
-
): string {
|
|
324
|
-
return `${baseColor}-${level}-${theme}-${isHovered}`
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function calculateElevationShadow(
|
|
328
|
-
baseColor: string,
|
|
329
|
-
level: ElevationLevel,
|
|
330
|
-
theme: 'light' | 'dark',
|
|
331
|
-
isHovered: boolean = false
|
|
332
|
-
): ViewStyle {
|
|
333
|
-
const config = elevationSystem[level]
|
|
334
|
-
|
|
335
|
-
// Base level has no shadow
|
|
336
|
-
if (level === 0) {
|
|
337
|
-
return {}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Calculate the actual elevated surface color
|
|
341
|
-
const elevatedSurfaceColor = calculateElevationSurface(baseColor, level, theme)
|
|
342
|
-
|
|
343
|
-
// Rim light: calculated from BASE color (consistent across all elevations)
|
|
344
|
-
// This represents a consistent light source - only size changes with elevation
|
|
345
|
-
const rimLightColor = calculateRimLightColor(baseColor, level)
|
|
346
|
-
|
|
347
|
-
// Dark shadow: calculated from ELEVATED surface color
|
|
348
|
-
// The shadow is cast by the element, so it should match the surface
|
|
349
|
-
const darkShadowColors = calculateDarkShadowColors(elevatedSurfaceColor)
|
|
350
|
-
|
|
351
|
-
// Get shadow intensity config (for dark shadow)
|
|
352
|
-
const intensityConfig = {
|
|
353
|
-
subtle: { offset: 1, blur: 2 },
|
|
354
|
-
medium: { offset: 2, blur: 4 },
|
|
355
|
-
strong: { offset: 2, blur: 5 },
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const { offset, blur } = intensityConfig[config.shadowIntensity]
|
|
359
|
-
|
|
360
|
-
// Get rim light config (scales with elevation level)
|
|
361
|
-
const rimConfig = getRimLightConfig()
|
|
362
|
-
|
|
363
|
-
if (config.shadowStyle === 'pressed') {
|
|
364
|
-
// Inset shadow (pressed elements)
|
|
365
|
-
if (isHovered) {
|
|
366
|
-
// Reduce shadow depth for hover
|
|
367
|
-
const hoverOffset = Math.max(1, Math.round(offset / 2))
|
|
368
|
-
const hoverBlur = Math.max(1, Math.round(blur / 2))
|
|
369
|
-
return Platform.select({
|
|
370
|
-
web: {
|
|
371
|
-
boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
|
|
372
|
-
} as any,
|
|
373
|
-
default: {
|
|
374
|
-
shadowOpacity: 0,
|
|
375
|
-
elevation: 0,
|
|
376
|
-
},
|
|
377
|
-
}) as ViewStyle
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return Platform.select({
|
|
381
|
-
web: {
|
|
382
|
-
boxShadow: `inset ${offset}px ${offset}px ${blur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
|
|
383
|
-
} as any,
|
|
384
|
-
default: {
|
|
385
|
-
shadowOpacity: 0,
|
|
386
|
-
elevation: 0,
|
|
387
|
-
},
|
|
388
|
-
}) as ViewStyle
|
|
389
|
-
} else {
|
|
390
|
-
// Raised shadow (floating elements)
|
|
391
|
-
if (isHovered) {
|
|
392
|
-
// Reduce shadow depth for hover
|
|
393
|
-
const hoverOffset = Math.max(1, Math.round(offset / 2))
|
|
394
|
-
const hoverBlur = Math.max(1, Math.round(blur / 2))
|
|
395
|
-
return Platform.select({
|
|
396
|
-
web: {
|
|
397
|
-
boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
|
|
398
|
-
} as any,
|
|
399
|
-
default: {
|
|
400
|
-
shadowColor: '#000',
|
|
401
|
-
shadowOffset: { width: hoverOffset, height: hoverOffset },
|
|
402
|
-
shadowOpacity: 0.2,
|
|
403
|
-
shadowRadius: hoverBlur / 2,
|
|
404
|
-
elevation: hoverOffset,
|
|
405
|
-
},
|
|
406
|
-
}) as ViewStyle
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return Platform.select({
|
|
410
|
-
web: {
|
|
411
|
-
boxShadow: `${offset}px ${offset}px ${blur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`,
|
|
412
|
-
} as any,
|
|
413
|
-
default: {
|
|
414
|
-
shadowColor: '#000',
|
|
415
|
-
shadowOffset: { width: offset, height: offset },
|
|
416
|
-
shadowOpacity: 0.3,
|
|
417
|
-
shadowRadius: blur / 2,
|
|
418
|
-
elevation: offset,
|
|
419
|
-
},
|
|
420
|
-
}) as ViewStyle
|
|
421
|
-
}
|
|
72
|
+
export function getPressedRecessShadow(fillColor: string, _mode: ThemeMode = 'dark'): ViewStyle {
|
|
73
|
+
const { boxShadow } = insetWell(fillColor) as unknown as { boxShadow: string }
|
|
74
|
+
return Platform.select({
|
|
75
|
+
web: { boxShadow } as unknown as ViewStyle,
|
|
76
|
+
default: {},
|
|
77
|
+
}) as ViewStyle
|
|
422
78
|
}
|
|
423
79
|
|
|
424
80
|
/**
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* @param baseColor - Base surface color (hex)
|
|
429
|
-
* @param level - Elevation level
|
|
430
|
-
* @param theme - Current theme ('light' or 'dark')
|
|
431
|
-
* @param isHovered - Whether element is hovered
|
|
432
|
-
* @returns ViewStyle with shadow properties
|
|
433
|
-
*/
|
|
434
|
-
/**
|
|
435
|
-
* The lowest elevation that still casts an outward shadow.
|
|
436
|
-
*
|
|
437
|
-
* On a near-black surface a drop-shadow is inert: at levels 1-3 the shadow is
|
|
438
|
-
* dark-on-dark and contributes nothing a viewer can see, while still costing a
|
|
439
|
-
* composite layer. Material dropped shadow-as-hierarchy for the same reason.
|
|
440
|
-
* Inline hierarchy is the hairline's job (`hairline-*`), which composites by a
|
|
441
|
-
* constant amount over any plane.
|
|
442
|
-
*
|
|
443
|
-
* At 4+ the element is genuinely FLOATING over a backdrop — menu, popover,
|
|
444
|
-
* toast — where a large soft shadow describes separation from the page rather
|
|
445
|
-
* than a rank in a stack. Those should also carry a hairline ring.
|
|
81
|
+
* The treatment an absolute elevation wears: a recess below 0, nothing at 0,
|
|
82
|
+
* a lift above it. Compose after `backgroundColor`.
|
|
446
83
|
*/
|
|
447
|
-
export const FLOATING_ELEVATION_MIN: ElevationLevel = 4
|
|
448
|
-
|
|
449
84
|
export function getElevationShadow(
|
|
450
|
-
baseColor: string,
|
|
451
85
|
level: ElevationLevel,
|
|
452
|
-
|
|
453
|
-
|
|
86
|
+
mode: ThemeMode = 'dark',
|
|
87
|
+
opts: LiftOptions = {}
|
|
454
88
|
): ViewStyle {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (elevationShadowCache.has(cacheKey)) {
|
|
459
|
-
return elevationShadowCache.get(cacheKey)!
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Calculate shadow. Outward drop-shadow is gated to FLOATING levels only —
|
|
463
|
-
// see FLOATING_ELEVATION_MIN. Inset levels keep theirs; a recess reads from
|
|
464
|
-
// the inner shadow, which is a different mechanism from a cast shadow.
|
|
465
|
-
const result =
|
|
466
|
-
level > 0 && level < FLOATING_ELEVATION_MIN
|
|
467
|
-
? {}
|
|
468
|
-
: calculateElevationShadow(baseColor, level, theme, isHovered)
|
|
469
|
-
|
|
470
|
-
// Cache result
|
|
471
|
-
if (elevationShadowCache.size >= MAX_SHADOW_CACHE_SIZE) {
|
|
472
|
-
const firstKey = elevationShadowCache.keys().next().value!
|
|
473
|
-
elevationShadowCache.delete(firstKey)
|
|
474
|
-
}
|
|
475
|
-
elevationShadowCache.set(cacheKey, result)
|
|
476
|
-
|
|
477
|
-
return result
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Elevation level backing a `<Surface pressed>` recess: the shallow `inset`
|
|
482
|
-
* (-1) step. Pressed exposes ONE level of depth — pressed-2 is intentionally
|
|
483
|
-
* not surfaced (the deeper `-2` deep-inset stays available to `elevation`).
|
|
484
|
-
*/
|
|
485
|
-
export const PRESSED_ELEVATION_LEVEL: ElevationLevel = -1
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Inner-shadow recess for a pressed (sunken) surface, tuned to the well's own
|
|
489
|
-
* fill colour so the rim/shadow tints track the darker fill. Composes WITH the
|
|
490
|
-
* darker pressed fill: on web it adds an inset boxShadow; on the native path
|
|
491
|
-
* (no inset-shadow support) it's a no-op and the recess reads via the fill
|
|
492
|
-
* alone — so a pressed surface stays legibly sunken with or without the shadow.
|
|
493
|
-
*
|
|
494
|
-
* @param fillColor - The pressed surface's own fill (one ramp step down).
|
|
495
|
-
* @param theme - Current theme ('light' or 'dark').
|
|
496
|
-
*/
|
|
497
|
-
export function getPressedRecessShadow(fillColor: string, theme: 'light' | 'dark'): ViewStyle {
|
|
498
|
-
return getElevationShadow(fillColor, PRESSED_ELEVATION_LEVEL, theme)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Get base surface color from theme.
|
|
503
|
-
* This is the foundation color that all elevations are calculated from.
|
|
504
|
-
*
|
|
505
|
-
* IMPORTANT: Base colors must be chosen to allow sufficient lightening.
|
|
506
|
-
*
|
|
507
|
-
* **Light Mode**: Cannot use `#FFFFFF` (white) - cannot lighten white!
|
|
508
|
-
* - Use `#F3F4F6` (neutral[100]) - can lighten to `#FAFAFA` → `#FFFFFF`
|
|
509
|
-
* - This allows raised elevations to be lighter (closer to white)
|
|
510
|
-
*
|
|
511
|
-
* **Dark Mode**: Need lighter base to allow sufficient lightening
|
|
512
|
-
* - Use `surface-elevated` (#2A2827, TD-surface-tokens ramp) - allows further lightening
|
|
513
|
-
*
|
|
514
|
-
* Can be overridden to use custom base colors for special cases.
|
|
515
|
-
*/
|
|
516
|
-
export function getBaseSurfaceColor(theme: 'light' | 'dark'): string {
|
|
517
|
-
// Use semantic token values so elevation colors stay in sync with the token pipeline.
|
|
518
|
-
// Light mode: surface-raised (#F3F4F6 / neutral[100]) - NOT white, so we can lighten it
|
|
519
|
-
// Dark mode: surface-elevated (#2A2827, TD-surface-tokens ramp) - allows sufficient lightening
|
|
520
|
-
|
|
521
|
-
return theme === 'light'
|
|
522
|
-
? semanticColorsLight['surface-raised']
|
|
523
|
-
: semanticColorsDark['surface-elevated']
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* Component-specific elevation ranges
|
|
528
|
-
* Defines which elevation levels each component type can use
|
|
529
|
-
*/
|
|
530
|
-
export const componentElevationRanges = {
|
|
531
|
-
button: {
|
|
532
|
-
allowed: [2] as const,
|
|
533
|
-
default: 2 as const,
|
|
534
|
-
hover: 3 as const,
|
|
535
|
-
},
|
|
536
|
-
card: {
|
|
537
|
-
allowed: [1, 2, 3] as const,
|
|
538
|
-
default: 2 as const,
|
|
539
|
-
hover: 3 as const,
|
|
540
|
-
},
|
|
541
|
-
input: {
|
|
542
|
-
allowed: [-2, -1, 0] as const,
|
|
543
|
-
default: -1 as const,
|
|
544
|
-
},
|
|
545
|
-
overlay: {
|
|
546
|
-
allowed: [4, 5] as const,
|
|
547
|
-
default: 4 as const,
|
|
548
|
-
},
|
|
549
|
-
} as const
|
|
550
|
-
|
|
551
|
-
export type ComponentType = keyof typeof componentElevationRanges
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Validate and clamp elevation to component's allowed range
|
|
555
|
-
*/
|
|
556
|
-
export function getValidatedElevation(
|
|
557
|
-
component: ComponentType,
|
|
558
|
-
requested: ElevationLevel
|
|
559
|
-
): ElevationLevel {
|
|
560
|
-
const range = componentElevationRanges[component]
|
|
561
|
-
if ((range.allowed as readonly ElevationLevel[]).includes(requested)) {
|
|
562
|
-
return requested
|
|
563
|
-
}
|
|
564
|
-
// Clamp to nearest allowed elevation
|
|
565
|
-
const sorted = [...range.allowed].sort((a, b) => a - b)
|
|
566
|
-
if (requested < sorted[0]) return sorted[0]
|
|
567
|
-
if (requested > sorted[sorted.length - 1]) return sorted[sorted.length - 1]
|
|
568
|
-
// Find closest
|
|
569
|
-
return sorted.reduce((prev, curr) =>
|
|
570
|
-
Math.abs(curr - requested) < Math.abs(prev - requested) ? curr : prev
|
|
571
|
-
) as ElevationLevel
|
|
89
|
+
if (level < 0) return getPressedRecessShadow(getElevationSurface(level, mode), mode)
|
|
90
|
+
if (level === 0) return {}
|
|
91
|
+
return liftStyle(level as LiftStep, mode, opts)
|
|
572
92
|
}
|
|
573
93
|
|
|
574
94
|
// =============================================================================
|
|
575
95
|
// Glow Shadow Support
|
|
576
96
|
// =============================================================================
|
|
577
97
|
|
|
578
|
-
export type GlowIntensity = 'subtle' | 'medium' | 'strong'
|
|
98
|
+
export type GlowIntensity = 'tight' | 'subtle' | 'medium' | 'strong'
|
|
579
99
|
|
|
100
|
+
// Four fixed intensities, chosen by EMPHASIS rather than by the element's size:
|
|
101
|
+
// a caller says how loud the glow should be, not how big the thing is. `tight`
|
|
102
|
+
// is the close halo a small indicator wore before glows were centralised; it is
|
|
103
|
+
// kept as a rung on the scale so that look stays reachable.
|
|
580
104
|
const glowConfig: Record<GlowIntensity, { blur: number; spread: number; opacity: number }> = {
|
|
105
|
+
tight: { blur: 4, spread: 0, opacity: 0.4 },
|
|
581
106
|
subtle: { blur: 12, spread: 0, opacity: 0.25 },
|
|
582
107
|
medium: { blur: 20, spread: 2, opacity: 0.4 },
|
|
583
108
|
strong: { blur: 30, spread: 4, opacity: 0.55 },
|
|
584
109
|
}
|
|
585
110
|
|
|
586
111
|
/**
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
* Creates a colored radial glow around an element, useful for:
|
|
590
|
-
* - Active recording indicators (orange glow)
|
|
591
|
-
* - Success states (green glow)
|
|
592
|
-
* - Error/danger indicators (red glow)
|
|
593
|
-
* - Focus rings and attention-drawing elements
|
|
594
|
-
*
|
|
595
|
-
* @param color - Glow color (hex string, e.g. '#FF7900')
|
|
596
|
-
* @param intensity - Glow intensity level
|
|
597
|
-
* @returns ViewStyle with platform-specific shadow properties
|
|
112
|
+
* A coloured radial glow around an element, for emphasis and active states:
|
|
113
|
+
* a live recording indicator, a success or error pulse, a focus halo.
|
|
598
114
|
*/
|
|
599
115
|
export function getGlowShadow(color: string, intensity: GlowIntensity = 'medium'): ViewStyle {
|
|
600
116
|
const config = glowConfig[intensity]
|
|
@@ -604,7 +120,7 @@ export function getGlowShadow(color: string, intensity: GlowIntensity = 'medium'
|
|
|
604
120
|
return Platform.select({
|
|
605
121
|
web: {
|
|
606
122
|
boxShadow: `0 0 ${config.blur}px ${config.spread}px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${config.opacity})`,
|
|
607
|
-
} as
|
|
123
|
+
} as unknown as ViewStyle,
|
|
608
124
|
default: {
|
|
609
125
|
shadowColor: color,
|
|
610
126
|
shadowOffset: { width: 0, height: 0 },
|