@camstack/addon-admin-ui 0.1.2 → 0.1.4

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 (127) hide show
  1. package/dist/assets/index-BoVZEQ1j.js +598 -0
  2. package/dist/assets/index-DwSc8ann.css +1 -0
  3. package/{index.html → dist/index.html} +3 -1
  4. package/dist/server/addon.d.ts +11 -0
  5. package/dist/server/addon.js +50 -0
  6. package/dist/server/addon.js.map +1 -0
  7. package/package.json +4 -1
  8. package/src/App.tsx +0 -71
  9. package/src/components/addons/AddonCard.tsx +0 -355
  10. package/src/components/addons/AddonUploadZone.tsx +0 -69
  11. package/src/components/addons/CapabilityBadge.tsx +0 -55
  12. package/src/components/addons/CapabilityMap.tsx +0 -133
  13. package/src/components/addons/UpdatesList.tsx +0 -108
  14. package/src/components/agents/AgentCard.tsx +0 -281
  15. package/src/components/agents/AgentLogs.tsx +0 -231
  16. package/src/components/agents/ProcessList.tsx +0 -127
  17. package/src/components/agents/ProcessTree.tsx +0 -369
  18. package/src/components/agents/TaskList.tsx +0 -68
  19. package/src/components/cameras/CameraCard.tsx +0 -60
  20. package/src/components/cameras/LiveEventsPanel.tsx +0 -91
  21. package/src/components/cameras/ProviderSection.tsx +0 -50
  22. package/src/components/cameras/StreamArea.tsx +0 -107
  23. package/src/components/cameras/tabs/AddonsTab.tsx +0 -113
  24. package/src/components/cameras/tabs/CameraEventsTab.tsx +0 -129
  25. package/src/components/cameras/tabs/PipelineTab.tsx +0 -118
  26. package/src/components/cameras/tabs/StreamsTab.tsx +0 -114
  27. package/src/components/dashboard/BlockPicker.tsx +0 -54
  28. package/src/components/dashboard/BlockWrapper.tsx +0 -97
  29. package/src/components/dashboard/DashboardGrid.tsx +0 -160
  30. package/src/components/dashboard/block-registry.ts +0 -15
  31. package/src/components/dashboard/blocks/PipelineStagesBlock.tsx +0 -39
  32. package/src/components/dashboard/blocks/StorageBlock.tsx +0 -66
  33. package/src/components/dashboard/blocks/SystemStatusBlock.tsx +0 -67
  34. package/src/components/dashboard/blocks/index.ts +0 -32
  35. package/src/components/device/DeviceHeader.tsx +0 -116
  36. package/src/components/device/FloatingPanel.tsx +0 -132
  37. package/src/components/device/FloatingPanelManager.tsx +0 -167
  38. package/src/components/device/PanelContent.tsx +0 -196
  39. package/src/components/device/QuickConfigWizard.tsx +0 -507
  40. package/src/components/device/tabs/DetectionConfigTab.tsx +0 -96
  41. package/src/components/device/tabs/EventsTab.tsx +0 -19
  42. package/src/components/device/tabs/LogsTab.tsx +0 -22
  43. package/src/components/device/tabs/OverviewTab.tsx +0 -104
  44. package/src/components/device/tabs/ProviderSettingsTab.tsx +0 -34
  45. package/src/components/device/tabs/RecordingTab.tsx +0 -47
  46. package/src/components/device/tabs/ReplTab.tsx +0 -153
  47. package/src/components/device/tabs/TrackTrailTab.tsx +0 -49
  48. package/src/components/device/tabs/ZonesTab.tsx +0 -98
  49. package/src/components/device/zone-editor/ZoneCanvas.tsx +0 -354
  50. package/src/components/device/zone-editor/ZoneForm.tsx +0 -128
  51. package/src/components/device/zone-editor/ZoneList.tsx +0 -150
  52. package/src/components/form-builder/FormBuilder.tsx +0 -135
  53. package/src/components/form-builder/FormField.tsx +0 -732
  54. package/src/components/form-builder/ModelSelector.tsx +0 -239
  55. package/src/components/integrations/AddDeviceDialog.tsx +0 -205
  56. package/src/components/integrations/CompactDeviceCard.tsx +0 -35
  57. package/src/components/integrations/DeviceCard.tsx +0 -29
  58. package/src/components/integrations/DeviceDiscoveryStep.tsx +0 -105
  59. package/src/components/integrations/DeviceGrid.tsx +0 -79
  60. package/src/components/integrations/DeviceGroupHeader.tsx +0 -17
  61. package/src/components/integrations/DiscoveredDeviceCard.tsx +0 -26
  62. package/src/components/integrations/IntegrationCard.tsx +0 -40
  63. package/src/components/integrations/IntegrationWizard.tsx +0 -172
  64. package/src/components/integrations/ProviderConfigForm.tsx +0 -89
  65. package/src/components/integrations/ProviderPicker.tsx +0 -91
  66. package/src/components/integrations/SnapshotPopover.tsx +0 -68
  67. package/src/components/metrics/AgentLoad.tsx +0 -105
  68. package/src/components/metrics/IntegrationUsage.tsx +0 -73
  69. package/src/components/metrics/PipelineStatus.tsx +0 -74
  70. package/src/components/metrics/ProcessResources.tsx +0 -123
  71. package/src/components/pipeline/PhaseSettings.tsx +0 -131
  72. package/src/components/shared/CapabilityBadges.tsx +0 -30
  73. package/src/components/shared/ProviderIcon.tsx +0 -42
  74. package/src/components/shared/StatusBadge.tsx +0 -23
  75. package/src/components/shared/WebRtcPlayer.tsx +0 -211
  76. package/src/components/timeline/EventMarker.tsx +0 -32
  77. package/src/components/timeline/TimelineBar.tsx +0 -131
  78. package/src/components/ui/ConfirmDialog.tsx +0 -115
  79. package/src/components/ui/ToastContainer.tsx +0 -92
  80. package/src/contexts/auth-context.tsx +0 -91
  81. package/src/hooks/useBackendClient.ts +0 -6
  82. package/src/hooks/useTheme.ts +0 -1
  83. package/src/i18n/en.json +0 -164
  84. package/src/i18n/index.ts +0 -29
  85. package/src/i18n/it.json +0 -164
  86. package/src/index.css +0 -63
  87. package/src/layouts/AddonPageLoader.tsx +0 -120
  88. package/src/layouts/AppLayout.tsx +0 -254
  89. package/src/layouts/ProtectedRoute.tsx +0 -25
  90. package/src/lib/addon-page-context.ts +0 -29
  91. package/src/lib/backend.ts +0 -16
  92. package/src/main.tsx +0 -21
  93. package/src/pages/AccessDenied.tsx +0 -22
  94. package/src/pages/Cameras.tsx +0 -127
  95. package/src/pages/Dashboard.tsx +0 -6
  96. package/src/pages/DeviceDetail.tsx +0 -175
  97. package/src/pages/IntegrationDetail.tsx +0 -222
  98. package/src/pages/Integrations.tsx +0 -333
  99. package/src/pages/Login.tsx +0 -106
  100. package/src/pages/Metrics.tsx +0 -18
  101. package/src/pages/PipelineConfig.tsx +0 -282
  102. package/src/pages/Showroom.tsx +0 -351
  103. package/src/pages/Timeline.tsx +0 -269
  104. package/src/pages/system/Addons.tsx +0 -396
  105. package/src/pages/system/Agents.tsx +0 -362
  106. package/src/pages/system/Logs.tsx +0 -131
  107. package/src/pages/system/Models.tsx +0 -102
  108. package/src/pages/system/Processes.tsx +0 -129
  109. package/src/pages/system/Repl.tsx +0 -148
  110. package/src/pages/system/Settings.tsx +0 -168
  111. package/src/pages/system/Users.tsx +0 -174
  112. package/src/server/addon.ts +0 -54
  113. package/src/types/config-ui.ts +0 -28
  114. package/src/types/dashboard.ts +0 -39
  115. package/tsconfig.json +0 -29
  116. package/tsconfig.server.json +0 -16
  117. package/tsup.config.ts +0 -20
  118. package/vite.config.ts +0 -68
  119. /package/{public → dist}/brand/logo-dark.svg +0 -0
  120. /package/{public → dist}/brand/logo-horizontal-dark.svg +0 -0
  121. /package/{public → dist}/brand/logo-horizontal-light.svg +0 -0
  122. /package/{public → dist}/brand/logo-light.svg +0 -0
  123. /package/{public → dist}/brand/logo-wide-dark.svg +0 -0
  124. /package/{public → dist}/brand/logo-wide-light.svg +0 -0
  125. /package/{public → dist}/favicon.svg +0 -0
  126. /package/{public → dist}/vendor/react-jsx-runtime.mjs +0 -0
  127. /package/{public → dist}/vendor/react.mjs +0 -0
@@ -1,282 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react'
2
- import { GitBranch, Save, ChevronDown } from 'lucide-react'
3
- import { useTranslation } from 'react-i18next'
4
- import { useBackendClient } from '../hooks/useBackendClient'
5
- import { PipelineBuilder } from '@camstack/ui-library'
6
- import type { PipelineStepDisplayConfig } from '@camstack/ui-library'
7
- import type {
8
- PipelineSchema,
9
- InferenceCapabilities,
10
- PipelineTemplate,
11
- } from '@camstack/types'
12
- import { PhaseSettings } from '../components/pipeline/PhaseSettings'
13
-
14
- // ---------------------------------------------------------------------------
15
- // Global orchestrator settings
16
- // ---------------------------------------------------------------------------
17
- interface OrchestratorSettings {
18
- motionFps: number
19
- detectionFps: number
20
- cooldownMs: number
21
- maxConcurrentInferences: number | null // null = auto-detect
22
- }
23
-
24
- const DEFAULT_ORCHESTRATOR: OrchestratorSettings = {
25
- motionFps: 2,
26
- detectionFps: 5,
27
- cooldownMs: 10000,
28
- maxConcurrentInferences: null,
29
- }
30
-
31
- // No client-side tree building — steps come from pipeline.getDefaultSteps() server-side
32
-
33
- // ---------------------------------------------------------------------------
34
- // Component
35
- // ---------------------------------------------------------------------------
36
-
37
- export function PipelineConfigPage() {
38
- const { t } = useTranslation()
39
- const client = useBackendClient()
40
-
41
- const [loading, setLoading] = useState(true)
42
- const [schema, setSchema] = useState<PipelineSchema | null>(null)
43
- const [capabilities, setCapabilities] = useState<InferenceCapabilities | null>(null)
44
- const [steps, setSteps] = useState<readonly PipelineStepDisplayConfig[]>([])
45
- const [templates, setTemplates] = useState<PipelineTemplate[]>([])
46
- const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null)
47
- const [orchestrator, setOrchestrator] = useState<OrchestratorSettings>(DEFAULT_ORCHESTRATOR)
48
- const [isDirty, setIsDirty] = useState(false)
49
- const [isSaving, setIsSaving] = useState(false)
50
- const [settingsOpen, setSettingsOpen] = useState(true)
51
-
52
- // Load schema + capabilities + templates on mount
53
- useEffect(() => {
54
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
- const trpc = (client as any).trpc
56
- if (!trpc) {
57
- setLoading(false)
58
- return
59
- }
60
-
61
- Promise.all([
62
- trpc.pipeline?.getSchema?.query?.().catch(() => null) as Promise<PipelineSchema | null>,
63
- trpc.inference?.getCapabilities?.query?.().catch(() => null) as Promise<InferenceCapabilities | null>,
64
- trpc.pipeline?.getGlobalSteps?.query?.().catch(() => null) as Promise<PipelineStepDisplayConfig[] | null>,
65
- trpc.pipeline?.getOrchestratorSettings?.query?.().catch(() => null) as Promise<OrchestratorSettings | null>,
66
- trpc.pipeline?.listTemplates?.query?.().catch(() => []) as Promise<PipelineTemplate[]>,
67
- ])
68
- .then(([sch, caps, globalSteps, orchSettings, tpls]) => {
69
- if (sch) setSchema(sch)
70
- if (caps) setCapabilities(caps)
71
- if (globalSteps) setSteps(globalSteps)
72
- if (orchSettings) setOrchestrator(orchSettings)
73
- setTemplates(tpls ?? [])
74
- setLoading(false)
75
- })
76
- .catch(() => setLoading(false))
77
- }, [client])
78
-
79
- const handleStepsChange = useCallback((newSteps: readonly PipelineStepDisplayConfig[]) => {
80
- setSteps(newSteps)
81
- setIsDirty(true)
82
- }, [])
83
-
84
- // Template handlers
85
- const handleSelectTemplate = useCallback(
86
- (id: string | null) => {
87
- setSelectedTemplateId(id)
88
- if (id && schema) {
89
- const tpl = templates.find((t) => t.id === id)
90
- if (tpl?.steps) {
91
- const schemaMap = new Map(
92
- schema.slots.flatMap((s) => s.addons.map((a) => [a.id, a] as const)),
93
- )
94
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
- const convertStep = (s: any): PipelineStepDisplayConfig => {
96
- const addonSchema = schemaMap.get(s.addonId)
97
- return {
98
- addonId: s.addonId,
99
- addonName: addonSchema?.name ?? s.addonId,
100
- slot: addonSchema?.slot ?? 'detector',
101
- inputClasses: addonSchema ? [...addonSchema.inputClasses] : [],
102
- outputClasses: addonSchema ? [...addonSchema.outputClasses] : [],
103
- enabled: s.enabled ?? true,
104
- agentId: s.agentId ?? 'hub',
105
- runtime: s.runtime ?? 'node',
106
- backend: s.backend ?? 'cpu',
107
- modelId: s.modelId ?? addonSchema?.defaultModelId ?? '',
108
- confidence: s.confidence ?? addonSchema?.defaultConfidence ?? 0.3,
109
- classFilters: s.classFilters ? [...s.classFilters] : [],
110
- children: (s.children ?? []).map(convertStep),
111
- }
112
- }
113
- setSteps(tpl.steps.map(convertStep))
114
- }
115
- }
116
- },
117
- [schema, templates],
118
- )
119
-
120
- const handleSaveTemplate = useCallback(
121
- async (name: string, s: readonly PipelineStepDisplayConfig[]) => {
122
- try {
123
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
- const trpc = (client as any).trpc
125
- const tpl = (await trpc.pipeline.saveTemplate.mutate({ name, steps: s })) as PipelineTemplate
126
- setTemplates((prev) => [tpl, ...prev])
127
- setSelectedTemplateId(tpl.id)
128
- } catch {
129
- // ignore — endpoint may not be registered yet
130
- }
131
- },
132
- [client],
133
- )
134
-
135
- const handleUpdateTemplate = useCallback(
136
- async (id: string, s: readonly PipelineStepDisplayConfig[]) => {
137
- try {
138
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
- const trpc = (client as any).trpc
140
- const tpl = (await trpc.pipeline.updateTemplate.mutate({ id, steps: s })) as PipelineTemplate
141
- setTemplates((prev) => prev.map((t) => (t.id === id ? tpl : t)))
142
- } catch {
143
- // ignore
144
- }
145
- },
146
- [client],
147
- )
148
-
149
- const handleDeleteTemplate = useCallback(
150
- async (id: string) => {
151
- try {
152
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
- const trpc = (client as any).trpc
154
- await trpc.pipeline.deleteTemplate.mutate({ id })
155
- setTemplates((prev) => prev.filter((t) => t.id !== id))
156
- if (selectedTemplateId === id) setSelectedTemplateId(null)
157
- } catch {
158
- // ignore
159
- }
160
- },
161
- [client, selectedTemplateId],
162
- )
163
-
164
- async function handleSave() {
165
- setIsSaving(true)
166
- try {
167
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
- const trpc = (client as any).trpc
169
- await Promise.all([
170
- trpc.pipeline.setGlobalSteps.mutate({ steps }),
171
- trpc.pipeline.setOrchestratorSettings.mutate(orchestrator),
172
- ])
173
- setIsDirty(false)
174
- } catch {
175
- // ignore
176
- } finally {
177
- setIsSaving(false)
178
- }
179
- }
180
-
181
- function updateOrchestrator<K extends keyof OrchestratorSettings>(key: K, value: OrchestratorSettings[K]) {
182
- setOrchestrator((prev) => ({ ...prev, [key]: value }))
183
- setIsDirty(true)
184
- }
185
-
186
- if (loading) {
187
- return (
188
- <div className="flex items-center justify-center h-full">
189
- <div className="text-sm text-foreground-subtle animate-pulse">
190
- Loading pipeline schema…
191
- </div>
192
- </div>
193
- )
194
- }
195
-
196
- if (!schema || !capabilities) {
197
- return (
198
- <div className="flex items-center justify-center h-full">
199
- <div className="text-sm text-foreground-subtle">
200
- Pipeline schema not available. Make sure detection addons are loaded.
201
- </div>
202
- </div>
203
- )
204
- }
205
-
206
- return (
207
- <div className="flex h-full overflow-hidden">
208
- {/* Main pipeline area */}
209
- <div className="flex-1 flex flex-col overflow-hidden">
210
- {/* Header */}
211
- <div className="flex items-center justify-between gap-4 px-6 py-4 border-b border-border shrink-0">
212
- <div className="flex items-center gap-2">
213
- <GitBranch className="h-4 w-4 text-foreground-subtle" />
214
- <h1 className="text-lg font-semibold text-foreground">
215
- {t('nav.pipeline', 'Pipeline Config')}
216
- </h1>
217
- </div>
218
- <div className="flex items-center gap-3">
219
- {isDirty && (
220
- <span className="text-[10px] text-amber-400 font-medium">Unsaved changes</span>
221
- )}
222
- <button
223
- onClick={handleSave}
224
- disabled={!isDirty || isSaving}
225
- className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
226
- isDirty && !isSaving
227
- ? 'bg-primary text-white hover:bg-primary/90'
228
- : 'bg-surface text-foreground-subtle border border-border cursor-not-allowed'
229
- }`}
230
- >
231
- <Save className="h-3.5 w-3.5" />
232
- {isSaving ? 'Saving…' : 'Save Pipeline'}
233
- </button>
234
- </div>
235
- </div>
236
-
237
- {/* PipelineBuilder from ui-library */}
238
- <div className="flex-1 overflow-y-auto p-6">
239
- <PipelineBuilder
240
- schema={schema}
241
- capabilities={capabilities}
242
- steps={steps}
243
- onChange={handleStepsChange}
244
- templates={templates}
245
- selectedTemplateId={selectedTemplateId}
246
- onSelectTemplate={handleSelectTemplate}
247
- onSaveTemplate={handleSaveTemplate}
248
- onUpdateTemplate={handleUpdateTemplate}
249
- onDeleteTemplate={handleDeleteTemplate}
250
- />
251
- </div>
252
- </div>
253
-
254
- {/* Settings sidebar */}
255
- <aside className="w-72 shrink-0 border-l border-border flex flex-col overflow-hidden bg-surface/40">
256
- <button
257
- onClick={() => setSettingsOpen(!settingsOpen)}
258
- className="flex items-center justify-between gap-2 px-4 py-3 border-b border-border text-xs font-semibold text-foreground uppercase tracking-wider hover:bg-surface-hover transition-colors"
259
- >
260
- <span>Settings</span>
261
- <ChevronDown
262
- className={`h-3.5 w-3.5 text-foreground-subtle transition-transform ${settingsOpen ? '' : '-rotate-90'}`}
263
- />
264
- </button>
265
- {settingsOpen && (
266
- <div className="flex-1 overflow-y-auto p-4">
267
- <PhaseSettings
268
- motionFps={orchestrator.motionFps}
269
- detectionFps={orchestrator.detectionFps}
270
- cooldownMs={orchestrator.cooldownMs}
271
- maxConcurrentInferences={orchestrator.maxConcurrentInferences}
272
- onMotionFpsChange={(v) => updateOrchestrator('motionFps', v)}
273
- onDetectionFpsChange={(v) => updateOrchestrator('detectionFps', v)}
274
- onCooldownMsChange={(v) => updateOrchestrator('cooldownMs', v)}
275
- onMaxConcurrentInferencesChange={(v) => updateOrchestrator('maxConcurrentInferences', v)}
276
- />
277
- </div>
278
- )}
279
- </aside>
280
- </div>
281
- )
282
- }
@@ -1,351 +0,0 @@
1
- import { useState } from 'react'
2
- import { StatusBadge } from '../components/shared/StatusBadge'
3
- import { CapabilityBadges } from '../components/shared/CapabilityBadges'
4
- import { ProviderIcon } from '../components/shared/ProviderIcon'
5
- import { FormBuilder } from '../components/form-builder/FormBuilder'
6
- import type { ConfigUISchema } from '../types/config-ui'
7
-
8
- // ---------------------------------------------------------------------------
9
- // Helper components
10
- // ---------------------------------------------------------------------------
11
-
12
- function Section({ title, children }: { title: string; children: React.ReactNode }) {
13
- return (
14
- <div className="rounded-lg border border-border bg-surface overflow-hidden">
15
- <div className="border-b border-border px-4 py-2.5">
16
- <h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">{title}</h2>
17
- </div>
18
- <div className="p-4">{children}</div>
19
- </div>
20
- )
21
- }
22
-
23
- function ColorSwatch({ name, cssVar, hex }: { name: string; cssVar?: string; hex?: string }) {
24
- const bg = cssVar ? `var(${cssVar})` : hex
25
- return (
26
- <div className="flex flex-col items-center gap-1.5">
27
- <div
28
- className="h-10 w-10 rounded-lg border border-border shadow-inner"
29
- style={{ backgroundColor: bg }}
30
- />
31
- <span className="text-[10px] text-foreground-subtle text-center leading-tight">{name}</span>
32
- </div>
33
- )
34
- }
35
-
36
- // ---------------------------------------------------------------------------
37
- // Demo FormBuilder schema
38
- // ---------------------------------------------------------------------------
39
-
40
- const DEMO_SCHEMA: ConfigUISchema = {
41
- sections: [
42
- {
43
- id: 'demo',
44
- title: 'Demo Fields',
45
- columns: 2,
46
- fields: [
47
- { key: 'name', type: 'text', label: 'Name', placeholder: 'Enter name' },
48
- { key: 'count', type: 'number', label: 'Count', min: 0, max: 100, unit: 'items' },
49
- { key: 'enabled', type: 'boolean', label: 'Enabled' },
50
- { key: 'mode', type: 'select', label: 'Mode', options: [{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }] },
51
- { key: 'tags', type: 'tags', label: 'Tags', span: 2, suggestions: ['camera', 'zone', 'alert'] },
52
- { key: 'confidence', type: 'slider', label: 'Confidence', min: 0, max: 100, step: 5, unit: '%', span: 2 },
53
- { key: 'info', type: 'info', label: 'Note', content: 'This is an informational message for the user.', variant: 'info' },
54
- ],
55
- },
56
- ],
57
- }
58
-
59
- // ---------------------------------------------------------------------------
60
- // Page
61
- // ---------------------------------------------------------------------------
62
-
63
- export function ShowroomPage() {
64
- const [formValues, setFormValues] = useState<Record<string, unknown>>({
65
- name: 'Example',
66
- count: 10,
67
- enabled: true,
68
- mode: 'a',
69
- tags: ['camera'],
70
- confidence: 60,
71
- })
72
-
73
- return (
74
- <div className="p-6 space-y-6 max-w-5xl mx-auto">
75
- <div className="mb-6">
76
- <h1 className="text-xl font-bold text-foreground">Component Showroom</h1>
77
- <p className="text-sm text-foreground-subtle mt-1">All UI components at a glance</p>
78
- </div>
79
-
80
- {/* ------------------------------------------------------------------ */}
81
- {/* 1. Typography */}
82
- {/* ------------------------------------------------------------------ */}
83
- <Section title="Typography">
84
- <div className="space-y-3">
85
- <h1 className="text-2xl font-bold text-foreground">Heading 1 — Display title</h1>
86
- <h2 className="text-xl font-semibold text-foreground">Heading 2 — Section title</h2>
87
- <h3 className="text-base font-semibold text-foreground">Heading 3 — Card title</h3>
88
- <h4 className="text-sm font-semibold text-foreground">Heading 4 — Sub-section</h4>
89
- <p className="text-sm text-foreground">Body text — Regular paragraph content goes here. It uses the foreground color.</p>
90
- <p className="text-sm text-foreground-subtle">Subtle text — Secondary/descriptive text with reduced emphasis.</p>
91
- <p className="text-xs text-foreground-subtle uppercase tracking-wider font-semibold">Label / Caption</p>
92
- <p className="text-xs text-foreground-disabled">Disabled / placeholder text</p>
93
- </div>
94
- </Section>
95
-
96
- {/* ------------------------------------------------------------------ */}
97
- {/* 2. Colors */}
98
- {/* ------------------------------------------------------------------ */}
99
- <Section title="Colors">
100
- <div className="flex flex-wrap gap-4">
101
- <ColorSwatch name="primary" cssVar="--color-primary" />
102
- <ColorSwatch name="background" cssVar="--color-background" />
103
- <ColorSwatch name="surface" cssVar="--color-surface" />
104
- <ColorSwatch name="foreground" cssVar="--color-foreground" />
105
- <ColorSwatch name="success" cssVar="--color-success" />
106
- <ColorSwatch name="warning" cssVar="--color-warning" />
107
- <ColorSwatch name="danger" cssVar="--color-danger" />
108
- <ColorSwatch name="info" cssVar="--color-info" />
109
- <ColorSwatch name="border" cssVar="--color-border" />
110
- <div className="w-px" />
111
- <ColorSwatch name="frigate" hex="#3b82f6" />
112
- <ColorSwatch name="scrypted" hex="#a855f7" />
113
- <ColorSwatch name="reolink" hex="#06b6d4" />
114
- <ColorSwatch name="ha" hex="#22d3ee" />
115
- <ColorSwatch name="rtsp" hex="#78716c" />
116
- </div>
117
- </Section>
118
-
119
- {/* ------------------------------------------------------------------ */}
120
- {/* 3. Buttons */}
121
- {/* ------------------------------------------------------------------ */}
122
- <Section title="Buttons">
123
- <div className="flex flex-wrap gap-3">
124
- <button className="inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-white hover:opacity-90 transition-opacity">
125
- Primary
126
- </button>
127
- <button className="inline-flex items-center gap-1.5 rounded-md border border-border bg-surface px-4 py-2 text-sm font-medium text-foreground hover:bg-surface-hover transition-colors">
128
- Secondary
129
- </button>
130
- <button className="inline-flex items-center gap-1.5 rounded-md border border-danger/40 bg-danger/10 px-4 py-2 text-sm font-medium text-danger hover:bg-danger/20 transition-colors">
131
- Danger
132
- </button>
133
- <button disabled className="inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-white opacity-40 cursor-not-allowed">
134
- Disabled
135
- </button>
136
- <button className="inline-flex items-center gap-1.5 rounded-md border border-warning/40 bg-warning/10 px-4 py-2 text-sm font-medium text-warning hover:bg-warning/20 transition-colors">
137
- Warning
138
- </button>
139
- <button className="inline-flex items-center gap-1.5 rounded-md border border-success/40 bg-success/10 px-4 py-2 text-sm font-medium text-success hover:bg-success/20 transition-colors">
140
- Success
141
- </button>
142
- </div>
143
- </Section>
144
-
145
- {/* ------------------------------------------------------------------ */}
146
- {/* 4. Inputs */}
147
- {/* ------------------------------------------------------------------ */}
148
- <Section title="Inputs">
149
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
150
- <div>
151
- <label className="block text-xs font-medium text-foreground mb-1">Text input</label>
152
- <input
153
- type="text"
154
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
155
- placeholder="Type something..."
156
- />
157
- </div>
158
- <div>
159
- <label className="block text-xs font-medium text-foreground mb-1">Number input</label>
160
- <input
161
- type="number"
162
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
163
- placeholder="0"
164
- />
165
- </div>
166
- <div>
167
- <label className="block text-xs font-medium text-foreground mb-1">Password input</label>
168
- <input
169
- type="password"
170
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
171
- placeholder="••••••••"
172
- />
173
- </div>
174
- <div>
175
- <label className="block text-xs font-medium text-foreground mb-1">Select</label>
176
- <select className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30">
177
- <option>Option A</option>
178
- <option>Option B</option>
179
- <option>Option C</option>
180
- </select>
181
- </div>
182
- <div className="md:col-span-2">
183
- <label className="block text-xs font-medium text-foreground mb-1">Textarea</label>
184
- <textarea
185
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30 resize-y min-h-[60px]"
186
- placeholder="Enter multi-line text..."
187
- rows={3}
188
- />
189
- </div>
190
- <div>
191
- <label className="flex items-center gap-2 cursor-pointer select-none">
192
- <input type="checkbox" className="h-3.5 w-3.5 rounded border-border accent-primary" defaultChecked />
193
- <span className="text-xs font-medium text-foreground">Checkbox (checked)</span>
194
- </label>
195
- </div>
196
- <div>
197
- <div className="flex items-center justify-between">
198
- <span className="text-xs font-medium text-foreground">Toggle switch</span>
199
- <ToggleDemo />
200
- </div>
201
- </div>
202
- <div>
203
- <label className="block text-xs font-medium text-foreground mb-1">Slider</label>
204
- <input type="range" className="w-full h-1 accent-primary cursor-pointer" min={0} max={100} defaultValue={60} />
205
- </div>
206
- <div>
207
- <label className="block text-xs font-medium text-foreground mb-1">Tags example</label>
208
- <div className="flex flex-wrap gap-1.5 rounded-md border border-border bg-background px-2 py-1.5 min-h-[38px]">
209
- {['camera', 'zone', 'alert'].map((tag) => (
210
- <span key={tag} className="inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-xs text-primary">
211
- {tag}
212
- </span>
213
- ))}
214
- </div>
215
- </div>
216
- </div>
217
- </Section>
218
-
219
- {/* ------------------------------------------------------------------ */}
220
- {/* 5. Badges */}
221
- {/* ------------------------------------------------------------------ */}
222
- <Section title="Badges">
223
- <div className="space-y-3">
224
- <div>
225
- <p className="text-xs text-foreground-subtle mb-2">StatusBadge</p>
226
- <div className="flex flex-wrap gap-2">
227
- {(['running', 'stopped', 'error', 'online', 'offline', 'idle'] as const).map((s) => (
228
- <StatusBadge key={s} status={s} />
229
- ))}
230
- </div>
231
- </div>
232
- <div>
233
- <p className="text-xs text-foreground-subtle mb-2">CapabilityBadges</p>
234
- <CapabilityBadges capabilities={['streaming', 'detection', 'recording', 'audio']} />
235
- </div>
236
- </div>
237
- </Section>
238
-
239
- {/* ------------------------------------------------------------------ */}
240
- {/* 6. Cards */}
241
- {/* ------------------------------------------------------------------ */}
242
- <Section title="Cards">
243
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
244
- <div className="rounded-lg bg-surface p-4">
245
- <h3 className="text-sm font-semibold text-foreground mb-1">Surface card</h3>
246
- <p className="text-xs text-foreground-subtle">Uses `bg-surface`. Default card background.</p>
247
- </div>
248
- <div className="rounded-lg border border-border bg-surface p-4">
249
- <h3 className="text-sm font-semibold text-foreground mb-1">Bordered card</h3>
250
- <p className="text-xs text-foreground-subtle">Uses `border border-border bg-surface`.</p>
251
- </div>
252
- <div className="rounded-lg border border-primary/30 bg-primary/5 p-4">
253
- <h3 className="text-sm font-semibold text-primary mb-1">Accent card</h3>
254
- <p className="text-xs text-foreground-subtle">Highlighted with primary color.</p>
255
- </div>
256
- <div className="rounded-lg bg-background p-4">
257
- <h3 className="text-sm font-semibold text-foreground mb-1">Background card</h3>
258
- <p className="text-xs text-foreground-subtle">Uses `bg-background`. Inset card.</p>
259
- </div>
260
- </div>
261
- </Section>
262
-
263
- {/* ------------------------------------------------------------------ */}
264
- {/* 7. Provider Icons */}
265
- {/* ------------------------------------------------------------------ */}
266
- <Section title="Provider Icons">
267
- <div className="flex flex-wrap gap-4">
268
- {(['frigate', 'scrypted', 'reolink', 'homeassistant', 'rtsp', 'onvif'] as const).map((p) => (
269
- <ProviderIcon key={p} type={p} size="lg" showLabel />
270
- ))}
271
- </div>
272
- </Section>
273
-
274
- {/* ------------------------------------------------------------------ */}
275
- {/* 8. Form Builder Demo */}
276
- {/* ------------------------------------------------------------------ */}
277
- <Section title="Form Builder Demo">
278
- <FormBuilder
279
- schema={DEMO_SCHEMA}
280
- values={formValues}
281
- onChange={setFormValues}
282
- />
283
- <details className="mt-3">
284
- <summary className="text-[10px] text-foreground-subtle cursor-pointer hover:text-foreground">
285
- View current values
286
- </summary>
287
- <pre className="mt-2 rounded-md bg-background p-3 text-[10px] text-foreground-subtle overflow-auto">
288
- {JSON.stringify(formValues, null, 2)}
289
- </pre>
290
- </details>
291
- </Section>
292
-
293
- {/* ------------------------------------------------------------------ */}
294
- {/* 9. Info Boxes */}
295
- {/* ------------------------------------------------------------------ */}
296
- <Section title="Info Boxes">
297
- <div className="space-y-2">
298
- {(
299
- [
300
- { variant: 'info', label: 'Info', content: 'This is an informational message. Use it for tips and hints.' },
301
- { variant: 'warning', label: 'Warning', content: 'This action may have side effects. Proceed with caution.' },
302
- { variant: 'success', label: 'Success', content: 'Operation completed successfully. All systems are green.' },
303
- { variant: 'danger', label: 'Danger', content: 'This operation is irreversible. Data will be permanently deleted.' },
304
- ] as const
305
- ).map(({ variant, label, content }) => {
306
- const STYLES = {
307
- info: { border: 'border-info', bg: 'bg-info/5', text: 'text-info' },
308
- warning: { border: 'border-warning', bg: 'bg-warning/5', text: 'text-warning' },
309
- success: { border: 'border-success', bg: 'bg-success/5', text: 'text-success' },
310
- danger: { border: 'border-danger', bg: 'bg-danger/5', text: 'text-danger' },
311
- }
312
- const s = STYLES[variant]
313
- return (
314
- <div key={variant} className={`rounded-md border-l-4 px-3 py-2.5 ${s.border} ${s.bg}`}>
315
- <p className={`text-xs font-semibold mb-0.5 ${s.text}`}>{label}</p>
316
- <p className="text-xs text-foreground-subtle leading-relaxed">{content}</p>
317
- </div>
318
- )
319
- })}
320
- </div>
321
- </Section>
322
- </div>
323
- )
324
- }
325
-
326
- // ---------------------------------------------------------------------------
327
- // Local ToggleDemo to avoid hook extraction issues
328
- // ---------------------------------------------------------------------------
329
-
330
- function ToggleDemo() {
331
- const [on, setOn] = useState(true)
332
- return (
333
- <button
334
- type="button"
335
- role="switch"
336
- aria-checked={on}
337
- onClick={() => setOn((v) => !v)}
338
- className={[
339
- 'relative inline-flex h-4 w-8 shrink-0 items-center rounded-full transition-colors duration-150 cursor-pointer',
340
- on ? 'bg-primary' : 'bg-foreground-subtle/30',
341
- ].join(' ')}
342
- >
343
- <span
344
- className={[
345
- 'inline-block h-3 w-3 rounded-full bg-white shadow transition-transform duration-150',
346
- on ? 'translate-x-4' : 'translate-x-0.5',
347
- ].join(' ')}
348
- />
349
- </button>
350
- )
351
- }