@loykin/designkit 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -2,8 +2,44 @@
2
2
 
3
3
  React page template library. Provides ready-to-use page layouts for admin and dashboard applications.
4
4
 
5
- For AI-assisted implementation in consuming applications, see the
6
- [DesignKit Consumer Agent Guide](docs/consumer-guide.md).
5
+ ## Building with AI
6
+
7
+ Do not generate an application workflow from a template name alone. DesignKit
8
+ ships versioned implementation guides that connect routes, query boundaries,
9
+ loading behavior, action placement, and the correct page templates.
10
+
11
+ | Product workflow | Guide ID | Connected destinations |
12
+ | ------------------- | --------------------- | -------------------------------------- |
13
+ | Administrative CRUD | `managed-table` | table → detail Sheet; create/edit page |
14
+ | Publishing | `publishing-workflow` | blog collection → article route |
15
+ | Commerce | `commerce-workflow` | filtered catalog → product route |
16
+
17
+ List the guides included with the installed package:
18
+
19
+ ```bash
20
+ npx @loykin/designkit guide list
21
+ ```
22
+
23
+ Print a complete contract for a developer or coding agent:
24
+
25
+ ```bash
26
+ npx @loykin/designkit guide managed-table
27
+ npx @loykin/designkit guide publishing-workflow --prompt
28
+ npx @loykin/designkit guide commerce-workflow
29
+ ```
30
+
31
+ `--prompt` adds a short implementation instruction before the canonical
32
+ Markdown. Copy the complete output into the AI task. The same contracts are
33
+ published under `@loykin/designkit/guides/*` and rendered in the Playground's
34
+ **Guides** group. See the [Implementation Guide Index](./docs/guides/README.md)
35
+ for the selection rules.
36
+
37
+ Template demos are visual API references. Only entries in the **Guides** group
38
+ are end-to-end implementation contracts.
39
+
40
+ For lower-level component composition, follow the published compound-component
41
+ contract below. The same rules are included in the package's TypeScript
42
+ declarations so editors and coding agents can discover them.
7
43
 
8
44
  ## Installation
9
45
 
@@ -22,8 +58,8 @@ Import Tailwind and DesignKit from the same global CSS entry:
22
58
 
23
59
  ```css
24
60
  /* globals.css */
25
- @import "tailwindcss";
26
- @import "@loykin/designkit/styles";
61
+ @import 'tailwindcss';
62
+ @import '@loykin/designkit/styles';
27
63
  ```
28
64
 
29
65
  `@loykin/designkit/styles` registers the package's compiled JavaScript as a
@@ -43,9 +79,7 @@ export function UsersPage() {
43
79
  description="Manage your team members."
44
80
  actions={<Button>Add User</Button>}
45
81
  >
46
- <DataBodyTemplate.Body>
47
- {/* your content */}
48
- </DataBodyTemplate.Body>
82
+ <DataBodyTemplate.Body>{/* your content */}</DataBodyTemplate.Body>
49
83
  </DataBodyTemplate>
50
84
  )
51
85
  }
@@ -59,6 +93,12 @@ export function UsersPage() {
59
93
 
60
94
  General-purpose page shell. Accepts `.Body`, `.Tab`, and `.Section` child slots which determine the layout mode automatically.
61
95
 
96
+ > **Compound-component contract:** `DataBodyTemplate` is the required root.
97
+ > Never render `.Group`, `.Tab`, `.Section`, `.Body`, `.Summary`, `.Row`, or
98
+ > `.Field` without it. Choose one primary mode—direct content/groups, `Tab`,
99
+ > `Section`, or `Body`—and do not mix those modes in one root. Invalid standalone
100
+ > usage throws at render time.
101
+
62
102
  ```tsx
63
103
  <DataBodyTemplate
64
104
  topBar={<PageTopBar left="Admin / Users" />}
@@ -70,16 +110,16 @@ General-purpose page shell. Accepts `.Body`, `.Tab`, and `.Section` child slots
70
110
  </DataBodyTemplate>
71
111
  ```
72
112
 
73
- | Prop | Type | Description |
74
- |---|---|---|
75
- | `topBar` | `ReactNode` | Top breadcrumb bar. Pass `<PageTopBar left="..." />` or omit. |
76
- | `title` | `ReactNode` | Page title |
77
- | `description` | `ReactNode` | Subtitle below the title |
78
- | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
79
- | `actions` | `ReactNode` | Page-level actions (Add, Export, etc.) next to the title |
80
- | `toolbarLeft` / `toolbarRight` | `ReactNode` | Toolbar slots above the content area |
81
- | `theme` | `CSSProperties` | Inline CSS variable overrides |
82
- | `className` | `string` | Class applied to the page root |
113
+ | Prop | Type | Description |
114
+ | ------------------------------ | --------------- | ------------------------------------------------------------- |
115
+ | `topBar` | `ReactNode` | Top breadcrumb bar. Pass `<PageTopBar left="..." />` or omit. |
116
+ | `title` | `ReactNode` | Page title |
117
+ | `description` | `ReactNode` | Subtitle below the title |
118
+ | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
119
+ | `actions` | `ReactNode` | Page-level actions (Add, Export, etc.) next to the title |
120
+ | `toolbarLeft` / `toolbarRight` | `ReactNode` | Toolbar slots above the content area |
121
+ | `theme` | `CSSProperties` | Inline CSS variable overrides |
122
+ | `className` | `string` | Class applied to the page root |
83
123
 
84
124
  #### DataBodyTemplate.Body
85
125
 
@@ -87,9 +127,7 @@ Single-pane content. Use for full-height layouts.
87
127
 
88
128
  ```tsx
89
129
  <DataBodyTemplate title="Users">
90
- <DataBodyTemplate.Body>
91
- {/* your content */}
92
- </DataBodyTemplate.Body>
130
+ <DataBodyTemplate.Body>{/* your content */}</DataBodyTemplate.Body>
93
131
  </DataBodyTemplate>
94
132
  ```
95
133
 
@@ -121,7 +159,9 @@ Settings-style layout with left navigation and right content panel.
121
159
  <form onSubmit={handleSubmit} className="space-y-3">
122
160
  <Label htmlFor="name">Name</Label>
123
161
  <Input id="name" defaultValue="Acme Corp" />
124
- <Button type="submit" size="sm">Save</Button>
162
+ <Button type="submit" size="sm">
163
+ Save
164
+ </Button>
125
165
  </form>
126
166
  </DataBodyTemplate.Group>
127
167
  </DataBodyTemplate.Section>
@@ -135,34 +175,38 @@ Settings-style layout with left navigation and right content panel.
135
175
 
136
176
  Groups content within a tab or section. The `layout` prop controls the visual structure.
137
177
 
138
- | layout | Use case |
139
- |---|---|
178
+ | layout | Use case |
179
+ | ------------ | --------------------------------------------- |
140
180
  | `horizontal` | Label on left, input on right (settings form) |
141
- | `stacked` | Label above, input below |
142
- | `inline` | Table-style rows (detail view) |
143
- | `split` | Left list + right detail |
144
-
145
- | Prop | Type | Description |
146
- |---|---|---|
147
- | `layout` | `GroupLayout` | Visual structure — see table above |
148
- | `variant` | `'card' \| 'plain' \| 'bordered'` | Wrapper style (defaults per layout) |
149
- | `title` | `ReactNode` | Group heading |
150
- | `description` | `ReactNode` | Subtitle below the heading |
151
- | `actions` | `ReactNode` | Action slot next to the heading |
152
- | `danger` | `boolean` | Renders title in destructive color |
153
- | `className` | `string` | Class applied to the group root element |
181
+ | `stacked` | Label above, input below |
182
+ | `inline` | Table-style rows (detail view) |
183
+ | `split` | Left list + right detail |
184
+
185
+ `layout` defaults to `stacked`; omit it when the stacked arrangement is intended.
186
+
187
+ | Prop | Type | Description |
188
+ | ------------- | --------------------------------- | --------------------------------------- |
189
+ | `layout` | `GroupLayout` | Visual structure — see table above |
190
+ | `variant` | `'card' \| 'plain' \| 'bordered'` | Wrapper style (defaults per layout) |
191
+ | `title` | `ReactNode` | Group heading |
192
+ | `description` | `ReactNode` | Subtitle below the heading |
193
+ | `actions` | `ReactNode` | Action slot next to the heading |
194
+ | `danger` | `boolean` | Renders title in destructive color |
195
+ | `className` | `string` | Class applied to the group root element |
154
196
 
155
197
  ```tsx
156
- <DataBodyTemplate.Tab id="settings" label="Settings">
157
- <DataBodyTemplate.Group layout="horizontal" title="Identity" description="Basic information">
158
- <DataBodyTemplate.Row label="Name" required>
159
- <Input defaultValue="Sarah Kim" />
160
- </DataBodyTemplate.Row>
161
- <DataBodyTemplate.Row label="Email">
162
- <Input type="email" defaultValue="sarah@acme.com" />
163
- </DataBodyTemplate.Row>
164
- </DataBodyTemplate.Group>
165
- </DataBodyTemplate.Tab>
198
+ <DataBodyTemplate title="Settings">
199
+ <DataBodyTemplate.Tab id="settings" label="Settings">
200
+ <DataBodyTemplate.Group layout="horizontal" title="Identity" description="Basic information">
201
+ <DataBodyTemplate.Row label="Name" required>
202
+ <Input defaultValue="Sarah Kim" />
203
+ </DataBodyTemplate.Row>
204
+ <DataBodyTemplate.Row label="Email">
205
+ <Input type="email" defaultValue="sarah@acme.com" />
206
+ </DataBodyTemplate.Row>
207
+ </DataBodyTemplate.Group>
208
+ </DataBodyTemplate.Tab>
209
+ </DataBodyTemplate>
166
210
  ```
167
211
 
168
212
  #### DataBodyTemplate.Field
@@ -170,10 +214,14 @@ Groups content within a tab or section. The `layout` prop controls the visual st
170
214
  Read-only key-value display.
171
215
 
172
216
  ```tsx
173
- <DataBodyTemplate.Group layout="inline" title="Identity">
174
- <DataBodyTemplate.Field label="Email">sarah@acme.com</DataBodyTemplate.Field>
175
- <DataBodyTemplate.Field label="Role"><Badge>Admin</Badge></DataBodyTemplate.Field>
176
- </DataBodyTemplate.Group>
217
+ <DataBodyTemplate title="Profile">
218
+ <DataBodyTemplate.Group layout="inline" title="Identity">
219
+ <DataBodyTemplate.Field label="Email">sarah@acme.com</DataBodyTemplate.Field>
220
+ <DataBodyTemplate.Field label="Role">
221
+ <Badge>Admin</Badge>
222
+ </DataBodyTemplate.Field>
223
+ </DataBodyTemplate.Group>
224
+ </DataBodyTemplate>
177
225
  ```
178
226
 
179
227
  #### DataBodyTemplate.Summary
@@ -185,7 +233,9 @@ Pinned summary area below the header, above tabs.
185
233
  <DataBodyTemplate.Summary>
186
234
  <StatCards />
187
235
  </DataBodyTemplate.Summary>
188
- <DataBodyTemplate.Tab id="details" label="Details">...</DataBodyTemplate.Tab>
236
+ <DataBodyTemplate.Tab id="details" label="Details">
237
+ ...
238
+ </DataBodyTemplate.Tab>
189
239
  </DataBodyTemplate>
190
240
  ```
191
241
 
@@ -216,7 +266,10 @@ handle stays inside the clipped panel corner:
216
266
  --designkit-resize-handle-inset: clamp(4px, calc(var(--radius) * 0.45), 16px);
217
267
  --designkit-resize-handle-mark-size: clamp(6px, calc(var(--radius) * 0.55), 12px);
218
268
  --designkit-resize-handle-color: rgba(0, 0, 0, 0.35);
219
- --designkit-resize-handle-mark-radius: min(var(--radius), var(--designkit-resize-handle-mark-size));
269
+ --designkit-resize-handle-mark-radius: min(
270
+ var(--radius),
271
+ var(--designkit-resize-handle-mark-size)
272
+ );
220
273
  }
221
274
 
222
275
  .dark .layout-dashboard .react-grid-item:not(.react-grid-placeholder) {
@@ -233,20 +286,25 @@ import type { PanelViewerProps } from '@loykin/dashboardkit'
233
286
 
234
287
  // Register panel types once at module level
235
288
  const engine = createDashboardEngine()
236
- engine.registerPanel(definePanel({
237
- id: 'stat',
238
- name: 'Stat',
239
- optionsSchema: {},
240
- viewer({ data, loading }: PanelViewerProps<unknown, unknown>) {
241
- if (loading) return null
242
- return <div className="text-2xl font-bold">{String(data ?? '—')}</div>
243
- },
244
- }))
289
+ engine.registerPanel(
290
+ definePanel({
291
+ id: 'stat',
292
+ name: 'Stat',
293
+ optionsSchema: {},
294
+ viewer({ data, loading }: PanelViewerProps<unknown, unknown>) {
295
+ if (loading) return null
296
+ return <div className="text-2xl font-bold">{String(data ?? '—')}</div>
297
+ },
298
+ }),
299
+ )
245
300
 
246
301
  export function MyDashboard() {
247
302
  useLoadDashboard(engine, config)
248
303
  const envVar = useVariable(engine, 'env')
249
- const variables = useMemo(() => ({ env: (envVar.value as string) ?? 'production' }), [envVar.value])
304
+ const variables = useMemo(
305
+ () => ({ env: (envVar.value as string) ?? 'production' }),
306
+ [envVar.value],
307
+ )
250
308
  const [editable, setEditable] = useState(false)
251
309
 
252
310
  return (
@@ -256,7 +314,8 @@ export function MyDashboard() {
256
314
  >
257
315
  <DashboardGrid engine={engine} editable={editable}>
258
316
  {({ panelType, config, data, rawData, loading, error, ref }) => {
259
- const Viewer = engine.getPanelPlugin(panelType)?.viewer as React.FC<PanelViewerProps<unknown, unknown>> | undefined
317
+ const Viewer = engine.getPanelPlugin(panelType)?.viewer as
318
+ React.FC<PanelViewerProps<unknown, unknown>> | undefined
260
319
  return (
261
320
  <DashboardPanel
262
321
  ref={ref}
@@ -268,10 +327,14 @@ export function MyDashboard() {
268
327
  >
269
328
  {Viewer && (
270
329
  <Viewer
271
- panel={config} options={config.options}
272
- data={data} rawData={rawData}
273
- width={0} height={0}
274
- loading={loading} error={error}
330
+ panel={config}
331
+ options={config.options}
332
+ data={data}
333
+ rawData={rawData}
334
+ width={0}
335
+ height={0}
336
+ loading={loading}
337
+ error={error}
275
338
  variables={variables}
276
339
  />
277
340
  )}
@@ -286,31 +349,31 @@ export function MyDashboard() {
286
349
 
287
350
  **DashboardBodyTemplate props:**
288
351
 
289
- | Prop | Type | Description |
290
- |---|---|---|
291
- | `topBar` | `ReactNode` | Top bar — breadcrumb, edit/refresh controls |
292
- | `variableBar` | `ReactNode` | Variable dropdown strip between top bar and panels |
293
- | `title` | `ReactNode` | Dashboard title (omit if topBar covers it) |
294
- | `description` | `ReactNode` | Subtitle |
295
- | `toolbar` | `ReactNode` | Toolbar slot next to the title |
296
- | `theme` | `CSSProperties` | Inline CSS variable overrides |
297
- | `className` | `string` | Class applied to the page root |
298
- | `contentClassName` | `string` | Class applied to the panel grid area |
352
+ | Prop | Type | Description |
353
+ | ------------------ | --------------- | -------------------------------------------------- |
354
+ | `topBar` | `ReactNode` | Top bar — breadcrumb, edit/refresh controls |
355
+ | `variableBar` | `ReactNode` | Variable dropdown strip between top bar and panels |
356
+ | `title` | `ReactNode` | Dashboard title (omit if topBar covers it) |
357
+ | `description` | `ReactNode` | Subtitle |
358
+ | `toolbar` | `ReactNode` | Toolbar slot next to the title |
359
+ | `theme` | `CSSProperties` | Inline CSS variable overrides |
360
+ | `className` | `string` | Class applied to the page root |
361
+ | `contentClassName` | `string` | Class applied to the panel grid area |
299
362
 
300
363
  **DashboardPanel props:**
301
364
 
302
365
  Panel card component. Wrap your panel viewer in `DashboardPanel` for consistent chrome across all panels.
303
366
 
304
- | Prop | Type | Description |
305
- |---|---|---|
306
- | `title` | `string` | Panel title |
307
- | `description` | `string` | Panel subtitle |
308
- | `loading` | `boolean` | Shows loading spinner overlay |
309
- | `error` | `string` | Shows error state, hides children |
310
- | `editable` | `boolean` | Shows drag handle and edit ring |
311
- | `headerRight` | `ReactNode` | Action slot in panel header — hidden until hover |
312
- | `transparent` | `boolean` | Removes card border and background |
313
- | `ref` | forwarded | Attach `DashboardGrid`'s `ref` for viewport virtualization |
367
+ | Prop | Type | Description |
368
+ | ------------- | ----------- | ---------------------------------------------------------- |
369
+ | `title` | `string` | Panel title |
370
+ | `description` | `string` | Panel subtitle |
371
+ | `loading` | `boolean` | Shows loading spinner overlay |
372
+ | `error` | `string` | Shows error state, hides children |
373
+ | `editable` | `boolean` | Shows drag handle and edit ring |
374
+ | `headerRight` | `ReactNode` | Action slot in panel header — hidden until hover |
375
+ | `transparent` | `boolean` | Removes card border and background |
376
+ | `ref` | forwarded | Attach `DashboardGrid`'s `ref` for viewport virtualization |
314
377
 
315
378
  ---
316
379
 
@@ -326,7 +389,11 @@ export function SqlEditorPage() {
326
389
  <WorkbenchBodyTemplate
327
390
  topBar={<PageTopBar left="Data / Query editor" />}
328
391
  title="SQL editor"
329
- headerRight={<Button variant="outline" size="sm">Run</Button>}
392
+ headerRight={
393
+ <Button variant="outline" size="sm">
394
+ Run
395
+ </Button>
396
+ }
330
397
  leftPane={<SchemaBrowser />}
331
398
  mainPane={<SqlEditor />}
332
399
  bottomPane={<ResultsGrid />}
@@ -338,18 +405,18 @@ export function SqlEditorPage() {
338
405
 
339
406
  **WorkbenchBodyTemplate props:**
340
407
 
341
- | Prop | Type | Description |
342
- |---|---|---|
343
- | `topBar` | `ReactNode` | Top bar above the workbench |
344
- | `title` / `description` | `ReactNode` | Header copy above the panes |
345
- | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
346
- | `headerRight` / `actions` | `ReactNode` | Header action slots |
347
- | `leftPane` / `rightPane` | `ReactNode` | Optional side panes |
348
- | `mainPane` | `ReactNode` | Primary editor or preview area |
349
- | `bottomPane` | `ReactNode` | Optional result, query, or logs pane |
350
- | `resizable` | `boolean` | Enables pane drag handles |
351
- | `leftPaneCollapsed` / `rightPaneCollapsed` / `bottomPaneCollapsed` | `boolean` | Hides optional panes while preserving the template layout model |
352
- | `leftPaneWidth` / `rightPaneWidth` / `bottomPaneHeight` | `number` | Initial pane sizes in pixels |
408
+ | Prop | Type | Description |
409
+ | ------------------------------------------------------------------ | ----------- | --------------------------------------------------------------- |
410
+ | `topBar` | `ReactNode` | Top bar above the workbench |
411
+ | `title` / `description` | `ReactNode` | Header copy above the panes |
412
+ | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
413
+ | `headerRight` / `actions` | `ReactNode` | Header action slots |
414
+ | `leftPane` / `rightPane` | `ReactNode` | Optional side panes |
415
+ | `mainPane` | `ReactNode` | Primary editor or preview area |
416
+ | `bottomPane` | `ReactNode` | Optional result, query, or logs pane |
417
+ | `resizable` | `boolean` | Enables pane drag handles |
418
+ | `leftPaneCollapsed` / `rightPaneCollapsed` / `bottomPaneCollapsed` | `boolean` | Hides optional panes while preserving the template layout model |
419
+ | `leftPaneWidth` / `rightPaneWidth` / `bottomPaneHeight` | `number` | Initial pane sizes in pixels |
353
420
 
354
421
  ---
355
422
 
@@ -362,9 +429,9 @@ import { useState } from 'react'
362
429
  import { FormWizardBodyTemplate, type FormWizardStep } from '@loykin/designkit'
363
430
 
364
431
  const steps: FormWizardStep[] = [
365
- { key: 'info', title: 'Basic Info', content: <BasicInfoForm /> },
366
- { key: 'config', title: 'Configuration', content: <ConfigForm /> },
367
- { key: 'review', title: 'Review', content: <ReviewStep /> },
432
+ { key: 'info', title: 'Basic Info', content: <BasicInfoForm /> },
433
+ { key: 'config', title: 'Configuration', content: <ConfigForm /> },
434
+ { key: 'review', title: 'Review', content: <ReviewStep /> },
368
435
  ]
369
436
 
370
437
  export function OnboardingPage() {
@@ -414,21 +481,23 @@ export function SignInPage() {
414
481
  <Label htmlFor="password">Password</Label>
415
482
  <Input id="password" type="password" />
416
483
  </div>
417
- <Button type="submit" className="w-full">Sign In</Button>
484
+ <Button type="submit" className="w-full">
485
+ Sign In
486
+ </Button>
418
487
  </form>
419
488
  </LoginBodyTemplate>
420
489
  )
421
490
  }
422
491
  ```
423
492
 
424
- | Prop | Type | Default | Description |
425
- |---|---|---|---|
426
- | `layout` | `'centered' \| 'split'` | `'centered'` | Centered card or split-panel with brand side |
427
- | `card` | `'card' \| 'plain'` | `'plain'` | Wrap form content in a card border |
428
- | `cardWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Form card width |
429
- | `bg` | `'default' \| 'subtle' \| 'none'` | `'default'` | Background style |
430
- | `side` | `'left' \| 'right'` | `'left'` | Brand panel side (split layout only) |
431
- | `brand` | `ReactNode` | built-in | Custom brand/logo panel content |
493
+ | Prop | Type | Default | Description |
494
+ | ----------- | --------------------------------- | ------------ | -------------------------------------------- |
495
+ | `layout` | `'centered' \| 'split'` | `'centered'` | Centered card or split-panel with brand side |
496
+ | `card` | `'card' \| 'plain'` | `'plain'` | Wrap form content in a card border |
497
+ | `cardWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Form card width |
498
+ | `bg` | `'default' \| 'subtle' \| 'none'` | `'default'` | Background style |
499
+ | `side` | `'left' \| 'right'` | `'left'` | Brand panel side (split layout only) |
500
+ | `brand` | `ReactNode` | built-in | Custom brand/logo panel content |
432
501
 
433
502
  ---
434
503
 
@@ -436,10 +505,15 @@ export function SignInPage() {
436
505
 
437
506
  ```tsx
438
507
  import {
439
- Avatar, AvatarFallback, AvatarImage,
508
+ Avatar,
509
+ AvatarFallback,
510
+ AvatarImage,
440
511
  Badge,
441
512
  Button,
442
- Card, CardContent, CardHeader, CardTitle,
513
+ Card,
514
+ CardContent,
515
+ CardHeader,
516
+ CardTitle,
443
517
  Checkbox,
444
518
  DropdownMenu,
445
519
  EmptyState,
@@ -448,16 +522,30 @@ import {
448
522
  NavigationMenu,
449
523
  Popover,
450
524
  ScrollArea,
451
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
525
+ Select,
526
+ SelectContent,
527
+ SelectItem,
528
+ SelectTrigger,
529
+ SelectValue,
452
530
  Separator,
453
- Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger,
531
+ Sheet,
532
+ SheetContent,
533
+ SheetHeader,
534
+ SheetTitle,
535
+ SheetTrigger,
454
536
  Sidebar,
455
537
  Skeleton,
456
538
  Slider,
457
539
  Switch,
458
540
  Table,
459
- Tabs, TabsList, TabsTrigger, TabsContent,
460
- Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,
541
+ Tabs,
542
+ TabsList,
543
+ TabsTrigger,
544
+ TabsContent,
545
+ Tooltip,
546
+ TooltipContent,
547
+ TooltipProvider,
548
+ TooltipTrigger,
461
549
  PageTopBar,
462
550
  } from '@loykin/designkit'
463
551
  ```
@@ -469,17 +557,17 @@ Top breadcrumb / action bar placed at the top of a page template via the `topBar
469
557
  ```tsx
470
558
  import { PageTopBar } from '@loykin/designkit'
471
559
 
472
- <PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
560
+ ;<PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
473
561
  ```
474
562
 
475
- | Prop | Type | Default | Description |
476
- |---|---|---|---|
477
- | `left` | `ReactNode` | — | Left content. A plain string is parsed as `/`-separated breadcrumbs. |
478
- | `right` | `ReactNode` | — | Right-aligned actions |
479
- | `variant` | `'ghost' \| 'default'` | `'ghost'` | `'default'` adds a bottom border |
480
- | `sidebarTrigger` | `false \| ReactNode` | auto | Mobile sidebar open button. `undefined` auto-detects SidebarProvider context; `false` suppresses; pass a `ReactNode` for a custom trigger. |
481
- | `height` | `string` | `var(--designkit-toolbar-height)` | Bar height |
482
- | `className` | `string` | — | Class applied to the bar root |
563
+ | Prop | Type | Default | Description |
564
+ | ---------------- | ---------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
565
+ | `left` | `ReactNode` | — | Left content. A plain string is parsed as `/`-separated breadcrumbs. |
566
+ | `right` | `ReactNode` | — | Right-aligned actions |
567
+ | `variant` | `'ghost' \| 'default'` | `'ghost'` | `'default'` adds a bottom border |
568
+ | `sidebarTrigger` | `false \| ReactNode` | auto | Mobile sidebar open button. `undefined` auto-detects SidebarProvider context; `false` suppresses; pass a `ReactNode` for a custom trigger. |
569
+ | `height` | `string` | `var(--designkit-toolbar-height)` | Bar height |
570
+ | `className` | `string` | — | Class applied to the bar root |
483
571
 
484
572
  **Mobile sidebar trigger**
485
573
 
@@ -501,7 +589,7 @@ When `PageTopBar` is rendered inside a `SidebarShell` on a mobile viewport, it a
501
589
  import { EmptyState } from '@loykin/designkit'
502
590
  import { Users } from 'lucide-react'
503
591
 
504
- <EmptyState
592
+ ;<EmptyState
505
593
  icon={Users}
506
594
  title="No users yet"
507
595
  description="Add your first user to get started."
@@ -515,18 +603,18 @@ import { Users } from 'lucide-react'
515
603
 
516
604
  Designkit maps shadcn/ui CSS variables onto its own `--designkit-*` tokens. Changing your shadcn theme automatically updates all designkit components.
517
605
 
518
- | What | How |
519
- |---|---|
520
- | Colors, radius, typography | shadcn/ui theme variables (`--primary`, `--radius`, etc.) |
521
- | Spacing, density, padding | `--designkit-density`, `--designkit-page-padding-*`, `--designkit-panel-gap` |
522
- | Per-page overrides | `className` or `theme` prop |
606
+ | What | How |
607
+ | -------------------------- | ---------------------------------------------------------------------------- |
608
+ | Colors, radius, typography | shadcn/ui theme variables (`--primary`, `--radius`, etc.) |
609
+ | Spacing, density, padding | `--designkit-density`, `--designkit-page-padding-*`, `--designkit-panel-gap` |
610
+ | Per-page overrides | `className` or `theme` prop |
523
611
 
524
612
  ```css
525
613
  :root {
526
- --designkit-density: 1; /* 0.85 compact / 1 default / 1.15 comfortable */
614
+ --designkit-density: 1; /* 0.85 compact / 1 default / 1.15 comfortable */
527
615
  --designkit-page-padding-x: 1.5rem;
528
616
  --designkit-page-padding-y: 1rem;
529
- --designkit-panel-gap: 1rem;
617
+ --designkit-panel-gap: 1rem;
530
618
  }
531
619
  ```
532
620
 
@@ -558,9 +646,9 @@ Or via `theme` prop:
558
646
  If your app also uses `@loykin/gridkit`, import its styles **after** designkit:
559
647
 
560
648
  ```css
561
- @import "tailwindcss";
562
- @import "@loykin/designkit/styles";
563
- @import "@loykin/gridkit/styles"; /* must come last — uses @layer gridkit */
649
+ @import 'tailwindcss';
650
+ @import '@loykin/designkit/styles';
651
+ @import '@loykin/gridkit/styles'; /* must come last — uses @layer gridkit */
564
652
  ```
565
653
 
566
654
  gridkit registers a named `@layer gridkit`. Importing it before `tailwindcss` or `designkit/styles` causes layer ordering conflicts where utility overrides resolve incorrectly.
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from 'node:fs/promises'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
8
+ const packageJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'))
9
+ const guideRoot = join(packageRoot, packageJson.designkit.guideManifest, '..')
10
+ const manifest = JSON.parse(await readFile(join(guideRoot, 'manifest.json'), 'utf8'))
11
+ const args = process.argv.slice(2)
12
+
13
+ function usage() {
14
+ return `@loykin/designkit ${packageJson.version}
15
+
16
+ Versioned implementation guides for developers and coding agents.
17
+
18
+ Usage:
19
+ designkit guide list
20
+ designkit guide <guide-id>
21
+ designkit guide <guide-id> --prompt
22
+ designkit guide list --json
23
+
24
+ Examples:
25
+ npx @loykin/designkit guide managed-table
26
+ npx @loykin/designkit guide publishing-workflow --prompt
27
+ npx @loykin/designkit guide commerce-workflow
28
+ `
29
+ }
30
+
31
+ function listGuides() {
32
+ const width = Math.max(...manifest.guides.map((guide) => guide.id.length))
33
+ return [
34
+ `@loykin/designkit ${packageJson.version} implementation guides`,
35
+ '',
36
+ ...manifest.guides.map((guide) => `${guide.id.padEnd(width)} ${guide.summary}`),
37
+ '',
38
+ 'Print one guide with: designkit guide <guide-id>',
39
+ ].join('\n')
40
+ }
41
+
42
+ function fail(message) {
43
+ process.stderr.write(`${message}\n\n${usage()}`)
44
+ process.exitCode = 1
45
+ }
46
+
47
+ const [command, subject, ...flags] = args
48
+ const wantsJson = args.includes('--json')
49
+ const wantsPrompt = args.includes('--prompt')
50
+
51
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
52
+ process.stdout.write(usage())
53
+ } else if (command !== 'guide') {
54
+ fail(`Unknown command: ${command}`)
55
+ } else if (!subject || subject === 'list') {
56
+ process.stdout.write(
57
+ wantsJson
58
+ ? `${JSON.stringify({ packageVersion: packageJson.version, ...manifest }, null, 2)}\n`
59
+ : `${listGuides()}\n`,
60
+ )
61
+ } else {
62
+ const guide = manifest.guides.find((candidate) => candidate.id === subject)
63
+ if (!guide) {
64
+ fail(`Unknown guide: ${subject}`)
65
+ } else if (flags.some((flag) => !['--json', '--prompt'].includes(flag))) {
66
+ fail(`Unknown option: ${flags.find((flag) => !['--json', '--prompt'].includes(flag))}`)
67
+ } else if (wantsJson) {
68
+ process.stdout.write(
69
+ `${JSON.stringify({ packageVersion: packageJson.version, ...guide }, null, 2)}\n`,
70
+ )
71
+ } else {
72
+ const contract = await readFile(join(guideRoot, guide.contract), 'utf8')
73
+ const prompt = wantsPrompt
74
+ ? `Implement this workflow with @loykin/designkit ${packageJson.version}. Follow the complete contract below. Preserve route, query, action-placement, loading, and page-template boundaries. Do not nest page-level templates.\n\n`
75
+ : ''
76
+ process.stdout.write(`${prompt}${contract.trim()}\n`)
77
+ }
78
+ }