// frontend/src/pages/dev/StyleGuide.jsx // Development reference only — showcases every UI component with all variants and states. // Not linked from the sidebar. Route: /dev/styleguide (no auth required). import { useState } from 'react' import Button from '@/components/ui/Button' import RowActions from '@/components/ui/RowActions' import StatusBadge from '@/components/ui/StatusBadge' import FormField from '@/components/ui/FormField' import Modal from '@/components/ui/Modal' import DataTable from '@/components/ui/DataTable' import Pagination from '@/components/ui/Pagination' import Spinner from '@/components/ui/Spinner' import PageHeader from '@/components/ui/PageHeader' import { ProfilePageHeader, ProfilePageHeaderActions } from '@/components/ui/ProfilePageHeader' import Card from '@/components/ui/Card' import Tabs from '@/components/ui/Tabs' import { ToastProvider, useToast } from '@/components/ui/Toast' import SearchBar from '@/components/ui/SearchBar' import Select from '@/components/ui/Select' import HeaderSearch from '@/components/ui/HeaderSearch' import Breadcrumbs from '@/components/ui/Breadcrumbs' import ConfirmDialog from '@/components/ui/ConfirmDialog' import Icon, { ICON_NAMES } from '@/components/ui/Icon' import PillButton from '@/components/ui/PillButton' import SegmentedControl from '@/components/ui/SegmentedControl' import IconButtonGroup from '@/components/ui/IconButtonGroup' import DateTimePicker from '@/components/ui/DateTimePicker' // Asset SVG URLs — grouped by folder (loaded at module init, no runtime fetch) const _globalIconUrls = import.meta.glob('../../assets/global-icons/*.svg', { query: '?url', import: 'default', eager: true }) const _sideMenuIconUrls = import.meta.glob('../../assets/side-menu-icons/*.svg', { query: '?url', import: 'default', eager: true }) const _commsIconUrls = import.meta.glob('../../assets/comms/*.svg', { query: '?url', import: 'default', eager: true }) const _otherIconUrls = import.meta.glob('../../assets/other-icons/*.svg', { query: '?url', import: 'default', eager: true }) const _customerStatusUrls = import.meta.glob('../../assets/customer-status/*.svg', { query: '?url', import: 'default', eager: true }) // Helper: { '/path/to/foo-bar.svg': url } → [{ name: 'foo-bar', url }] function urlMapToList(map) { return Object.entries(map).map(([path, url]) => { const name = path.split('/').pop().replace(/\.svg$/, '') return { name, url } }).sort((a, b) => a.name.localeCompare(b.name)) } const ASSET_ICON_GROUPS = [ { title: 'Global Icons', items: urlMapToList(_globalIconUrls) }, { title: 'Side Menu Icons', items: urlMapToList(_sideMenuIconUrls) }, { title: 'Comms', items: urlMapToList(_commsIconUrls) }, { title: 'Other Icons', items: urlMapToList(_otherIconUrls) }, { title: 'Customer Status', items: urlMapToList(_customerStatusUrls) }, ] // --------------------------------------------------------------------------- // Layout helpers — local to this page only // --------------------------------------------------------------------------- function Section({ title, id, children }) { return (

{title}

{children}
) } function Subsection({ title, children }) { return (

{title}

{children}
) } function DemoRow({ children, wrap = false }) { return (
{children}
) } function DemoGrid({ cols = 3, children }) { return (
{children}
) } function DemoCell({ label, children, centered = false }) { return (
{children} {label}
) } function Swatch({ token, value }) { return (
{token}
) } // --------------------------------------------------------------------------- // Demo sub-components (need hooks, so they're standalone components) // --------------------------------------------------------------------------- function DateTimePickerDemo() { const [val, setVal] = useState('') const [val2, setVal2] = useState('2026-03-05T14:30:00.000Z') return (
{val && ( ISO: {val} )} {}} error="This field is required" /> {}} disabled />
) } function TabsLineDemo() { const [active, setActive] = useState('overview') const tabs = [ { key: 'overview', label: 'Overview' }, { key: 'financials', label: 'Financials' }, { key: 'orders', label: 'Orders' }, { key: 'support', label: 'Support' }, ] return (
Active tab: {active}
) } function TabsPillDemo() { const [active, setActive] = useState('devices') const tabs = [ { key: 'devices', label: 'Devices' }, { key: 'firmware', label: 'Firmware' }, { key: 'mqtt', label: 'MQTT' }, ] return } function TabsIconDemo() { const [active, setActive] = useState('customers') const tabs = [ { key: 'customers', label: 'Customers', count: 142, icon: , }, { key: 'orders', label: 'Orders', count: 28, icon: , }, { key: 'settings', label: 'Settings', icon: , }, ] return } function ToastDemo() { const { toast } = useToast() return ( ) } function HeaderSearchDemo() { const [value, setValue] = useState('') return } function SearchBarControlledDemo() { const [value, setValue] = useState('Bell Unit') return (

Current value: "{value}"

) } function ConfirmDialogDemo() { const [dangerOpen, setDangerOpen] = useState(false) const [primaryOpen, setPrimaryOpen] = useState(false) const [loading, setLoading] = useState(false) function handleConfirm(setter) { setLoading(true) setTimeout(() => { setLoading(false); setter(false) }, 1800) } return (
variant="danger"
variant="primary"
setDangerOpen(false)} onConfirm={() => handleConfirm(setDangerOpen)} title="Delete Device" message="This will permanently delete Bell Unit Alpha and all associated data. This action cannot be undone." confirmLabel="Delete" variant="danger" loading={loading} /> setPrimaryOpen(false)} onConfirm={() => handleConfirm(setPrimaryOpen)} title="Publish Firmware" message="This will push firmware v2.3.1 to all connected devices in this group. Confirm to proceed." confirmLabel="Publish" variant="primary" loading={loading} />
) } // --------------------------------------------------------------------------- // Sample data for DataTable demos // --------------------------------------------------------------------------- // All columns definition for the interactive column-management demo const TABLE_ALL_COLUMNS = [ { key: 'name', label: 'Name', sortable: true, alwaysOn: true }, { key: 'serial', label: 'Serial', width: '140px' }, { key: 'location', label: 'Location', sortable: true }, { key: 'status', label: 'Status', width: '120px', render: (row) => {row.status} }, { key: 'actions', label: '', width: '110px', align: 'right', render: () => (
, color: 'var(--color-info)', onClick: () => {} }, { label: 'Delete', icon: , color: 'var(--color-danger)', onClick: () => {}, divider: true }, ]} />
) }, ] const TABLE_DATA = [ { id: 1, name: 'Bell Unit Alpha', serial: 'SN-00001', location: 'London', status: 'Online', statusVariant: 'success' }, { id: 2, name: 'Bell Unit Beta', serial: 'SN-00002', location: 'Berlin', status: 'Pending', statusVariant: 'warning' }, { id: 3, name: 'Bell Unit Gamma', serial: 'SN-00003', location: 'Athens', status: 'Offline', statusVariant: 'danger' }, { id: 4, name: 'Bell Unit Delta', serial: 'SN-00004', location: 'New York', status: 'Info', statusVariant: 'info' }, ] // Static column list for non-interactive demos const TABLE_COLUMNS = TABLE_ALL_COLUMNS // --------------------------------------------------------------------------- // Main style guide // --------------------------------------------------------------------------- export default function StyleGuide() { const [modalOpen, setModalOpen] = useState(false) const [modalSize, setModalSize] = useState('md') const [paginationPage, setPaginationPage] = useState(3) const [paginationSize, setPaginationSize] = useState(20) const [formValues, setFormValues] = useState({ text: '', email: '', select: '', textarea: '' }) // DataTable interactive demo state const [tableSortKey, setTableSortKey] = useState('') const [tableSortDir, setTableSortDir] = useState('asc') const [tableVisibleKeys, setTableVisibleKeys] = useState( TABLE_ALL_COLUMNS.map((c) => c.key) ) // Filter bar + table demo state const [filterSearch, setFilterSearch] = useState('') const [filterStatus, setFilterStatus] = useState('') const [filterLocation, setFilterLocation] = useState('') const filteredTableData = TABLE_DATA.filter((row) => { const matchSearch = !filterSearch || row.name.toLowerCase().includes(filterSearch.toLowerCase()) || row.serial.toLowerCase().includes(filterSearch.toLowerCase()) const matchStatus = !filterStatus || row.status.toLowerCase() === filterStatus.toLowerCase() const matchLocation = !filterLocation || row.location.toLowerCase() === filterLocation.toLowerCase() return matchSearch && matchStatus && matchLocation }) const sortedTableData = tableSortKey ? [...TABLE_DATA].sort((a, b) => { const va = (a[tableSortKey] || '').toString().toLowerCase() const vb = (b[tableSortKey] || '').toString().toLowerCase() if (va < vb) return tableSortDir === 'asc' ? -1 : 1 if (va > vb) return tableSortDir === 'asc' ? 1 : -1 return 0 }) : TABLE_DATA const visibleTableColumns = TABLE_ALL_COLUMNS.filter((c) => tableVisibleKeys.includes(c.key)) const handleFormChange = (name) => (e) => setFormValues((v) => ({ ...v, [name]: e.target.value })) return (
{/* ── Dev banner ───────────────────────────────────────────────────── */}
⚠ Development Style Guide — Do not ship in production navigation
{/* ── Page title ─────────────────────────────────────────────────── */}

BellSystems v2 — Component Style Guide

Every UI component with every variant and state. For development reference only.

{/* ── TOC ────────────────────────────────────────────────────────── */} {/* ================================================================ 1. COLOR TOKENS ================================================================ */}
{[ '--color-bg-abyss', '--color-bg-base', '--color-bg-void', '--color-bg-surface', '--color-bg-elevated', '--color-bg-island', '--color-bg-float', ].map((t) => )} {[ '--color-primary', '--color-primary-hover', '--color-primary-container', '--color-primary-subtle', '--color-primary-deep', '--color-primary-inverse', ].map((t) => )} {[ '--color-success', '--color-success-bg', '--color-warning', '--color-warning-bg', '--color-danger', '--color-danger-bg', '--color-info', '--color-info-bg', ].map((t) => )} {['--color-text-primary', '--color-text-secondary', '--color-text-muted', '--color-text-accent'].map( (t) => (
Aa
{t}
) )}
{['--color-border', '--color-border-strong', '--color-border-focus'].map((t) => (
{t}
))}
{/* ================================================================ 2. TYPOGRAPHY ================================================================ */}
{[ { token: '--font-size-2xl', label: '2xl — 56px — Hero KPIs', sample: '1,284' }, { token: '--font-size-xl', label: 'xl — 24px — Page Titles', sample: 'Device Inventory' }, { token: '--font-size-lg', label: 'lg — 18px — Section Headings', sample: 'Recent Activity' }, { token: '--font-size-md', label: 'md — 16px — Card Titles', sample: 'Connection Status' }, { token: '--font-size-base',label: 'base — 14px — Body / Table rows', sample: 'Bell Unit Alpha SN-00001' }, { token: '--font-size-sm', label: 'sm — 12px — Captions / Helper text', sample: 'Last seen 5 minutes ago' }, { token: '--font-size-xs', label: 'xs — 11px — Labels / Chips', sample: 'DEVICE STATUS' }, { token: '--font-size-2xs', label: '2xs — 10px — Sidebar section headers / Micro labels', sample: 'BELL CLOUD' }, ].map(({ token, label, sample }) => (
{label} {sample}
))}
{[ { token: '--font-weight-normal', label: 'normal / 400' }, { token: '--font-weight-medium', label: 'medium / 500' }, { token: '--font-weight-semibold', label: 'semibold / 600' }, { token: '--font-weight-bold', label: 'bold / 700' }, ].map(({ token, label }) => (
Hamburgefons {label}
))}
{[ { token: '--tracking-display', label: '-0.02em — Hero KPI numbers' }, { token: '--tracking-tight', label: '-0.01em — Barlow Condensed headings' }, { token: '--tracking-normal', label: '0em — Default body text' }, { token: '--tracking-snug', label: '0.04em — Sidebar section labels (uppercase)' }, { token: '--tracking-wide', label: '0.08em — Chip labels, all-caps badges' }, ].map(({ token, label }) => (
Bell Cloud {token} {label}
))}
--font-family-display — Barlow Condensed
Device Inventory
BellSystems Control Panel v2
--font-family-base — Onest
Bell Unit Alpha · SN-00042 · Firmware v2.3.1 · Last seen 5 minutes ago via MQTT
0123456789  ·  ABCDEFGHIJKLMNOPQRSTUVWXYZ  ·  abcdefghijklmnopqrstuvwxyz
--font-family-sidebar — Inter · Used for sidebar navigation (section labels, nav items)
Bell Cloud
Devices · Melodies · App Users
Dashboard — active/selected state (white)
SN-00042  |  MAC: AA:BB:CC:DD:EE:FF  |  fw-v2.3.1-stable
{/* ================================================================ 3. SPACING ================================================================ */}
{[1, 2, 3, 4, 5, 6, 8, 10, 12, 16].map((n) => (
--space-{n}
{n * 4}px
))}
{/* ================================================================ 4. BUTTON ================================================================ */}

At rest the button is invisible. It becomes visible on row hover or when open. This is the standard last-column button for all tables.

{['Bell Unit Alpha', 'Bell Unit Beta', 'Bell Unit Gamma'].map((name) => ( ))}
Name Status Actions
{name} Online
size="sm"
size="md"
size="lg"
icon (left)
iconRight (right)
{['sm', 'md', 'lg'].map((s) => ( ))} {['primary', 'secondary', 'ghost', 'danger', 'success', 'table-actions'].map((v) => ( {['sm', 'md', 'lg'].map((s) => ( ))} ))}
variant size="{s}"
{v}
{/* ================================================================ 5. STATUS BADGE ================================================================ */}
{['success', 'warning', 'danger', 'info', 'primary', 'neutral'].map((v) => (
{v} variant="{v}"
))}
{['success', 'warning', 'danger', 'info', 'primary', 'neutral'].map((v) => (
{v} size="sm"
))}
{['success', 'warning', 'danger', 'info', 'primary', 'neutral'].map((v) => ( {v} ))} {[ ['online', 'success'], ['active', 'success'], ['connected', 'success'], ['sold', 'success'], ['claimed', 'success'], ['provisioned','success'], ['flashed', 'info'], ['manufactured','info'], ['pending', 'warning'], ['offline', 'danger'], ['error', 'danger'], ['inactive', 'danger'], ['open', 'primary'], ['draft', 'neutral'], ['sent', 'info'], ['accepted', 'success'], ['rejected', 'danger'], ['paid', 'success'], ['unpaid', 'warning'], ['overdue', 'danger'], ['cancelled', 'neutral'], ['completed', 'success'], ['processing', 'info'], ['shipped', 'primary'], ['delivered', 'success'], ['returned', 'warning'], ].map(([status, v]) => ( {status} ))}
{/* ================================================================ 5b. PILL BUTTON ================================================================ */}
{['neutral', 'primary', 'success', 'warning', 'danger', 'info'].map((v) => (
{v} variant="{v}"
))}
Melody Title Live English
{/* ================================================================ 6a. SEGMENTED CONTROL ================================================================ */}
{['secondary', 'primary', 'ghost'].map((v) => (
{}} options={[ { value: 'inbound', label: 'Inbound' }, { value: 'outbound', label: 'Outbound' }, ]} /> variant="{v}"
))}
{['sm', 'md', 'lg'].map((s) => (
{}} options={[ { value: 'all', label: 'All' }, { value: 'unread', label: 'Unread' }, { value: 'read', label: 'Read' }, ]} /> size="{s}"
))}
{}} options={[ { value: 'sales', label: 'Sales' }, { value: 'support', label: 'Support' }, { value: 'both', label: 'All' }, ]} /> {}} options={[ { value: 'all', label: 'All' }, { value: 'unread', label: 'Unread' }, { value: 'read', label: 'Read' }, { value: 'important', label: 'Bookmarked' }, ]} /> {}} options={[ { value: 'inbound', label: 'Inbound' }, { value: 'outbound', label: 'Outbound' }, ]} />
{/* ================================================================ 6b. ICON BUTTON GROUP ================================================================ */}
, onClick: () => {}, }, { label: 'Sync', icon: , onClick: () => {}, }, ]} /> , onClick: () => {} }, { label: 'Delete', icon: , onClick: () => {} }, { label: 'Share', icon: , onClick: () => {} }, ]} /> {['sm', 'md', 'lg'].map((s) => (
, onClick: () => {} }, { label: 'Sync', icon: , onClick: () => {} }, ]} /> size="{s}"
))}
, onClick: () => {} }, { label: 'Sync', disabled: true, icon: , onClick: () => {} }, ]} />
{/* ================================================================ 6. FORM FIELD ================================================================ */}
{}} /> {}} /> {}} /> {}} /> {}} /> {}} /> {}} /> {}} /> {}} /> {}} />
{/* ================================================================ 7. SPINNER ================================================================ */}
{['sm', 'md', 'lg'].map((s) => (
size="{s}"
))}
{[ ['var(--color-primary)', 'primary'], ['var(--color-success)', 'success'], ['var(--color-warning)', 'warning'], ['var(--color-danger)', 'danger'], ['var(--color-text-muted)', 'muted'], ].map(([color, label]) => (
{label}
))}
{/* ================================================================ 8. DATA TABLE ================================================================ */}

Click a column header to sort. Drag column headers left/right to reorder. Use the grid icon (far right of header) to show/hide columns.

{ setTableSortKey(key); setTableSortDir(dir) }} allColumns={TABLE_ALL_COLUMNS} visibleKeys={tableVisibleKeys} onColumnChange={setTableVisibleKeys} onRowClick={() => {}} />
{}} selectedIds={new Set([2])} />

SearchBar expands to fill remaining space. Filter selects and page-size select are fixed-width with flexShrink: 0. Use flexWrap: 'nowrap' on the row so everything stays on one line.

{(filterSearch || filterStatus || filterLocation) && ( )}
{}} />
{/* ================================================================ 9. ROW ACTIONS ================================================================ */}

The locked last column of every table. Click to open the action menu. Closes on any outside click.

, onClick: () => {} }, { label: 'Edit', icon: , color: 'var(--color-info)', onClick: () => {} }, { label: 'Delete', icon: , color: 'var(--color-danger)', onClick: () => {}, divider: true }, ]} /> , color: 'var(--color-info)', onClick: () => {} }, { label: 'Delete', icon: , color: 'var(--color-danger)', onClick: () => {}, divider: true }, ]} /> , onClick: () => {} }]} />
{/* ================================================================ 10. PAGINATION ================================================================ */}
{}} /> {}} /> {}} /> {}} />
{/* ================================================================ 10. PAGE HEADER ================================================================ */} {/* ================================================================ 10b. PROFILE PAGE HEADER ================================================================ */}
Active} />
Blocked} />
{}} badge={Active} />
Active} >
{}} badge={Active} >
{/* ================================================================ 11. MODAL ================================================================ */} {/* ================================================================ 12. CARD ================================================================ */}

Body content goes here.

Body content goes here.

Body content goes here.

} > Online
{r.status} }, ]} data={[ { id: '#ORD-001', status: 'Paid', sv: 'success' }, { id: '#ORD-002', status: 'Pending', sv: 'warning' }, { id: '#ORD-003', status: 'Overdue', sv: 'danger' }, ]} />
{/* ================================================================ 13. TABS ================================================================ */}
{/* ================================================================ 14. TOAST ================================================================ */}
{/* ================================================================ 15. SEARCH BAR ================================================================ */} {/* ================================================================ 16. HEADER SEARCH ================================================================ */}

Used exclusively in the fixed top bar. Background: --color-bg-elevated. Fully rounded (--radius-full). No border, no shadow. Active state uses --color-bg-island, no outline.

{/* ================================================================ 17. BREADCRUMBS ================================================================ */} {/* ================================================================ 17. CONFIRM DIALOG ================================================================ */}
{/* ================================================================ 18. ICON ================================================================ */}
{ICON_NAMES.map((name) => (
))}
{[ ['var(--color-primary)', 'primary'], ['var(--color-success)', 'success'], ['var(--color-warning)', 'warning'], ['var(--color-danger)', 'danger'], ['var(--color-info)', 'info'], ['var(--color-text-muted)', 'muted'], ].map(([color, label]) => (
{label}
))}
{/* Asset SVGs from /assets/ — displayed with brightness filter for dark bg */} {ASSET_ICON_GROUPS.map(group => ( {group.items.map(({ name, url }) => ( {name} ))} ))}
{/* ── Masonry Grid ────────────────────────────────────────────────── */}

The default layout for all content pages with multiple variable-height sections. Sections fill left-to-right across the top, then each new section drops into the shortest column automatically. Use masonry-grid masonry-grid--N (N = 2, 3, or 4) as a wrapper inside .page-wrapper.

{['Field one', 'Field two', 'Field three'].map(f => (
{f}
))}
Field one
{['Field one', 'Field two'].map(f => (
{f}
))}
{[ { label: 'Section A', rows: 3 }, { label: 'Section B', rows: 1 }, { label: 'Section C', rows: 4 }, { label: 'Section D → col 2 (shortest)', rows: 2, accent: true }, { label: 'Section E → col 1 (next shortest)', rows: 1, accent: true }, ].map(({ label, rows, accent }) => (
{Array.from({ length: rows }).map((_, i) => (
))}
))}
{`// Inside .page-wrapper — sections auto-flow into shortest column
… … … {/* drops into shortest column */}
// Available: masonry-grid--2 masonry-grid--3 masonry-grid--4 // Responsive: collapses to fewer columns at 1024px, single column at 768px`}
{/* ── DateTimePicker ─────────────────────────────────────────────── */}
{`import DateTimePicker from '@/components/ui/DateTimePicker'

// value is an ISO string or ''; onChange receives an ISO string
 setForm(f => ({ ...f, occurred_at: iso }))}
/>

// With validation error
`}
{/* ── Footer ─────────────────────────────────────────────────────── */}
BellSystems Control Panel v2 — Style Guide /dev/styleguide
) }