@loykin/designkit 0.0.2 → 0.0.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.
package/README.md CHANGED
@@ -2,9 +2,52 @@
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, follow the published
6
- compound-component contract below. The same rules are included in the package's
7
- TypeScript declarations so editors and coding agents can discover them.
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
+ | Forms | `form-workflow` | create, edit, and settings pages |
15
+ | Publishing | `publishing-workflow` | blog collection → article route |
16
+ | Commerce | `commerce-workflow` | filtered catalog → product route |
17
+
18
+ The executable **Forms / Stacked Form** Playground guide uses React Hook Form
19
+ to demonstrate shared state, controlled DesignKit inputs, validation, and
20
+ modular section components. React Hook Form is installed in the Playground
21
+ only; it is not a dependency or peer dependency of `@loykin/designkit` and is
22
+ not required to preserve the form design contract.
23
+
24
+ List the guides included with the installed package:
25
+
26
+ ```bash
27
+ npx @loykin/designkit guide list
28
+ ```
29
+
30
+ Print a complete contract for a developer or coding agent:
31
+
32
+ ```bash
33
+ npx @loykin/designkit guide managed-table
34
+ npx @loykin/designkit guide form-workflow --prompt
35
+ npx @loykin/designkit guide publishing-workflow --prompt
36
+ npx @loykin/designkit guide commerce-workflow
37
+ ```
38
+
39
+ `--prompt` adds a short implementation instruction before the canonical
40
+ Markdown. Copy the complete output into the AI task. The same contracts are
41
+ published under `@loykin/designkit/guides/*` and rendered in the Playground's
42
+ **Guides** group. See the [Implementation Guide Index](./docs/guides/README.md)
43
+ for the selection rules.
44
+
45
+ Template demos are visual API references. Only entries in the **Guides** group
46
+ are end-to-end implementation contracts.
47
+
48
+ For lower-level component composition, follow the published compound-component
49
+ contract below. The same rules are included in the package's TypeScript
50
+ declarations so editors and coding agents can discover them.
8
51
 
9
52
  ## Installation
10
53
 
@@ -23,8 +66,8 @@ Import Tailwind and DesignKit from the same global CSS entry:
23
66
 
24
67
  ```css
25
68
  /* globals.css */
26
- @import "tailwindcss";
27
- @import "@loykin/designkit/styles";
69
+ @import 'tailwindcss';
70
+ @import '@loykin/designkit/styles';
28
71
  ```
29
72
 
30
73
  `@loykin/designkit/styles` registers the package's compiled JavaScript as a
@@ -44,9 +87,7 @@ export function UsersPage() {
44
87
  description="Manage your team members."
45
88
  actions={<Button>Add User</Button>}
46
89
  >
47
- <DataBodyTemplate.Body>
48
- {/* your content */}
49
- </DataBodyTemplate.Body>
90
+ <DataBodyTemplate.Body>{/* your content */}</DataBodyTemplate.Body>
50
91
  </DataBodyTemplate>
51
92
  )
52
93
  }
@@ -77,16 +118,16 @@ General-purpose page shell. Accepts `.Body`, `.Tab`, and `.Section` child slots
77
118
  </DataBodyTemplate>
78
119
  ```
79
120
 
80
- | Prop | Type | Description |
81
- |---|---|---|
82
- | `topBar` | `ReactNode` | Top breadcrumb bar. Pass `<PageTopBar left="..." />` or omit. |
83
- | `title` | `ReactNode` | Page title |
84
- | `description` | `ReactNode` | Subtitle below the title |
85
- | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
86
- | `actions` | `ReactNode` | Page-level actions (Add, Export, etc.) next to the title |
87
- | `toolbarLeft` / `toolbarRight` | `ReactNode` | Toolbar slots above the content area |
88
- | `theme` | `CSSProperties` | Inline CSS variable overrides |
89
- | `className` | `string` | Class applied to the page root |
121
+ | Prop | Type | Description |
122
+ | ------------------------------ | --------------- | ------------------------------------------------------------- |
123
+ | `topBar` | `ReactNode` | Top breadcrumb bar. Pass `<PageTopBar left="..." />` or omit. |
124
+ | `title` | `ReactNode` | Page title |
125
+ | `description` | `ReactNode` | Subtitle below the title |
126
+ | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
127
+ | `actions` | `ReactNode` | Page-level actions (Add, Export, etc.) next to the title |
128
+ | `toolbarLeft` / `toolbarRight` | `ReactNode` | Toolbar slots above the content area |
129
+ | `theme` | `CSSProperties` | Inline CSS variable overrides |
130
+ | `className` | `string` | Class applied to the page root |
90
131
 
91
132
  #### DataBodyTemplate.Body
92
133
 
@@ -94,9 +135,7 @@ Single-pane content. Use for full-height layouts.
94
135
 
95
136
  ```tsx
96
137
  <DataBodyTemplate title="Users">
97
- <DataBodyTemplate.Body>
98
- {/* your content */}
99
- </DataBodyTemplate.Body>
138
+ <DataBodyTemplate.Body>{/* your content */}</DataBodyTemplate.Body>
100
139
  </DataBodyTemplate>
101
140
  ```
102
141
 
@@ -128,7 +167,9 @@ Settings-style layout with left navigation and right content panel.
128
167
  <form onSubmit={handleSubmit} className="space-y-3">
129
168
  <Label htmlFor="name">Name</Label>
130
169
  <Input id="name" defaultValue="Acme Corp" />
131
- <Button type="submit" size="sm">Save</Button>
170
+ <Button type="submit" size="sm">
171
+ Save
172
+ </Button>
132
173
  </form>
133
174
  </DataBodyTemplate.Group>
134
175
  </DataBodyTemplate.Section>
@@ -142,24 +183,24 @@ Settings-style layout with left navigation and right content panel.
142
183
 
143
184
  Groups content within a tab or section. The `layout` prop controls the visual structure.
144
185
 
145
- | layout | Use case |
146
- |---|---|
186
+ | layout | Use case |
187
+ | ------------ | --------------------------------------------- |
147
188
  | `horizontal` | Label on left, input on right (settings form) |
148
- | `stacked` | Label above, input below |
149
- | `inline` | Table-style rows (detail view) |
150
- | `split` | Left list + right detail |
189
+ | `stacked` | Label above, input below |
190
+ | `inline` | Table-style rows (detail view) |
191
+ | `split` | Left list + right detail |
151
192
 
152
193
  `layout` defaults to `stacked`; omit it when the stacked arrangement is intended.
153
194
 
154
- | Prop | Type | Description |
155
- |---|---|---|
156
- | `layout` | `GroupLayout` | Visual structure — see table above |
157
- | `variant` | `'card' \| 'plain' \| 'bordered'` | Wrapper style (defaults per layout) |
158
- | `title` | `ReactNode` | Group heading |
159
- | `description` | `ReactNode` | Subtitle below the heading |
160
- | `actions` | `ReactNode` | Action slot next to the heading |
161
- | `danger` | `boolean` | Renders title in destructive color |
162
- | `className` | `string` | Class applied to the group root element |
195
+ | Prop | Type | Description |
196
+ | ------------- | --------------------------------- | --------------------------------------- |
197
+ | `layout` | `GroupLayout` | Visual structure — see table above |
198
+ | `variant` | `'card' \| 'plain' \| 'bordered'` | Wrapper style (defaults per layout) |
199
+ | `title` | `ReactNode` | Group heading |
200
+ | `description` | `ReactNode` | Subtitle below the heading |
201
+ | `actions` | `ReactNode` | Action slot next to the heading |
202
+ | `danger` | `boolean` | Renders title in destructive color |
203
+ | `className` | `string` | Class applied to the group root element |
163
204
 
164
205
  ```tsx
165
206
  <DataBodyTemplate title="Settings">
@@ -184,7 +225,9 @@ Read-only key-value display.
184
225
  <DataBodyTemplate title="Profile">
185
226
  <DataBodyTemplate.Group layout="inline" title="Identity">
186
227
  <DataBodyTemplate.Field label="Email">sarah@acme.com</DataBodyTemplate.Field>
187
- <DataBodyTemplate.Field label="Role"><Badge>Admin</Badge></DataBodyTemplate.Field>
228
+ <DataBodyTemplate.Field label="Role">
229
+ <Badge>Admin</Badge>
230
+ </DataBodyTemplate.Field>
188
231
  </DataBodyTemplate.Group>
189
232
  </DataBodyTemplate>
190
233
  ```
@@ -198,7 +241,9 @@ Pinned summary area below the header, above tabs.
198
241
  <DataBodyTemplate.Summary>
199
242
  <StatCards />
200
243
  </DataBodyTemplate.Summary>
201
- <DataBodyTemplate.Tab id="details" label="Details">...</DataBodyTemplate.Tab>
244
+ <DataBodyTemplate.Tab id="details" label="Details">
245
+ ...
246
+ </DataBodyTemplate.Tab>
202
247
  </DataBodyTemplate>
203
248
  ```
204
249
 
@@ -229,7 +274,10 @@ handle stays inside the clipped panel corner:
229
274
  --designkit-resize-handle-inset: clamp(4px, calc(var(--radius) * 0.45), 16px);
230
275
  --designkit-resize-handle-mark-size: clamp(6px, calc(var(--radius) * 0.55), 12px);
231
276
  --designkit-resize-handle-color: rgba(0, 0, 0, 0.35);
232
- --designkit-resize-handle-mark-radius: min(var(--radius), var(--designkit-resize-handle-mark-size));
277
+ --designkit-resize-handle-mark-radius: min(
278
+ var(--radius),
279
+ var(--designkit-resize-handle-mark-size)
280
+ );
233
281
  }
234
282
 
235
283
  .dark .layout-dashboard .react-grid-item:not(.react-grid-placeholder) {
@@ -246,20 +294,25 @@ import type { PanelViewerProps } from '@loykin/dashboardkit'
246
294
 
247
295
  // Register panel types once at module level
248
296
  const engine = createDashboardEngine()
249
- engine.registerPanel(definePanel({
250
- id: 'stat',
251
- name: 'Stat',
252
- optionsSchema: {},
253
- viewer({ data, loading }: PanelViewerProps<unknown, unknown>) {
254
- if (loading) return null
255
- return <div className="text-2xl font-bold">{String(data ?? '—')}</div>
256
- },
257
- }))
297
+ engine.registerPanel(
298
+ definePanel({
299
+ id: 'stat',
300
+ name: 'Stat',
301
+ optionsSchema: {},
302
+ viewer({ data, loading }: PanelViewerProps<unknown, unknown>) {
303
+ if (loading) return null
304
+ return <div className="text-2xl font-bold">{String(data ?? '—')}</div>
305
+ },
306
+ }),
307
+ )
258
308
 
259
309
  export function MyDashboard() {
260
310
  useLoadDashboard(engine, config)
261
311
  const envVar = useVariable(engine, 'env')
262
- const variables = useMemo(() => ({ env: (envVar.value as string) ?? 'production' }), [envVar.value])
312
+ const variables = useMemo(
313
+ () => ({ env: (envVar.value as string) ?? 'production' }),
314
+ [envVar.value],
315
+ )
263
316
  const [editable, setEditable] = useState(false)
264
317
 
265
318
  return (
@@ -269,7 +322,8 @@ export function MyDashboard() {
269
322
  >
270
323
  <DashboardGrid engine={engine} editable={editable}>
271
324
  {({ panelType, config, data, rawData, loading, error, ref }) => {
272
- const Viewer = engine.getPanelPlugin(panelType)?.viewer as React.FC<PanelViewerProps<unknown, unknown>> | undefined
325
+ const Viewer = engine.getPanelPlugin(panelType)?.viewer as
326
+ React.FC<PanelViewerProps<unknown, unknown>> | undefined
273
327
  return (
274
328
  <DashboardPanel
275
329
  ref={ref}
@@ -281,10 +335,14 @@ export function MyDashboard() {
281
335
  >
282
336
  {Viewer && (
283
337
  <Viewer
284
- panel={config} options={config.options}
285
- data={data} rawData={rawData}
286
- width={0} height={0}
287
- loading={loading} error={error}
338
+ panel={config}
339
+ options={config.options}
340
+ data={data}
341
+ rawData={rawData}
342
+ width={0}
343
+ height={0}
344
+ loading={loading}
345
+ error={error}
288
346
  variables={variables}
289
347
  />
290
348
  )}
@@ -299,31 +357,31 @@ export function MyDashboard() {
299
357
 
300
358
  **DashboardBodyTemplate props:**
301
359
 
302
- | Prop | Type | Description |
303
- |---|---|---|
304
- | `topBar` | `ReactNode` | Top bar — breadcrumb, edit/refresh controls |
305
- | `variableBar` | `ReactNode` | Variable dropdown strip between top bar and panels |
306
- | `title` | `ReactNode` | Dashboard title (omit if topBar covers it) |
307
- | `description` | `ReactNode` | Subtitle |
308
- | `toolbar` | `ReactNode` | Toolbar slot next to the title |
309
- | `theme` | `CSSProperties` | Inline CSS variable overrides |
310
- | `className` | `string` | Class applied to the page root |
311
- | `contentClassName` | `string` | Class applied to the panel grid area |
360
+ | Prop | Type | Description |
361
+ | ------------------ | --------------- | -------------------------------------------------- |
362
+ | `topBar` | `ReactNode` | Top bar — breadcrumb, edit/refresh controls |
363
+ | `variableBar` | `ReactNode` | Variable dropdown strip between top bar and panels |
364
+ | `title` | `ReactNode` | Dashboard title (omit if topBar covers it) |
365
+ | `description` | `ReactNode` | Subtitle |
366
+ | `toolbar` | `ReactNode` | Toolbar slot next to the title |
367
+ | `theme` | `CSSProperties` | Inline CSS variable overrides |
368
+ | `className` | `string` | Class applied to the page root |
369
+ | `contentClassName` | `string` | Class applied to the panel grid area |
312
370
 
313
371
  **DashboardPanel props:**
314
372
 
315
373
  Panel card component. Wrap your panel viewer in `DashboardPanel` for consistent chrome across all panels.
316
374
 
317
- | Prop | Type | Description |
318
- |---|---|---|
319
- | `title` | `string` | Panel title |
320
- | `description` | `string` | Panel subtitle |
321
- | `loading` | `boolean` | Shows loading spinner overlay |
322
- | `error` | `string` | Shows error state, hides children |
323
- | `editable` | `boolean` | Shows drag handle and edit ring |
324
- | `headerRight` | `ReactNode` | Action slot in panel header — hidden until hover |
325
- | `transparent` | `boolean` | Removes card border and background |
326
- | `ref` | forwarded | Attach `DashboardGrid`'s `ref` for viewport virtualization |
375
+ | Prop | Type | Description |
376
+ | ------------- | ----------- | ---------------------------------------------------------- |
377
+ | `title` | `string` | Panel title |
378
+ | `description` | `string` | Panel subtitle |
379
+ | `loading` | `boolean` | Shows loading spinner overlay |
380
+ | `error` | `string` | Shows error state, hides children |
381
+ | `editable` | `boolean` | Shows drag handle and edit ring |
382
+ | `headerRight` | `ReactNode` | Action slot in panel header — hidden until hover |
383
+ | `transparent` | `boolean` | Removes card border and background |
384
+ | `ref` | forwarded | Attach `DashboardGrid`'s `ref` for viewport virtualization |
327
385
 
328
386
  ---
329
387
 
@@ -339,7 +397,11 @@ export function SqlEditorPage() {
339
397
  <WorkbenchBodyTemplate
340
398
  topBar={<PageTopBar left="Data / Query editor" />}
341
399
  title="SQL editor"
342
- headerRight={<Button variant="outline" size="sm">Run</Button>}
400
+ headerRight={
401
+ <Button variant="outline" size="sm">
402
+ Run
403
+ </Button>
404
+ }
343
405
  leftPane={<SchemaBrowser />}
344
406
  mainPane={<SqlEditor />}
345
407
  bottomPane={<ResultsGrid />}
@@ -351,18 +413,18 @@ export function SqlEditorPage() {
351
413
 
352
414
  **WorkbenchBodyTemplate props:**
353
415
 
354
- | Prop | Type | Description |
355
- |---|---|---|
356
- | `topBar` | `ReactNode` | Top bar above the workbench |
357
- | `title` / `description` | `ReactNode` | Header copy above the panes |
358
- | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
359
- | `headerRight` / `actions` | `ReactNode` | Header action slots |
360
- | `leftPane` / `rightPane` | `ReactNode` | Optional side panes |
361
- | `mainPane` | `ReactNode` | Primary editor or preview area |
362
- | `bottomPane` | `ReactNode` | Optional result, query, or logs pane |
363
- | `resizable` | `boolean` | Enables pane drag handles |
364
- | `leftPaneCollapsed` / `rightPaneCollapsed` / `bottomPaneCollapsed` | `boolean` | Hides optional panes while preserving the template layout model |
365
- | `leftPaneWidth` / `rightPaneWidth` / `bottomPaneHeight` | `number` | Initial pane sizes in pixels |
416
+ | Prop | Type | Description |
417
+ | ------------------------------------------------------------------ | ----------- | --------------------------------------------------------------- |
418
+ | `topBar` | `ReactNode` | Top bar above the workbench |
419
+ | `title` / `description` | `ReactNode` | Header copy above the panes |
420
+ | `status` | `ReactNode` | Badge or tag rendered inline next to the title |
421
+ | `headerRight` / `actions` | `ReactNode` | Header action slots |
422
+ | `leftPane` / `rightPane` | `ReactNode` | Optional side panes |
423
+ | `mainPane` | `ReactNode` | Primary editor or preview area |
424
+ | `bottomPane` | `ReactNode` | Optional result, query, or logs pane |
425
+ | `resizable` | `boolean` | Enables pane drag handles |
426
+ | `leftPaneCollapsed` / `rightPaneCollapsed` / `bottomPaneCollapsed` | `boolean` | Hides optional panes while preserving the template layout model |
427
+ | `leftPaneWidth` / `rightPaneWidth` / `bottomPaneHeight` | `number` | Initial pane sizes in pixels |
366
428
 
367
429
  ---
368
430
 
@@ -375,9 +437,9 @@ import { useState } from 'react'
375
437
  import { FormWizardBodyTemplate, type FormWizardStep } from '@loykin/designkit'
376
438
 
377
439
  const steps: FormWizardStep[] = [
378
- { key: 'info', title: 'Basic Info', content: <BasicInfoForm /> },
379
- { key: 'config', title: 'Configuration', content: <ConfigForm /> },
380
- { key: 'review', title: 'Review', content: <ReviewStep /> },
440
+ { key: 'info', title: 'Basic Info', content: <BasicInfoForm /> },
441
+ { key: 'config', title: 'Configuration', content: <ConfigForm /> },
442
+ { key: 'review', title: 'Review', content: <ReviewStep /> },
381
443
  ]
382
444
 
383
445
  export function OnboardingPage() {
@@ -427,21 +489,23 @@ export function SignInPage() {
427
489
  <Label htmlFor="password">Password</Label>
428
490
  <Input id="password" type="password" />
429
491
  </div>
430
- <Button type="submit" className="w-full">Sign In</Button>
492
+ <Button type="submit" className="w-full">
493
+ Sign In
494
+ </Button>
431
495
  </form>
432
496
  </LoginBodyTemplate>
433
497
  )
434
498
  }
435
499
  ```
436
500
 
437
- | Prop | Type | Default | Description |
438
- |---|---|---|---|
439
- | `layout` | `'centered' \| 'split'` | `'centered'` | Centered card or split-panel with brand side |
440
- | `card` | `'card' \| 'plain'` | `'plain'` | Wrap form content in a card border |
441
- | `cardWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Form card width |
442
- | `bg` | `'default' \| 'subtle' \| 'none'` | `'default'` | Background style |
443
- | `side` | `'left' \| 'right'` | `'left'` | Brand panel side (split layout only) |
444
- | `brand` | `ReactNode` | built-in | Custom brand/logo panel content |
501
+ | Prop | Type | Default | Description |
502
+ | ----------- | --------------------------------- | ------------ | -------------------------------------------- |
503
+ | `layout` | `'centered' \| 'split'` | `'centered'` | Centered card or split-panel with brand side |
504
+ | `card` | `'card' \| 'plain'` | `'plain'` | Wrap form content in a card border |
505
+ | `cardWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Form card width |
506
+ | `bg` | `'default' \| 'subtle' \| 'none'` | `'default'` | Background style |
507
+ | `side` | `'left' \| 'right'` | `'left'` | Brand panel side (split layout only) |
508
+ | `brand` | `ReactNode` | built-in | Custom brand/logo panel content |
445
509
 
446
510
  ---
447
511
 
@@ -449,10 +513,15 @@ export function SignInPage() {
449
513
 
450
514
  ```tsx
451
515
  import {
452
- Avatar, AvatarFallback, AvatarImage,
516
+ Avatar,
517
+ AvatarFallback,
518
+ AvatarImage,
453
519
  Badge,
454
520
  Button,
455
- Card, CardContent, CardHeader, CardTitle,
521
+ Card,
522
+ CardContent,
523
+ CardHeader,
524
+ CardTitle,
456
525
  Checkbox,
457
526
  DropdownMenu,
458
527
  EmptyState,
@@ -461,16 +530,30 @@ import {
461
530
  NavigationMenu,
462
531
  Popover,
463
532
  ScrollArea,
464
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
533
+ Select,
534
+ SelectContent,
535
+ SelectItem,
536
+ SelectTrigger,
537
+ SelectValue,
465
538
  Separator,
466
- Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger,
539
+ Sheet,
540
+ SheetContent,
541
+ SheetHeader,
542
+ SheetTitle,
543
+ SheetTrigger,
467
544
  Sidebar,
468
545
  Skeleton,
469
546
  Slider,
470
547
  Switch,
471
548
  Table,
472
- Tabs, TabsList, TabsTrigger, TabsContent,
473
- Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,
549
+ Tabs,
550
+ TabsList,
551
+ TabsTrigger,
552
+ TabsContent,
553
+ Tooltip,
554
+ TooltipContent,
555
+ TooltipProvider,
556
+ TooltipTrigger,
474
557
  PageTopBar,
475
558
  } from '@loykin/designkit'
476
559
  ```
@@ -482,17 +565,17 @@ Top breadcrumb / action bar placed at the top of a page template via the `topBar
482
565
  ```tsx
483
566
  import { PageTopBar } from '@loykin/designkit'
484
567
 
485
- <PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
568
+ ;<PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
486
569
  ```
487
570
 
488
- | Prop | Type | Default | Description |
489
- |---|---|---|---|
490
- | `left` | `ReactNode` | — | Left content. A plain string is parsed as `/`-separated breadcrumbs. |
491
- | `right` | `ReactNode` | — | Right-aligned actions |
492
- | `variant` | `'ghost' \| 'default'` | `'ghost'` | `'default'` adds a bottom border |
493
- | `sidebarTrigger` | `false \| ReactNode` | auto | Mobile sidebar open button. `undefined` auto-detects SidebarProvider context; `false` suppresses; pass a `ReactNode` for a custom trigger. |
494
- | `height` | `string` | `var(--designkit-toolbar-height)` | Bar height |
495
- | `className` | `string` | — | Class applied to the bar root |
571
+ | Prop | Type | Default | Description |
572
+ | ---------------- | ---------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
573
+ | `left` | `ReactNode` | — | Left content. A plain string is parsed as `/`-separated breadcrumbs. |
574
+ | `right` | `ReactNode` | — | Right-aligned actions |
575
+ | `variant` | `'ghost' \| 'default'` | `'ghost'` | `'default'` adds a bottom border |
576
+ | `sidebarTrigger` | `false \| ReactNode` | auto | Mobile sidebar open button. `undefined` auto-detects SidebarProvider context; `false` suppresses; pass a `ReactNode` for a custom trigger. |
577
+ | `height` | `string` | `var(--designkit-toolbar-height)` | Bar height |
578
+ | `className` | `string` | — | Class applied to the bar root |
496
579
 
497
580
  **Mobile sidebar trigger**
498
581
 
@@ -514,7 +597,7 @@ When `PageTopBar` is rendered inside a `SidebarShell` on a mobile viewport, it a
514
597
  import { EmptyState } from '@loykin/designkit'
515
598
  import { Users } from 'lucide-react'
516
599
 
517
- <EmptyState
600
+ ;<EmptyState
518
601
  icon={Users}
519
602
  title="No users yet"
520
603
  description="Add your first user to get started."
@@ -528,18 +611,18 @@ import { Users } from 'lucide-react'
528
611
 
529
612
  Designkit maps shadcn/ui CSS variables onto its own `--designkit-*` tokens. Changing your shadcn theme automatically updates all designkit components.
530
613
 
531
- | What | How |
532
- |---|---|
533
- | Colors, radius, typography | shadcn/ui theme variables (`--primary`, `--radius`, etc.) |
534
- | Spacing, density, padding | `--designkit-density`, `--designkit-page-padding-*`, `--designkit-panel-gap` |
535
- | Per-page overrides | `className` or `theme` prop |
614
+ | What | How |
615
+ | -------------------------- | ---------------------------------------------------------------------------- |
616
+ | Colors, radius, typography | shadcn/ui theme variables (`--primary`, `--radius`, etc.) |
617
+ | Spacing, density, padding | `--designkit-density`, `--designkit-page-padding-*`, `--designkit-panel-gap` |
618
+ | Per-page overrides | `className` or `theme` prop |
536
619
 
537
620
  ```css
538
621
  :root {
539
- --designkit-density: 1; /* 0.85 compact / 1 default / 1.15 comfortable */
622
+ --designkit-density: 1; /* 0.85 compact / 1 default / 1.15 comfortable */
540
623
  --designkit-page-padding-x: 1.5rem;
541
624
  --designkit-page-padding-y: 1rem;
542
- --designkit-panel-gap: 1rem;
625
+ --designkit-panel-gap: 1rem;
543
626
  }
544
627
  ```
545
628
 
@@ -571,9 +654,9 @@ Or via `theme` prop:
571
654
  If your app also uses `@loykin/gridkit`, import its styles **after** designkit:
572
655
 
573
656
  ```css
574
- @import "tailwindcss";
575
- @import "@loykin/designkit/styles";
576
- @import "@loykin/gridkit/styles"; /* must come last — uses @layer gridkit */
657
+ @import 'tailwindcss';
658
+ @import '@loykin/designkit/styles';
659
+ @import '@loykin/gridkit/styles'; /* must come last — uses @layer gridkit */
577
660
  ```
578
661
 
579
662
  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
+ }