@titan-design/react-ui 0.6.0 → 0.7.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.
Files changed (248) hide show
  1. package/dist/bodymap.js +12 -7
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +12 -7
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +855 -124
  6. package/dist/index.d.ts +855 -124
  7. package/dist/index.js +2276 -801
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2230 -802
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1255 -798
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1255 -798
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +16 -82
  20. package/dist/theme/index.d.ts +16 -82
  21. package/dist/theme/index.js +13 -115
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +13 -115
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +14 -6
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +14 -6
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/arch/Architecture.stories.tsx +870 -0
  31. package/src/arch/annotations.json +48 -0
  32. package/src/arch/arch-graph.json +4301 -0
  33. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  34. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  35. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  36. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  37. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  38. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  39. package/src/components/custom/Table/Table.stories.tsx +1 -1
  40. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  41. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  42. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  43. package/src/components/custom/TimerReadout/index.ts +1 -0
  44. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  45. package/src/components/custom/Typography/Typography.stories.tsx +1 -1
  46. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  47. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  48. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
  49. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  50. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  51. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  52. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  53. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  54. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  55. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  56. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  57. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  58. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  59. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  60. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  61. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  62. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  63. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
  64. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  65. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  66. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  67. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  68. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  69. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  70. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  71. package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
  72. package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
  73. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  74. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  75. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  76. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  77. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  78. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  79. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  80. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  81. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  82. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  83. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  84. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  85. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  86. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  87. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  88. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  89. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
  90. package/src/components/custom/Workout/README.md +146 -4
  91. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  92. package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
  93. package/src/components/custom/Workout/RestTimer.tsx +15 -10
  94. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  95. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  96. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  97. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  98. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  99. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  100. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  101. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  102. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  103. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  104. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  105. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  106. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  107. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  108. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  109. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  110. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  111. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  112. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  113. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  114. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  115. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  116. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  117. package/src/components/custom/Workout/SetBar.tsx +171 -0
  118. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  119. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  120. package/src/components/custom/Workout/SetRow.tsx +169 -232
  121. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  122. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  123. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  124. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  125. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  126. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  127. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  128. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  129. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  130. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  131. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  132. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  133. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  134. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  135. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  136. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  137. package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
  138. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  139. package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
  140. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  141. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  142. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  143. package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
  144. package/src/components/custom/Workout/VelocityStrip.test.tsx +376 -141
  145. package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
  146. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  147. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  148. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  149. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  150. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  151. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  152. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  153. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  154. package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
  155. package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
  156. package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
  157. package/src/components/custom/Workout/index.ts +55 -2
  158. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  159. package/src/components/custom/Workout/metricText.tsx +34 -0
  160. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  161. package/src/components/custom/Workout/paceTone.ts +27 -0
  162. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  163. package/src/components/custom/index.ts +1 -0
  164. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  165. package/src/components/icons/icons.stories.tsx +67 -2
  166. package/src/components/icons/icons.test.tsx +32 -2
  167. package/src/components/icons/icons.tsx +108 -0
  168. package/src/components/shell/BrandLockup.stories.tsx +3 -3
  169. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  170. package/src/components/shell/DashboardShell.test.tsx +42 -0
  171. package/src/components/shell/DashboardShell.tsx +74 -0
  172. package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
  173. package/src/components/shell/DeviceMenu.stories.tsx +5 -5
  174. package/src/components/shell/DeviceRow.stories.tsx +3 -3
  175. package/src/components/shell/NavItem.stories.tsx +52 -0
  176. package/src/components/shell/NavItem.test.tsx +35 -0
  177. package/src/components/shell/NavItem.tsx +75 -0
  178. package/src/components/shell/README.md +30 -0
  179. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  180. package/src/components/shell/SessionStatePill.stories.tsx +3 -3
  181. package/src/components/shell/SessionStatePill.tsx +1 -1
  182. package/src/components/shell/SideNav.stories.tsx +81 -0
  183. package/src/components/shell/SideNav.test.tsx +51 -0
  184. package/src/components/shell/SideNav.tsx +74 -0
  185. package/src/components/shell/TopBar.stories.tsx +5 -5
  186. package/src/components/shell/index.ts +5 -0
  187. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  188. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  189. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  190. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  191. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  192. package/src/components/ui/button/Button.stories.tsx +1 -1
  193. package/src/components/ui/card/Card.stories.tsx +1 -1
  194. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  195. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  196. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  197. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  198. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  199. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  200. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  201. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  202. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  203. package/src/components/ui/index.ts +1 -0
  204. package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
  205. package/src/components/ui/indicator/Indicator.test.tsx +1 -0
  206. package/src/components/ui/indicator/Indicator.tsx +6 -1
  207. package/src/components/ui/input/Input.stories.tsx +1 -1
  208. package/src/components/ui/link/Link.stories.tsx +1 -1
  209. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  210. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  211. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  212. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  213. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  214. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  215. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  216. package/src/components/ui/section/Section.stories.tsx +1 -1
  217. package/src/components/ui/select/Select.stories.tsx +1 -1
  218. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  219. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  220. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  221. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  222. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  223. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  224. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  225. package/src/components/ui/tile/Tile.test.tsx +48 -0
  226. package/src/components/ui/tile/Tile.tsx +64 -0
  227. package/src/components/ui/tile/index.ts +2 -0
  228. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  229. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  230. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  231. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  232. package/src/hooks/index.ts +2 -0
  233. package/src/hooks/useTimer.test.ts +150 -0
  234. package/src/hooks/useTimer.ts +77 -0
  235. package/src/index.ts +3 -0
  236. package/src/pages.ts +1 -1
  237. package/src/stories/PresetShowcase.stories.tsx +1 -1
  238. package/src/stories/ThemePresets.stories.tsx +1 -1
  239. package/src/theme/ColorSystem.stories.tsx +1 -1
  240. package/src/theme/Colors.stories.tsx +5 -8
  241. package/src/theme/config.ts +4 -0
  242. package/src/theme/elevation.stories.tsx +1 -1
  243. package/src/theme/global.css +5 -1
  244. package/src/theme/shadows.stories.tsx +1 -1
  245. package/src/theme/tokens/primitives.ts +11 -107
  246. package/src/theme/tokens/semantic.ts +8 -0
  247. package/src/theme/workout-tokens.ts +11 -7
  248. package/tailwind.config.js +5 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * SHEET · Session Rail — set-type encodings (R&D). Throwaway specimen at real rail width, on real tokens.
3
+ * Explores the cases the flat `sets × reps @ load` strip doesn't model:
4
+ * 1. Variable rep-range — range-max segments; the variable zone (floor→max) upcoming part gets its OWN color.
5
+ * 2. Drop set — ONE bar, sub-loads separated by thin NOTCHES (< the between-set gap); reps velocity-colored.
6
+ * 3. Myo-rep / rest-pause — activation chunk + mini-clusters separated by small cluster gaps.
7
+ * Gap hierarchy: butted reps (0) < notch (2, drop sub-load) < cluster gap (3, myo rest-pause) < SET_GAP (5, sets).
8
+ * Hardens into a SetStrip model extension (SetState variants: range/drop/myo) — a post-harden follow-up.
9
+ */
10
+ import type { Meta, StoryObj } from '@storybook/react-vite'
11
+ import { Fragment } from 'react'
12
+ import { primitiveColors, primitiveRamps as ramp } from '../../../theme/tokens/primitives'
13
+ import { GREY, RAIL_W, SET_GAP, T_PRIMARY, T_SECONDARY, T_TERTIARY, INSET, velColor, reps, Page, sectionTitle } from './setHeadingKit'
14
+
15
+ // ---- variable-zone (floor→max) upcoming color — real ramp candidates (no invented hex)
16
+ const VAR_GREY = primitiveColors.charcoal[200] // #3C3C3C — lighter charcoal (the paler-grey reference)
17
+ const VAR_BLUE_800 = ramp.blue[800] // #14407E — clearly blue, a touch brighter
18
+ const VAR_BLUE_900 = ramp.blue[900] // #112C5A — dark muted navy, recessive
19
+ const VAR_CYAN_900 = ramp.cyan[900] // #0B3149 — teal alt, sidesteps info-blue adjacency
20
+ const NOTCH = 2 // drop sub-load divider
21
+ const CLUSTER_GAP = 3 // myo rest-pause divider
22
+
23
+ type Chunk = { colors: string[]; gapAfter?: number }
24
+ function Bar({ chunks, h }: { chunks: Chunk[]; h: number }) {
25
+ return (
26
+ <div style={{ display: 'flex', width: '100%', height: h, borderRadius: 2, overflow: 'hidden' }}>
27
+ {chunks.map((ch, ci) => (
28
+ <Fragment key={ci}>
29
+ {ch.colors.map((c, i) => (
30
+ <div key={i} style={{ flex: 1, minWidth: 0, height: '100%', background: c }} />
31
+ ))}
32
+ {ci < chunks.length - 1 && <div style={{ flex: `0 0 ${ch.gapAfter ?? NOTCH}px`, height: '100%', background: INSET }} />}
33
+ </Fragment>
34
+ ))}
35
+ </div>
36
+ )
37
+ }
38
+
39
+ // ---- 1. variable rep-range: `max` segments; committed 0..floor, variable floor..max; variable-todo gets VAR color
40
+ function rangeColors(floor: number, max: number, doneVels: number[], varColor: string): string[] {
41
+ return Array.from({ length: max }, (_, i) => {
42
+ if (i < doneVels.length) return velColor(doneVels[i])
43
+ return i < floor ? GREY : varColor
44
+ })
45
+ }
46
+ function RangeBar({ floor, max, done, varColor, h }: { floor: number; max: number; done: number[]; varColor: string; h: number }) {
47
+ return <Bar chunks={[{ colors: rangeColors(floor, max, done, varColor) }]} h={h} />
48
+ }
49
+
50
+ // ---- 2. drop set: one bar, sub-loads butted internally, separated by thin notches
51
+ function DropBar({ subloads, h }: { subloads: number[][]; h: number }) {
52
+ const chunks: Chunk[] = subloads.map((vels, i) => ({ colors: vels.map(velColor), gapAfter: NOTCH, ...(i === subloads.length - 1 ? { gapAfter: undefined } : {}) }))
53
+ return <Bar chunks={chunks} h={h} />
54
+ }
55
+
56
+ // ---- 3. myo-rep: activation chunk + mini-clusters, separated by cluster gaps
57
+ function MyoBar({ activation, clusters, h }: { activation: number[]; clusters: number[][]; h: number }) {
58
+ const chunks: Chunk[] = [{ colors: activation.map(velColor), gapAfter: CLUSTER_GAP }, ...clusters.map((c) => ({ colors: c.map(velColor), gapAfter: CLUSTER_GAP }))]
59
+ return <Bar chunks={chunks} h={h} />
60
+ }
61
+
62
+ // ---- 3b. UPCOMING myo, unknown length: grey activation (expected) + a blue "clusters-to-failure" trail
63
+ // that fades and leaves the right edge open — signalling "N more clusters, count unknown".
64
+ function MyoUpcomingBar({ activationLen, blue, h }: { activationLen: number; blue: string; h: number }) {
65
+ const fades = [1, 0.6, 0.32] // decreasing opacity per expected cluster → "and maybe more"
66
+ return (
67
+ <div style={{ display: 'flex', width: '100%', height: h, gap: 0 }}>
68
+ <div style={{ display: 'flex', flex: activationLen, height: '100%', borderRadius: '2px 0 0 2px', overflow: 'hidden' }}>
69
+ {Array.from({ length: activationLen }, (_, i) => (
70
+ <div key={i} style={{ flex: 1, height: '100%', background: GREY }} />
71
+ ))}
72
+ </div>
73
+ {fades.map((op, ci) => (
74
+ <Fragment key={ci}>
75
+ <div style={{ flex: `0 0 ${CLUSTER_GAP}px`, height: '100%', background: INSET }} />
76
+ <div style={{ display: 'flex', flex: 5, height: '100%', overflow: 'hidden', opacity: op }}>
77
+ {Array.from({ length: 5 }, (_, i) => (
78
+ <div key={i} style={{ flex: 1, height: '100%', background: blue }} />
79
+ ))}
80
+ </div>
81
+ </Fragment>
82
+ ))}
83
+ </div>
84
+ )
85
+ }
86
+
87
+ // ---- row scaffold at real rail width, on the sunk inset surface
88
+ function Row({ name, meta, children }: { name: string; meta: string; children: React.ReactNode }) {
89
+ return (
90
+ <div style={{ width: RAIL_W, background: INSET, padding: '9px 12px', borderRadius: 4 }}>
91
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 6 }}>
92
+ <span style={{ fontSize: 14, fontWeight: 800, color: T_PRIMARY }}>{name}</span>
93
+ <div style={{ flex: 1 }} />
94
+ <span style={{ fontSize: 10, fontFamily: 'monospace', color: T_TERTIARY }}>{meta}</span>
95
+ </div>
96
+ {children}
97
+ </div>
98
+ )
99
+ }
100
+ function Col({ label, children }: { label: string; children: React.ReactNode }) {
101
+ return (
102
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
103
+ <span style={{ ...sectionTitle, color: T_SECONDARY }}>{label}</span>
104
+ {children}
105
+ </div>
106
+ )
107
+ }
108
+
109
+ const H = 8
110
+
111
+ function Sheet() {
112
+ return (
113
+ <Page title="Session Rail — set-type encodings" note="R&D for prescriptions the flat sets×reps strip doesn't model. Rail width, real velocity ramp pins. Rep-range: range-max segments, the variable zone (floor→max) upcoming part gets its own color. Drop: one bar, sub-loads split by thin notches. Myo: activation + mini-clusters split by cluster gaps.">
114
+ <div style={{ display: 'flex', gap: 40, flexWrap: 'wrap', alignItems: 'flex-start' }}>
115
+ {/* 1. variable rep-range — real ramp candidates for the variable zone */}
116
+ <Col label="1 · variable rep-range — LOCKED: cyan 900">
117
+ {([
118
+ ['grey · charcoal 200', VAR_GREY],
119
+ ['blue 800 · #14407E', VAR_BLUE_800],
120
+ ['blue 900 · #112C5A', VAR_BLUE_900],
121
+ ['cyan 900 · #0B3149', VAR_CYAN_900],
122
+ ] as const).map(([label, c]) => (
123
+ <Row key={label} name="Face Pull" meta="3 × 15–20 @ 40">
124
+ <span style={{ fontSize: 9, color: T_TERTIARY, display: 'block', marginBottom: 5 }}>{label}</span>
125
+ <div style={{ display: 'flex', flexDirection: 'column', gap: SET_GAP }}>
126
+ <RangeBar floor={15} max={20} done={[]} varColor={c} h={H} />
127
+ <RangeBar floor={15} max={20} done={reps(12, 0.9)} varColor={c} h={H} />
128
+ <RangeBar floor={15} max={20} done={reps(17, 0.8)} varColor={c} h={H} />
129
+ </div>
130
+ </Row>
131
+ ))}
132
+ <span style={{ fontSize: 10, color: T_TERTIARY, maxWidth: RAIL_W }}>Each block: todo · 12 done (committed) · 17 done (into the variable zone). Variable-todo = the swatch; committed-todo = grey.</span>
133
+ </Col>
134
+
135
+ {/* 2. drop set */}
136
+ <Col label="2 · drop set">
137
+ <Row name="Lat Pulldown" meta="1 drop · 10→8→6 @ 120→80">
138
+ <DropBar subloads={[reps(10, 0.85), reps(8, 0.7), reps(6, 0.6)]} h={H} />
139
+ </Row>
140
+ <Row name="Leg Extension" meta="1 drop · 12→10→8→6 @ 90→45">
141
+ <DropBar subloads={[reps(12, 0.8), reps(10, 0.7), reps(8, 0.62), reps(6, 0.55)]} h={H} />
142
+ </Row>
143
+ <span style={{ fontSize: 10, color: T_TERTIARY, maxWidth: RAIL_W }}>One set, no rest; notches mark each load drop. Notch (2px) &lt; set gap (5px) so drops read as sub-structure of a single set.</span>
144
+ </Col>
145
+
146
+ {/* 3. myo-rep */}
147
+ <Col label="3 · myo-rep / rest-pause">
148
+ <Row name="Cable Curl" meta="myo · done · 15 + 5 + 5 + 5">
149
+ <MyoBar activation={reps(15, 0.75)} clusters={[reps(5, 0.6), reps(5, 0.55), reps(5, 0.5)]} h={H} />
150
+ </Row>
151
+ <Row name="Triceps Pushdown" meta="myo · done · 12 + 4 + 4">
152
+ <MyoBar activation={reps(12, 0.7)} clusters={[reps(4, 0.55), reps(4, 0.5)]} h={H} />
153
+ </Row>
154
+ <span style={{ fontSize: 10, color: T_TERTIARY, maxWidth: RAIL_W }}>Done: activation to near-failure, then rest-pause clusters. Cluster gap (3px) between notch and set gap.</span>
155
+ <span style={{ ...sectionTitle, color: T_SECONDARY, marginTop: 6 }}>upcoming · length unknown</span>
156
+ <Row name="Cable Curl" meta="myo · ~15 + clusters to failure">
157
+ <MyoUpcomingBar activationLen={15} blue={VAR_CYAN_900} h={H} />
158
+ </Row>
159
+ <Row name="Triceps Pushdown" meta="myo · ~12 + clusters to failure">
160
+ <MyoUpcomingBar activationLen={12} blue={VAR_CYAN_900} h={H} />
161
+ </Row>
162
+ <span style={{ fontSize: 10, color: T_TERTIARY, maxWidth: RAIL_W }}>{'Grey activation (expected) + blue "clusters-to-failure" trail that fades and leaves the right edge open — reuses the variable/opportunity blue, signalling "more coming, count unknown."'}</span>
163
+ </Col>
164
+ </div>
165
+ </Page>
166
+ )
167
+ }
168
+
169
+ const meta: Meta = {
170
+ title: 'Lab/Explorations/Set Types',
171
+ parameters: { layout: 'fullscreen' },
172
+ }
173
+ export default meta
174
+ type Story = StoryObj
175
+
176
+ export const Encodings: Story = { render: () => <Sheet /> }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * SHEET · Workout Expansion (page 2) — what a rail item reveals when expanded.
3
+ * Anchored on the REAL unexpanded rail row (`ExerciseCardHeading`) as the persistent header, then the
4
+ * revealed body in two REAL-component directions so the fork is concrete:
5
+ * A — Table (real `SetTableHeader` + real `SetRow`s — the current expanded body).
6
+ * C — Embed (real `VelocityStrip` full/expanded per set + a compact caption — the bar IS the set).
7
+ * B (drop PREV) is a trivial prop on the table, noted but not forked here. Same data across both: set 1 done
8
+ * · set 2 LIVE 5/10 · set 3 upcoming, with velocities that DECAY within a set so the "shape" reading is real.
9
+ * OPEN: A-vs-C · live-set treatment in the body · read-only density (drop TEMPO row? per-row strips?). DECISIONS doc.
10
+ */
11
+ import type { Meta, StoryObj } from '@storybook/react-vite'
12
+ import { View, Text } from 'react-native'
13
+ import { ExerciseCardHeading } from './ExerciseCardHeading'
14
+ import { SetTableHeader } from './SetTableHeader'
15
+ import { SetRow, type SetRowProps } from './SetRow'
16
+ import { VelocityStrip } from './VelocityStrip'
17
+ import { type SetStripSet } from './SetStrip'
18
+ import {
19
+ INSET,
20
+ INSET_SHADOW,
21
+ RAISED,
22
+ BORDER_SUBTLE,
23
+ GREEN,
24
+ T_PRIMARY,
25
+ T_SECONDARY,
26
+ T_TERTIARY,
27
+ Page,
28
+ sectionTitle,
29
+ monoTag,
30
+ } from './setHeadingKit'
31
+
32
+ // Per-rep mean velocity that DECAYS across a set (fast → slow) so bar height/color carries shape.
33
+ const decay = (n: number, start: number, span = 0.5) =>
34
+ Array.from({ length: n }, (_, r) => +(start - (span * r) / Math.max(1, n - 1)).toFixed(3))
35
+
36
+ // ---- shared data (real prop shapes) ------------------------------------------------
37
+ // Heading strip (SetStripSet[]): set1 done · set2 active 5/10 · set3 todo.
38
+ const HEADING_STATES: SetStripSet[] = [
39
+ { status: 'done', velocities: decay(10, 1.05) },
40
+ { status: 'active', velocities: decay(5, 0.95, 0.3), planned: 10 },
41
+ { status: 'todo', planned: 10 },
42
+ ]
43
+ // Expanded-body per-set rows (real SetRowProps): set 1 done · set 2 LIVE · set 3 todo.
44
+ const BODY_SETS: SetRowProps[] = [
45
+ {
46
+ state: 'done',
47
+ setNumber: 1,
48
+ reps: 10,
49
+ weight: 90,
50
+ rpe: 7.5,
51
+ unit: 'lbs',
52
+ velocities: decay(10, 1.05),
53
+ },
54
+ {
55
+ state: 'live',
56
+ setNumber: 2,
57
+ unit: 'lbs',
58
+ target: { reps: 10, weight: 90 },
59
+ reps: 10,
60
+ weight: 90,
61
+ velocities: decay(5, 0.95, 0.3),
62
+ },
63
+ { state: 'todo', setNumber: 3, unit: 'lbs', target: { reps: 10, weight: 90 } },
64
+ ]
65
+
66
+ // ---- the real unexpanded rail row, used as the persistent expansion header ---------
67
+ function RailHeader() {
68
+ return (
69
+ <View style={{ position: 'relative', zIndex: 2, backgroundColor: RAISED }}>
70
+ <ExerciseCardHeading
71
+ name="Cable Chest Press"
72
+ sets={3}
73
+ reps={10}
74
+ load={90}
75
+ unit="lbs"
76
+ tempo={[2, 1, 2, 0]}
77
+ indicator="info"
78
+ setStates={HEADING_STATES}
79
+ />
80
+ </View>
81
+ )
82
+ }
83
+
84
+ // ---- A · table body: real SetTableHeader + real SetRow (LOCKED: drop PREV, keep strips)
85
+ function TableBody() {
86
+ return (
87
+ <View>
88
+ <SetTableHeader unit="lbs" showPrevious={false} />
89
+ {BODY_SETS.map((s, i) => (
90
+ <SetRow key={i} {...s} />
91
+ ))}
92
+ </View>
93
+ )
94
+ }
95
+
96
+ // ---- C · embed body: real VelocityStrip (full/expanded) per set + compact caption ---
97
+ const capText = {
98
+ fontFamily: 'Inter, sans-serif',
99
+ fontSize: 11,
100
+ fontWeight: '600' as const,
101
+ color: T_PRIMARY,
102
+ }
103
+ const dimText = { ...capText, color: T_SECONDARY, fontWeight: '500' as const }
104
+ function EmbedSetRow({ s }: { s: SetRowProps }) {
105
+ const live = s.state === 'live'
106
+ const todo = s.state === 'todo'
107
+ const vels = s.state === 'done' || s.state === 'live' ? s.velocities : []
108
+ const rpe = s.state === 'todo' ? null : s.rpe
109
+ return (
110
+ <View
111
+ style={{
112
+ paddingVertical: 7,
113
+ paddingHorizontal: 10,
114
+ marginHorizontal: 2,
115
+ marginBottom: 4,
116
+ borderRadius: 8,
117
+ opacity: todo ? 0.55 : 1,
118
+ ...(live
119
+ ? {
120
+ backgroundColor: 'rgba(46,213,115,0.06)',
121
+ borderWidth: 1,
122
+ borderColor: 'rgba(46,213,115,0.30)',
123
+ }
124
+ : {}),
125
+ }}
126
+ >
127
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', marginBottom: 6 }}>
128
+ <Text style={{ ...dimText, width: 22, color: T_TERTIARY }}>{s.setNumber}</Text>
129
+ {s.state === 'live' ? (
130
+ <Text style={{ ...capText, color: GREEN, fontWeight: '700' }}>
131
+ {vels.length}/{s.target.reps} × {s.target.weight}
132
+ </Text>
133
+ ) : s.state === 'done' ? (
134
+ <Text style={capText}>
135
+ {s.reps} × {s.weight}
136
+ </Text>
137
+ ) : (
138
+ <Text style={dimText}>
139
+ {s.target.reps} × {s.target.weight}
140
+ </Text>
141
+ )}
142
+ <View style={{ flex: 1 }} />
143
+ {rpe != null && <Text style={dimText}>RPE {rpe}</Text>}
144
+ {live && <Text style={{ ...capText, color: GREEN, fontWeight: '700' }}>live</Text>}
145
+ </View>
146
+ {vels.length > 0 ? (
147
+ <VelocityStrip velocities={vels} variant="expanded" showInfo={false} />
148
+ ) : (
149
+ <View style={{ height: 24, flexDirection: 'row', gap: 2 }}>
150
+ {Array.from({ length: s.state === 'todo' ? s.target.reps : 10 }).map((_, i) => (
151
+ <View
152
+ key={i}
153
+ style={{
154
+ flex: 1,
155
+ height: 6,
156
+ alignSelf: 'flex-end',
157
+ backgroundColor: '#2C2C2C',
158
+ borderRadius: 1,
159
+ opacity: 0.5,
160
+ }}
161
+ />
162
+ ))}
163
+ </View>
164
+ )}
165
+ </View>
166
+ )
167
+ }
168
+ function EmbedBody() {
169
+ return (
170
+ <View style={{ paddingTop: 6 }}>
171
+ {BODY_SETS.map((s, i) => (
172
+ <EmbedSetRow key={i} s={s} />
173
+ ))}
174
+ </View>
175
+ )
176
+ }
177
+
178
+ // ---- expanded item = real rail header + revealed body ------------------------------
179
+ function ExpandedItem({ label, children }: { label: string; children: React.ReactNode }) {
180
+ return (
181
+ <View style={{ gap: 8 }}>
182
+ <Text style={{ ...monoTag, color: T_SECONDARY }}>{label}</Text>
183
+ <View
184
+ style={
185
+ {
186
+ width: 260,
187
+ backgroundColor: INSET,
188
+ boxShadow: INSET_SHADOW,
189
+ borderRadius: 4,
190
+ overflow: 'hidden',
191
+ } as object
192
+ }
193
+ >
194
+ <RailHeader />
195
+ <View
196
+ style={{
197
+ backgroundColor: INSET,
198
+ paddingBottom: 8,
199
+ borderTopWidth: 1,
200
+ borderTopColor: BORDER_SUBTLE,
201
+ }}
202
+ >
203
+ {children}
204
+ </View>
205
+ </View>
206
+ </View>
207
+ )
208
+ }
209
+
210
+ const meta: Meta = {
211
+ title: 'Lab/Explorations/Workout Expansion',
212
+ parameters: { layout: 'fullscreen' },
213
+ }
214
+ export default meta
215
+ type Story = StoryObj
216
+
217
+ export const DirectionStudy: Story = {
218
+ name: 'Expanded item · A table / C embed-in-bar (real components)',
219
+ render: () => (
220
+ <Page
221
+ title="Workout expansion — direction study (page 2)"
222
+ note="LOCKED (2026-07-10): body = direction A, the SetTableHeader+SetRow table, with PREV dropped (real showPrevious={false}) and the per-row velocity strips kept. C (per-rep VelocityStrip becomes the set row) shown for the record — rejected as too novel; the table already carries velocity shape via the per-row strip. Anchored on the REAL unexpanded rail row (ExerciseCardHeading) as the persistent header; data: set 1 done · set 2 live 5/10 · set 3 upcoming, velocities decaying within a set."
223
+ >
224
+ <View style={{ flexDirection: 'row', gap: 44, alignItems: 'flex-start', flexWrap: 'wrap' }}>
225
+ <View style={{ gap: 12 }}>
226
+ <Text style={sectionTitle}>A · Table (current body)</Text>
227
+ <ExpandedItem label="real SetTableHeader + SetRow">
228
+ <TableBody />
229
+ </ExpandedItem>
230
+ </View>
231
+ <View style={{ gap: 12 }}>
232
+ <Text style={sectionTitle}>C · Embed in the bar</Text>
233
+ <ExpandedItem label="real VelocityStrip (full) + caption">
234
+ <EmbedBody />
235
+ </ExpandedItem>
236
+ </View>
237
+ </View>
238
+ </Page>
239
+ ),
240
+ }
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { ScheduleTiles } from './ScheduleTiles'
3
+
4
+ const HOUR = 60 * 60 * 1000
5
+ const DAY = 24 * HOUR
6
+
7
+ // Deterministic reference: fix `now` and offset `when` from it so the `soon`
8
+ // accent (and the relative Until text) is stable across renders.
9
+ const NOW = Date.now()
10
+
11
+ const meta: Meta<typeof ScheduleTiles> = {
12
+ title: 'Workout/ScheduleTiles',
13
+ component: ScheduleTiles,
14
+ tags: ['autodocs'],
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ '**Molecule.** An HStack of [Tile](?path=/docs/components-atoms-tile--docs); Until reuses [DateTime](?path=/docs/custom-datetime--docs) relative. Used-by ↑ SessionHeader.',
20
+ },
21
+ },
22
+ },
23
+ }
24
+
25
+ export default meta
26
+ type Story = StoryObj<typeof ScheduleTiles>
27
+
28
+ export const TwoDaysOut: Story = {
29
+ args: {
30
+ now: NOW,
31
+ when: NOW + 2 * DAY,
32
+ },
33
+ }
34
+
35
+ export const SoonUnderADay: Story = {
36
+ args: {
37
+ now: NOW,
38
+ when: NOW + 5 * HOUR,
39
+ },
40
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { ScheduleTiles, compactUntil } from './ScheduleTiles'
4
+ import { primitiveRamps } from '../../../theme/tokens/primitives'
5
+
6
+ const HOUR = 60 * 60 * 1000
7
+ const DAY = 24 * HOUR
8
+ const ORANGE = primitiveRamps.orange[400]
9
+
10
+ // A fixed reference; the compact Until label is deterministic from (when - now).
11
+ const NOW = Date.now()
12
+
13
+ describe('ScheduleTiles', () => {
14
+ it('renders Date, Time, and Until tiles', () => {
15
+ render(<ScheduleTiles now={NOW} when={NOW + 2 * DAY} />)
16
+ expect(screen.getByText('Date')).toBeInTheDocument()
17
+ expect(screen.getByText('Time')).toBeInTheDocument()
18
+ expect(screen.getByText('Until')).toBeInTheDocument()
19
+ })
20
+
21
+ it('does not accent the Until value when more than a day away', () => {
22
+ const when = NOW + 2 * DAY
23
+ render(<ScheduleTiles now={NOW} when={when} />)
24
+ const value = screen.getByText(compactUntil(when - NOW))
25
+ expect(value.style.color).toBe('')
26
+ })
27
+
28
+ it('accents the Until value orange when less than a day away', () => {
29
+ const when = NOW + 5 * HOUR
30
+ render(<ScheduleTiles now={NOW} when={when} />)
31
+ const value = screen.getByText(compactUntil(when - NOW))
32
+ expect(value).toHaveStyle({ color: ORANGE })
33
+ })
34
+ })
@@ -0,0 +1,70 @@
1
+ import { useState } from 'react'
2
+ import { type ViewProps } from 'react-native'
3
+ import { primitiveRamps } from '../../../theme/tokens/primitives'
4
+ import { HStack } from '../../ui/stack'
5
+ import { Tile } from '../../ui/tile/Tile'
6
+ import { formatDateTime } from '../DateTime/DateTime'
7
+
8
+ const DAY_MS = 24 * 60 * 60 * 1000
9
+
10
+ /** Accent applied to the Until value when the session is < 1 day away. */
11
+ const SOON_ACCENT = primitiveRamps.orange[400]
12
+
13
+ /** Compact time-until label: `30m` / `5h` / `2d` / `3w`. Deterministic from the delta. */
14
+ export function compactUntil(ms: number): string {
15
+ if (ms <= 0) return 'now'
16
+ const min = Math.round(ms / 60000)
17
+ if (min < 60) return `${min}m`
18
+ const hr = Math.round(min / 60)
19
+ if (hr < 24) return `${hr}h`
20
+ const d = Math.round(hr / 24)
21
+ if (d < 7) return `${d}d`
22
+ return `${Math.round(d / 7)}w`
23
+ }
24
+
25
+ type Gap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12
26
+
27
+ export interface ScheduleTilesProps extends ViewProps {
28
+ /** The scheduled moment (timestamp in ms or Date object). */
29
+ when: number | Date
30
+ /** Reference "now"; injectable so the until/accent calc is deterministic in tests. Defaults to the mount time. */
31
+ now?: number
32
+ /** Gap between the tiles (default 1). */
33
+ gap?: Gap
34
+ }
35
+
36
+ /**
37
+ * ScheduleTiles — Date / Time / Until row for an upcoming session.
38
+ *
39
+ * An HStack of three {@link Tile}s derived from a single scheduled moment. The
40
+ * Until value reuses {@link formatDateTime} relative formatting and turns
41
+ * accent-orange only when the session is less than a day away.
42
+ *
43
+ * @example
44
+ * <ScheduleTiles when={Date.now() + 5 * 60 * 60 * 1000} />
45
+ */
46
+ export function ScheduleTiles({ when, now, gap = 1, ...props }: ScheduleTilesProps) {
47
+ // Capture a stable mount-time "now" when the prop is omitted (Date.now() is
48
+ // impure, so it lives in a hook initializer per the render-purity rule).
49
+ const [mountNow] = useState(() => Date.now())
50
+ const reference = now ?? mountNow
51
+ const whenMs = when instanceof Date ? when.getTime() : when
52
+ const soon = whenMs - reference < DAY_MS
53
+ const time = new Date(whenMs).toLocaleTimeString(undefined, {
54
+ hour: 'numeric',
55
+ minute: '2-digit',
56
+ hour12: true,
57
+ })
58
+
59
+ return (
60
+ <HStack gap={gap} {...props}>
61
+ <Tile label="Date" value={formatDateTime(when, 'short')} />
62
+ <Tile label="Time" value={time} />
63
+ <Tile
64
+ label="Until"
65
+ value={compactUntil(whenMs - reference)}
66
+ valueColor={soon ? SOON_ACCENT : undefined}
67
+ />
68
+ </HStack>
69
+ )
70
+ }
@@ -0,0 +1,99 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { SegmentedBar } from './SegmentedBar'
4
+
5
+ /**
6
+ * `SegmentedBar` — a horizontal track split into weighted, individually-fillable
7
+ * segments. Each segment is a flex slot holding a left-aligned colour fill; `pulse`
8
+ * segments breathe on a shared active-pulse loop and an optional `marker` drops a
9
+ * vertical line at a fraction of the width. Colours are passed-in literal hex.
10
+ */
11
+ const meta: Meta<typeof SegmentedBar> = {
12
+ title: 'Workout/SegmentedBar',
13
+ component: SegmentedBar,
14
+ tags: ['autodocs'],
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ '**Atom.** A horizontal track split into weighted, individually-fillable ' +
20
+ 'segments (weight · fill · colour · pulse) with an optional marker line — the ' +
21
+ 'presentational primitive the per-set strips build on. ' +
22
+ 'Used-by ↑ [SetStrip](?path=/docs/workout-setstrip--docs).',
23
+ },
24
+ },
25
+ },
26
+ argTypes: {
27
+ height: { control: { type: 'range', min: 2, max: 24, step: 1 } },
28
+ gap: { control: { type: 'range', min: 0, max: 12, step: 1 } },
29
+ radius: { control: { type: 'range', min: 0, max: 12, step: 1 } },
30
+ },
31
+ decorators: [
32
+ (Story) => (
33
+ <View style={{ width: 240, padding: 16, backgroundColor: '#131313' }}>
34
+ <Story />
35
+ </View>
36
+ ),
37
+ ],
38
+ }
39
+
40
+ export default meta
41
+ type Story = StoryObj<typeof SegmentedBar>
42
+
43
+ export const EqualSegments: Story = {
44
+ args: {
45
+ height: 8,
46
+ segments: [
47
+ { color: '#D14343' },
48
+ { color: '#FF7900' },
49
+ { color: '#F9B415' },
50
+ { color: '#2ED573' },
51
+ ],
52
+ },
53
+ }
54
+
55
+ export const Weighted: Story = {
56
+ args: {
57
+ height: 12,
58
+ gap: 4,
59
+ radius: 6,
60
+ segments: [
61
+ { color: '#2ED573', weight: 3 },
62
+ { color: '#F9B415', weight: 2 },
63
+ { color: '#D14343', weight: 1 },
64
+ ],
65
+ },
66
+ parameters: {
67
+ docs: { description: { story: 'Segments sized by `weight`, with a gap and rounded slots.' } },
68
+ },
69
+ }
70
+
71
+ export const PartialFills: Story = {
72
+ args: {
73
+ height: 12,
74
+ gap: 4,
75
+ radius: 6,
76
+ segments: [
77
+ { color: '#2ED573', fill: 1 },
78
+ { color: '#F9B415', fill: 0.6 },
79
+ { color: '#01B5D1', fill: 0.3, pulse: true },
80
+ ],
81
+ },
82
+ parameters: {
83
+ docs: {
84
+ description: { story: 'Each slot fills a fraction of its width; the last one pulses.' },
85
+ },
86
+ },
87
+ }
88
+
89
+ export const WithMarker: Story = {
90
+ args: {
91
+ height: 10,
92
+ radius: 5,
93
+ segments: [{ color: '#01B5D1', fill: 0.7 }],
94
+ marker: { position: 0.5, color: '#FFFFFF' },
95
+ },
96
+ parameters: {
97
+ docs: { description: { story: 'A single fill with a target marker at the halfway point.' } },
98
+ },
99
+ }