@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,74 @@
1
+ import type { ReactNode } from 'react'
2
+ import { View, Text } from 'react-native'
3
+ import { cn } from '../../utils/cn'
4
+ import { SideNav, type SideNavItem } from './SideNav'
5
+ import { TopBar } from './TopBar'
6
+ import { type Device } from './DeviceRow'
7
+ import { type SessionState } from './SessionStatePill'
8
+
9
+ /**
10
+ * `DashboardShell` — the wall-dashboard chrome: a persistent {@link SideNav} rail +
11
+ * {@link TopBar} over a main content region. A thin frame that wires the shell-nav
12
+ * organisms together and exposes a `children` content slot, so the dashboard app
13
+ * mounts one component instead of re-assembling the chrome. Driven entirely by props.
14
+ */
15
+ export interface DashboardShellProps {
16
+ /** Active nav category key. */
17
+ activeKey?: string
18
+ /** Nav categories, top → bottom. Defaults to SideNav's four dashboard categories. */
19
+ navItems?: SideNavItem[]
20
+ /** A category with off-view live activity → a quiet cue on that item. */
21
+ liveKey?: string | null
22
+ /** Global session state → the TopBar status pill. */
23
+ state?: SessionState
24
+ /** Connected devices → the TopBar connection glyph + dropdown. */
25
+ devices?: Device[]
26
+ /** TopBar brand subtitle. */
27
+ subtitle?: string
28
+ onNavigate?: (key: string) => void
29
+ onSelectDevice?: (device: Device) => void
30
+ /** Main content region. A placeholder renders when omitted. */
31
+ children?: ReactNode
32
+ className?: string
33
+ }
34
+
35
+ const DEMO_DEVICES: Device[] = [
36
+ { id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
37
+ { id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
38
+ ]
39
+
40
+ function ContentPlaceholder() {
41
+ return (
42
+ <View className="flex-1 items-center justify-center">
43
+ <Text className="font-heading text-sm text-text-tertiary">main content region</Text>
44
+ </View>
45
+ )
46
+ }
47
+
48
+ export function DashboardShell({
49
+ activeKey = 'live',
50
+ navItems,
51
+ liveKey = null,
52
+ state = 'live',
53
+ devices = DEMO_DEVICES,
54
+ subtitle = 'wall dashboard',
55
+ onNavigate,
56
+ onSelectDevice,
57
+ children,
58
+ className,
59
+ }: DashboardShellProps) {
60
+ return (
61
+ <View className={cn('flex-1 flex-row bg-surface-base', className)}>
62
+ <SideNav activeKey={activeKey} items={navItems} liveKey={liveKey} onNavigate={onNavigate} />
63
+ <View className="flex-1">
64
+ <TopBar
65
+ state={state}
66
+ devices={devices}
67
+ subtitle={subtitle}
68
+ onSelectDevice={onSelectDevice}
69
+ />
70
+ <View className="flex-1">{children ?? <ContentPlaceholder />}</View>
71
+ </View>
72
+ </View>
73
+ )
74
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { DeviceIndicator } from './DeviceIndicator'
3
3
 
4
4
  const meta: Meta<typeof DeviceIndicator> = {
5
- title: 'Shell/TopBar/DeviceMenu/DeviceIndicator',
5
+ title: 'Shell/DeviceIndicator',
6
6
  component: DeviceIndicator,
7
7
  tags: ['autodocs'],
8
8
  args: { status: 'connected' },
@@ -15,7 +15,7 @@ const meta: Meta<typeof DeviceIndicator> = {
15
15
  docs: {
16
16
  description: {
17
17
  component:
18
- '**Molecule.** Composes [BluetoothIcon](?path=/docs/icons--docs), color-coded by ' +
18
+ '**Molecule.** Composes [BluetoothIcon](?path=/docs/foundations-icons--docs), color-coded by ' +
19
19
  'connection state via a `text-status-*-vivid` token — the glyph color alone carries the fault ' +
20
20
  '(no separate badge). Switch the `status` control to see connected / degraded / lost.',
21
21
  },
@@ -10,7 +10,7 @@ const DEVICES: Device[] = [
10
10
  ]
11
11
 
12
12
  const meta: Meta<typeof DeviceMenu> = {
13
- title: 'Shell/TopBar/DeviceMenu',
13
+ title: 'Shell/DeviceMenu',
14
14
  component: DeviceMenu,
15
15
  tags: ['autodocs'],
16
16
  args: { devices: DEVICES, isOpen: false },
@@ -32,10 +32,10 @@ const meta: Meta<typeof DeviceMenu> = {
32
32
  description: {
33
33
  component:
34
34
  '**Organism.** The device glyph + its dropdown. Composes ' +
35
- '[Popover](?path=/docs/components-popover--docs) + ' +
36
- '[DeviceIndicator](?path=/docs/shell-topbar-devicemenu-deviceindicator--docs) (trigger) + ' +
37
- '[DeviceRow](?path=/docs/shell-topbar-devicemenu-devicerow--docs) (list) + ' +
38
- '[Typography](?path=/docs/custom-typography--docs) (header). Aggregates the bound devices ' +
35
+ '[Popover](?path=/docs/components-molecules-popover--docs) + ' +
36
+ '[DeviceIndicator](?path=/docs/shell-deviceindicator--docs) (trigger) + ' +
37
+ '[DeviceRow](?path=/docs/shell-devicerow--docs) (list) + ' +
38
+ '[Typography](?path=/docs/foundations-typography--docs) (header). Aggregates the bound devices ' +
39
39
  '(worst-of) for the glyph state. Click the glyph, or toggle the `isOpen` control.',
40
40
  },
41
41
  },
@@ -3,7 +3,7 @@ import { View } from 'react-native'
3
3
  import { DeviceRow } from './DeviceRow'
4
4
 
5
5
  const meta: Meta<typeof DeviceRow> = {
6
- title: 'Shell/TopBar/DeviceMenu/DeviceRow',
6
+ title: 'Shell/DeviceRow',
7
7
  component: DeviceRow,
8
8
  tags: ['autodocs'],
9
9
  args: {
@@ -25,8 +25,8 @@ const meta: Meta<typeof DeviceRow> = {
25
25
  description: {
26
26
  component:
27
27
  '**Molecule.** One device row: status dot · name · Bluetooth id. Composes ' +
28
- '[Indicator](?path=/docs/components-indicator--docs) (status dot) + ' +
29
- '[Typography](?path=/docs/custom-typography--docs) (`body2` name, `mono` id). ' +
28
+ '[Indicator](?path=/docs/components-atoms-indicator--docs) (status dot) + ' +
29
+ '[Typography](?path=/docs/foundations-typography--docs) (`body2` name, `mono` id). ' +
30
30
  'Edit the `device` control (set `state` to `lost` / `available`, `slot` to `null`) to explore.',
31
31
  },
32
32
  },
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { NavItem } from './NavItem'
4
+ import { ActivityIcon } from '../icons'
5
+
6
+ const meta: Meta<typeof NavItem> = {
7
+ title: 'Shell/NavItem',
8
+ component: NavItem,
9
+ tags: ['autodocs'],
10
+ args: { label: 'Live', active: false, live: false },
11
+ argTypes: {
12
+ label: { control: 'text' },
13
+ active: { control: 'boolean' },
14
+ live: { control: 'boolean' },
15
+ icon: { control: false },
16
+ onPress: { action: 'press' },
17
+ },
18
+ decorators: [
19
+ (Story) => (
20
+ <View className="w-[60px] items-center bg-background-base py-2">
21
+ <Story />
22
+ </View>
23
+ ),
24
+ ],
25
+ parameters: {
26
+ docs: {
27
+ description: {
28
+ component:
29
+ '**Molecule.** One category button — a glyph over an uppercase micro-label in a 46×46 target, ' +
30
+ 'spanning the full 60px rail so the active accent bar sits flush to the edge. Composes an ' +
31
+ '[icon](?path=/docs/foundations-icons--docs) + [Typography](?path=/docs/foundations-typography--docs). Toggle ' +
32
+ '`active` / `live` to see the states.',
33
+ },
34
+ },
35
+ },
36
+ render: (args) => <NavItem {...args} icon={<ActivityIcon size={20} color="currentColor" />} />,
37
+ }
38
+ export default meta
39
+ type Story = StoryObj<typeof NavItem>
40
+
41
+ export const Default: Story = {}
42
+ export const Active: Story = { args: { active: true } }
43
+ export const LiveElsewhere: Story = {
44
+ args: { live: true },
45
+ parameters: {
46
+ docs: {
47
+ description: {
48
+ story: 'Not the active view, but a set is running for this category → muted-green label.',
49
+ },
50
+ },
51
+ },
52
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { NavItem } from './NavItem'
4
+ import { ActivityIcon } from '../icons'
5
+
6
+ const icon = <ActivityIcon size={20} color="currentColor" />
7
+
8
+ describe('NavItem', () => {
9
+ it('renders the label and is exposed as a tab', () => {
10
+ render(<NavItem icon={icon} label="Live" />)
11
+ expect(screen.getByRole('tab', { name: 'Live' })).toBeInTheDocument()
12
+ })
13
+
14
+ it('shows the accent bar when active', () => {
15
+ render(<NavItem icon={icon} label="Live" active />)
16
+ expect(screen.getByTestId('nav-item-accent')).toBeInTheDocument()
17
+ })
18
+
19
+ it('hides the accent bar when inactive', () => {
20
+ render(<NavItem icon={icon} label="Live" />)
21
+ expect(screen.queryByTestId('nav-item-accent')).toBeNull()
22
+ })
23
+
24
+ it('fires onPress when tapped', () => {
25
+ const onPress = vi.fn()
26
+ render(<NavItem icon={icon} label="Live" onPress={onPress} />)
27
+ fireEvent.click(screen.getByRole('tab', { name: 'Live' }))
28
+ expect(onPress).toHaveBeenCalledTimes(1)
29
+ })
30
+
31
+ it('renders the live-elsewhere state', () => {
32
+ const { container } = render(<NavItem icon={icon} label="Live" live />)
33
+ expect(container.firstChild).toBeInTheDocument()
34
+ })
35
+ })
@@ -0,0 +1,75 @@
1
+ import type { ReactNode } from 'react'
2
+ import { Pressable, View } from 'react-native'
3
+ import { cn } from '../../utils/cn'
4
+ import { Typography } from '../custom/Typography'
5
+
6
+ export interface NavItemProps {
7
+ /** The nav glyph (an icon from `components/icons`, rendered ~20px via `currentColor`). */
8
+ icon: ReactNode
9
+ /** Short label shown under the glyph (uppercased for display; used as the accessible name). */
10
+ label: string
11
+ /** Active category → left accent bar + brand-colored glyph & label. */
12
+ active?: boolean
13
+ /**
14
+ * A set is running for this category while it is NOT the active view
15
+ * (live-elsewhere) → a quiet muted-green label tint. Ignored when `active`.
16
+ */
17
+ live?: boolean
18
+ onPress?: () => void
19
+ className?: string
20
+ }
21
+
22
+ /**
23
+ * Shell S2 · NavItem — one category button in the {@link SideNav}: a 20px glyph
24
+ * over an uppercase micro-label in a 46×46 target. The button spans the full 60px
25
+ * rail so the active **left accent bar** sits flush to the rail's edge. States:
26
+ * active = accent bar + `brand-primary`; `live` (while not active) tints only the
27
+ * label `status-success-dark` (the glyph stays dim); otherwise dim `text-tertiary`.
28
+ */
29
+ export function NavItem({
30
+ icon,
31
+ label,
32
+ active = false,
33
+ live = false,
34
+ onPress,
35
+ className,
36
+ }: NavItemProps) {
37
+ const glyphColor = active ? 'text-brand-primary' : 'text-text-tertiary'
38
+ const labelColor = active
39
+ ? 'text-brand-primary'
40
+ : live
41
+ ? 'text-status-live-muted'
42
+ : 'text-text-tertiary'
43
+
44
+ return (
45
+ <Pressable
46
+ accessibilityRole="tab"
47
+ accessibilityState={{ selected: active }}
48
+ accessibilityLabel={label}
49
+ onPress={onPress}
50
+ className={cn('relative h-[46px] w-[60px] items-center justify-center', className)}
51
+ >
52
+ {active ? (
53
+ <View
54
+ testID="nav-item-accent"
55
+ className="absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px] bg-brand-primary"
56
+ />
57
+ ) : null}
58
+ <View
59
+ className={cn(
60
+ 'h-[46px] w-[46px] items-center justify-center gap-[3px] rounded-[11px]',
61
+ glyphColor
62
+ )}
63
+ >
64
+ {icon}
65
+ <Typography
66
+ variant="button"
67
+ color="inherit"
68
+ className={cn('text-[8.5px] font-bold uppercase tracking-[0.4px]', labelColor)}
69
+ >
70
+ {label}
71
+ </Typography>
72
+ </View>
73
+ </Pressable>
74
+ )
75
+ }
@@ -9,6 +9,23 @@ This README is the **index**: it maps each component's dependencies (**composes
9
9
  should be reaching for an existing primitive instead. Storybook mirrors this tree under `Shell/TopBar/…`,
10
10
  and every component's autodocs page repeats its "Composes" links.
11
11
 
12
+ ## Specimen: `SideNav` (the second shell unit, S2)
13
+
14
+ ```
15
+ SideNav ...................... organism — the 60px left rail (Shell/SideNav)
16
+ ├─ NavItem ................... molecule × 4 (Live · Review · Plan · Body)
17
+ │ ├─ icon ................... icons (shared primitive) — Activity/History/Layers/PersonStanding
18
+ │ └─ Typography ............. titan · button variant (uppercase micro-label)
19
+ └─ (accent bar) .............. bg-brand-primary edge bar on the active item
20
+ ```
21
+
22
+ Decisions locked 2026-07-08 (specimen `coordination/design-explorations/shell/S2-sidenav/`): lucide glyphs
23
+ (activity · history · layers · figure) · **Plan** label (narrower than "Program") · active = **left accent
24
+ bar** (short, centered on the icon+label) · **60px** icon+micro-label · live cue = **muted-green label**
25
+ (`status-live-muted`) on the Live item while a set runs off-Live · four items, no footer, no top-nav. Fixed
26
+ 60px at every width (labels sit under the glyph). `SideNav` is presentational — `activeKey` / `onNavigate` /
27
+ `liveKey`; the app owns routing + which key is live.
28
+
12
29
  ## Specimen: `TopBar` (the first shell unit, S1)
13
30
 
14
31
  ```
@@ -33,6 +50,8 @@ TopBar ....................... organism — the chrome band (Shell/TopBar)
33
50
 
34
51
  | Component | Tier | Composes ↓ | Used-by ↑ |
35
52
  |---|---|---|---|
53
+ | `SideNav` | organism | NavItem ×4, `components/icons` (Activity/History/Layers/PersonStanding) | dashboard app root *(planned)* |
54
+ | `NavItem` | molecule | icon, Typography | SideNav |
36
55
  | `TopBar` | organism | BrandLockup, SessionStatePill, Divider, DeviceMenu, DateTime, `surfaceGradient.chrome` | dashboard app root *(planned)* |
37
56
  | `BrandLockup` | molecule | VoltrasMark, Typography | TopBar |
38
57
  | `SessionStatePill` | molecule | Indicator, Typography | TopBar, **Live-view header** *(planned reuse)* |
@@ -79,8 +98,19 @@ Every leaf now composes a primitive rather than hand-rolling it:
79
98
  - **Lint guardrails** — components may not inline `linear-gradient` (use `surfaceGradient`); shell + icons may
80
99
  not use raw hex (use tokens).
81
100
 
101
+ **S2 shared substrate:** four nav glyphs added to `components/icons` (`ActivityIcon`, `HistoryIcon`,
102
+ `LayersIcon`, `PersonStandingIcon` — lucide-mirrored, like Dumbbell/Star), available system-wide.
103
+
82
104
  **Watch-list (known gaps to close as we go):**
83
105
  - **Dot primitive overlap** — titan has both `StatusDot` (Workout, semantic) and `Indicator` (ui, generic).
84
106
  The shell standardizes on `Indicator`; a future pass could consolidate.
85
107
  - **Other hand-rolled gradients** — `MesoCard`, `DeviationBar`, `MesoStatusCard`, `BodyMapDetailPanel` still
86
108
  inline `linear-gradient` strings; they should adopt `surfaceGradient` / `linearGradient`.
109
+ - **`status-live` token family (new, decoupled from success)** — introduced `status-live` (green-300, the
110
+ vivid LIVE-pill green) + `status-live-muted` (green-500 `#22A444`, the quiet nav cue) so "live" has its own
111
+ role: changing `status-success` no longer affects live, and vice-versa. Wired the full chain (semantic →
112
+ config → global.css → tailwind); `Indicator` gained a `live` color; the S1 LIVE pill was repointed
113
+ `success`→`live` (value-preserving). This realized a slice of TD-05.09 Fork 1b (wiring ramp steps as tokens).
114
+ - **`aria-selected` on `NavItem`** — RNW does not emit `aria-selected` from `accessibilityState={{selected}}`
115
+ in the jsdom test env, so active-state is asserted via the accent-bar testID. Confirm the on-device/RNW
116
+ build exposes selection to AT (may need an explicit `aria-selected` for full tab semantics).