@titan-design/react-ui 0.5.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.
- package/dist/bodymap.js +241 -189
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +241 -189
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +1017 -130
- package/dist/index.d.ts +1017 -130
- package/dist/index.js +3058 -1090
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2992 -1091
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1797 -1292
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1797 -1292
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +464 -268
- package/dist/theme/index.d.ts +464 -268
- package/dist/theme/index.js +303 -191
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +295 -192
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +160 -143
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +160 -143
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +3 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +83 -7
- package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
- package/src/components/custom/DateTime/DateTime.tsx +55 -15
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.tsx +6 -3
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.tsx +3 -5
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.tsx +3 -5
- package/src/components/custom/Typography/Typography.stories.tsx +46 -28
- package/src/components/custom/Typography/Typography.test.tsx +13 -3
- package/src/components/custom/Typography/Typography.tsx +5 -0
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +11 -16
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +6 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
- package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.tsx +7 -4
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +6 -6
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/InputBar.tsx +2 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +8 -8
- package/src/components/custom/Workout/IntensityBar.tsx +10 -6
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoCard.tsx +8 -3
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.tsx +5 -2
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +10 -7
- package/src/components/custom/Workout/README.md +146 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
- package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.tsx +17 -11
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +170 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/Sparkline.tsx +2 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
- package/src/components/custom/Workout/StatusDot.tsx +12 -9
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +29 -36
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
- package/src/components/custom/Workout/VelocityStrip.test.tsx +379 -144
- package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeekRow.tsx +3 -2
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.tsx +6 -2
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
- package/src/components/custom/Workout/icons.tsx +6 -67
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/SvgIcon.tsx +50 -0
- package/src/components/icons/icons.stories.tsx +122 -0
- package/src/components/icons/icons.test.tsx +73 -0
- package/src/components/icons/icons.tsx +153 -0
- package/src/components/icons/index.ts +4 -0
- package/src/components/index.ts +6 -0
- package/src/components/shell/BrandLockup.stories.tsx +27 -0
- package/src/components/shell/BrandLockup.test.tsx +23 -0
- package/src/components/shell/BrandLockup.tsx +47 -0
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
- package/src/components/shell/DeviceIndicator.test.tsx +26 -0
- package/src/components/shell/DeviceIndicator.tsx +52 -0
- package/src/components/shell/DeviceMenu.stories.tsx +47 -0
- package/src/components/shell/DeviceMenu.test.tsx +40 -0
- package/src/components/shell/DeviceMenu.tsx +65 -0
- package/src/components/shell/DeviceRow.stories.tsx +38 -0
- package/src/components/shell/DeviceRow.test.tsx +37 -0
- package/src/components/shell/DeviceRow.tsx +58 -0
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +116 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +27 -0
- package/src/components/shell/SessionStatePill.test.tsx +25 -0
- package/src/components/shell/SessionStatePill.tsx +46 -0
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +49 -0
- package/src/components/shell/TopBar.test.tsx +36 -0
- package/src/components/shell/TopBar.tsx +89 -0
- package/src/components/shell/index.ts +13 -0
- package/src/components/ui/alert/Alert.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +38 -2
- package/src/components/ui/indicator/Indicator.test.tsx +20 -1
- package/src/components/ui/indicator/Indicator.tsx +50 -6
- package/src/components/ui/indicator/index.ts +1 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -2
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +323 -0
- package/src/theme/Colors.stories.tsx +74 -9
- package/src/theme/barrel.ts +1 -0
- package/src/theme/config.ts +20 -3
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/extracted-colors-dataviz.ts +21 -0
- package/src/theme/extracted-colors-ui.ts +20 -0
- package/src/theme/global.css +69 -53
- package/src/theme/gradients.test.ts +25 -0
- package/src/theme/gradients.ts +36 -0
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.test.ts +232 -0
- package/src/theme/tokens/primitives.ts +238 -87
- package/src/theme/tokens/semantic.ts +101 -81
- package/src/theme/workout-tokens.ts +26 -21
- package/src/utils/avatar-color.ts +3 -3
- package/tailwind.config.js +15 -3
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { SessionStatePill } from './SessionStatePill'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof SessionStatePill> = {
|
|
5
|
+
title: 'Shell/SessionStatePill',
|
|
6
|
+
component: SessionStatePill,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
args: { state: 'live' },
|
|
9
|
+
argTypes: {
|
|
10
|
+
state: { control: 'select', options: ['live', 'rest', 'idle'] },
|
|
11
|
+
label: { control: 'text' },
|
|
12
|
+
},
|
|
13
|
+
parameters: {
|
|
14
|
+
docs: {
|
|
15
|
+
description: {
|
|
16
|
+
component:
|
|
17
|
+
'**Molecule** (= the ledger’s reusable StatusPill — also used by the Live-view header). Composes ' +
|
|
18
|
+
'[Indicator](?path=/docs/components-atoms-indicator--docs) (pulse `ping` + vivid color for live) + ' +
|
|
19
|
+
'[Typography](?path=/docs/foundations-typography--docs) (`monoLabel`). Use the `state` control to switch.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
export default meta
|
|
25
|
+
type Story = StoryObj<typeof SessionStatePill>
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SessionStatePill } from './SessionStatePill'
|
|
5
|
+
|
|
6
|
+
describe('SessionStatePill', () => {
|
|
7
|
+
it('labels each state', () => {
|
|
8
|
+
const { rerender } = render(<SessionStatePill state="live" />)
|
|
9
|
+
expect(screen.getByText('LIVE')).toBeInTheDocument()
|
|
10
|
+
rerender(<SessionStatePill state="rest" />)
|
|
11
|
+
expect(screen.getByText('REST')).toBeInTheDocument()
|
|
12
|
+
rerender(<SessionStatePill state="idle" />)
|
|
13
|
+
expect(screen.getByText('IDLE')).toBeInTheDocument()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('supports a custom label', () => {
|
|
17
|
+
render(<SessionStatePill state="live" label="SET LIVE" />)
|
|
18
|
+
expect(screen.getByText('SET LIVE')).toBeInTheDocument()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('has no accessibility violations', async () => {
|
|
22
|
+
const { container } = render(<SessionStatePill state="live" />)
|
|
23
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { View, type ViewProps } from 'react-native'
|
|
2
|
+
import { cn } from '../../utils/cn'
|
|
3
|
+
import { Indicator, type IndicatorColor, type IndicatorPulse } from '../ui/indicator'
|
|
4
|
+
import { Typography, type TypographyColor } from '../custom/Typography'
|
|
5
|
+
|
|
6
|
+
export type SessionState = 'live' | 'rest' | 'idle'
|
|
7
|
+
|
|
8
|
+
export interface SessionStatePillProps extends ViewProps {
|
|
9
|
+
/** Session state → dot color + pulse + label. */
|
|
10
|
+
state: SessionState
|
|
11
|
+
/** Override the default label (LIVE / REST / IDLE). */
|
|
12
|
+
label?: string
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const stateConfig: Record<
|
|
17
|
+
SessionState,
|
|
18
|
+
{
|
|
19
|
+
label: string
|
|
20
|
+
color: IndicatorColor
|
|
21
|
+
pulse: boolean | IndicatorPulse
|
|
22
|
+
textColor: TypographyColor
|
|
23
|
+
}
|
|
24
|
+
> = {
|
|
25
|
+
// live = vivid green with an expanding ring; rest = solid amber (no pulse — operator); idle is dim.
|
|
26
|
+
live: { label: 'LIVE', color: 'live', pulse: 'ping', textColor: 'primary' },
|
|
27
|
+
rest: { label: 'REST', color: 'warning', pulse: false, textColor: 'primary' },
|
|
28
|
+
idle: { label: 'IDLE', color: 'default', pulse: false, textColor: 'secondary' },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The global session-state readout (dot + label). Shared: the top bar's status
|
|
33
|
+
* and the Live-view header both use it (this is the ledger's "StatusPill",
|
|
34
|
+
* Family C — built once). S1 · SessionStatePill.
|
|
35
|
+
*/
|
|
36
|
+
export function SessionStatePill({ state, label, className, ...props }: SessionStatePillProps) {
|
|
37
|
+
const cfg = stateConfig[state]
|
|
38
|
+
return (
|
|
39
|
+
<View className={cn('flex-row items-center gap-2', className)} {...props}>
|
|
40
|
+
<Indicator size="md" color={cfg.color} pulse={cfg.pulse} />
|
|
41
|
+
<Typography variant="monoLabel" color={cfg.textColor} className="text-[11px]">
|
|
42
|
+
{label ?? cfg.label}
|
|
43
|
+
</Typography>
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useArgs } from 'storybook/preview-api'
|
|
3
|
+
import { View } from 'react-native'
|
|
4
|
+
import { SideNav } from './SideNav'
|
|
5
|
+
import { Typography } from '../custom/Typography'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof SideNav> = {
|
|
8
|
+
title: 'Shell/SideNav',
|
|
9
|
+
component: SideNav,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
args: { activeKey: 'live', liveKey: null },
|
|
12
|
+
argTypes: {
|
|
13
|
+
activeKey: { control: 'select', options: ['live', 'review', 'program', 'body'] },
|
|
14
|
+
liveKey: { control: 'select', options: [null, 'live', 'review', 'program', 'body'] },
|
|
15
|
+
onNavigate: { action: 'navigate' },
|
|
16
|
+
},
|
|
17
|
+
// Interactive AND Controls-synced: clicking a category writes `activeKey` back to the
|
|
18
|
+
// story args (canvas + Controls stay in lock-step) and still fires the `onNavigate`
|
|
19
|
+
// action. `useArgs` must be called directly in the story/render fn (not a child
|
|
20
|
+
// component), so the rules-of-hooks lint is disabled here — the idiomatic SB pattern.
|
|
21
|
+
render: (args) => {
|
|
22
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
23
|
+
const [, updateArgs] = useArgs()
|
|
24
|
+
return (
|
|
25
|
+
<SideNav
|
|
26
|
+
{...args}
|
|
27
|
+
onNavigate={(key) => {
|
|
28
|
+
updateArgs({ activeKey: key })
|
|
29
|
+
args.onNavigate?.(key)
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
},
|
|
34
|
+
// Render in-context: full-height shell frame so the rail snaps to the left edge and
|
|
35
|
+
// stretches with the viewport (resize the canvas height to see it hold).
|
|
36
|
+
decorators: [
|
|
37
|
+
(Story) => (
|
|
38
|
+
<View className="min-h-screen flex-row bg-background-default">
|
|
39
|
+
<Story />
|
|
40
|
+
<View className="flex-1 items-center justify-center">
|
|
41
|
+
<Typography variant="body2" color="tertiary">
|
|
42
|
+
main viewport
|
|
43
|
+
</Typography>
|
|
44
|
+
</View>
|
|
45
|
+
</View>
|
|
46
|
+
),
|
|
47
|
+
],
|
|
48
|
+
parameters: {
|
|
49
|
+
layout: 'fullscreen',
|
|
50
|
+
docs: {
|
|
51
|
+
description: {
|
|
52
|
+
component:
|
|
53
|
+
'**Organism** (shell region). The persistent 60px left rail switching Live · Review · Plan · ' +
|
|
54
|
+
'Body. Composes [NavItem](?path=/docs/shell-navitem--docs) × the four categories + the ' +
|
|
55
|
+
'shared [icon set](?path=/docs/foundations-icons--docs). Presentational — drive it with `activeKey` / ' +
|
|
56
|
+
'`onNavigate` / `liveKey`. Active = left accent bar; `liveKey` (when not active) tints that ' +
|
|
57
|
+
'label a muted green.\n\n' +
|
|
58
|
+
'**Try it:** stories render in a full-height shell frame — the rail pins to the left edge and ' +
|
|
59
|
+
'stretches to the viewport, items staying top-aligned. **Resize the canvas** to see it hold at ' +
|
|
60
|
+
'any height. Use the **Controls** to move `activeKey` / `liveKey`.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
export default meta
|
|
66
|
+
type Story = StoryObj<typeof SideNav>
|
|
67
|
+
|
|
68
|
+
export const Default: Story = {}
|
|
69
|
+
|
|
70
|
+
export const LiveElsewhere: Story = {
|
|
71
|
+
args: { activeKey: 'program', liveKey: 'live' },
|
|
72
|
+
parameters: {
|
|
73
|
+
docs: {
|
|
74
|
+
description: {
|
|
75
|
+
story:
|
|
76
|
+
'A set runs on Live while the operator is on Plan → the Live item carries a quiet green label cue. ' +
|
|
77
|
+
'(Still clickable — try switching away and back.)',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent, within } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { SideNav } from './SideNav'
|
|
5
|
+
|
|
6
|
+
describe('SideNav', () => {
|
|
7
|
+
it('renders the four default categories as tabs in a tablist', () => {
|
|
8
|
+
render(<SideNav activeKey="live" />)
|
|
9
|
+
expect(screen.getByRole('tablist')).toBeInTheDocument()
|
|
10
|
+
;['Live', 'Review', 'Plan', 'Body'].forEach((name) => {
|
|
11
|
+
expect(screen.getByRole('tab', { name })).toBeInTheDocument()
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('shows the accent bar only on the active category', () => {
|
|
16
|
+
render(<SideNav activeKey="program" />)
|
|
17
|
+
expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
|
|
18
|
+
expect(
|
|
19
|
+
within(screen.getByRole('tab', { name: 'Plan' })).getByTestId('nav-item-accent')
|
|
20
|
+
).toBeInTheDocument()
|
|
21
|
+
expect(
|
|
22
|
+
within(screen.getByRole('tab', { name: 'Live' })).queryByTestId('nav-item-accent')
|
|
23
|
+
).toBeNull()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('reports the tapped key via onNavigate', () => {
|
|
27
|
+
const onNavigate = vi.fn()
|
|
28
|
+
render(<SideNav activeKey="live" onNavigate={onNavigate} />)
|
|
29
|
+
fireEvent.click(screen.getByRole('tab', { name: 'Body' }))
|
|
30
|
+
expect(onNavigate).toHaveBeenCalledWith('body')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('keeps the active item as active even when it is also the liveKey (no double-state)', () => {
|
|
34
|
+
render(<SideNav activeKey="live" liveKey="live" />)
|
|
35
|
+
// active wins: exactly one accent bar, on Live (the live cue never applies to the active view)
|
|
36
|
+
expect(screen.getAllByTestId('nav-item-accent')).toHaveLength(1)
|
|
37
|
+
expect(
|
|
38
|
+
within(screen.getByRole('tab', { name: 'Live' })).getByTestId('nav-item-accent')
|
|
39
|
+
).toBeInTheDocument()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('accepts custom items', () => {
|
|
43
|
+
render(<SideNav activeKey="a" items={[{ key: 'a', label: 'Alpha', icon: null }]} />)
|
|
44
|
+
expect(screen.getByRole('tab', { name: 'Alpha' })).toBeInTheDocument()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('has no accessibility violations', async () => {
|
|
48
|
+
const { container } = render(<SideNav activeKey="live" />)
|
|
49
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
50
|
+
})
|
|
51
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { ActivityIcon, HistoryIcon, LayersIcon, PersonStandingIcon } from '../icons'
|
|
5
|
+
import { NavItem } from './NavItem'
|
|
6
|
+
|
|
7
|
+
export interface SideNavItem {
|
|
8
|
+
/** Stable category key — matches `activeKey` / `liveKey`. */
|
|
9
|
+
key: string
|
|
10
|
+
/** Micro-label shown under the glyph. */
|
|
11
|
+
label: string
|
|
12
|
+
/** The glyph node (rendered ~20px via `currentColor`). */
|
|
13
|
+
icon: ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SideNavProps {
|
|
17
|
+
/** Category items, top → bottom. Defaults to the four dashboard categories. */
|
|
18
|
+
items?: SideNavItem[]
|
|
19
|
+
/** Key of the active category. */
|
|
20
|
+
activeKey: string
|
|
21
|
+
/** Called with the tapped item's key. */
|
|
22
|
+
onNavigate?: (key: string) => void
|
|
23
|
+
/**
|
|
24
|
+
* Key of a category with live activity while it is NOT active (e.g. a set
|
|
25
|
+
* running off its view) → a quiet green cue on that item's label.
|
|
26
|
+
*/
|
|
27
|
+
liveKey?: string | null
|
|
28
|
+
className?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The four dashboard categories with their locked S2 glyphs (activity · history · layers · figure). */
|
|
32
|
+
export const defaultNavItems: SideNavItem[] = [
|
|
33
|
+
{ key: 'live', label: 'Live', icon: <ActivityIcon size={20} color="currentColor" /> },
|
|
34
|
+
{ key: 'review', label: 'Review', icon: <HistoryIcon size={20} color="currentColor" /> },
|
|
35
|
+
{ key: 'program', label: 'Plan', icon: <LayersIcon size={20} color="currentColor" /> },
|
|
36
|
+
{ key: 'body', label: 'Body', icon: <PersonStandingIcon size={20} color="currentColor" /> },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Shell S2 · SideNav — the persistent 60px left rail that switches the main
|
|
41
|
+
* viewport between categories (Live · Review · Program · Body). Presentational:
|
|
42
|
+
* it renders `items` and reports taps via `onNavigate`; the app owns routing and
|
|
43
|
+
* which key is `live`. Active item shows a left accent bar; `liveKey` (when not
|
|
44
|
+
* the active view) tints that item's label a muted green. Fixed 60px at every
|
|
45
|
+
* width — labels sit under the glyph, so they never change the rail width.
|
|
46
|
+
*/
|
|
47
|
+
export function SideNav({
|
|
48
|
+
items = defaultNavItems,
|
|
49
|
+
activeKey,
|
|
50
|
+
onNavigate,
|
|
51
|
+
liveKey = null,
|
|
52
|
+
className,
|
|
53
|
+
}: SideNavProps) {
|
|
54
|
+
return (
|
|
55
|
+
<View
|
|
56
|
+
accessibilityRole="tablist"
|
|
57
|
+
className={cn(
|
|
58
|
+
'w-[60px] items-center gap-[6px] border-r border-border bg-background-base py-3',
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{items.map((item) => (
|
|
63
|
+
<NavItem
|
|
64
|
+
key={item.key}
|
|
65
|
+
icon={item.icon}
|
|
66
|
+
label={item.label}
|
|
67
|
+
active={item.key === activeKey}
|
|
68
|
+
live={item.key === liveKey && item.key !== activeKey}
|
|
69
|
+
onPress={() => onNavigate?.(item.key)}
|
|
70
|
+
/>
|
|
71
|
+
))}
|
|
72
|
+
</View>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { TopBar } from './TopBar'
|
|
3
|
+
import { type Device } from './DeviceRow'
|
|
4
|
+
|
|
5
|
+
const DEVICES: Device[] = [
|
|
6
|
+
{ id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
7
|
+
{ id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
|
|
8
|
+
{ id: 'Voltra-77E0', nickname: 'Spare', slot: null, state: 'available' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof TopBar> = {
|
|
12
|
+
title: 'Shell/TopBar',
|
|
13
|
+
component: TopBar,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
args: { state: 'live', devices: DEVICES },
|
|
16
|
+
argTypes: {
|
|
17
|
+
state: { control: 'select', options: ['live', 'rest', 'idle'] },
|
|
18
|
+
devices: { control: 'object' },
|
|
19
|
+
subtitle: { control: 'text' },
|
|
20
|
+
showSubtitle: { control: 'boolean' },
|
|
21
|
+
showClock: { control: 'boolean' },
|
|
22
|
+
time: { control: false },
|
|
23
|
+
onSelectDevice: { control: false },
|
|
24
|
+
},
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: 'fullscreen',
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component:
|
|
30
|
+
'**Organism** — the persistent shell chrome band, and the root of the top-bar family ' +
|
|
31
|
+
'(see `shell/README.md` for the full dependency map). Composes ' +
|
|
32
|
+
'[BrandLockup](?path=/docs/shell-brandlockup--docs) + ' +
|
|
33
|
+
'[SessionStatePill](?path=/docs/shell-sessionstatepill--docs) + ' +
|
|
34
|
+
'[Divider](?path=/docs/components-atoms-divider--docs) (`bg-border-prominent`) + ' +
|
|
35
|
+
'[DeviceMenu](?path=/docs/shell-devicemenu--docs) + ' +
|
|
36
|
+
'[DateTime](?path=/docs/components-molecules-datetime--docs) (`variant="mono"` live clock). ' +
|
|
37
|
+
'Background = the shared `surfaceGradient.chrome` primitive.\n\n' +
|
|
38
|
+
'**Try it:** use the **Controls** to change `state`, edit `devices` (set one to `lost` to see the ' +
|
|
39
|
+
'fault), or toggle `showSubtitle` / `showClock`. **Resize the canvas** to watch the ' +
|
|
40
|
+
'container-responsive collapse (SIZE-D01) — subtitle drops < 1024px, clock < 720px. ' +
|
|
41
|
+
'Click the device glyph to open the menu.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
export default meta
|
|
47
|
+
type Story = StoryObj<typeof TopBar>
|
|
48
|
+
|
|
49
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { TopBar } from './TopBar'
|
|
5
|
+
import { type Device } from './DeviceRow'
|
|
6
|
+
|
|
7
|
+
const devices: Device[] = [
|
|
8
|
+
{ id: 'Voltra-A3F2', nickname: 'Left Cable', slot: 'L', state: 'connected' },
|
|
9
|
+
{ id: 'Voltra-9B1C', nickname: 'Right Cable', slot: 'R', state: 'connected' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const AT_1612 = new Date(2024, 0, 1, 16, 12) // local 16:12
|
|
13
|
+
|
|
14
|
+
describe('TopBar', () => {
|
|
15
|
+
it('renders brand, state and a 24h clock', () => {
|
|
16
|
+
render(<TopBar state="live" devices={devices} time={AT_1612} showClock />)
|
|
17
|
+
expect(screen.getByText('VOLTRAS')).toBeInTheDocument()
|
|
18
|
+
expect(screen.getByText('LIVE')).toBeInTheDocument()
|
|
19
|
+
expect(screen.getByText('16:12')).toBeInTheDocument()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('hides the clock when showClock is false', () => {
|
|
23
|
+
render(<TopBar state="live" devices={devices} time={AT_1612} showClock={false} />)
|
|
24
|
+
expect(screen.queryByText('16:12')).not.toBeInTheDocument()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('hides the subtitle when showSubtitle is false', () => {
|
|
28
|
+
render(<TopBar state="rest" devices={devices} showSubtitle={false} />)
|
|
29
|
+
expect(screen.queryByText('/ wall dashboard')).not.toBeInTheDocument()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('has no accessibility violations', async () => {
|
|
33
|
+
const { container } = render(<TopBar state="live" devices={devices} time={AT_1612} showClock />)
|
|
34
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { View, type LayoutChangeEvent } from 'react-native'
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { surfaceGradient } from '../../theme/gradients'
|
|
5
|
+
import { Divider } from '../ui/divider'
|
|
6
|
+
import { DateTime } from '../custom/DateTime'
|
|
7
|
+
import { BrandLockup } from './BrandLockup'
|
|
8
|
+
import { SessionStatePill, type SessionState } from './SessionStatePill'
|
|
9
|
+
import { DeviceMenu } from './DeviceMenu'
|
|
10
|
+
import { type Device } from './DeviceRow'
|
|
11
|
+
|
|
12
|
+
export interface TopBarProps {
|
|
13
|
+
/** Global session state → the status pill. */
|
|
14
|
+
state: SessionState
|
|
15
|
+
/** Devices for the connection glyph + dropdown. */
|
|
16
|
+
devices: Device[]
|
|
17
|
+
/** The moment to display in the clock (Date/timestamp). Omit for a live ticking clock. */
|
|
18
|
+
time?: number | Date
|
|
19
|
+
/** Brand subtitle. Default "wall dashboard". */
|
|
20
|
+
subtitle?: string
|
|
21
|
+
/** Force the subtitle on/off; defaults to container-responsive (hidden below ~1024px). */
|
|
22
|
+
showSubtitle?: boolean
|
|
23
|
+
/** Force the clock on/off; defaults to container-responsive (hidden below ~720px). */
|
|
24
|
+
showClock?: boolean
|
|
25
|
+
onSelectDevice?: (device: Device) => void
|
|
26
|
+
className?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// SIZE-D01: responsiveness is container-driven (measured width), not a fixed size prop.
|
|
30
|
+
const SUBTITLE_MIN = 1024
|
|
31
|
+
const CLOCK_MIN = 720
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* S1 · TopBar — the persistent shell chrome band. Brand (left) + a right cluster
|
|
35
|
+
* of session state · device glyph · clock (clock edge-pinned so spacing stays
|
|
36
|
+
* stable as the state text changes). Collapses subtitle then clock as its
|
|
37
|
+
* container narrows.
|
|
38
|
+
*/
|
|
39
|
+
export function TopBar({
|
|
40
|
+
state,
|
|
41
|
+
devices,
|
|
42
|
+
time,
|
|
43
|
+
subtitle = 'wall dashboard',
|
|
44
|
+
showSubtitle,
|
|
45
|
+
showClock,
|
|
46
|
+
onSelectDevice,
|
|
47
|
+
className,
|
|
48
|
+
}: TopBarProps) {
|
|
49
|
+
const [width, setWidth] = useState(SUBTITLE_MIN)
|
|
50
|
+
const onLayout = (e: LayoutChangeEvent) => setWidth(e.nativeEvent.layout.width)
|
|
51
|
+
|
|
52
|
+
const subtitleVisible = showSubtitle ?? width >= SUBTITLE_MIN
|
|
53
|
+
const clockVisible = showClock ?? width >= CLOCK_MIN
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<View
|
|
57
|
+
onLayout={onLayout}
|
|
58
|
+
// shared chrome gradient (web); solid bg-surface-elevated is the native fallback
|
|
59
|
+
style={surfaceGradient.chrome() as object}
|
|
60
|
+
className={cn(
|
|
61
|
+
'h-[46px] flex-row items-center gap-[14px] px-4 bg-surface-elevated border-b border-border',
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
<BrandLockup subtitle={subtitle} showSubtitle={subtitleVisible} />
|
|
66
|
+
|
|
67
|
+
{/* right cluster — order: state · device · time (time pinned to the edge) */}
|
|
68
|
+
<View className="ml-auto flex-row items-center gap-[12px]">
|
|
69
|
+
<SessionStatePill state={state} />
|
|
70
|
+
<Divider orientation="vertical" className="h-4 bg-border-prominent" />
|
|
71
|
+
<DeviceMenu devices={devices} onSelectDevice={onSelectDevice} />
|
|
72
|
+
{clockVisible ? (
|
|
73
|
+
<>
|
|
74
|
+
<Divider orientation="vertical" className="h-4 bg-border-prominent" />
|
|
75
|
+
<DateTime
|
|
76
|
+
value={time}
|
|
77
|
+
live={time == null}
|
|
78
|
+
format="time"
|
|
79
|
+
hour12={false}
|
|
80
|
+
variant="mono"
|
|
81
|
+
color="secondary"
|
|
82
|
+
className="text-[11px] min-w-[38px] text-right"
|
|
83
|
+
/>
|
|
84
|
+
</>
|
|
85
|
+
) : null}
|
|
86
|
+
</View>
|
|
87
|
+
</View>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Shell chrome — persistent dashboard frame (nav / top bar / rail / drawers / strip).
|
|
2
|
+
// S1 · Top bar family. (Icons live in the shared `components/icons` primitive.)
|
|
3
|
+
export * from './BrandLockup'
|
|
4
|
+
export * from './SessionStatePill'
|
|
5
|
+
export * from './DeviceIndicator'
|
|
6
|
+
export * from './DeviceRow'
|
|
7
|
+
export * from './DeviceMenu'
|
|
8
|
+
export * from './TopBar'
|
|
9
|
+
// S2 · Side nav family.
|
|
10
|
+
export * from './NavItem'
|
|
11
|
+
export * from './SideNav'
|
|
12
|
+
// S3 · Dashboard shell — composes the nav + top-bar chrome over a content slot.
|
|
13
|
+
export * from './DashboardShell'
|
|
@@ -4,7 +4,7 @@ import { View, Text } from 'react-native'
|
|
|
4
4
|
import { Autocomplete, AutocompleteOption } from './Autocomplete'
|
|
5
5
|
|
|
6
6
|
const meta: Meta<typeof Autocomplete> = {
|
|
7
|
-
title: 'Components/Autocomplete',
|
|
7
|
+
title: 'Components/Molecules/Autocomplete',
|
|
8
8
|
component: Autocomplete,
|
|
9
9
|
tags: ['autodocs'],
|
|
10
10
|
argTypes: {
|
|
@@ -3,7 +3,7 @@ import { Text } from 'react-native'
|
|
|
3
3
|
import { Breadcrumbs, BreadcrumbItem } from './Breadcrumbs'
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof Breadcrumbs> = {
|
|
6
|
-
title: 'Components/Breadcrumbs',
|
|
6
|
+
title: 'Components/Molecules/Breadcrumbs',
|
|
7
7
|
component: Breadcrumbs,
|
|
8
8
|
tags: ['autodocs'],
|
|
9
9
|
argTypes: {
|
|
@@ -5,7 +5,7 @@ import { Drawer, DrawerBody, DrawerFooter } from './Drawer'
|
|
|
5
5
|
import { Button, ButtonText } from '../button'
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof Drawer> = {
|
|
8
|
-
title: 'Components/Drawer',
|
|
8
|
+
title: 'Components/Organisms/Drawer',
|
|
9
9
|
component: Drawer,
|
|
10
10
|
tags: ['autodocs'],
|
|
11
11
|
argTypes: {
|