@titan-design/react-ui 0.10.0 → 0.12.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/dist/bodymap.js +173 -343
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +173 -343
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +416 -18
- package/dist/index.d.ts +416 -18
- package/dist/index.js +2512 -1307
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2480 -1299
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1644 -1575
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1645 -1576
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +409 -415
- package/dist/theme/index.d.ts +409 -415
- package/dist/theme/index.js +234 -408
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +227 -399
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +141 -140
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +141 -140
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/arch/Architecture.stories.tsx +9 -9
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +2 -2
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
- package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
- package/src/components/custom/Workout/DeviationBar.tsx +3 -2
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +7 -7
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +11 -5
- package/src/components/custom/Workout/InputBar.tsx +2 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
- package/src/components/custom/Workout/MesoCard.tsx +3 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
- package/src/components/custom/Workout/RestTimer.tsx +2 -2
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionHeader.tsx +1 -1
- package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +63 -18
- package/src/components/custom/Workout/SetBar.tsx +3 -3
- package/src/components/custom/Workout/SetRow.test.tsx +12 -9
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/StatusDot.tsx +5 -4
- package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
- package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +199 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
- package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
- package/src/components/custom/Workout/WeightBadge.tsx +2 -1
- package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
- package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +3 -3
- package/src/components/shell/DeviceMenu.tsx +2 -2
- package/src/components/shell/SideNav.tsx +1 -1
- package/src/components/shell/TopBar.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -2
- package/src/components/ui/badge/Badge.tsx +2 -2
- package/src/components/ui/card/Card.tsx +3 -3
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +2 -2
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +3 -3
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.tsx +4 -4
- package/src/components/ui/select/Select.tsx +3 -3
- package/src/components/ui/surface/Surface.stories.tsx +3 -3
- package/src/components/ui/surface/Surface.test.tsx +32 -19
- package/src/components/ui/surface/Surface.tsx +3 -3
- package/src/components/ui/surface/SurfaceContext.ts +19 -29
- package/src/components/ui/surface/surface.contract.test.ts +57 -44
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
- package/src/stories/PresetShowcase.stories.tsx +21 -21
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +874 -0
- package/src/theme/ColorPrimitives.stories.tsx +348 -0
- package/src/theme/Depth.stories.tsx +312 -0
- package/src/theme/DepthCalibration.stories.tsx +655 -0
- package/src/theme/ThemeProvider.tsx +1 -1
- package/src/theme/barrel.ts +7 -14
- package/src/theme/color-stories.coverage.test.ts +125 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/color-utils.ts +197 -0
- package/src/theme/config.ts +0 -8
- package/src/theme/depth-calibration.test.tsx +97 -0
- package/src/theme/elevation.test.ts +62 -1
- package/src/theme/elevation.ts +34 -8
- package/src/theme/global.css +47 -53
- package/src/theme/grey-ramp.test.ts +166 -0
- package/src/theme/materials.test.ts +106 -0
- package/src/theme/materials.ts +211 -0
- package/src/theme/presets/audiobook.ts +4 -4
- package/src/theme/presets/types.ts +3 -3
- package/src/theme/semantic-contrast.test.ts +83 -0
- package/src/theme/tokens/primitives.ts +62 -115
- package/src/theme/tokens/semantic.ts +130 -82
- package/src/utils/colors.ts +1 -1
- package/tailwind.config.js +2 -5
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -508
- package/src/theme/Colors.stories.tsx +0 -323
- package/src/theme/shadows.stories.tsx +0 -523
- package/src/theme/shadows.ts +0 -439
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// The newest-rep grow-from-bottom entrance, shared by SetBarChart (hero / dual / ROM)
|
|
2
|
+
// and VelocityStrip's framed + bare expanded charts. Promoted out of VelocityStrip so
|
|
3
|
+
// every value-height bar family animates the live rep identically instead of re-rolling
|
|
4
|
+
// the hook.
|
|
5
|
+
import { useEffect, useState } from 'react'
|
|
6
|
+
import { Animated, Easing } from 'react-native'
|
|
7
|
+
|
|
8
|
+
const ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1)
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Growth-factor overshoot for a new-peak bar: it grows past its full height then
|
|
12
|
+
* settles back to 1 (a small bounce), rather than the plain 0→1 grow every other
|
|
13
|
+
* bar gets. Modest (12%) since it stretches actual bar HEIGHT, not a uniform scale
|
|
14
|
+
* — a large overshoot here reads as a much bigger jump than the same factor did
|
|
15
|
+
* against the old whole-bar scale pop.
|
|
16
|
+
*/
|
|
17
|
+
const PEAK_OVERSHOOT = 1.12
|
|
18
|
+
|
|
19
|
+
function getReducedMotionPreference(): boolean {
|
|
20
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false
|
|
21
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Track the OS "reduce motion" preference; falls back to `false` (jsdom/SSR). */
|
|
25
|
+
export function usePrefersReducedMotion(): boolean {
|
|
26
|
+
const [reduced, setReduced] = useState(getReducedMotionPreference)
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
|
|
29
|
+
const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
|
|
30
|
+
const handler = () => setReduced(mq.matches)
|
|
31
|
+
handler()
|
|
32
|
+
mq.addEventListener?.('change', handler)
|
|
33
|
+
return () => mq.removeEventListener?.('change', handler)
|
|
34
|
+
}, [])
|
|
35
|
+
return reduced
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The newest-rep entrance shared by SetBarChart and the framed / bare `expanded` charts:
|
|
40
|
+
* the bar GROWS UP FROM THE BASELINE (0 → full height), reading as though it's tracking
|
|
41
|
+
* the rep as it lands. A new set peak overshoots past full height then settles (a small
|
|
42
|
+
* bounce) instead of the plain grow. Honors reduced motion (jumps straight to full
|
|
43
|
+
* height, no animation). Returns a 0→1(+overshoot) growth-factor `Animated.Value`;
|
|
44
|
+
* callers interpolate it against their own target height. Inert while `active` is
|
|
45
|
+
* false or no live rep is present.
|
|
46
|
+
*/
|
|
47
|
+
export function useLiveRepGrowth(
|
|
48
|
+
active: boolean,
|
|
49
|
+
liveRepIndex: number | undefined,
|
|
50
|
+
liveVelocity: number | undefined,
|
|
51
|
+
isNewPeak: boolean
|
|
52
|
+
): Animated.Value {
|
|
53
|
+
const prefersReducedMotion = usePrefersReducedMotion()
|
|
54
|
+
const [liveGrowth] = useState(() => new Animated.Value(1))
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!active || liveRepIndex == null || liveVelocity == null) return
|
|
57
|
+
if (prefersReducedMotion) {
|
|
58
|
+
liveGrowth.setValue(1)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
liveGrowth.setValue(0)
|
|
62
|
+
if (isNewPeak) {
|
|
63
|
+
Animated.sequence([
|
|
64
|
+
Animated.timing(liveGrowth, {
|
|
65
|
+
toValue: PEAK_OVERSHOOT,
|
|
66
|
+
duration: 220,
|
|
67
|
+
easing: Easing.out(Easing.quad),
|
|
68
|
+
useNativeDriver: false,
|
|
69
|
+
}),
|
|
70
|
+
Animated.spring(liveGrowth, {
|
|
71
|
+
toValue: 1,
|
|
72
|
+
friction: 5,
|
|
73
|
+
tension: 140,
|
|
74
|
+
useNativeDriver: false,
|
|
75
|
+
}),
|
|
76
|
+
]).start()
|
|
77
|
+
} else {
|
|
78
|
+
// Plain entrance: a clean ease-out grow with no overshoot (ANIMATION_EASING has
|
|
79
|
+
// no control point above y=1) — it reads as tracking the rep, not bouncing.
|
|
80
|
+
Animated.timing(liveGrowth, {
|
|
81
|
+
toValue: 1,
|
|
82
|
+
duration: 300,
|
|
83
|
+
easing: ANIMATION_EASING,
|
|
84
|
+
useNativeDriver: false,
|
|
85
|
+
}).start()
|
|
86
|
+
}
|
|
87
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveGrowth])
|
|
88
|
+
return liveGrowth
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { ANIMATION_EASING }
|
|
@@ -137,7 +137,7 @@ export const VerticalWithContent: Story = {
|
|
|
137
137
|
<Text className="text-text-secondary text-sm">
|
|
138
138
|
Fill in your profile details to continue.
|
|
139
139
|
</Text>
|
|
140
|
-
<View className="bg-surface-input border border-
|
|
140
|
+
<View className="bg-surface-input border border-hairline rounded-md px-3 py-2">
|
|
141
141
|
<Text className="text-text-secondary text-sm">Display Name</Text>
|
|
142
142
|
</View>
|
|
143
143
|
</View>
|
|
@@ -91,7 +91,7 @@ function StepConnector({ index }: StepConnectorProps) {
|
|
|
91
91
|
<View
|
|
92
92
|
className={cn(
|
|
93
93
|
'flex-1 h-0.5 mx-2',
|
|
94
|
-
isCompleted ? 'bg-brand-primary' : 'bg-
|
|
94
|
+
isCompleted ? 'bg-brand-primary' : 'bg-hairline'
|
|
95
95
|
)}
|
|
96
96
|
/>
|
|
97
97
|
)
|
|
@@ -101,7 +101,7 @@ function StepConnector({ index }: StepConnectorProps) {
|
|
|
101
101
|
<View
|
|
102
102
|
className={cn(
|
|
103
103
|
'w-0.5 h-8 ml-4 my-1',
|
|
104
|
-
isCompleted ? 'bg-brand-primary' : 'bg-
|
|
104
|
+
isCompleted ? 'bg-brand-primary' : 'bg-hairline'
|
|
105
105
|
)}
|
|
106
106
|
/>
|
|
107
107
|
)
|
|
@@ -171,7 +171,7 @@ export function StepIndicator({
|
|
|
171
171
|
const statusStyles: Record<StepStatus, string> = {
|
|
172
172
|
completed: 'bg-brand-primary border-brand-primary',
|
|
173
173
|
active: 'bg-brand-primary border-brand-primary',
|
|
174
|
-
upcoming: 'bg-surface-base border-
|
|
174
|
+
upcoming: 'bg-surface-base border-hairline-strong',
|
|
175
175
|
error: 'bg-status-error border-status-error',
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -43,7 +43,7 @@ export function DeviceMenu({
|
|
|
43
43
|
<PopoverTrigger>
|
|
44
44
|
<DeviceIndicator status={status} />
|
|
45
45
|
</PopoverTrigger>
|
|
46
|
-
<PopoverContent className="right-0 left-auto mt-[10px] w-[326px] bg-surface-elevated border border-
|
|
46
|
+
<PopoverContent className="right-0 left-auto mt-[10px] w-[326px] bg-surface-elevated border border-hairline-strong rounded-[10px] p-[7px]">
|
|
47
47
|
<Typography
|
|
48
48
|
variant="monoLabel"
|
|
49
49
|
color="tertiary"
|
|
@@ -52,7 +52,7 @@ export function DeviceMenu({
|
|
|
52
52
|
Devices — {boundCount} bound · {availCount} available
|
|
53
53
|
</Typography>
|
|
54
54
|
{devices.map((device, i) => (
|
|
55
|
-
<View key={device.id} className={cn(i > 0 && 'border-t border-
|
|
55
|
+
<View key={device.id} className={cn(i > 0 && 'border-t border-hairline')}>
|
|
56
56
|
<DeviceRow
|
|
57
57
|
device={device}
|
|
58
58
|
onPress={onSelectDevice ? () => onSelectDevice(device) : undefined}
|
|
@@ -55,7 +55,7 @@ export function SideNav({
|
|
|
55
55
|
<View
|
|
56
56
|
accessibilityRole="tablist"
|
|
57
57
|
className={cn(
|
|
58
|
-
'w-[60px] items-center gap-[6px] border-r border-
|
|
58
|
+
'w-[60px] items-center gap-[6px] border-r border-hairline bg-background-base py-3',
|
|
59
59
|
className
|
|
60
60
|
)}
|
|
61
61
|
>
|
|
@@ -58,7 +58,7 @@ export function TopBar({
|
|
|
58
58
|
// shared chrome gradient (web); solid bg-surface-elevated is the native fallback
|
|
59
59
|
style={surfaceGradient.chrome() as object}
|
|
60
60
|
className={cn(
|
|
61
|
-
'h-[46px] flex-row items-center gap-[14px] px-4 bg-surface-elevated border-b border-
|
|
61
|
+
'h-[46px] flex-row items-center gap-[14px] px-4 bg-surface-elevated border-b border-hairline',
|
|
62
62
|
className
|
|
63
63
|
)}
|
|
64
64
|
>
|
|
@@ -287,7 +287,7 @@ export const FormExample: Story = {
|
|
|
287
287
|
isRequired
|
|
288
288
|
/>
|
|
289
289
|
|
|
290
|
-
<View className="pt-2 border-t border-
|
|
290
|
+
<View className="pt-2 border-t border-hairline">
|
|
291
291
|
<Text className="text-sm text-text-secondary">
|
|
292
292
|
Selected: {country ? `${countryOptions.find(c => c.value === country)?.label}` : '-'}
|
|
293
293
|
{city ? `, ${cityOptions[country!]?.find(c => c.value === city)?.label}` : ''}
|
|
@@ -238,7 +238,7 @@ export function Autocomplete<T extends string = string>({
|
|
|
238
238
|
<View
|
|
239
239
|
className={cn(
|
|
240
240
|
'absolute z-50 top-full left-0 right-0 mt-1',
|
|
241
|
-
'bg-surface-elevated rounded-md shadow-lg border border-
|
|
241
|
+
'bg-surface-elevated rounded-md shadow-lg border border-hairline',
|
|
242
242
|
'max-h-60 overflow-hidden'
|
|
243
243
|
)}
|
|
244
244
|
>
|
|
@@ -67,7 +67,7 @@ export function Avatar({
|
|
|
67
67
|
accessibilityLabel={alt || resolvedFallback || 'Avatar'}
|
|
68
68
|
className={cn(
|
|
69
69
|
'items-center justify-center rounded-full overflow-hidden',
|
|
70
|
-
!nameColor && 'bg-
|
|
70
|
+
!nameColor && 'bg-hairline-strong',
|
|
71
71
|
styles.container,
|
|
72
72
|
className
|
|
73
73
|
)}
|
|
@@ -102,7 +102,7 @@ const badgeColors: Record<string, string> = {
|
|
|
102
102
|
success: 'bg-status-success',
|
|
103
103
|
error: 'bg-status-error',
|
|
104
104
|
warning: 'bg-status-warning',
|
|
105
|
-
default: 'bg-
|
|
105
|
+
default: 'bg-hairline-strong',
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -25,7 +25,7 @@ export interface BadgeProps extends ViewProps {
|
|
|
25
25
|
|
|
26
26
|
const variantColorStyles: Record<BadgeVariant, Record<BadgeColor, string>> = {
|
|
27
27
|
solid: {
|
|
28
|
-
default: 'bg-
|
|
28
|
+
default: 'bg-hairline-strong text-text-inverse',
|
|
29
29
|
primary: 'bg-brand-primary text-text-inverse',
|
|
30
30
|
secondary: 'bg-brand-secondary text-text-inverse',
|
|
31
31
|
success: 'bg-status-success text-text-inverse',
|
|
@@ -43,7 +43,7 @@ const variantColorStyles: Record<BadgeVariant, Record<BadgeColor, string>> = {
|
|
|
43
43
|
info: 'bg-status-info-subtle text-status-info',
|
|
44
44
|
},
|
|
45
45
|
outline: {
|
|
46
|
-
default: 'border border-
|
|
46
|
+
default: 'border border-hairline-strong text-text-secondary',
|
|
47
47
|
primary: 'border border-brand-primary text-brand-primary',
|
|
48
48
|
secondary: 'border border-brand-secondary text-brand-secondary',
|
|
49
49
|
success: 'border border-status-success text-status-success',
|
|
@@ -45,10 +45,10 @@ export interface CardProps extends ViewProps {
|
|
|
45
45
|
|
|
46
46
|
const variantStyles: Record<CardVariant, string> = {
|
|
47
47
|
elevated: '', // Will be set dynamically via elevation system
|
|
48
|
-
outline: 'border-2 border-
|
|
48
|
+
outline: 'border-2 border-hairline-strong', // Thicker border with stronger contrast
|
|
49
49
|
filled: '', // Will be set dynamically via elevation system
|
|
50
|
-
accent: 'border border-
|
|
51
|
-
subtle: 'border border-
|
|
50
|
+
accent: 'border border-hairline',
|
|
51
|
+
subtle: 'border border-hairline-subtle',
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -91,7 +91,7 @@ export const Checkbox = forwardRef<View, CheckboxProps>(function Checkbox(
|
|
|
91
91
|
styles.box,
|
|
92
92
|
isChecked || isIndeterminate
|
|
93
93
|
? 'bg-brand-primary border-brand-primary'
|
|
94
|
-
: 'bg-transparent border-
|
|
94
|
+
: 'bg-transparent border-hairline',
|
|
95
95
|
isInvalid && 'border-status-error',
|
|
96
96
|
!isDisabled && 'web:hover:border-brand-primary'
|
|
97
97
|
)}
|
|
@@ -180,7 +180,7 @@ export function Accordion({
|
|
|
180
180
|
|
|
181
181
|
return (
|
|
182
182
|
<AccordionContext.Provider value={{ openIndices, toggleIndex, allowMultiple }}>
|
|
183
|
-
<View className={cn('w-full divide-y divide-
|
|
183
|
+
<View className={cn('w-full divide-y divide-hairline', className)} {...props}>
|
|
184
184
|
{React.Children.map(children, (child, index) => {
|
|
185
185
|
if (React.isValidElement(child)) {
|
|
186
186
|
return React.cloneElement(child as React.ReactElement<any>, { index })
|
|
@@ -125,7 +125,7 @@ export function Drawer({
|
|
|
125
125
|
>
|
|
126
126
|
{/* Header */}
|
|
127
127
|
{(title || showCloseButton) && (
|
|
128
|
-
<View className="flex-row items-center justify-between px-4 py-3 border-b border-
|
|
128
|
+
<View className="flex-row items-center justify-between px-4 py-3 border-b border-hairline">
|
|
129
129
|
{title && (
|
|
130
130
|
<Text className="text-lg font-semibold text-text-primary">
|
|
131
131
|
{title}
|
|
@@ -165,7 +165,7 @@ export interface DrawerHeaderProps extends ViewProps {
|
|
|
165
165
|
export function DrawerHeader({ className, children, ...props }: DrawerHeaderProps) {
|
|
166
166
|
return (
|
|
167
167
|
<View
|
|
168
|
-
className={cn('px-4 py-3 border-b border-
|
|
168
|
+
className={cn('px-4 py-3 border-b border-hairline', className)}
|
|
169
169
|
{...props}
|
|
170
170
|
>
|
|
171
171
|
{children}
|
|
@@ -217,7 +217,7 @@ export function DrawerFooter({ className, children, ...props }: DrawerFooterProp
|
|
|
217
217
|
<View
|
|
218
218
|
className={cn(
|
|
219
219
|
'flex-row items-center justify-end gap-3',
|
|
220
|
-
'px-4 py-3 border-t border-
|
|
220
|
+
'px-4 py-3 border-t border-hairline',
|
|
221
221
|
className
|
|
222
222
|
)}
|
|
223
223
|
{...props}
|
|
@@ -116,7 +116,7 @@ export function MenuList({ children, className }: MenuListProps) {
|
|
|
116
116
|
<View
|
|
117
117
|
className={cn(
|
|
118
118
|
'absolute z-50 top-full left-0 mt-1 min-w-[160px]',
|
|
119
|
-
'bg-surface-elevated rounded-lg shadow-lg border border-
|
|
119
|
+
'bg-surface-elevated rounded-lg shadow-lg border border-hairline',
|
|
120
120
|
'py-1 overflow-hidden',
|
|
121
121
|
className
|
|
122
122
|
)}
|
|
@@ -195,7 +195,7 @@ export interface MenuDividerProps {
|
|
|
195
195
|
* Divider between menu items.
|
|
196
196
|
*/
|
|
197
197
|
export function MenuDivider({ className }: MenuDividerProps) {
|
|
198
|
-
return <View className={cn('h-px bg-
|
|
198
|
+
return <View className={cn('h-px bg-hairline my-1', className)} />
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
export interface MenuGroupProps {
|
|
@@ -27,7 +27,7 @@ export interface PillProps extends ViewProps {
|
|
|
27
27
|
|
|
28
28
|
const variantColorStyles: Record<PillVariant, Record<PillColor, string>> = {
|
|
29
29
|
subtle: {
|
|
30
|
-
default: 'bg-surface-raised border-
|
|
30
|
+
default: 'bg-surface-raised border-hairline-subtle text-text-secondary',
|
|
31
31
|
primary: 'bg-brand-primary/10 border-brand-primary/25 text-brand-primary',
|
|
32
32
|
secondary: 'bg-brand-secondary/10 border-brand-secondary/25 text-brand-secondary',
|
|
33
33
|
success: 'bg-status-success/10 border-status-success/25 text-status-success',
|
|
@@ -36,7 +36,7 @@ const variantColorStyles: Record<PillVariant, Record<PillColor, string>> = {
|
|
|
36
36
|
info: 'bg-status-info/10 border-status-info/25 text-status-info',
|
|
37
37
|
},
|
|
38
38
|
outline: {
|
|
39
|
-
default: 'border-
|
|
39
|
+
default: 'border-hairline text-text-secondary',
|
|
40
40
|
primary: 'border-brand-primary text-brand-primary',
|
|
41
41
|
secondary: 'border-brand-secondary text-brand-secondary',
|
|
42
42
|
success: 'border-status-success text-status-success',
|
|
@@ -152,12 +152,12 @@ export const WithFormContent: Story = {
|
|
|
152
152
|
<Text className="text-text-secondary text-xs">
|
|
153
153
|
Enter your new display name below.
|
|
154
154
|
</Text>
|
|
155
|
-
<View className="bg-surface-input border border-
|
|
155
|
+
<View className="bg-surface-input border border-hairline rounded-md px-3 py-2">
|
|
156
156
|
<Text className="text-text-secondary text-sm">John Doe</Text>
|
|
157
157
|
</View>
|
|
158
158
|
<View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
|
|
159
159
|
<PopoverCloseButton>
|
|
160
|
-
<View className="px-3 py-1.5 rounded-md bg-surface-elevated border border-
|
|
160
|
+
<View className="px-3 py-1.5 rounded-md bg-surface-elevated border border-hairline">
|
|
161
161
|
<Text className="text-text-primary text-sm">Cancel</Text>
|
|
162
162
|
</View>
|
|
163
163
|
</PopoverCloseButton>
|
|
@@ -188,7 +188,7 @@ export function PopoverContent({ children, className }: PopoverContentProps) {
|
|
|
188
188
|
<View
|
|
189
189
|
className={cn(
|
|
190
190
|
'absolute z-50 min-w-[200px]',
|
|
191
|
-
'bg-surface-elevated rounded-lg shadow-lg border border-
|
|
191
|
+
'bg-surface-elevated rounded-lg shadow-lg border border-hairline',
|
|
192
192
|
'p-4',
|
|
193
193
|
placementStyles[placement],
|
|
194
194
|
className
|
|
@@ -230,7 +230,7 @@ export function CircularProgress({
|
|
|
230
230
|
cy={center}
|
|
231
231
|
r={radius}
|
|
232
232
|
fill="none"
|
|
233
|
-
stroke="var(--color-
|
|
233
|
+
stroke="var(--color-hairline-default)"
|
|
234
234
|
strokeWidth={strokeWidth}
|
|
235
235
|
/>
|
|
236
236
|
{/* Progress arc */}
|
|
@@ -325,7 +325,7 @@ export function ProgressSteps({
|
|
|
325
325
|
className={cn(
|
|
326
326
|
'rounded-full',
|
|
327
327
|
dot,
|
|
328
|
-
index <= currentStep ? colorStyles[color] : 'bg-
|
|
328
|
+
index <= currentStep ? colorStyles[color] : 'bg-hairline-strong'
|
|
329
329
|
)}
|
|
330
330
|
/>
|
|
331
331
|
|
|
@@ -335,7 +335,7 @@ export function ProgressSteps({
|
|
|
335
335
|
className={cn(
|
|
336
336
|
'flex-1 mx-1',
|
|
337
337
|
connector,
|
|
338
|
-
index < currentStep ? colorStyles[color] : 'bg-
|
|
338
|
+
index < currentStep ? colorStyles[color] : 'bg-hairline-strong'
|
|
339
339
|
)}
|
|
340
340
|
/>
|
|
341
341
|
)}
|
|
@@ -188,7 +188,7 @@ export const FormExample: Story = {
|
|
|
188
188
|
<Radio value="yearly">Yearly (save 20%)</Radio>
|
|
189
189
|
</RadioGroup>
|
|
190
190
|
|
|
191
|
-
<View className="pt-4 border-t border-
|
|
191
|
+
<View className="pt-4 border-t border-hairline">
|
|
192
192
|
<Text className="text-sm text-text-secondary">
|
|
193
193
|
Selected: {plan} plan, billed {billing}
|
|
194
194
|
</Text>
|
|
@@ -117,10 +117,10 @@ const sizeConfig: Record<RadioSize, { outer: string; inner: string; text: string
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
const colorStyles: Record<RadioColor, { checked: string; unchecked: string }> = {
|
|
120
|
-
primary: { checked: 'border-brand-primary bg-brand-primary', unchecked: 'border-
|
|
121
|
-
secondary: { checked: 'border-brand-secondary bg-brand-secondary', unchecked: 'border-
|
|
122
|
-
success: { checked: 'border-status-success bg-status-success', unchecked: 'border-
|
|
123
|
-
error: { checked: 'border-status-error bg-status-error', unchecked: 'border-
|
|
120
|
+
primary: { checked: 'border-brand-primary bg-brand-primary', unchecked: 'border-hairline-strong' },
|
|
121
|
+
secondary: { checked: 'border-brand-secondary bg-brand-secondary', unchecked: 'border-hairline-strong' },
|
|
122
|
+
success: { checked: 'border-status-success bg-status-success', unchecked: 'border-hairline-strong' },
|
|
123
|
+
error: { checked: 'border-status-error bg-status-error', unchecked: 'border-hairline-strong' },
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
@@ -145,7 +145,7 @@ export function Select<T extends string = string>({
|
|
|
145
145
|
isInvalid
|
|
146
146
|
? 'border-border-input-error'
|
|
147
147
|
: variant === 'filled'
|
|
148
|
-
? 'border-
|
|
148
|
+
? 'border-hairline-subtle'
|
|
149
149
|
: 'border-border-input',
|
|
150
150
|
!isDisabled && !isInvalid && 'web:hover:border-border-input-hover',
|
|
151
151
|
isOpen && 'border-border-input-focus',
|
|
@@ -187,7 +187,7 @@ export function Select<T extends string = string>({
|
|
|
187
187
|
<View
|
|
188
188
|
className={cn(
|
|
189
189
|
'absolute z-50 top-full left-0 right-0 mt-1',
|
|
190
|
-
'bg-surface-elevated rounded-md shadow-lg border border-
|
|
190
|
+
'bg-surface-elevated rounded-md shadow-lg border border-hairline',
|
|
191
191
|
'max-h-60 overflow-hidden'
|
|
192
192
|
)}
|
|
193
193
|
>
|
|
@@ -229,7 +229,7 @@ function SelectOption<T>({ option }: SelectOptionComponentProps<T>) {
|
|
|
229
229
|
<View
|
|
230
230
|
className={cn(
|
|
231
231
|
'w-4 h-4 mr-3 rounded border',
|
|
232
|
-
selected ? 'bg-brand-primary border-brand-primary' : 'border-
|
|
232
|
+
selected ? 'bg-brand-primary border-brand-primary' : 'border-hairline-strong'
|
|
233
233
|
)}
|
|
234
234
|
>
|
|
235
235
|
{selected && <Text className="text-white text-xs text-center">✓</Text>}
|
|
@@ -15,8 +15,8 @@ const meta: Meta<typeof Surface> = {
|
|
|
15
15
|
},
|
|
16
16
|
level: {
|
|
17
17
|
control: 'select',
|
|
18
|
-
options: ['
|
|
19
|
-
description: 'Named
|
|
18
|
+
options: ['frame', 'background', 'base', 'elevated', 'raised', 'overlay'],
|
|
19
|
+
description: 'Named grey plane (flat background from a semantic token)',
|
|
20
20
|
},
|
|
21
21
|
pressed: {
|
|
22
22
|
control: 'boolean',
|
|
@@ -88,7 +88,7 @@ export const WithGlow: Story = {
|
|
|
88
88
|
),
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// The named-plane model: flat, full-bleed
|
|
91
|
+
// The named-plane model: flat, full-bleed grey planes straight from the
|
|
92
92
|
// semantic surface tokens — reaching the darker nav/page shades the numeric
|
|
93
93
|
// lighten model can't. These back the shell / rail / stage.
|
|
94
94
|
export const NamedPlanes: Story = {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
useSurface,
|
|
12
12
|
useSurfaceMode,
|
|
13
13
|
} from './SurfaceContext'
|
|
14
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
14
15
|
import { getPressedRecessShadow } from '../../../theme/elevation'
|
|
15
16
|
|
|
16
17
|
// A descendant probe that renders the on-surface colour + mode it resolves from
|
|
@@ -157,17 +158,17 @@ describe('Surface (pressed well)', () => {
|
|
|
157
158
|
expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: '#1C1916' })
|
|
158
159
|
})
|
|
159
160
|
|
|
160
|
-
it('clamps at the
|
|
161
|
+
it('clamps at the frame floor: pressed directly in background does not underflow', () => {
|
|
161
162
|
render(
|
|
162
163
|
<Surface level="background">
|
|
163
164
|
<Surface pressed testID="well" />
|
|
164
165
|
</Surface>
|
|
165
166
|
)
|
|
166
|
-
// background (#1C1916) steps down to the
|
|
167
|
-
expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: '#
|
|
167
|
+
// background (#1C1916) steps down to the frame floor (#100D0A), the bezel.
|
|
168
|
+
expect(screen.getByTestId('well')).toHaveStyle({ backgroundColor: '#100D0A' })
|
|
168
169
|
})
|
|
169
170
|
|
|
170
|
-
it('does not step below the floor: pressed within a floor-pressed well stays at
|
|
171
|
+
it('does not step below the floor: pressed within a floor-pressed well stays at frame', () => {
|
|
171
172
|
render(
|
|
172
173
|
<Surface level="background">
|
|
173
174
|
<Surface pressed>
|
|
@@ -175,7 +176,7 @@ describe('Surface (pressed well)', () => {
|
|
|
175
176
|
</Surface>
|
|
176
177
|
</Surface>
|
|
177
178
|
)
|
|
178
|
-
expect(screen.getByTestId('deeper')).toHaveStyle({ backgroundColor: '#
|
|
179
|
+
expect(screen.getByTestId('deeper')).toHaveStyle({ backgroundColor: '#100D0A' })
|
|
179
180
|
})
|
|
180
181
|
|
|
181
182
|
it('publishes the stepped-down level to descendants so a nested press steps again', () => {
|
|
@@ -230,9 +231,9 @@ describe('pressedLevel helper', () => {
|
|
|
230
231
|
['raised', 'elevated'],
|
|
231
232
|
['elevated', 'base'],
|
|
232
233
|
['base', 'background'],
|
|
233
|
-
['background', '
|
|
234
|
-
['
|
|
235
|
-
] as const)('steps %s down to %s (clamped at
|
|
234
|
+
['background', 'frame'],
|
|
235
|
+
['frame', 'frame'],
|
|
236
|
+
] as const)('steps %s down to %s (clamped at frame)', (parent, expected) => {
|
|
236
237
|
expect(pressedLevel(parent)).toBe(expected)
|
|
237
238
|
})
|
|
238
239
|
})
|
|
@@ -253,14 +254,15 @@ describe('Surface on-surface colour context', () => {
|
|
|
253
254
|
<Probe role="tertiary" label="t" />
|
|
254
255
|
</Surface>
|
|
255
256
|
)
|
|
256
|
-
|
|
257
|
-
expect(screen.getByTestId('
|
|
258
|
-
expect(screen.getByTestId('
|
|
257
|
+
const t = getSemanticColors('dark')
|
|
258
|
+
expect(screen.getByTestId('p')).toHaveStyle({ color: t['text-primary'] })
|
|
259
|
+
expect(screen.getByTestId('s')).toHaveStyle({ color: t['text-secondary'] })
|
|
260
|
+
expect(screen.getByTestId('t')).toHaveStyle({ color: t['text-tertiary'] })
|
|
259
261
|
})
|
|
260
262
|
|
|
261
263
|
it('resolves dark on-surface colours even with no enclosing Surface', () => {
|
|
262
264
|
render(<Probe role="primary" label="p" />)
|
|
263
|
-
expect(screen.getByTestId('p')).toHaveStyle({ color: '
|
|
265
|
+
expect(screen.getByTestId('p')).toHaveStyle({ color: getSemanticColors('dark')['text-primary'] })
|
|
264
266
|
})
|
|
265
267
|
|
|
266
268
|
it('flows an overridden theme to descendant text', () => {
|
|
@@ -292,15 +294,26 @@ describe('surface colour helpers', () => {
|
|
|
292
294
|
expect(surfaceBackground('base', 'light')).toBe('#FFFFFF')
|
|
293
295
|
})
|
|
294
296
|
|
|
295
|
-
it('resolves the
|
|
296
|
-
|
|
297
|
+
it('resolves the frame floor from its own semantic token', () => {
|
|
298
|
+
// The old `inset` level had no token and fell back to a primitive. `frame`
|
|
299
|
+
// has `background-frame`, which is what let SurfaceContext drop the special
|
|
300
|
+
// case in surfaceBackground().
|
|
301
|
+
expect(surfaceBackground('frame', 'dark')).toBe(getSemanticColors('dark')['background-frame'])
|
|
297
302
|
})
|
|
298
303
|
|
|
299
|
-
it('onSurfaceColors returns the
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
+
it('onSurfaceColors returns the text ramp as literal hex', () => {
|
|
305
|
+
const t = getSemanticColors('dark')
|
|
306
|
+
const got = onSurfaceColors('dark')
|
|
307
|
+
expect(got).toEqual({
|
|
308
|
+
primary: t['text-primary'],
|
|
309
|
+
secondary: t['text-secondary'],
|
|
310
|
+
tertiary: t['text-tertiary'],
|
|
304
311
|
})
|
|
312
|
+
// The actual contract this test guards: LITERAL hex, never a var() — under
|
|
313
|
+
// the RNW alias resolveColor() returns 'var(--color-…)', which silently
|
|
314
|
+
// breaks any consumer doing colour maths on the result.
|
|
315
|
+
for (const [role, value] of Object.entries(got)) {
|
|
316
|
+
expect(value, role).toMatch(/^#[0-9A-F]{6}$/i)
|
|
317
|
+
}
|
|
305
318
|
})
|
|
306
319
|
})
|
|
@@ -28,7 +28,7 @@ export interface SurfaceProps extends ViewProps {
|
|
|
28
28
|
*/
|
|
29
29
|
elevation?: ElevationLevel
|
|
30
30
|
/**
|
|
31
|
-
* Named
|
|
31
|
+
* Named grey plane (shell / rail / stage). Sets the background directly
|
|
32
32
|
* from a semantic surface token — reaching the darker nav/page planes the
|
|
33
33
|
* lighten model can't — and drops the default rounding + auto shadow so the
|
|
34
34
|
* plane reads full-bleed. Composable with `elevation`'s glow.
|
|
@@ -61,7 +61,7 @@ export interface SurfaceProps extends ViewProps {
|
|
|
61
61
|
* A container that OWNS its background and establishes an on-surface colour
|
|
62
62
|
* context. Two grounded ways to pick the background:
|
|
63
63
|
* - `elevation` (numeric): the raised-card model — lighten-from-base + shadow.
|
|
64
|
-
* - `level` (named): a flat
|
|
64
|
+
* - `level` (named): a flat grey plane straight from a semantic token,
|
|
65
65
|
* for the shell / rail / stage backgrounds.
|
|
66
66
|
* Either way, descendants read the surface via {@link useOnSurfaceColor} /
|
|
67
67
|
* {@link useSurfaceMode} rather than hard-coding `getSemanticColors('dark')`.
|
|
@@ -86,7 +86,7 @@ export function Surface({
|
|
|
86
86
|
// Three grounded ways to own a background, in precedence order:
|
|
87
87
|
// - pressed: a well ONE ramp step DOWN from the parent level (relative),
|
|
88
88
|
// with an inner-shadow recess — clamps at the `inset` floor.
|
|
89
|
-
// - level (named plane): a flat
|
|
89
|
+
// - level (named plane): a flat grey plane straight from a token.
|
|
90
90
|
// - elevation (numeric): the raised-card model — lighten-from-base + shadow.
|
|
91
91
|
const baseColor = getBaseSurfaceColor(mode)
|
|
92
92
|
const resolvedLevel = pressed ? pressedLevel(inherited.level) : (level ?? inherited.level)
|