@titan-design/react-ui 0.11.0 → 0.12.1

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.
Files changed (152) hide show
  1. package/dist/bodymap.js +173 -343
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +173 -343
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +27 -22
  6. package/dist/index.d.ts +27 -22
  7. package/dist/index.js +366 -561
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +360 -553
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/pages.js +242 -407
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +242 -407
  14. package/dist/pages.mjs.map +1 -1
  15. package/dist/theme/index.d.mts +250 -1157
  16. package/dist/theme/index.d.ts +250 -1157
  17. package/dist/theme/index.js +234 -408
  18. package/dist/theme/index.js.map +1 -1
  19. package/dist/theme/index.mjs +227 -399
  20. package/dist/theme/index.mjs.map +1 -1
  21. package/dist/theme/tokens-css.js +141 -140
  22. package/dist/theme/tokens-css.js.map +1 -1
  23. package/dist/theme/tokens-css.mjs +141 -140
  24. package/dist/theme/tokens-css.mjs.map +1 -1
  25. package/dist/theme/tokens.d.mts +904 -0
  26. package/dist/theme/tokens.d.ts +904 -0
  27. package/dist/theme/tokens.js +856 -0
  28. package/dist/theme/tokens.js.map +1 -0
  29. package/dist/theme/tokens.mjs +833 -0
  30. package/dist/theme/tokens.mjs.map +1 -0
  31. package/package.json +7 -1
  32. package/src/arch/Architecture.stories.tsx +9 -9
  33. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  34. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +3 -3
  35. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  36. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +3 -3
  37. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +1 -1
  38. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +2 -2
  39. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  40. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +4 -4
  41. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +3 -3
  42. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  43. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -12
  44. package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
  45. package/src/components/custom/Table/Table.tsx +2 -2
  46. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
  47. package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
  48. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  49. package/src/components/custom/Workout/BodyMap.tsx +2 -2
  50. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
  51. package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
  52. package/src/components/custom/Workout/DeviationBar.tsx +3 -2
  53. package/src/components/custom/Workout/ExerciseCard.test.tsx +3 -3
  54. package/src/components/custom/Workout/ExerciseCard.tsx +2 -2
  55. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -4
  56. package/src/components/custom/Workout/InputBar.tsx +2 -2
  57. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
  58. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
  59. package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
  60. package/src/components/custom/Workout/MesoCard.tsx +3 -1
  61. package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
  62. package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
  63. package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
  64. package/src/components/custom/Workout/README.md +1 -1
  65. package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
  66. package/src/components/custom/Workout/RestTimer.tsx +2 -2
  67. package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
  68. package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
  69. package/src/components/custom/Workout/SessionHeader.tsx +1 -1
  70. package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
  71. package/src/components/custom/Workout/SessionRail.tsx +13 -5
  72. package/src/components/custom/Workout/SetBar.tsx +3 -3
  73. package/src/components/custom/Workout/SetRow.test.tsx +6 -3
  74. package/src/components/custom/Workout/StatusDot.tsx +5 -4
  75. package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
  76. package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
  77. package/src/components/custom/Workout/VelocityStrip.test.tsx +5 -2
  78. package/src/components/custom/Workout/VelocityStrip.tsx +4 -4
  79. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
  80. package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
  81. package/src/components/custom/Workout/WeightBadge.tsx +2 -1
  82. package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
  83. package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
  84. package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
  85. package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
  86. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  87. package/src/components/custom/charts/SetBarChart.tsx +3 -3
  88. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  89. package/src/components/custom/stepper/Stepper.tsx +3 -3
  90. package/src/components/shell/DeviceMenu.tsx +2 -2
  91. package/src/components/shell/README.md +1 -1
  92. package/src/components/shell/SessionRailSpecimen.stories.tsx +1 -1
  93. package/src/components/shell/SideNav.tsx +1 -1
  94. package/src/components/shell/TopBar.tsx +1 -1
  95. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  96. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  97. package/src/components/ui/avatar/Avatar.tsx +2 -2
  98. package/src/components/ui/badge/Badge.tsx +2 -2
  99. package/src/components/ui/card/Card.tsx +3 -3
  100. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  101. package/src/components/ui/collapse/Collapse.tsx +1 -1
  102. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  103. package/src/components/ui/drawer/Drawer.tsx +3 -3
  104. package/src/components/ui/form-field/FormField.tsx +1 -1
  105. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  106. package/src/components/ui/menu/Menu.tsx +2 -2
  107. package/src/components/ui/pill/Pill.tsx +2 -2
  108. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  109. package/src/components/ui/popover/Popover.tsx +1 -1
  110. package/src/components/ui/progress/Progress.tsx +3 -3
  111. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  112. package/src/components/ui/radio/Radio.tsx +4 -4
  113. package/src/components/ui/select/Select.tsx +3 -3
  114. package/src/components/ui/surface/Surface.stories.tsx +3 -3
  115. package/src/components/ui/surface/Surface.test.tsx +32 -19
  116. package/src/components/ui/surface/Surface.tsx +3 -3
  117. package/src/components/ui/surface/SurfaceContext.ts +19 -29
  118. package/src/components/ui/surface/surface.contract.test.ts +58 -45
  119. package/src/components/ui/switch/Switch.tsx +1 -1
  120. package/src/components/ui/tabs/Tabs.tsx +2 -2
  121. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
  122. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
  123. package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
  124. package/src/stories/PresetShowcase.stories.tsx +21 -21
  125. package/src/theme/ColorPalettes.stories.tsx +41 -38
  126. package/src/theme/ColorPrimitives.stories.tsx +89 -41
  127. package/src/theme/Depth.stories.tsx +312 -0
  128. package/src/theme/DepthCalibration.stories.tsx +655 -0
  129. package/src/theme/ThemeProvider.tsx +1 -1
  130. package/src/theme/barrel.ts +7 -14
  131. package/src/theme/color-stories.coverage.test.ts +15 -20
  132. package/src/theme/color-story-kit.tsx +1 -1
  133. package/src/theme/color-utils.ts +197 -0
  134. package/src/theme/config.ts +0 -8
  135. package/src/theme/depth-calibration.test.tsx +97 -0
  136. package/src/theme/elevation.test.ts +62 -1
  137. package/src/theme/elevation.ts +34 -8
  138. package/src/theme/global.css +47 -53
  139. package/src/theme/grey-ramp.test.ts +166 -0
  140. package/src/theme/materials.test.ts +106 -0
  141. package/src/theme/materials.ts +211 -0
  142. package/src/theme/presets/audiobook.ts +4 -4
  143. package/src/theme/presets/types.ts +3 -3
  144. package/src/theme/semantic-contrast.test.ts +83 -0
  145. package/src/theme/token-export-guard.test.ts +104 -0
  146. package/src/theme/tokens/primitives.ts +63 -116
  147. package/src/theme/tokens/semantic.ts +130 -82
  148. package/src/utils/colors.ts +1 -1
  149. package/tailwind.config.js +2 -5
  150. package/src/theme/bar-paper.ts +0 -59
  151. package/src/theme/shadows.stories.tsx +0 -523
  152. package/src/theme/shadows.ts +0 -439
@@ -1,523 +0,0 @@
1
- import React from 'react'
2
- import type { Meta, StoryObj } from '@storybook/react-vite'
3
- import { View, Text } from 'react-native'
4
- import {
5
- neumorphicShadows,
6
- getHoverColors,
7
- type ShadowIntensity,
8
- } from './shadows'
9
-
10
- const meta: Meta = {
11
- title: 'Foundations/Shadows',
12
- }
13
-
14
- export default meta
15
- type Story = StoryObj
16
-
17
- // Sample button component to demonstrate shadows
18
- interface ShadowBoxProps {
19
- label: string
20
- style: any
21
- bgColor?: string
22
- textColor?: string
23
- }
24
-
25
- function ShadowBox({ label, style, bgColor, textColor }: ShadowBoxProps) {
26
- return (
27
- <View
28
- className={bgColor ? undefined : 'bg-surface-elevated'}
29
- style={[
30
- bgColor ? { backgroundColor: bgColor } : undefined,
31
- {
32
- paddingVertical: 12,
33
- paddingHorizontal: 20,
34
- borderRadius: 8,
35
- minWidth: 120,
36
- alignItems: 'center',
37
- },
38
- style,
39
- ]}
40
- >
41
- <Text className={textColor ? undefined : 'text-text-primary font-semibold text-sm'} style={textColor ? { color: textColor, fontWeight: '600', fontSize: 14 } : undefined}>
42
- {label}
43
- </Text>
44
- </View>
45
- )
46
- }
47
-
48
- // Section header
49
- function SectionHeader({ children }: { children: string }) {
50
- return (
51
- <Text className="text-text-secondary text-sm font-semibold mb-3 mt-6">
52
- {children}
53
- </Text>
54
- )
55
- }
56
-
57
- // Subsection header
58
- function SubHeader({ children }: { children: string }) {
59
- return (
60
- <Text className="text-text-secondary text-xs mb-2 mt-4">
61
- {children}
62
- </Text>
63
- )
64
- }
65
-
66
- export const NeumorphicShadows: Story = {
67
- render: () => (
68
- <View className="p-6">
69
- <Text className="text-text-primary text-xl font-bold mb-2">
70
- Neumorphic Shadows
71
- </Text>
72
- <Text className="text-text-secondary text-sm mb-6">
73
- Raised = floating above surface, Pressed = pushed into surface
74
- </Text>
75
-
76
- {/* Charcoal surface */}
77
- <View className="bg-surface-elevated p-6 rounded-xl mb-6">
78
- <Text className="text-text-secondary text-xs mb-4">
79
- Surface: Charcoal (#2C2C2C)
80
- </Text>
81
-
82
- <SubHeader>Raised (element floats above surface)</SubHeader>
83
- <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap' }}>
84
- <ShadowBox label="Subtle" style={neumorphicShadows.charcoal.raised.subtle} />
85
- <ShadowBox label="Medium" style={neumorphicShadows.charcoal.raised.medium} />
86
- <ShadowBox label="Strong" style={neumorphicShadows.charcoal.raised.strong} />
87
- </View>
88
-
89
- <SubHeader>Pressed (element pushed into surface)</SubHeader>
90
- <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap' }}>
91
- <ShadowBox label="Subtle" style={neumorphicShadows.charcoal.pressed.subtle} />
92
- <ShadowBox label="Medium" style={neumorphicShadows.charcoal.pressed.medium} />
93
- <ShadowBox label="Strong" style={neumorphicShadows.charcoal.pressed.strong} />
94
- </View>
95
- </View>
96
-
97
- {/* Code example */}
98
- <View className="bg-surface-base p-4 rounded-lg">
99
- <Text className="text-text-secondary text-[11px] font-mono">
100
- {`import { neumorphicShadows } from '@titan-design/react-ui'
101
-
102
- // Apply to a View
103
- <View style={neumorphicShadows.charcoal.raised.subtle} />
104
- <View style={neumorphicShadows.charcoal.pressed.medium} />
105
-
106
- /* Raised - soft shadow + sharp rim light */
107
- box-shadow:
108
- 2px 2px 4px rgba(0,0,0,0.5),
109
- -2px -2px 1px rgba(255,255,255,0.12);
110
-
111
- /* Pressed - inset shadow + thin rim light */
112
- box-shadow:
113
- inset 2px 2px 4px rgba(0,0,0,0.6),
114
- inset -1px -1px 1px rgba(255,255,255,0.12);`}
115
- </Text>
116
- </View>
117
- </View>
118
- ),
119
- decorators: [
120
- (Story) => (
121
- <View className="bg-background-base min-h-screen p-4">
122
- <Story />
123
- </View>
124
- ),
125
- ],
126
- }
127
-
128
- export const IntensityLevels: Story = {
129
- render: () => (
130
- <View className="p-6">
131
- <Text className="text-text-primary text-xl font-bold mb-2">
132
- Shadow Intensity Levels
133
- </Text>
134
- <Text className="text-text-secondary text-sm mb-6">
135
- subtle (1px/2px) → medium (2px/4px) → strong (2px/5px)
136
- </Text>
137
-
138
- <View className="bg-surface-elevated p-6 rounded-xl">
139
- <SubHeader>Raised Intensities</SubHeader>
140
- <View style={{ flexDirection: 'row', gap: 24, marginBottom: 24 }}>
141
- <View style={{ alignItems: 'center', gap: 8 }}>
142
- <ShadowBox label="Subtle" style={neumorphicShadows.charcoal.raised.subtle} />
143
- <Text className="text-text-secondary text-[10px]">1px / 2px blur</Text>
144
- </View>
145
- <View style={{ alignItems: 'center', gap: 8 }}>
146
- <ShadowBox label="Medium" style={neumorphicShadows.charcoal.raised.medium} />
147
- <Text className="text-text-secondary text-[10px]">2px / 4px blur</Text>
148
- </View>
149
- <View style={{ alignItems: 'center', gap: 8 }}>
150
- <ShadowBox label="Strong" style={neumorphicShadows.charcoal.raised.strong} />
151
- <Text className="text-text-secondary text-[10px]">2px / 5px blur</Text>
152
- </View>
153
- </View>
154
-
155
- <SubHeader>Pressed Intensities (thin 1px rim highlight)</SubHeader>
156
- <View style={{ flexDirection: 'row', gap: 24 }}>
157
- <View style={{ alignItems: 'center', gap: 8 }}>
158
- <ShadowBox label="Subtle" style={neumorphicShadows.charcoal.pressed.subtle} />
159
- <Text className="text-text-secondary text-[10px]">1px / 2px blur</Text>
160
- </View>
161
- <View style={{ alignItems: 'center', gap: 8 }}>
162
- <ShadowBox label="Medium" style={neumorphicShadows.charcoal.pressed.medium} />
163
- <Text className="text-text-secondary text-[10px]">2px / 4px blur</Text>
164
- </View>
165
- <View style={{ alignItems: 'center', gap: 8 }}>
166
- <ShadowBox label="Strong" style={neumorphicShadows.charcoal.pressed.strong} />
167
- <Text className="text-text-secondary text-[10px]">2px / 5px blur</Text>
168
- </View>
169
- </View>
170
- </View>
171
- </View>
172
- ),
173
- decorators: [
174
- (Story) => (
175
- <View className="bg-background-base min-h-screen p-4">
176
- <Story />
177
- </View>
178
- ),
179
- ],
180
- }
181
-
182
- // Interactive button using color math - adapts to any background brightness
183
- interface ColorMathButtonProps {
184
- surface: 'charcoal' | 'neutralDark' | 'neutralLight'
185
- bgColor: string
186
- textColor?: string
187
- }
188
-
189
- function ColorMathButton({ surface, bgColor, textColor = '#fff' }: ColorMathButtonProps) {
190
- const [isActive, setIsActive] = React.useState(false)
191
- const [isHovered, setIsHovered] = React.useState(false)
192
-
193
- const shadows = neumorphicShadows[surface]
194
- const hoverColors = getHoverColors(bgColor, 'medium')
195
-
196
- const getShadowStyle = () => {
197
- if (isActive) {
198
- return isHovered ? shadows.pressedHover.medium : shadows.pressed.medium
199
- }
200
- return isHovered ? shadows.raisedHover.medium : shadows.raised.medium
201
- }
202
-
203
- // Use color math for hover - automatically adapts to brightness!
204
- const getBgColor = () => {
205
- if (!isHovered) return bgColor
206
- return isActive ? hoverColors.pressed : hoverColors.raised
207
- }
208
-
209
- return (
210
- <View
211
- // @ts-expect-error - web events
212
- onMouseEnter={() => setIsHovered(true)}
213
- onMouseLeave={() => setIsHovered(false)}
214
- onClick={() => setIsActive(!isActive)}
215
- style={[
216
- {
217
- backgroundColor: getBgColor(),
218
- paddingVertical: 12,
219
- paddingHorizontal: 20,
220
- borderRadius: 8,
221
- minWidth: 100,
222
- alignItems: 'center',
223
- cursor: 'pointer',
224
- transition: 'box-shadow 0.2s ease, background-color 0.15s ease',
225
- } as any,
226
- getShadowStyle(),
227
- ]}
228
- >
229
- <Text style={{ color: textColor, fontWeight: '600', fontSize: 14 }}>
230
- {isActive ? 'Active' : 'Inactive'}
231
- </Text>
232
- </View>
233
- )
234
- }
235
-
236
-
237
- export const SurfaceVariants: Story = {
238
- render: function Render() {
239
- // Show calculated hover colors for each surface
240
- const charcoalHover = getHoverColors('#3C3C3C', 'medium')
241
- const neutralDarkHover = getHoverColors('#374151', 'medium')
242
- const neutralLightHover = getHoverColors('#E5E7EB', 'medium')
243
-
244
- return (
245
- <View className="p-6">
246
- <Text className="text-text-primary text-xl font-bold mb-2">
247
- Surface Variants with Color Math
248
- </Text>
249
- <Text className="text-text-secondary text-sm mb-6">
250
- Hover colors calculated using HSV - just pass any background color!
251
- </Text>
252
-
253
- {/* Charcoal Surface */}
254
- <View className="bg-surface-elevated p-6 rounded-xl mb-4">
255
- <Text className="text-text-secondary text-xs mb-2">Charcoal Surface (#2C2C2C)</Text>
256
- <Text className="text-text-secondary text-[10px] mb-4">
257
- Hover: raised → {charcoalHover.raised}, pressed → {charcoalHover.pressed}
258
- </Text>
259
- <View style={{ flexDirection: 'row', gap: 16 }}>
260
- <ColorMathButton surface="charcoal" bgColor="#3C3C3C" />
261
- <ColorMathButton surface="charcoal" bgColor="#3C3C3C" />
262
- </View>
263
- </View>
264
-
265
- {/* Neutral Dark Surface */}
266
- <View className="bg-surface-elevated p-6 rounded-xl mb-4">
267
- <Text className="text-text-secondary text-xs mb-2">Neutral Dark Surface (#1F2937)</Text>
268
- <Text className="text-text-secondary text-[10px] mb-4">
269
- Hover: raised → {neutralDarkHover.raised}, pressed → {neutralDarkHover.pressed}
270
- </Text>
271
- <View style={{ flexDirection: 'row', gap: 16 }}>
272
- <ColorMathButton surface="neutralDark" bgColor="#374151" />
273
- <ColorMathButton surface="neutralDark" bgColor="#374151" />
274
- </View>
275
- </View>
276
-
277
- {/* Neutral Light Surface */}
278
- <View className="bg-surface-elevated p-6 rounded-xl">
279
- <Text className="text-text-secondary text-xs mb-2">Neutral Light Surface (#E5E7EB)</Text>
280
- <Text className="text-text-secondary text-[10px] mb-4">
281
- Hover: raised → {neutralLightHover.raised}, pressed → {neutralLightHover.pressed}
282
- </Text>
283
- <View style={{ flexDirection: 'row', gap: 16 }}>
284
- <ColorMathButton surface="neutralLight" bgColor="#E5E7EB" textColor="#374151" />
285
- <ColorMathButton surface="neutralLight" bgColor="#E5E7EB" textColor="#374151" />
286
- </View>
287
- </View>
288
-
289
- {/* Code example */}
290
- <View className="bg-surface-base p-4 rounded-lg mt-4">
291
- <Text className="text-text-secondary text-[11px] font-mono">
292
- {`import { getHoverColors, lighten, darken } from '@titan-design/react-ui'
293
-
294
- // Get hover colors for a button background
295
- const hover = getHoverColors('#3C3C3C', 'medium')
296
- // → { raised: '#414141', pressed: '#373737' }
297
-
298
- // Or use individual functions (HSV-based)
299
- lighten('#3C3C3C', 0.02) // Increase V by 0.02
300
- darken('#3C3C3C', 0.02) // Decrease V by 0.02`}
301
- </Text>
302
- </View>
303
- </View>
304
- )
305
- },
306
- decorators: [
307
- (Story) => (
308
- <View className="bg-background-base min-h-screen p-4">
309
- <Story />
310
- </View>
311
- ),
312
- ],
313
- }
314
-
315
- export const HoverStates: Story = {
316
- render: () => (
317
- <View className="p-6">
318
- <Text className="text-text-primary text-xl font-bold mb-2">
319
- Hover State Shadows
320
- </Text>
321
- <Text className="text-text-secondary text-sm mb-6">
322
- Hover reduces shadow depth to indicate interactivity (removes rim light, halves offset/blur)
323
- </Text>
324
-
325
- <View className="bg-surface-elevated p-6 rounded-xl">
326
- <SubHeader>Raised → Raised Hover (medium intensity)</SubHeader>
327
- <View style={{ flexDirection: 'row', gap: 24, marginBottom: 24 }}>
328
- <View style={{ alignItems: 'center', gap: 8 }}>
329
- <ShadowBox label="Raised" style={neumorphicShadows.charcoal.raised.medium} />
330
- <Text className="text-text-secondary text-[10px]">Normal</Text>
331
- </View>
332
- <View style={{ alignItems: 'center', gap: 8 }}>
333
- <ShadowBox label="Raised Hover" style={neumorphicShadows.charcoal.raisedHover.medium} />
334
- <Text className="text-text-secondary text-[10px]">Hover</Text>
335
- </View>
336
- <View style={{ alignItems: 'center', gap: 8 }}>
337
- <ShadowBox label="Flat" style={neumorphicShadows.charcoal.flat} />
338
- <Text className="text-text-secondary text-[10px]">Alt: Flat</Text>
339
- </View>
340
- </View>
341
-
342
- <SubHeader>Pressed → Pressed Hover (medium intensity)</SubHeader>
343
- <View style={{ flexDirection: 'row', gap: 24, marginBottom: 24 }}>
344
- <View style={{ alignItems: 'center', gap: 8 }}>
345
- <ShadowBox label="Pressed" style={neumorphicShadows.charcoal.pressed.medium} />
346
- <Text className="text-text-secondary text-[10px]">Normal</Text>
347
- </View>
348
- <View style={{ alignItems: 'center', gap: 8 }}>
349
- <ShadowBox label="Pressed Hover" style={neumorphicShadows.charcoal.pressedHover.medium} />
350
- <Text className="text-text-secondary text-[10px]">Hover</Text>
351
- </View>
352
- <View style={{ alignItems: 'center', gap: 8 }}>
353
- <ShadowBox label="Flat" style={neumorphicShadows.charcoal.flat} />
354
- <Text className="text-text-secondary text-[10px]">Alt: Flat</Text>
355
- </View>
356
- </View>
357
-
358
- <SubHeader>All Intensities - Raised Hover</SubHeader>
359
- <View style={{ flexDirection: 'row', gap: 16, marginBottom: 24 }}>
360
- <View style={{ alignItems: 'center', gap: 8 }}>
361
- <ShadowBox label="Subtle" style={neumorphicShadows.charcoal.raisedHover.subtle} />
362
- </View>
363
- <View style={{ alignItems: 'center', gap: 8 }}>
364
- <ShadowBox label="Medium" style={neumorphicShadows.charcoal.raisedHover.medium} />
365
- </View>
366
- <View style={{ alignItems: 'center', gap: 8 }}>
367
- <ShadowBox label="Strong" style={neumorphicShadows.charcoal.raisedHover.strong} />
368
- </View>
369
- </View>
370
-
371
- <SubHeader>All Intensities - Pressed Hover</SubHeader>
372
- <View style={{ flexDirection: 'row', gap: 16 }}>
373
- <View style={{ alignItems: 'center', gap: 8 }}>
374
- <ShadowBox label="Subtle" style={neumorphicShadows.charcoal.pressedHover.subtle} />
375
- </View>
376
- <View style={{ alignItems: 'center', gap: 8 }}>
377
- <ShadowBox label="Medium" style={neumorphicShadows.charcoal.pressedHover.medium} />
378
- </View>
379
- <View style={{ alignItems: 'center', gap: 8 }}>
380
- <ShadowBox label="Strong" style={neumorphicShadows.charcoal.pressedHover.strong} />
381
- </View>
382
- </View>
383
- </View>
384
-
385
- <View className="bg-surface-base p-4 rounded-lg mt-4">
386
- <Text className="text-text-secondary text-[11px] font-mono">
387
- {`/* Hover shadows - reduced depth, rim light preserved */
388
-
389
- /* Raised Hover (medium) - rim offset stays same to prevent "shrink" */
390
- box-shadow:
391
- 1px 1px 2px rgba(0,0,0,0.5), /* reduced dark shadow */
392
- -2px -2px 1px rgba(255,255,255,0.12); /* same rim offset */
393
-
394
- /* Pressed Hover (medium) */
395
- box-shadow:
396
- inset 1px 1px 2px rgba(0,0,0,0.6), /* reduced dark shadow */
397
- inset -1px -1px 1px rgba(255,255,255,0.12); /* same rim offset */
398
-
399
- /* Alternative: Flat (no shadow) */
400
- box-shadow: none;`}
401
- </Text>
402
- </View>
403
- </View>
404
- ),
405
- decorators: [
406
- (Story) => (
407
- <View className="bg-background-base min-h-screen p-4">
408
- <Story />
409
- </View>
410
- ),
411
- ],
412
- }
413
-
414
- export const InteractiveDemo: Story = {
415
- render: function Render() {
416
- const [activeStates, setActiveStates] = React.useState({
417
- subtle: false,
418
- medium: false,
419
- strong: false,
420
- })
421
- const [hoveredStates, setHoveredStates] = React.useState({
422
- subtle: false,
423
- medium: false,
424
- strong: false,
425
- })
426
-
427
- const intensities: ShadowIntensity[] = ['subtle', 'medium', 'strong']
428
- const bgColor = '#3C3C3C'
429
- const hoverColors = getHoverColors(bgColor, 'medium')
430
-
431
- const getShadowStyle = (intensity: ShadowIntensity) => {
432
- const isActive = activeStates[intensity]
433
- const isHovered = hoveredStates[intensity]
434
-
435
- if (isActive) {
436
- return isHovered
437
- ? neumorphicShadows.charcoal.pressedHover[intensity]
438
- : neumorphicShadows.charcoal.pressed[intensity]
439
- }
440
- return isHovered
441
- ? neumorphicShadows.charcoal.raisedHover[intensity]
442
- : neumorphicShadows.charcoal.raised[intensity]
443
- }
444
-
445
- const getBgColor = (intensity: ShadowIntensity) => {
446
- const isActive = activeStates[intensity]
447
- const isHovered = hoveredStates[intensity]
448
- if (!isHovered) return bgColor
449
- return isActive ? hoverColors.pressed : hoverColors.raised
450
- }
451
-
452
- const getStateLabel = (intensity: ShadowIntensity) => {
453
- const isActive = activeStates[intensity]
454
- const isHovered = hoveredStates[intensity]
455
- if (isHovered) return isActive ? 'Pressed + Hover' : 'Raised + Hover'
456
- return isActive ? 'Pressed' : 'Raised'
457
- }
458
-
459
- return (
460
- <View className="p-6">
461
- <Text className="text-text-primary text-xl font-bold mb-2">
462
- Interactive Neumorphic Buttons
463
- </Text>
464
- <Text className="text-text-secondary text-sm mb-6">
465
- Click to toggle. Hover colors calculated via color math (adapts to brightness).
466
- </Text>
467
-
468
- <View className="bg-surface-elevated p-8 rounded-xl">
469
- <View className="flex-row gap-8 justify-center">
470
- {intensities.map((intensity) => (
471
- <View key={intensity} className="items-center gap-3">
472
- <Text className="text-text-secondary text-xs capitalize">
473
- {intensity}
474
- </Text>
475
- <View
476
- // @ts-expect-error - web events
477
- onMouseEnter={() => setHoveredStates(s => ({ ...s, [intensity]: true }))}
478
- onMouseLeave={() => setHoveredStates(s => ({ ...s, [intensity]: false }))}
479
- onClick={() => setActiveStates(s => ({ ...s, [intensity]: !s[intensity] }))}
480
- style={[
481
- {
482
- backgroundColor: getBgColor(intensity),
483
- paddingVertical: 16,
484
- paddingHorizontal: 24,
485
- borderRadius: 12,
486
- cursor: 'pointer',
487
- transition: 'box-shadow 0.2s ease, background-color 0.15s ease, transform 0.15s ease',
488
- transform: activeStates[intensity] ? 'scale(0.98)' : 'scale(1)',
489
- minWidth: 120,
490
- alignItems: 'center',
491
- } as any,
492
- getShadowStyle(intensity),
493
- ]}
494
- >
495
- <Text className="text-text-primary font-bold text-sm">
496
- {activeStates[intensity] ? 'Active' : 'Inactive'}
497
- </Text>
498
- </View>
499
- <Text className="text-text-secondary text-[10px]">
500
- {getStateLabel(intensity)}
501
- </Text>
502
- </View>
503
- ))}
504
- </View>
505
-
506
- <View className="mt-6 items-center">
507
- <Text className="text-text-secondary text-[11px]">
508
- {bgColor} → raised: {hoverColors.raised} | pressed: {hoverColors.pressed}
509
- </Text>
510
- </View>
511
- </View>
512
- </View>
513
- )
514
- },
515
- decorators: [
516
- (Story) => (
517
- <View className="bg-background-base min-h-screen p-4">
518
- <Story />
519
- </View>
520
- ),
521
- ],
522
- }
523
-