@titan-design/react-ui 0.9.3 → 0.10.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 +78 -24
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +78 -24
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +31 -8
- package/dist/index.d.ts +31 -8
- package/dist/index.js +214 -113
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +210 -114
- package/dist/index.mjs.map +1 -1
- package/dist/pages.js +214 -157
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +214 -157
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +129 -34
- package/dist/theme/index.d.ts +129 -34
- package/dist/theme/index.js +95 -19
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +92 -20
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +88 -24
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +88 -24
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/custom/Workout/BaseBadge.tsx +5 -5
- package/src/components/custom/Workout/RowInventory.stories.tsx +5 -2
- package/src/components/custom/Workout/S3FullRail.stories.tsx +13 -1
- package/src/components/custom/Workout/SessionHeader.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.stories.tsx +4 -1
- package/src/components/custom/Workout/SessionRail.tsx +5 -5
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +16 -25
- package/src/components/custom/Workout/SupersetWrapper.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +3 -7
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +4 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +2 -2
- package/src/components/ui/surface/Surface.stories.tsx +195 -1
- package/src/components/ui/surface/Surface.test.tsx +148 -9
- package/src/components/ui/surface/Surface.tsx +37 -9
- package/src/components/ui/surface/SurfaceContext.ts +48 -8
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/surface/surface.contract.test.ts +283 -0
- package/src/theme/ColorSystem.stories.tsx +232 -47
- package/src/theme/config.ts +12 -0
- package/src/theme/elevation.ts +90 -67
- package/src/theme/global.css +35 -9
- package/src/theme/tokens/primitives.ts +40 -0
- package/src/theme/tokens/semantic.ts +63 -14
- package/tailwind.config.js +8 -0
|
@@ -22,6 +22,9 @@ import { WeightBadge } from './WeightBadge'
|
|
|
22
22
|
import { PrBadge } from './PrBadge'
|
|
23
23
|
import { PlaceholderStrip } from './PlaceholderStrip'
|
|
24
24
|
import { SupersetWrapper } from './SupersetWrapper'
|
|
25
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
26
|
+
|
|
27
|
+
const t = getSemanticColors('dark')
|
|
25
28
|
|
|
26
29
|
// ----------------------------------------------------------------------------- gallery scaffold
|
|
27
30
|
class Boundary extends Component<{ children: ReactNode }, { err: string | null }> {
|
|
@@ -364,10 +367,10 @@ export const AllRows: Story = {
|
|
|
364
367
|
</Cell>
|
|
365
368
|
<Cell name="SupersetWrapper" entity="grouping bracket" expand="no">
|
|
366
369
|
<SupersetWrapper label="SS1" color="#FF7900">
|
|
367
|
-
<View style={{ padding: 10, backgroundColor: '
|
|
370
|
+
<View style={{ padding: 10, backgroundColor: t['surface-raised'], borderRadius: 8 }}>
|
|
368
371
|
<Text style={{ color: '#F3F4F6' }}>Exercise A</Text>
|
|
369
372
|
</View>
|
|
370
|
-
<View style={{ padding: 10, backgroundColor: '
|
|
373
|
+
<View style={{ padding: 10, backgroundColor: t['surface-raised'], borderRadius: 8 }}>
|
|
371
374
|
<Text style={{ color: '#F3F4F6' }}>Exercise B</Text>
|
|
372
375
|
</View>
|
|
373
376
|
</SupersetWrapper>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
6
6
|
import { NavStub, Rail, Page, T_TERTIARY } from './setHeadingKit'
|
|
7
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
8
|
+
|
|
9
|
+
const t = getSemanticColors('dark')
|
|
7
10
|
|
|
8
11
|
type Args = { stripHeight: number }
|
|
9
12
|
const meta: Meta<Args> = {
|
|
@@ -25,7 +28,16 @@ export const NextToNav: Story = {
|
|
|
25
28
|
<div style={{ display: 'flex', alignItems: 'stretch', width: 'fit-content' }}>
|
|
26
29
|
<NavStub />
|
|
27
30
|
<Rail stripH={args.stripHeight} />
|
|
28
|
-
<div
|
|
31
|
+
<div
|
|
32
|
+
style={{
|
|
33
|
+
width: 140,
|
|
34
|
+
background: t['background-base'],
|
|
35
|
+
alignSelf: 'stretch',
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
29
41
|
<span style={{ fontSize: 10, color: T_TERTIARY, fontFamily: 'monospace' }}>stage</span>
|
|
30
42
|
</div>
|
|
31
43
|
</div>
|
|
@@ -8,8 +8,8 @@ import { MetricTiles, type MetricTileData } from './MetricTiles'
|
|
|
8
8
|
import { ScheduleTiles } from './ScheduleTiles'
|
|
9
9
|
import { paceTone, paceToneColor } from './paceTone'
|
|
10
10
|
|
|
11
|
-
// The header shares the SideNav's `background-base` (
|
|
12
|
-
// the rail header read as ONE continuous dark plane on the left — the sunk exercise list
|
|
11
|
+
// The header shares the SideNav's `background-base` (warm-tapered ramp shell, #1C1916) so the
|
|
12
|
+
// nav and the rail header read as ONE continuous dark plane on the left — the sunk exercise list
|
|
13
13
|
// sits raised off it. A `<Surface level="background">` owns that plane so the header no
|
|
14
14
|
// longer hand-sets it, and its title/label text resolve on-surface colours from context.
|
|
15
15
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { View, Text } from 'react-native'
|
|
3
3
|
import { primitiveRamps } from '../../../theme/tokens/primitives'
|
|
4
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
4
5
|
import { SessionRail, type SessionRailExercise } from './SessionRail'
|
|
5
6
|
|
|
7
|
+
const t = getSemanticColors('dark')
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* `SessionRail` (shell organism) — the live-workout exercise list: a flat raised
|
|
8
11
|
* `SessionHeader` glance (title, stat tiles, chunked pace bar) over a sunk, inset list
|
|
@@ -20,7 +23,7 @@ const meta: Meta<typeof SessionRail> = {
|
|
|
20
23
|
},
|
|
21
24
|
decorators: [
|
|
22
25
|
(Story) => (
|
|
23
|
-
<View className="min-h-screen flex-row" style={{ backgroundColor: '
|
|
26
|
+
<View className="min-h-screen flex-row" style={{ backgroundColor: t['background-base'] }}>
|
|
24
27
|
<Story />
|
|
25
28
|
<View className="flex-1 items-center justify-center">
|
|
26
29
|
<Text style={{ color: '#6B7280', fontSize: 12 }}>main viewport</Text>
|
|
@@ -9,13 +9,13 @@ import type { MetricTileData } from './MetricTiles'
|
|
|
9
9
|
import type { SetStripSet } from './SetStrip'
|
|
10
10
|
import type { ExerciseIndicatorKind } from './ExerciseIndicator'
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
// ONE dark plane (
|
|
14
|
-
// The exercise list is a LIGHTER inset panel — `<Surface level="elevated">` (
|
|
15
|
-
//
|
|
12
|
+
// Warm-tapered ramp surfaces (the locked S3 shell shades): the nav + the heading plane read as
|
|
13
|
+
// ONE dark plane (background shell, #1C1916, owned by SessionHeader's `<Surface level="background">`).
|
|
14
|
+
// The exercise list is a LIGHTER inset panel — `<Surface level="elevated">` (#2A2827) —
|
|
15
|
+
// recessed via its inner shadow yet paler than the header, so the transparent
|
|
16
16
|
// exercise headings on it never blend into the header plane. Surface owns both backgrounds,
|
|
17
17
|
// so the rail no longer hand-sets them.
|
|
18
|
-
const DIVIDER = primitiveColors.charcoal[300] // #2C2C2C — row divider (raised to read on #
|
|
18
|
+
const DIVIDER = primitiveColors.charcoal[300] // #2C2C2C — row divider (raised to read on surface-elevated #2A2827)
|
|
19
19
|
|
|
20
20
|
const DEFAULT_WIDTH = 246
|
|
21
21
|
|
|
@@ -2,6 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
|
2
2
|
import { View, Text } from 'react-native'
|
|
3
3
|
import { SupersetWrapper } from './SupersetWrapper'
|
|
4
4
|
import { ExerciseCard } from './ExerciseCard'
|
|
5
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
6
|
+
|
|
7
|
+
const t = getSemanticColors('dark')
|
|
5
8
|
|
|
6
9
|
const meta: Meta<typeof SupersetWrapper> = {
|
|
7
10
|
title: 'Workout/SupersetWrapper',
|
|
@@ -9,7 +12,7 @@ const meta: Meta<typeof SupersetWrapper> = {
|
|
|
9
12
|
tags: ['autodocs'],
|
|
10
13
|
decorators: [
|
|
11
14
|
(Story) => (
|
|
12
|
-
<View style={{ maxWidth: 400, padding: 16, backgroundColor: '
|
|
15
|
+
<View style={{ maxWidth: 400, padding: 16, backgroundColor: t['background-base'] }}>
|
|
13
16
|
<Story />
|
|
14
17
|
</View>
|
|
15
18
|
),
|
|
@@ -26,24 +29,20 @@ export const Default: Story = {
|
|
|
26
29
|
<View
|
|
27
30
|
style={{
|
|
28
31
|
padding: 12,
|
|
29
|
-
backgroundColor: '
|
|
32
|
+
backgroundColor: t['surface-raised'],
|
|
30
33
|
borderRadius: 8,
|
|
31
34
|
}}
|
|
32
35
|
>
|
|
33
|
-
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
34
|
-
Exercise A
|
|
35
|
-
</Text>
|
|
36
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>Exercise A</Text>
|
|
36
37
|
</View>
|
|
37
38
|
<View
|
|
38
39
|
style={{
|
|
39
40
|
padding: 12,
|
|
40
|
-
backgroundColor: '
|
|
41
|
+
backgroundColor: t['surface-raised'],
|
|
41
42
|
borderRadius: 8,
|
|
42
43
|
}}
|
|
43
44
|
>
|
|
44
|
-
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
45
|
-
Exercise B
|
|
46
|
-
</Text>
|
|
45
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>Exercise B</Text>
|
|
47
46
|
</View>
|
|
48
47
|
</>
|
|
49
48
|
),
|
|
@@ -58,24 +57,20 @@ export const CustomLabel: Story = {
|
|
|
58
57
|
<View
|
|
59
58
|
style={{
|
|
60
59
|
padding: 12,
|
|
61
|
-
backgroundColor: '
|
|
60
|
+
backgroundColor: t['surface-raised'],
|
|
62
61
|
borderRadius: 8,
|
|
63
62
|
}}
|
|
64
63
|
>
|
|
65
|
-
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
66
|
-
Exercise A1
|
|
67
|
-
</Text>
|
|
64
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>Exercise A1</Text>
|
|
68
65
|
</View>
|
|
69
66
|
<View
|
|
70
67
|
style={{
|
|
71
68
|
padding: 12,
|
|
72
|
-
backgroundColor: '
|
|
69
|
+
backgroundColor: t['surface-raised'],
|
|
73
70
|
borderRadius: 8,
|
|
74
71
|
}}
|
|
75
72
|
>
|
|
76
|
-
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
77
|
-
Exercise A2
|
|
78
|
-
</Text>
|
|
73
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>Exercise A2</Text>
|
|
79
74
|
</View>
|
|
80
75
|
</>
|
|
81
76
|
),
|
|
@@ -90,24 +85,20 @@ export const CustomColor: Story = {
|
|
|
90
85
|
<View
|
|
91
86
|
style={{
|
|
92
87
|
padding: 12,
|
|
93
|
-
backgroundColor: '
|
|
88
|
+
backgroundColor: t['surface-raised'],
|
|
94
89
|
borderRadius: 8,
|
|
95
90
|
}}
|
|
96
91
|
>
|
|
97
|
-
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
98
|
-
Exercise A
|
|
99
|
-
</Text>
|
|
92
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>Exercise A</Text>
|
|
100
93
|
</View>
|
|
101
94
|
<View
|
|
102
95
|
style={{
|
|
103
96
|
padding: 12,
|
|
104
|
-
backgroundColor: '
|
|
97
|
+
backgroundColor: t['surface-raised'],
|
|
105
98
|
borderRadius: 8,
|
|
106
99
|
}}
|
|
107
100
|
>
|
|
108
|
-
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>
|
|
109
|
-
Exercise B
|
|
110
|
-
</Text>
|
|
101
|
+
<Text style={{ color: '#F3F4F6', fontFamily: 'Inter, sans-serif' }}>Exercise B</Text>
|
|
111
102
|
</View>
|
|
112
103
|
</>
|
|
113
104
|
),
|
|
@@ -12,7 +12,7 @@ export interface SupersetWrapperProps {
|
|
|
12
12
|
const DEFAULTS = {
|
|
13
13
|
label: 'SS',
|
|
14
14
|
color: getSemanticColors('dark')['brand-primary'],
|
|
15
|
-
bgBase: '
|
|
15
|
+
bgBase: getSemanticColors('dark')['background-base'],
|
|
16
16
|
} as const
|
|
17
17
|
|
|
18
18
|
export function SupersetWrapper({
|
|
@@ -84,11 +84,7 @@ function activeNumberTone(elapsedMs: number, targetMs: number | null): string {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** The active number: `countup` elapsed (→ target) or `countdown` remaining (→ 0.0, then −). */
|
|
87
|
-
function liveReadoutText(
|
|
88
|
-
elapsedMs: number,
|
|
89
|
-
targetMs: number,
|
|
90
|
-
readout: TempoLiveReadout,
|
|
91
|
-
): string {
|
|
87
|
+
function liveReadoutText(elapsedMs: number, targetMs: number, readout: TempoLiveReadout): string {
|
|
92
88
|
const seconds = readout === 'countup' ? elapsedMs / 1000 : (targetMs - elapsedMs) / 1000
|
|
93
89
|
return seconds.toFixed(1)
|
|
94
90
|
}
|
|
@@ -308,7 +304,7 @@ export function TempoDisplay({
|
|
|
308
304
|
style={{
|
|
309
305
|
flexDirection: 'row',
|
|
310
306
|
alignItems: 'center',
|
|
311
|
-
backgroundColor: '
|
|
307
|
+
backgroundColor: t['surface-raised'],
|
|
312
308
|
paddingHorizontal: chromePadX,
|
|
313
309
|
paddingVertical: chromePadY,
|
|
314
310
|
borderRadius: chromeRadius,
|
|
@@ -378,7 +374,7 @@ export function TempoDisplay({
|
|
|
378
374
|
>
|
|
379
375
|
<View
|
|
380
376
|
style={{
|
|
381
|
-
backgroundColor: '
|
|
377
|
+
backgroundColor: t['surface-overlay'],
|
|
382
378
|
borderRadius: 6,
|
|
383
379
|
paddingVertical: 8,
|
|
384
380
|
paddingHorizontal: 12,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { View } from 'react-native'
|
|
3
3
|
import { VolumeLandmarkBar } from './VolumeLandmarkBar'
|
|
4
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
5
|
+
|
|
6
|
+
const t = getSemanticColors('dark')
|
|
4
7
|
|
|
5
8
|
const meta: Meta<typeof VolumeLandmarkBar> = {
|
|
6
9
|
title: 'Workout/DataViz/VolumeLandmarkBar',
|
|
@@ -28,7 +31,7 @@ const meta: Meta<typeof VolumeLandmarkBar> = {
|
|
|
28
31
|
},
|
|
29
32
|
decorators: [
|
|
30
33
|
(Story) => (
|
|
31
|
-
<View style={{ padding: 24, paddingBottom: 40, backgroundColor: '
|
|
34
|
+
<View style={{ padding: 24, paddingBottom: 40, backgroundColor: t['background-base'] }}>
|
|
32
35
|
<Story />
|
|
33
36
|
</View>
|
|
34
37
|
),
|
|
@@ -26,7 +26,7 @@ const statusBgColors: Record<WorkoutPillStatus, string> = {
|
|
|
26
26
|
completed: 'rgba(46,213,115,0.15)',
|
|
27
27
|
current: 'rgba(255,121,0,0.15)',
|
|
28
28
|
next: 'transparent',
|
|
29
|
-
upcoming: '
|
|
29
|
+
upcoming: t['surface-raised'],
|
|
30
30
|
missed: 'rgba(209,67,67,0.1)',
|
|
31
31
|
deload: 'rgba(186,41,150,0.15)',
|
|
32
32
|
}
|
|
@@ -72,7 +72,7 @@ function usePulse(enabled: boolean) {
|
|
|
72
72
|
easing: Easing.inOut(Easing.ease),
|
|
73
73
|
useNativeDriver: true,
|
|
74
74
|
}),
|
|
75
|
-
])
|
|
75
|
+
])
|
|
76
76
|
)
|
|
77
77
|
animation.start()
|
|
78
78
|
|
|
@@ -15,9 +15,13 @@ const meta: Meta<typeof Surface> = {
|
|
|
15
15
|
},
|
|
16
16
|
level: {
|
|
17
17
|
control: 'select',
|
|
18
|
-
options: ['background', 'base', 'elevated', 'raised', 'overlay'],
|
|
18
|
+
options: ['inset', 'background', 'base', 'elevated', 'raised', 'overlay'],
|
|
19
19
|
description: 'Named charcoal plane (flat background from a semantic token)',
|
|
20
20
|
},
|
|
21
|
+
pressed: {
|
|
22
|
+
control: 'boolean',
|
|
23
|
+
description: 'Sunken well: one ramp step DOWN from the parent level + inner-shadow recess',
|
|
24
|
+
},
|
|
21
25
|
theme: {
|
|
22
26
|
control: 'select',
|
|
23
27
|
options: ['light', 'dark'],
|
|
@@ -99,6 +103,62 @@ export const NamedPlanes: Story = {
|
|
|
99
103
|
),
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
// The pressed (sunken well) model: a `<Surface pressed>` renders ONE ramp step
|
|
107
|
+
// DOWN from its parent's level — the symmetric twin of a raised surface stepping
|
|
108
|
+
// up — plus an inner-shadow recess. It's RELATIVE: the same `pressed` well reads
|
|
109
|
+
// as a consistent recess on every host plane (base → background, raised →
|
|
110
|
+
// elevated, overlay → raised), not a single fixed black hole. It clamps at the
|
|
111
|
+
// `inset` floor so a well in `background` bottoms out at the ramp's pit.
|
|
112
|
+
function WellCard({ level }: { level: SurfaceLevel }) {
|
|
113
|
+
return (
|
|
114
|
+
<Surface level={level} style={{ padding: 16, gap: 12, flex: 1, borderRadius: 14 }}>
|
|
115
|
+
<Eyebrow>HOST · LEVEL = {level.toUpperCase()}</Eyebrow>
|
|
116
|
+
<Surface pressed style={{ padding: 16, gap: 4 }}>
|
|
117
|
+
<Eyebrow>PRESSED WELL</Eyebrow>
|
|
118
|
+
<OnSurfaceBody title="Sunken panel" sub="one step down + inner recess" />
|
|
119
|
+
</Surface>
|
|
120
|
+
</Surface>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const PressedWells: Story = {
|
|
125
|
+
render: () => (
|
|
126
|
+
<Surface level="background" style={{ flexDirection: 'row', gap: 16, padding: 24 }}>
|
|
127
|
+
{(['base', 'raised', 'overlay'] as SurfaceLevel[]).map((level) => (
|
|
128
|
+
<WellCard key={level} level={level} />
|
|
129
|
+
))}
|
|
130
|
+
</Surface>
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Clamp + nesting — a well in `background` bottoms out at the inset floor
|
|
135
|
+
// (#13100D), and pressing again inside it stays at the floor (no underflow),
|
|
136
|
+
// while a well in a lighter plane still steps down normally.
|
|
137
|
+
export const PressedClampAndNesting: Story = {
|
|
138
|
+
render: () => (
|
|
139
|
+
<View style={{ flexDirection: 'row', gap: 16, padding: 24 }}>
|
|
140
|
+
<Surface level="background" style={{ padding: 16, gap: 12, flex: 1, borderRadius: 14 }}>
|
|
141
|
+
<Eyebrow>HOST · LEVEL = BACKGROUND</Eyebrow>
|
|
142
|
+
<Surface pressed style={{ padding: 16, gap: 10 }}>
|
|
143
|
+
<Eyebrow>PRESSED → INSET FLOOR</Eyebrow>
|
|
144
|
+
<Surface pressed style={{ padding: 12 }}>
|
|
145
|
+
<Eyebrow>PRESSED AGAIN → CLAMPED</Eyebrow>
|
|
146
|
+
</Surface>
|
|
147
|
+
</Surface>
|
|
148
|
+
</Surface>
|
|
149
|
+
<Surface level="raised" style={{ padding: 16, gap: 12, flex: 1, borderRadius: 14 }}>
|
|
150
|
+
<Eyebrow>HOST · LEVEL = RAISED</Eyebrow>
|
|
151
|
+
<Surface pressed style={{ padding: 16, gap: 10 }}>
|
|
152
|
+
<Eyebrow>PRESSED → ELEVATED</Eyebrow>
|
|
153
|
+
<Surface pressed style={{ padding: 12 }}>
|
|
154
|
+
<Eyebrow>PRESSED AGAIN → BASE</Eyebrow>
|
|
155
|
+
</Surface>
|
|
156
|
+
</Surface>
|
|
157
|
+
</Surface>
|
|
158
|
+
</View>
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
|
|
102
162
|
// On-surface colour context: descendant text reads its colour from the Surface
|
|
103
163
|
// via `useOnSurfaceColor` — no hard-coded hex, so it never renders black when the
|
|
104
164
|
// tree mounts as raw RN in the standalone wall SPA.
|
|
@@ -138,3 +198,137 @@ export const LightTheme: Story = {
|
|
|
138
198
|
</View>
|
|
139
199
|
),
|
|
140
200
|
}
|
|
201
|
+
|
|
202
|
+
// ===========================================================================
|
|
203
|
+
// The Surface WORK — composition, context inheritance, and the real wall lockup.
|
|
204
|
+
// The stories above exercise the primitive in isolation; these show it doing the
|
|
205
|
+
// job it was built for (TD-05.12): stacking planes, propagating the on-surface
|
|
206
|
+
// colour context, and backing the migrated shell / rail / stage.
|
|
207
|
+
// ===========================================================================
|
|
208
|
+
|
|
209
|
+
/** An all-caps eyebrow whose colour resolves on whatever Surface encloses it. */
|
|
210
|
+
function Eyebrow({ children }: { children: string }) {
|
|
211
|
+
return (
|
|
212
|
+
<Text
|
|
213
|
+
style={{
|
|
214
|
+
color: useOnSurfaceColor('tertiary'),
|
|
215
|
+
fontSize: 10,
|
|
216
|
+
fontWeight: '700',
|
|
217
|
+
letterSpacing: 1,
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
{children}
|
|
221
|
+
</Text>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Primary + secondary body text, both on-surface — no hard-coded hex. */
|
|
226
|
+
function OnSurfaceBody({ title, sub }: { title: string; sub: string }) {
|
|
227
|
+
return (
|
|
228
|
+
<View style={{ gap: 2 }}>
|
|
229
|
+
<Text style={{ color: useOnSurfaceColor('primary'), fontSize: 14, fontWeight: '600' }}>
|
|
230
|
+
{title}
|
|
231
|
+
</Text>
|
|
232
|
+
<Text style={{ color: useOnSurfaceColor('secondary'), fontSize: 12 }}>{sub}</Text>
|
|
233
|
+
</View>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Nesting + context inheritance — the core mechanic. Each Surface publishes its
|
|
238
|
+
// level; the planes read darkest → lightest as they nest (background < base <
|
|
239
|
+
// elevated < raised). The labels call `useOnSurfaceColor` and stay legible on
|
|
240
|
+
// every plane WITHOUT a hard-coded hex — on-surface text resolves from the mode,
|
|
241
|
+
// so one set of roles reads correctly across all the dark planes.
|
|
242
|
+
export const NestedPlanes: Story = {
|
|
243
|
+
render: () => (
|
|
244
|
+
<Surface level="background" style={{ padding: 20, gap: 14 }}>
|
|
245
|
+
<Eyebrow>LEVEL = BACKGROUND</Eyebrow>
|
|
246
|
+
<Surface level="base" style={{ padding: 16, gap: 14, borderRadius: 14 }}>
|
|
247
|
+
<Eyebrow>LEVEL = BASE</Eyebrow>
|
|
248
|
+
<Surface level="elevated" style={{ padding: 16, gap: 14, borderRadius: 12 }}>
|
|
249
|
+
<Eyebrow>LEVEL = ELEVATED</Eyebrow>
|
|
250
|
+
<Surface level="raised" style={{ padding: 16, gap: 6, borderRadius: 10 }}>
|
|
251
|
+
<Eyebrow>LEVEL = RAISED</Eyebrow>
|
|
252
|
+
<OnSurfaceBody title="On-surface text" sub="resolves from the enclosing Surface" />
|
|
253
|
+
</Surface>
|
|
254
|
+
</Surface>
|
|
255
|
+
</Surface>
|
|
256
|
+
</Surface>
|
|
257
|
+
),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Theme inheritance — a Surface seeds the mode; the nested Surfaces and text with
|
|
261
|
+
// NO explicit `theme` inherit it. The SAME subtree renders on both grounds and
|
|
262
|
+
// stays legible, because on-surface colours resolve from the inherited mode
|
|
263
|
+
// rather than a fixed hex. (A numeric `elevation` Surface inherits mode too.)
|
|
264
|
+
function InheritingCard() {
|
|
265
|
+
return (
|
|
266
|
+
<Surface level="base" style={{ padding: 14, gap: 10, borderRadius: 12 }}>
|
|
267
|
+
<OnSurfaceBody title="Inherited mode" sub="no theme prop on this card" />
|
|
268
|
+
<Surface level="raised" style={{ padding: 12, borderRadius: 8 }}>
|
|
269
|
+
<Eyebrow>NESTED · STILL INHERITS</Eyebrow>
|
|
270
|
+
</Surface>
|
|
271
|
+
</Surface>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const ThemeInheritance: Story = {
|
|
276
|
+
render: () => (
|
|
277
|
+
<View style={{ flexDirection: 'row', gap: 16, padding: 24 }}>
|
|
278
|
+
<Surface theme="dark" level="background" style={{ padding: 16, gap: 10, flex: 1 }}>
|
|
279
|
+
<Eyebrow>THEME = DARK (SEEDS CONTEXT)</Eyebrow>
|
|
280
|
+
<InheritingCard />
|
|
281
|
+
</Surface>
|
|
282
|
+
<Surface theme="light" level="background" style={{ padding: 16, gap: 10, flex: 1 }}>
|
|
283
|
+
<Eyebrow>THEME = LIGHT (SEEDS CONTEXT)</Eyebrow>
|
|
284
|
+
<InheritingCard />
|
|
285
|
+
</Surface>
|
|
286
|
+
</View>
|
|
287
|
+
),
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// The wall lockup, as migrated (DashboardShell / SessionRail / stage). The shell
|
|
291
|
+
// is a `background` plane; the page a `base` plane; the session rail an
|
|
292
|
+
// `elevated` inset; the live stage a `raised` panel. Every eyebrow/label reads
|
|
293
|
+
// on-surface — this composition is what replaced the inline `getSemanticColors`
|
|
294
|
+
// text patches the standalone wall SPA used to carry.
|
|
295
|
+
function RailRow({ name, sets }: { name: string; sets: string }) {
|
|
296
|
+
return (
|
|
297
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
298
|
+
<Text style={{ color: useOnSurfaceColor('primary'), fontSize: 13 }}>{name}</Text>
|
|
299
|
+
<Text style={{ color: useOnSurfaceColor('tertiary'), fontSize: 13 }}>{sets}</Text>
|
|
300
|
+
</View>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export const WallLockup: Story = {
|
|
305
|
+
render: () => (
|
|
306
|
+
<Surface level="background" style={{ flexDirection: 'row', height: 320, padding: 12, gap: 12 }}>
|
|
307
|
+
{/* left nav — flush to the shell edge, on the same background plane */}
|
|
308
|
+
<View style={{ width: 56, alignItems: 'center', paddingTop: 10 }}>
|
|
309
|
+
<Eyebrow>NAV</Eyebrow>
|
|
310
|
+
</View>
|
|
311
|
+
{/* page — a base plane holding the rail + stage */}
|
|
312
|
+
<Surface
|
|
313
|
+
level="base"
|
|
314
|
+
style={{ flex: 1, flexDirection: 'row', padding: 12, gap: 12, borderRadius: 14 }}
|
|
315
|
+
>
|
|
316
|
+
{/* session rail — an elevated inset */}
|
|
317
|
+
<Surface level="elevated" style={{ width: 150, padding: 12, gap: 10, borderRadius: 12 }}>
|
|
318
|
+
<Eyebrow>SESSION</Eyebrow>
|
|
319
|
+
<RailRow name="Bench" sets="3/3" />
|
|
320
|
+
<RailRow name="Cable Row" sets="1/3" />
|
|
321
|
+
<RailRow name="Curl" sets="0/3" />
|
|
322
|
+
</Surface>
|
|
323
|
+
{/* live stage — a raised panel */}
|
|
324
|
+
<Surface
|
|
325
|
+
level="raised"
|
|
326
|
+
style={{ flex: 1, padding: 16, gap: 6, justifyContent: 'center', borderRadius: 12 }}
|
|
327
|
+
>
|
|
328
|
+
<Eyebrow>LIVE</Eyebrow>
|
|
329
|
+
<OnSurfaceBody title="Seated Cable Row" sub="set 2 · 8 reps · 0.42 m/s" />
|
|
330
|
+
</Surface>
|
|
331
|
+
</Surface>
|
|
332
|
+
</Surface>
|
|
333
|
+
),
|
|
334
|
+
}
|