@vadimcomanescu/nadicode-design-system 5.0.0 → 5.0.2

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 (124) hide show
  1. package/.agents/skills/seed/SKILL.md +100 -24
  2. package/.agents/skills/seed/references/composition.md +37 -21
  3. package/.agents/skills/seed/references/glass-and-effects.md +7 -5
  4. package/.agents/skills/seed/references/responsive.md +22 -63
  5. package/.agents/skills/seed/references/state-machines.md +54 -46
  6. package/contracts/release-governance-baseline.json +0 -1
  7. package/dist/{TeamPage-BX3RMSZU.js → TeamPage-UO3V2JDK.js} +33 -32
  8. package/dist/catalog/catalog.d.ts +66 -4
  9. package/dist/catalog/catalog.js +9 -9
  10. package/dist/catalog/components.d.ts +1 -2
  11. package/dist/catalog/components.js +27 -27
  12. package/dist/catalog/definitions/blocks-agent.d.ts +0 -1
  13. package/dist/catalog/definitions/blocks-agent.js +1 -1
  14. package/dist/catalog/definitions/blocks-auth.d.ts +16 -0
  15. package/dist/catalog/definitions/blocks-auth.js +1 -1
  16. package/dist/catalog/definitions/blocks-content.d.ts +18 -0
  17. package/dist/catalog/definitions/blocks-content.js +1 -1
  18. package/dist/catalog/definitions/blocks-crud.d.ts +22 -2
  19. package/dist/catalog/definitions/blocks-crud.js +1 -1
  20. package/dist/catalog/definitions/blocks-data.d.ts +2 -0
  21. package/dist/catalog/definitions/blocks-data.js +1 -1
  22. package/dist/catalog/definitions/blocks-marketing.d.ts +8 -0
  23. package/dist/catalog/definitions/blocks-marketing.js +1 -1
  24. package/dist/catalog/definitions/chat.d.ts +0 -1
  25. package/dist/catalog/definitions/chat.js +1 -1
  26. package/dist/catalog/definitions/index.d.ts +66 -4
  27. package/dist/catalog/definitions/index.js +8 -8
  28. package/dist/catalog/primitives/chat.js +2 -2
  29. package/dist/catalog/primitives/index.js +3 -3
  30. package/dist/{chunk-C7LO3JEF.js → chunk-33UWVOQ6.js} +44 -12
  31. package/dist/{chunk-IHGZJS7N.js → chunk-3FJG5MAT.js} +8 -2
  32. package/dist/{chunk-UOUERAB4.js → chunk-3LROWCZE.js} +26 -8
  33. package/dist/{chunk-UIYX2EMT.js → chunk-5RBO2IMZ.js} +7 -2
  34. package/dist/{chunk-7SRNVMBY.js → chunk-7LYRUSWM.js} +8 -3
  35. package/dist/{chunk-ZND7AAWG.js → chunk-A4QNTJUR.js} +4 -2
  36. package/dist/{chunk-P7CTBRHO.js → chunk-AP5SGSN7.js} +15 -5
  37. package/dist/{chunk-5MLOZVOQ.js → chunk-B4373MDA.js} +9 -4
  38. package/dist/{chunk-KV726HMK.js → chunk-BOWKE7OE.js} +4 -4
  39. package/dist/{chunk-NVBF4OWT.js → chunk-BY6LWOHB.js} +7 -2
  40. package/dist/{chunk-4TK2PXMJ.js → chunk-CTVV6JS6.js} +1 -1
  41. package/dist/{chunk-6KRT337C.js → chunk-E4KUPYHE.js} +1 -1
  42. package/dist/{chunk-3TYPQWKD.js → chunk-FSU7ZM5V.js} +7 -2
  43. package/dist/{chunk-QLPU2IEO.js → chunk-FX3GYS5O.js} +27 -4
  44. package/dist/{chunk-OMI2LLXM.js → chunk-H3KTAHJP.js} +17 -3
  45. package/dist/{chunk-UGXI4PES.js → chunk-IW36SVOH.js} +17 -7
  46. package/dist/{chunk-5L2RFJZR.js → chunk-JDHD4L6N.js} +1 -1
  47. package/dist/{chunk-CAET62YQ.js → chunk-NBDUZA66.js} +11 -4
  48. package/dist/{chunk-TNDMBLYP.js → chunk-NDQO7AO6.js} +15 -3
  49. package/dist/{chunk-MT76BPYV.js → chunk-PQOL3E2V.js} +1 -1
  50. package/dist/{chunk-ZNF6S3DQ.js → chunk-QCFDSOTV.js} +9 -5
  51. package/dist/{chunk-ID3N3MHQ.js → chunk-R4SBK6Y5.js} +7 -2
  52. package/dist/{chunk-GNKNT52O.js → chunk-TTDKPZ75.js} +1 -1
  53. package/dist/{chunk-BAB6AHOM.js → chunk-U43JTK45.js} +1 -1
  54. package/dist/{chunk-WHYKEOUV.js → chunk-U5QLJHYN.js} +0 -2
  55. package/dist/{chunk-4ACR6NGQ.js → chunk-UAN2YEI5.js} +3 -7
  56. package/dist/{chunk-NA6ZPKIK.js → chunk-UCFR7GLW.js} +11 -17
  57. package/dist/{chunk-R4DPHCHJ.js → chunk-UMEBNHKR.js} +9 -6
  58. package/dist/{chunk-A44E44UV.js → chunk-UPMSE6PQ.js} +7 -2
  59. package/dist/{chunk-HMN4K4L6.js → chunk-VQVWFCHF.js} +7 -7
  60. package/dist/{chunk-LZ66ADSL.js → chunk-WGQHM56J.js} +25 -25
  61. package/dist/{chunk-JPNNAU26.js → chunk-XLN4NVKU.js} +16 -28
  62. package/dist/{chunk-CPHVCY3M.js → chunk-XUWKGDUY.js} +1 -1
  63. package/dist/{chunk-FAKTXE3F.js → chunk-YEAJLVGB.js} +8 -4
  64. package/dist/{chunk-56TNQ2P4.js → chunk-YMAEXGD2.js} +1 -1
  65. package/dist/{chunk-YNBDA24W.js → chunk-ZKA5X3E4.js} +12 -3
  66. package/dist/{chunk-VUNXVYOH.js → chunk-ZL6BPQNN.js} +19 -10
  67. package/dist/{chunk-XYYZRQES.js → chunk-ZU7MDRCI.js} +1 -1
  68. package/dist/components/blocks/AgentConversationBlock.d.ts +2 -5
  69. package/dist/components/blocks/AgentConversationBlock.js +2 -2
  70. package/dist/components/blocks/AgentWorkbenchBlock.d.ts +2 -5
  71. package/dist/components/blocks/AgentWorkbenchBlock.js +2 -2
  72. package/dist/components/blocks/ApiKeysBlock.d.ts +1 -1
  73. package/dist/components/blocks/ApiKeysBlock.js +1 -1
  74. package/dist/components/blocks/AuthLayout.d.ts +1 -1
  75. package/dist/components/blocks/AuthLayout.js +2 -2
  76. package/dist/components/blocks/CommandPaletteBlock.d.ts +1 -1
  77. package/dist/components/blocks/CommandPaletteBlock.js +1 -1
  78. package/dist/components/blocks/ContactBlock.d.ts +1 -1
  79. package/dist/components/blocks/ContactBlock.js +1 -1
  80. package/dist/components/blocks/CreateBlock.d.ts +1 -1
  81. package/dist/components/blocks/CreateBlock.js +1 -1
  82. package/dist/components/blocks/CrudListBlock.d.ts +1 -1
  83. package/dist/components/blocks/CrudListBlock.js +1 -1
  84. package/dist/components/blocks/DashboardBlock.js +1 -1
  85. package/dist/components/blocks/HeroBlock.d.ts +1 -25
  86. package/dist/components/blocks/HeroBlock.js +1 -1
  87. package/dist/components/blocks/InteractiveAreaChartBlock.d.ts +1 -1
  88. package/dist/components/blocks/InteractiveAreaChartBlock.js +1 -1
  89. package/dist/components/blocks/LoginBlock.d.ts +1 -1
  90. package/dist/components/blocks/LoginBlock.js +1 -1
  91. package/dist/components/blocks/NewsletterBlock.d.ts +3 -1
  92. package/dist/components/blocks/NewsletterBlock.js +1 -1
  93. package/dist/components/blocks/OTPBlock.d.ts +1 -1
  94. package/dist/components/blocks/OTPBlock.js +1 -1
  95. package/dist/components/blocks/OnboardingBlock.d.ts +1 -1
  96. package/dist/components/blocks/OnboardingBlock.js +1 -1
  97. package/dist/components/blocks/OnboardingFlowBlock.d.ts +1 -1
  98. package/dist/components/blocks/OnboardingFlowBlock.js +2 -2
  99. package/dist/components/blocks/PasswordRecoveryBlock.d.ts +1 -1
  100. package/dist/components/blocks/PasswordRecoveryBlock.js +1 -1
  101. package/dist/components/blocks/ResetPasswordBlock.d.ts +1 -1
  102. package/dist/components/blocks/ResetPasswordBlock.js +1 -1
  103. package/dist/components/blocks/TwoFactorSetupBlock.d.ts +1 -1
  104. package/dist/components/blocks/TwoFactorSetupBlock.js +2 -2
  105. package/dist/components/blocks/WizardBlock.d.ts +1 -1
  106. package/dist/components/blocks/WizardBlock.js +1 -1
  107. package/dist/components/blocks/user/InviteUserModal.d.ts +1 -18
  108. package/dist/components/blocks/user/InviteUserModal.js +1 -1
  109. package/dist/components/ui/ChatMessage.d.ts +2 -6
  110. package/dist/components/ui/ChatMessage.js +1 -1
  111. package/dist/components/ui/FormWizard.d.ts +1 -2
  112. package/dist/components/ui/FormWizard.js +1 -1
  113. package/dist/components/ui/KanbanBoard.d.ts +3 -3
  114. package/dist/components/ui/KanbanBoard.js +1 -1
  115. package/dist/index.js +0 -137
  116. package/dist/lib/json-render/app.js +1 -1
  117. package/dist/lib/json-render/catalog.d.ts +28 -0
  118. package/dist/lib/json-render/catalog.js +10 -10
  119. package/dist/lib/json-render/registry.js +10 -10
  120. package/dist/lib/json-render/showcase-spec.js +1 -1
  121. package/dist/lib-index.d.ts +6 -24
  122. package/package.json +1 -5
  123. package/dist/catalog/types.d.ts +0 -4
  124. package/dist/catalog/types.js +0 -1
@@ -34,14 +34,27 @@ Each entry provides: `props` (Zod schema), `description`, `slots`, `events`, `ex
34
34
  import { seedComponents } from "@vadimcomanescu/nadicode-design-system/catalog/components"
35
35
  ```
36
36
 
37
- Usage:
37
+ Usage — spec pattern (preferred):
38
38
  ```tsx
39
- const { LoginBlock, HeroBlock } = seedComponents
40
- // All catalog components use BaseComponentProps — pass props object and emit callback
41
- <LoginBlock
42
- props={{ type: "login", showSocial: true }}
43
- emit={(event) => { if (event === 'submit') handleLogin() }}
44
- />
39
+ import { PageRenderer } from '@json-render/next'
40
+ import { registry } from '@/lib/registry'
41
+
42
+ // Pages are JSON specs rendered via PageRenderer
43
+ const spec: NextAppSpec = {
44
+ routes: {
45
+ '/login': {
46
+ page: {
47
+ root: 'shell',
48
+ elements: {
49
+ shell: { type: 'PageShell', props: { className: null }, children: ['login'] },
50
+ login: { type: 'LoginBlock', props: { type: 'login', error: null, email: null, password: null, fullName: null, showSocial: null, forgotPasswordHref: null, signUpHref: null, signInHref: null } },
51
+ },
52
+ },
53
+ },
54
+ },
55
+ }
56
+
57
+ <PageRenderer spec={spec} registry={registry} />
45
58
  ```
46
59
 
47
60
  **Catalog blocks** are imported exclusively from `seedComponents`. Do not import them via individual subpath exports (ADR 0009). The ESLint rule `nadicode/require-catalog-import` enforces this at lint time.
@@ -105,6 +118,47 @@ The catalog is machine-readable, tested, accessible, and themed. Hand-built bloc
105
118
  | Hardcoded hex in components | Map to semantic tokens | Brand colors via tokens only |
106
119
  | Building a block from 3+ primitives without checking catalog | Query `seedComponentDefinitions` first, use catalog block | Catalog-First Rule |
107
120
  | Reimplementing a catalog block locally | `const { BlockName } = seedComponents` | Prevents drift and wasted work |
121
+ | `emit('submit')` without `$bindState` | Wire form fields with `$bindState` + action params | Events are signals; data flows through state |
122
+
123
+ ---
124
+
125
+ ## Form Data Flow
126
+
127
+ `emit()` is a signal — it carries no data payload. Form field values travel through state bindings, not events.
128
+
129
+ ### The pattern
130
+
131
+ Wire each field to a state path with `$bindState`. Reference those paths in the action `params` when the submit event fires.
132
+
133
+ ```json
134
+ {
135
+ "type": "LoginBlock",
136
+ "props": {
137
+ "type": "login",
138
+ "email": { "$bindState": "/auth/email" },
139
+ "password": { "$bindState": "/auth/password" }
140
+ },
141
+ "on": {
142
+ "submit": {
143
+ "action": "login",
144
+ "params": {
145
+ "email": { "$state": "/auth/email" },
146
+ "password": { "$state": "/auth/password" }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ When the user submits, `emit('submit')` fires. The runtime resolves `$state` references against the current state store and passes `{ email, password }` to the `login` action handler.
154
+
155
+ ### Dual-mode guarantee
156
+
157
+ Blocks work both with and without bindings. Without `$bindState`, the block manages its own internal state and `emit('submit')` still fires — bindings just add the two-way state-sync layer that lets the spec read back the values.
158
+
159
+ ### Binding path convention
160
+
161
+ Use `/form/fieldName` for generic forms and `/auth/fieldName` for authentication fields. The leading slash is required. Paths are scoped to the `StateProvider` wrapping the page.
108
162
 
109
163
  ---
110
164
 
@@ -146,17 +200,39 @@ Token source of truth: `src/lib/tokens.config.js` (authored), `src/index.css` (g
146
200
 
147
201
  Use `Heading` for semantic headings. Use `Typography` for non-heading copy only.
148
202
 
149
- ```tsx
150
- import { seedComponents } from "@vadimcomanescu/nadicode-design-system/catalog/components"
151
- const { Heading, Typography } = seedComponents
152
-
153
- <Heading level={1}>Page title</Heading>
154
- <Heading level={2} size="section">Section title</Heading>
155
- <Heading level={3} size="label">Card title</Heading>
156
-
157
- <Typography>Body copy</Typography>
158
- <Typography variant="small">Meta text</Typography>
159
- <Typography variant="muted">Muted text</Typography>
203
+ ```json
204
+ {
205
+ "page-title": {
206
+ "type": "Heading",
207
+ "props": { "level": 1, "size": null, "className": null },
208
+ "children": ["Page title"]
209
+ },
210
+ "section-title": {
211
+ "type": "Heading",
212
+ "props": { "level": 2, "size": "section", "className": null },
213
+ "children": ["Section title"]
214
+ },
215
+ "card-title": {
216
+ "type": "Heading",
217
+ "props": { "level": 3, "size": "label", "className": null },
218
+ "children": ["Card title"]
219
+ },
220
+ "body-text": {
221
+ "type": "Typography",
222
+ "props": { "variant": null, "className": null },
223
+ "children": ["Body copy"]
224
+ },
225
+ "meta-text": {
226
+ "type": "Typography",
227
+ "props": { "variant": "small", "className": null },
228
+ "children": ["Meta text"]
229
+ },
230
+ "muted-text": {
231
+ "type": "Typography",
232
+ "props": { "variant": "muted", "className": null },
233
+ "children": ["Muted text"]
234
+ }
235
+ }
160
236
  ```
161
237
 
162
238
  **Heading props**
@@ -299,12 +375,12 @@ Do not copy DS catalog files into the app.
299
375
  **Test helpers**
300
376
  Consumer apps can import `SeedTestProvider` from `@vadimcomanescu/nadicode-design-system/test` when they need the package-owned test wrapper. The showcase app keeps its local `@/test/SeedTestProvider` wrapper for in-repo tests.
301
377
 
302
- **LanguageSwitcher** (`@vadimcomanescu/nadicode-design-system/language-switcher`)
303
- Props: `locales` (string[], default `['en','it']`), `value` (current locale), `onLocaleChange` (callback), `className`. Uses `useTranslations('components.languageSwitcher')` for labels. Without `onLocaleChange`, sets a `NEXT_LOCALE` cookie and calls `router.refresh()`.
378
+ **LanguageSwitcher** (catalog: `seedComponents.LanguageSwitcher`)
379
+ Consumed via the catalog. Schema props: `locales` (string[], default `['en','it']`), `value` (current locale), `className`. Fires `emit('localeChange')` for locale changes. Uses `useTranslations('components.languageSwitcher')` for labels. Without a `localeChange` handler, sets a `NEXT_LOCALE` cookie and calls `router.refresh()`.
304
380
 
305
- **SettingsModal** (`@vadimcomanescu/nadicode-design-system/settings-modal`)
306
- Exports: `defaultSettingsTabs`, `SettingsTabDef` (interface), `SettingsModal` (component).
307
- Props: `open`, `onOpenChange`, `defaultTab`, `tabs` (SettingsTabDef[]), `renderContent` (preferred: `(tab: string) => ReactNode`), `ContentComponent` (deprecated). Uses `useTranslations('pages.settings')` for tab labels and headings. The default language tab renders `LanguageSwitcher` automatically.
381
+ **SettingsModal** (catalog: `seedComponents.SettingsModal`)
382
+ Consumed via the catalog. Exports: `defaultSettingsTabs`, `SettingsTabDef` (interface).
383
+ Schema props: `open`, `defaultTab`, `tabs` (SettingsTabDef[]). Fires `emit('openChange')` and `emit('renderContent')` for modal state and tab content. Uses `useTranslations('pages.settings')` for tab labels and headings. The default language tab renders `LanguageSwitcher` automatically.
308
384
 
309
385
  ---
310
386
 
@@ -322,7 +398,7 @@ Every page = **Shell > Sections > Components**.
322
398
 
323
399
  | Intent | Shell | Components |
324
400
  |--------|-------|------------|
325
- | App (dashboard, settings, CRUD, analytics, agents) | `app-shell` | `Sidebar` + top bar + `SearchCommand` |
401
+ | App (dashboard, settings, CRUD, analytics, agents) | `app-shell` | `Sidebar` + top bar + `CommandPaletteBlock` |
326
402
  | Marketing (landing, pricing, blog) | `marketing-shell` | `HeaderBlock` + `FooterBlock` |
327
403
  | Auth | `auth-shell` | `AuthLayout` (split/centered) |
328
404
  | Onboarding | `onboarding-shell` | Minimal header + progress bar |
@@ -26,7 +26,7 @@ Shell -- persistent frame (sidebar, header, footer)
26
26
 
27
27
  | Intent | Shell | Components |
28
28
  |--------|-------|------------|
29
- | App pages (dashboard, settings, CRUD, analytics, agents) | `app-shell` | `Sidebar` + top bar + `SearchCommand` |
29
+ | App pages (dashboard, settings, CRUD, analytics, agents) | `app-shell` | `Sidebar` + top bar + `CommandPaletteBlock` |
30
30
  | Marketing (landing, pricing, blog) | `marketing-shell` | `HeaderBlock` + `FooterBlock` |
31
31
  | Auth (login, signup, reset) | `auth-shell` | `AuthLayout` (split-screen or centered) |
32
32
  | Onboarding | `onboarding-shell` | Minimal header + progress bar, no nav |
@@ -110,26 +110,42 @@ Is it marketing / public?
110
110
 
111
111
  ### Section Template
112
112
 
113
- ```tsx
114
- {/* App section */}
115
- <section className="py-6 md:py-8">
116
- <div className="flex items-center justify-between mb-4">
117
- <Heading level={3} size="subsection" >Section Title</Heading>
118
- <Button variant="outline" size="sm">Action</Button>
119
- </div>
120
- {/* Section content */}
121
- </section>
122
-
123
- {/* Marketing section */}
124
- <section className="py-16 md:py-24">
125
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
126
- <Heading level={2} size="section" className="mb-4">Section Title</Heading>
127
- <Typography variant="lead" className="mb-12 text-text-secondary">
128
- Section description
129
- </Typography>
130
- {/* Section content */}
131
- </div>
132
- </section>
113
+ App section — heading + action bar above content:
114
+
115
+ ```json
116
+ {
117
+ "component": "section",
118
+ "props": { "className": "py-6 md:py-8" },
119
+ "children": [
120
+ {
121
+ "component": "div",
122
+ "props": { "className": "flex items-center justify-between mb-4" },
123
+ "children": [
124
+ { "component": "Heading", "props": { "level": 3, "size": "subsection", "children": "Section Title" } },
125
+ { "component": "Button", "props": { "variant": "outline", "size": "sm", "children": "Action" } }
126
+ ]
127
+ }
128
+ ]
129
+ }
130
+ ```
131
+
132
+ Marketing section — centered heading + lead text above content:
133
+
134
+ ```json
135
+ {
136
+ "component": "section",
137
+ "props": { "className": "py-16 md:py-24" },
138
+ "children": [
139
+ {
140
+ "component": "div",
141
+ "props": { "className": "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center" },
142
+ "children": [
143
+ { "component": "Heading", "props": { "level": 2, "size": "section", "className": "mb-4", "children": "Section Title" } },
144
+ { "component": "Typography", "props": { "variant": "lead", "className": "mb-12 text-text-secondary", "children": "Section description" } }
145
+ ]
146
+ }
147
+ ]
148
+ }
133
149
  ```
134
150
 
135
151
  ---
@@ -95,13 +95,15 @@
95
95
 
96
96
  ### AmbientGrid
97
97
 
98
- Subtle CSS grid overlay for structural rhythm.
98
+ Subtle CSS grid overlay for structural rhythm. Cataloged — use via `seedComponents`.
99
99
 
100
100
  ```tsx
101
- import { AmbientGrid } from "@vadimcomanescu/nadicode-design-system/ambient-grid"
102
- <AmbientGrid /> // 24px cells, 0.06 opacity
103
- <AmbientGrid debug /> // 0.15 opacity for alignment checking
104
- <AmbientGrid size={32} /> // Custom cell size
101
+ import { seedComponents } from "@vadimcomanescu/nadicode-design-system/catalog/components"
102
+ <seedComponents.AmbientGrid props={{}} emit={() => {}} on={{}} />
103
+ // With custom cell size:
104
+ <seedComponents.AmbientGrid props={{ size: 32 }} emit={() => {}} on={{}} />
105
+ // Debug mode (0.15 opacity for alignment checking):
106
+ <seedComponents.AmbientGrid props={{ debug: true }} emit={() => {}} on={{}} />
105
107
  ```
106
108
 
107
109
  ### MeteorShower
@@ -99,78 +99,37 @@ Breakpoint contracts, mobile-first patterns, touch targets, and responsive rules
99
99
 
100
100
  ### Navigation Collapse
101
101
 
102
- ```tsx
103
- // Desktop: full sidebar visible
104
- // Mobile: sheet-based sidebar triggered by hamburger
105
- <SidebarProvider>
106
- <Sidebar className="hidden lg:flex">
107
- {/* Full sidebar */}
108
- </Sidebar>
109
- <main className="flex-1">
110
- <header className="lg:hidden flex items-center p-4">
111
- <SidebarTrigger />
112
- <span className="ml-3 font-medium">Page Title</span>
113
- </header>
114
- {children}
115
- </main>
116
- </SidebarProvider>
117
- ```
102
+ Desktop shows the full sidebar; mobile hides it behind a sheet triggered by a hamburger button.
103
+
104
+ - Sidebar element: `className="hidden lg:flex"` (visible only at `lg:` and up).
105
+ - Mobile header: `className="lg:hidden"` — contains `SidebarTrigger` and the page title.
106
+ - The `SidebarProvider` wraps the layout; the sheet/drawer behavior is built into the `Sidebar` block.
107
+
108
+ CSS classes that drive this pattern: `hidden lg:flex` on the sidebar, `lg:hidden` on the mobile header bar.
118
109
 
119
110
  ### Table to Card List
120
111
 
121
- ```tsx
122
- // Desktop: DataTable. Mobile: Card list.
123
- <div className="hidden sm:block">
124
- <DataTable columns={columns} data={data} />
125
- </div>
126
- <div className="sm:hidden space-y-3">
127
- {data.map((item) => (
128
- <Card key={item.id} className="p-4">
129
- {/* Card representation of row */}
130
- </Card>
131
- ))}
132
- </div>
133
- ```
112
+ At `sm:` and up, render `DataTable`. Below `sm:`, render a card list with one `Card` per row.
113
+
114
+ - Table wrapper: `className="hidden sm:block"`
115
+ - Card list wrapper: `className="sm:hidden space-y-3"`
116
+ - Each card uses `className="p-4"` and mirrors the key fields of the table row.
134
117
 
135
118
  ### Tab Navigation Scroll
136
119
 
137
- ```tsx
138
- // Horizontal scrollable tabs on mobile
139
- <div className="overflow-x-auto -mx-4 px-4">
140
- <TabsList className="inline-flex w-auto min-w-full sm:w-full">
141
- <TabsTrigger value="general">General</TabsTrigger>
142
- <TabsTrigger value="security">Security</TabsTrigger>
143
- <TabsTrigger value="billing">Billing</TabsTrigger>
144
- </TabsList>
145
- </div>
146
- ```
120
+ Settings and similar pages use horizontally scrollable tab lists on mobile so all tabs are reachable without wrapping.
121
+
122
+ - Outer wrapper: `className="overflow-x-auto -mx-4 px-4"` (negative margin bleeds to viewport edge, then re-pads).
123
+ - `TabsList`: `className="inline-flex w-auto min-w-full sm:w-full"` — `inline-flex w-auto` allows scroll; `min-w-full` ensures full width when content is narrow; `sm:w-full` snaps to full width on larger screens.
147
124
 
148
125
  ### Multi-Panel to Drawers
149
126
 
150
- ```tsx
151
- // Desktop: visible panels. Mobile: main content + drawer-triggered panels.
152
- <div className="flex h-dvh">
153
- <aside className="hidden xl:block w-[280px] border-r border-border">
154
- <TeamPanel />
155
- </aside>
156
- <main className="flex-1 flex flex-col">
157
- <header className="xl:hidden flex items-center gap-2 p-3 border-b border-border">
158
- <Sheet>
159
- <SheetTrigger asChild>
160
- <Button variant="ghost" size="icon"><UsersIcon size={16} /></Button>
161
- </SheetTrigger>
162
- <SheetContent side="left"><TeamPanel /></SheetContent>
163
- </Sheet>
164
- <span className="flex-1 text-center font-medium">Agent Chat</span>
165
- </header>
166
- <ConversationArea />
167
- <Composer />
168
- </main>
169
- <aside className="hidden xl:block w-[320px] border-l border-border">
170
- <WorkPanel />
171
- </aside>
172
- </div>
173
- ```
127
+ 3-panel agent workbench: both side panels are visible at `xl:`, the left panel is visible at `lg:`, and on mobile both panels move into sheet drawers.
128
+
129
+ - Left panel (`aside`): `className="hidden xl:block w-[280px] border-r border-border"`.
130
+ - Right panel (`aside`): `className="hidden xl:block w-[320px] border-l border-border"`.
131
+ - Mobile header: `className="xl:hidden"` — contains `Sheet`/`SheetTrigger` buttons to open each panel as a drawer.
132
+ - Main area: `className="flex-1 flex flex-col"` with `h-dvh` on the outer container (never `h-screen`).
174
133
 
175
134
  ---
176
135
 
@@ -79,39 +79,33 @@ Expected wait time?
79
79
 
80
80
  ### Form Implementation Pattern
81
81
 
82
- ```tsx
83
- import { useForm } from "react-hook-form"
84
- import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@vadimcomanescu/nadicode-design-system/form"
85
-
86
- function MyForm() {
87
- const form = useForm<FormValues>({ defaultValues: { name: "" } })
88
- const { formState: { isDirty, isSubmitting } } = form
89
-
90
- return (
91
- <Form {...form}>
92
- <form onSubmit={form.handleSubmit(onSubmit)}>
93
- <FormField
94
- control={form.control}
95
- name="name"
96
- render={({ field }) => (
97
- <FormItem>
98
- <FormLabel>Name</FormLabel> {/* Always stacked, above */}
99
- <FormControl>
100
- <Input {...field} />
101
- </FormControl>
102
- <FormMessage /> {/* Validation error */}
103
- </FormItem>
104
- )}
105
- />
106
- <Button type="submit" disabled={!isDirty || isSubmitting}>
107
- {isSubmitting ? <Spinner /> : "Save"}
108
- </Button>
109
- </form>
110
- </Form>
111
- )
82
+ Forms are specified as JSON element trees. Use `$bindState` to wire form fields to block state, and emit `"submit"` when the form is submitted. The block handles `isDirty` / `isSubmitting` state internally via `useBoundProp`.
83
+
84
+ ```json
85
+ {
86
+ "component": "FormBlock",
87
+ "props": {
88
+ "fields": [
89
+ {
90
+ "name": "name",
91
+ "label": "Name",
92
+ "type": "text",
93
+ "required": true
94
+ }
95
+ ],
96
+ "submitLabel": "Save"
97
+ },
98
+ "on": {
99
+ "submit": "handleSave"
100
+ }
112
101
  }
113
102
  ```
114
103
 
104
+ Key rules:
105
+ - Labels always stacked above their field (never inline).
106
+ - `FormMessage` (field-level validation error) renders below the field automatically.
107
+ - Submit button shows `Spinner` while `isSubmitting`; disabled when form is `pristine`.
108
+
115
109
  ---
116
110
 
117
111
  ## CRUD List States
@@ -213,22 +207,36 @@ function MyForm() {
213
207
 
214
208
  ### Streaming Implementation
215
209
 
216
- ```tsx
217
- <ChatMessage role="assistant">
218
- <ChatMessageContent isStreaming={isStreaming}>
219
- {streamingContent} {/* Updates as tokens arrive; shows cursor when isStreaming */}
220
- </ChatMessageContent>
221
- </ChatMessage>
222
-
223
- <ChatToolCall state="input-available"> {/* input-available | output-available | output-error */}
224
- <ChatToolCallHeader name="search_files" />
225
- <ChatToolCallContent>
226
- <ChatToolCallInput>{JSON.stringify({ query: "utils.ts" }, null, 2)}</ChatToolCallInput>
227
- {toolResult && <ChatToolCallOutput>{toolResult}</ChatToolCallOutput>}
228
- </ChatToolCallContent>
229
- </ChatToolCall>
230
-
231
- <ChatThinkingMessage variant="pill" /> {/* Animated dots; variant="dots" for inline */}
210
+ Streaming chat UI is composed in JSON spec element trees. The block controls `isStreaming` state internally and exposes it through `$bindState`. Each message, tool call, and thinking indicator maps to a catalog component:
211
+
212
+ - `ChatMessage` — wraps a single turn; `props.role` is `"assistant"` or `"user"`.
213
+ - `ChatMessageContent` renders streaming text; `props.isStreaming` shows the cursor while tokens arrive.
214
+ - `ChatToolCall` — shows a tool invocation; `props.state` is one of `"input-available"`, `"output-available"`, or `"output-error"`.
215
+ - `ChatThinkingMessage` — animated dots; `props.variant` is `"pill"` (block) or `"dots"` (inline).
216
+
217
+ Example spec fragment for a streaming assistant turn with a tool call:
218
+
219
+ ```json
220
+ [
221
+ {
222
+ "component": "ChatMessage",
223
+ "props": { "role": "assistant" },
224
+ "children": [
225
+ {
226
+ "component": "ChatMessageContent",
227
+ "props": { "$bindState": "isStreaming", "text": "$bindState:streamingContent" }
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "component": "ChatToolCall",
233
+ "props": { "state": "input-available", "name": "search_files", "input": { "query": "utils.ts" } }
234
+ },
235
+ {
236
+ "component": "ChatThinkingMessage",
237
+ "props": { "variant": "pill" }
238
+ }
239
+ ]
232
240
  ```
233
241
 
234
242
  ---
@@ -15,7 +15,6 @@
15
15
  "./brand-icons",
16
16
  "./catalog",
17
17
  "./catalog/components",
18
- "./catalog/types",
19
18
  "./consumer-intent-map",
20
19
  "./ds-check",
21
20
  "./ds-update",
@@ -1,5 +1,5 @@
1
- import { seedComponents } from './chunk-LZ66ADSL.js';
2
- import './chunk-MT76BPYV.js';
1
+ import { seedComponents } from './chunk-WGQHM56J.js';
2
+ import './chunk-PQOL3E2V.js';
3
3
  import './chunk-YGDO5KDY.js';
4
4
  import './chunk-EJQ73FJ5.js';
5
5
  import './chunk-4GLBFZVI.js';
@@ -11,24 +11,24 @@ import './chunk-5B3GLRX3.js';
11
11
  import './chunk-GXUQFXT7.js';
12
12
  import './chunk-65CUE2WL.js';
13
13
  import './chunk-L55GVKLY.js';
14
- import './chunk-56TNQ2P4.js';
14
+ import './chunk-YMAEXGD2.js';
15
15
  import './chunk-2NMP3J5R.js';
16
- import './chunk-JPNNAU26.js';
16
+ import './chunk-XLN4NVKU.js';
17
17
  import './chunk-A4TQNK7R.js';
18
18
  import './chunk-PD6WW7E5.js';
19
19
  import './chunk-XMGWLDNG.js';
20
20
  import './chunk-PYRHNONA.js';
21
21
  import './chunk-HF2HNDE7.js';
22
22
  import './chunk-PTJPPKDR.js';
23
- import './chunk-ZNF6S3DQ.js';
23
+ import './chunk-QCFDSOTV.js';
24
24
  import './chunk-HZTWLK7C.js';
25
- import './chunk-5MLOZVOQ.js';
26
- import './chunk-C7LO3JEF.js';
27
- import './chunk-ID3N3MHQ.js';
25
+ import './chunk-B4373MDA.js';
26
+ import './chunk-33UWVOQ6.js';
27
+ import './chunk-R4SBK6Y5.js';
28
28
  import './chunk-PQBVNNEG.js';
29
29
  import './chunk-B4YCI5NM.js';
30
30
  import './chunk-L77KWJJB.js';
31
- import './chunk-YNBDA24W.js';
31
+ import './chunk-ZKA5X3E4.js';
32
32
  import './chunk-PJ7DVYWA.js';
33
33
  import './chunk-HX3VXWNJ.js';
34
34
  import './chunk-IYK2ABFE.js';
@@ -39,28 +39,28 @@ import './chunk-BZYMCJHW.js';
39
39
  import './chunk-H466RJCI.js';
40
40
  import './chunk-DT6DGTVW.js';
41
41
  import './chunk-J5DRK4RF.js';
42
- import './chunk-IHGZJS7N.js';
42
+ import './chunk-3FJG5MAT.js';
43
43
  import './chunk-STNVWBJH.js';
44
44
  import { createAvatarDataUri } from './chunk-OWWQP3YW.js';
45
- import './chunk-3TYPQWKD.js';
45
+ import './chunk-FSU7ZM5V.js';
46
46
  import './chunk-DARC2ACH.js';
47
47
  import './chunk-4EHXVFQY.js';
48
- import './chunk-7SRNVMBY.js';
49
- import './chunk-NVBF4OWT.js';
48
+ import './chunk-7LYRUSWM.js';
49
+ import './chunk-BY6LWOHB.js';
50
50
  import './chunk-DSNPOAE6.js';
51
51
  import './chunk-JPA45CDE.js';
52
52
  import './chunk-Q4CRHV5T.js';
53
53
  import './chunk-KANK5FAG.js';
54
54
  import './chunk-6NL36QN3.js';
55
55
  import './chunk-CQ75K2DH.js';
56
- import './chunk-NA6ZPKIK.js';
57
- import './chunk-A44E44UV.js';
56
+ import './chunk-UCFR7GLW.js';
57
+ import './chunk-UPMSE6PQ.js';
58
58
  import './chunk-JVIRZNQ6.js';
59
59
  import './chunk-GE4WUAAE.js';
60
- import './chunk-OMI2LLXM.js';
61
- import './chunk-TNDMBLYP.js';
62
- import './chunk-P7CTBRHO.js';
63
- import './chunk-CPHVCY3M.js';
60
+ import './chunk-H3KTAHJP.js';
61
+ import './chunk-NDQO7AO6.js';
62
+ import './chunk-AP5SGSN7.js';
63
+ import './chunk-XUWKGDUY.js';
64
64
  import './chunk-D6MFOI3N.js';
65
65
  import './chunk-7AUNUDHM.js';
66
66
  import './chunk-4ZST7OY5.js';
@@ -73,10 +73,10 @@ import './chunk-IZ7A62GI.js';
73
73
  import './chunk-BVXSAVKY.js';
74
74
  import './chunk-GVOWGEGX.js';
75
75
  import './chunk-KMTLCN6S.js';
76
- import './chunk-BAB6AHOM.js';
77
- import './chunk-UIYX2EMT.js';
78
- import './chunk-R4DPHCHJ.js';
79
- import './chunk-QLPU2IEO.js';
76
+ import './chunk-U43JTK45.js';
77
+ import './chunk-5RBO2IMZ.js';
78
+ import './chunk-UMEBNHKR.js';
79
+ import './chunk-FX3GYS5O.js';
80
80
  import './chunk-OMGVZWRM.js';
81
81
  import './chunk-JVQQQS2M.js';
82
82
  import './chunk-DGSNEGJP.js';
@@ -86,7 +86,7 @@ import './chunk-ANBJ2OLC.js';
86
86
  import './chunk-LV4LBWCS.js';
87
87
  import './chunk-7OOO22KB.js';
88
88
  import './chunk-KWIIKHIB.js';
89
- import './chunk-GNKNT52O.js';
89
+ import './chunk-TTDKPZ75.js';
90
90
  import './chunk-STKNMEJA.js';
91
91
  import './chunk-SIXPY62Q.js';
92
92
  import './chunk-XZ3A33GP.js';
@@ -156,7 +156,7 @@ import './chunk-C4SNHMYC.js';
156
156
  import './chunk-B5QL76GA.js';
157
157
  import './chunk-KPO4PD6C.js';
158
158
  import './chunk-LO3MDQSJ.js';
159
- import './chunk-KV726HMK.js';
159
+ import './chunk-BOWKE7OE.js';
160
160
  import './chunk-B3BYBSF2.js';
161
161
  import './chunk-FOFGPWFS.js';
162
162
  import './chunk-VTAOHSPW.js';
@@ -179,7 +179,7 @@ import './chunk-RX5EUODB.js';
179
179
  import './chunk-4GQ5EMWR.js';
180
180
  import './chunk-TXRGQO5M.js';
181
181
  import './chunk-UN2SJ42K.js';
182
- import './chunk-WHYKEOUV.js';
182
+ import './chunk-U5QLJHYN.js';
183
183
  import './chunk-S2WSXZ7Y.js';
184
184
  import './chunk-KXZP6XI2.js';
185
185
  import './chunk-VJ5VD4UT.js';
@@ -208,7 +208,7 @@ import './chunk-MDAYDDTC.js';
208
208
  import './chunk-TV6CJ5NI.js';
209
209
  import './chunk-756Q7AC5.js';
210
210
  import './chunk-MB75U2OV.js';
211
- import './chunk-4ACR6NGQ.js';
211
+ import './chunk-UAN2YEI5.js';
212
212
  import './chunk-SEJXMNMK.js';
213
213
  import './chunk-VJIL7W55.js';
214
214
  import './chunk-JDLCIPSC.js';
@@ -367,6 +367,9 @@ import './chunk-QYZT24TS.js';
367
367
  import { jsxs, jsx } from 'react/jsx-runtime';
368
368
 
369
369
  var { InviteUserModal } = seedComponents;
370
+ var noop = () => {
371
+ };
372
+ var noopOn = () => ({ emit: noop, shouldPreventDefault: false, bound: false });
370
373
  var initialMembers = [
371
374
  {
372
375
  id: "1",
@@ -411,11 +414,9 @@ function TeamPage() {
411
414
  /* @__PURE__ */ jsx(
412
415
  InviteUserModal,
413
416
  {
414
- props: {},
415
- emit: () => {
416
- },
417
- on: () => ({ emit: () => {
418
- }, shouldPreventDefault: false, bound: false })
417
+ props: { email: null, role: null },
418
+ emit: noop,
419
+ on: noopOn
419
420
  }
420
421
  )
421
422
  ] }),