@loykin/designkit 0.0.2 → 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,9 +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, 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
+ | 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.
8
43
 
9
44
  ## Installation
10
45
 
@@ -23,8 +58,8 @@ Import Tailwind and DesignKit from the same global CSS entry:
23
58
 
24
59
  ```css
25
60
  /* globals.css */
26
- @import "tailwindcss";
27
- @import "@loykin/designkit/styles";
61
+ @import 'tailwindcss';
62
+ @import '@loykin/designkit/styles';
28
63
  ```
29
64
 
30
65
  `@loykin/designkit/styles` registers the package's compiled JavaScript as a
@@ -44,9 +79,7 @@ export function UsersPage() {
44
79
  description="Manage your team members."
45
80
  actions={<Button>Add User</Button>}
46
81
  >
47
- <DataBodyTemplate.Body>
48
- {/* your content */}
49
- </DataBodyTemplate.Body>
82
+ <DataBodyTemplate.Body>{/* your content */}</DataBodyTemplate.Body>
50
83
  </DataBodyTemplate>
51
84
  )
52
85
  }
@@ -77,16 +110,16 @@ General-purpose page shell. Accepts `.Body`, `.Tab`, and `.Section` child slots
77
110
  </DataBodyTemplate>
78
111
  ```
79
112
 
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 |
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 |
90
123
 
91
124
  #### DataBodyTemplate.Body
92
125
 
@@ -94,9 +127,7 @@ Single-pane content. Use for full-height layouts.
94
127
 
95
128
  ```tsx
96
129
  <DataBodyTemplate title="Users">
97
- <DataBodyTemplate.Body>
98
- {/* your content */}
99
- </DataBodyTemplate.Body>
130
+ <DataBodyTemplate.Body>{/* your content */}</DataBodyTemplate.Body>
100
131
  </DataBodyTemplate>
101
132
  ```
102
133
 
@@ -128,7 +159,9 @@ Settings-style layout with left navigation and right content panel.
128
159
  <form onSubmit={handleSubmit} className="space-y-3">
129
160
  <Label htmlFor="name">Name</Label>
130
161
  <Input id="name" defaultValue="Acme Corp" />
131
- <Button type="submit" size="sm">Save</Button>
162
+ <Button type="submit" size="sm">
163
+ Save
164
+ </Button>
132
165
  </form>
133
166
  </DataBodyTemplate.Group>
134
167
  </DataBodyTemplate.Section>
@@ -142,24 +175,24 @@ Settings-style layout with left navigation and right content panel.
142
175
 
143
176
  Groups content within a tab or section. The `layout` prop controls the visual structure.
144
177
 
145
- | layout | Use case |
146
- |---|---|
178
+ | layout | Use case |
179
+ | ------------ | --------------------------------------------- |
147
180
  | `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 |
181
+ | `stacked` | Label above, input below |
182
+ | `inline` | Table-style rows (detail view) |
183
+ | `split` | Left list + right detail |
151
184
 
152
185
  `layout` defaults to `stacked`; omit it when the stacked arrangement is intended.
153
186
 
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 |
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 |
163
196
 
164
197
  ```tsx
165
198
  <DataBodyTemplate title="Settings">
@@ -184,7 +217,9 @@ Read-only key-value display.
184
217
  <DataBodyTemplate title="Profile">
185
218
  <DataBodyTemplate.Group layout="inline" title="Identity">
186
219
  <DataBodyTemplate.Field label="Email">sarah@acme.com</DataBodyTemplate.Field>
187
- <DataBodyTemplate.Field label="Role"><Badge>Admin</Badge></DataBodyTemplate.Field>
220
+ <DataBodyTemplate.Field label="Role">
221
+ <Badge>Admin</Badge>
222
+ </DataBodyTemplate.Field>
188
223
  </DataBodyTemplate.Group>
189
224
  </DataBodyTemplate>
190
225
  ```
@@ -198,7 +233,9 @@ Pinned summary area below the header, above tabs.
198
233
  <DataBodyTemplate.Summary>
199
234
  <StatCards />
200
235
  </DataBodyTemplate.Summary>
201
- <DataBodyTemplate.Tab id="details" label="Details">...</DataBodyTemplate.Tab>
236
+ <DataBodyTemplate.Tab id="details" label="Details">
237
+ ...
238
+ </DataBodyTemplate.Tab>
202
239
  </DataBodyTemplate>
203
240
  ```
204
241
 
@@ -229,7 +266,10 @@ handle stays inside the clipped panel corner:
229
266
  --designkit-resize-handle-inset: clamp(4px, calc(var(--radius) * 0.45), 16px);
230
267
  --designkit-resize-handle-mark-size: clamp(6px, calc(var(--radius) * 0.55), 12px);
231
268
  --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));
269
+ --designkit-resize-handle-mark-radius: min(
270
+ var(--radius),
271
+ var(--designkit-resize-handle-mark-size)
272
+ );
233
273
  }
234
274
 
235
275
  .dark .layout-dashboard .react-grid-item:not(.react-grid-placeholder) {
@@ -246,20 +286,25 @@ import type { PanelViewerProps } from '@loykin/dashboardkit'
246
286
 
247
287
  // Register panel types once at module level
248
288
  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
- }))
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
+ )
258
300
 
259
301
  export function MyDashboard() {
260
302
  useLoadDashboard(engine, config)
261
303
  const envVar = useVariable(engine, 'env')
262
- 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
+ )
263
308
  const [editable, setEditable] = useState(false)
264
309
 
265
310
  return (
@@ -269,7 +314,8 @@ export function MyDashboard() {
269
314
  >
270
315
  <DashboardGrid engine={engine} editable={editable}>
271
316
  {({ panelType, config, data, rawData, loading, error, ref }) => {
272
- 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
273
319
  return (
274
320
  <DashboardPanel
275
321
  ref={ref}
@@ -281,10 +327,14 @@ export function MyDashboard() {
281
327
  >
282
328
  {Viewer && (
283
329
  <Viewer
284
- panel={config} options={config.options}
285
- data={data} rawData={rawData}
286
- width={0} height={0}
287
- 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}
288
338
  variables={variables}
289
339
  />
290
340
  )}
@@ -299,31 +349,31 @@ export function MyDashboard() {
299
349
 
300
350
  **DashboardBodyTemplate props:**
301
351
 
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 |
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 |
312
362
 
313
363
  **DashboardPanel props:**
314
364
 
315
365
  Panel card component. Wrap your panel viewer in `DashboardPanel` for consistent chrome across all panels.
316
366
 
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 |
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 |
327
377
 
328
378
  ---
329
379
 
@@ -339,7 +389,11 @@ export function SqlEditorPage() {
339
389
  <WorkbenchBodyTemplate
340
390
  topBar={<PageTopBar left="Data / Query editor" />}
341
391
  title="SQL editor"
342
- headerRight={<Button variant="outline" size="sm">Run</Button>}
392
+ headerRight={
393
+ <Button variant="outline" size="sm">
394
+ Run
395
+ </Button>
396
+ }
343
397
  leftPane={<SchemaBrowser />}
344
398
  mainPane={<SqlEditor />}
345
399
  bottomPane={<ResultsGrid />}
@@ -351,18 +405,18 @@ export function SqlEditorPage() {
351
405
 
352
406
  **WorkbenchBodyTemplate props:**
353
407
 
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 |
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 |
366
420
 
367
421
  ---
368
422
 
@@ -375,9 +429,9 @@ import { useState } from 'react'
375
429
  import { FormWizardBodyTemplate, type FormWizardStep } from '@loykin/designkit'
376
430
 
377
431
  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 /> },
432
+ { key: 'info', title: 'Basic Info', content: <BasicInfoForm /> },
433
+ { key: 'config', title: 'Configuration', content: <ConfigForm /> },
434
+ { key: 'review', title: 'Review', content: <ReviewStep /> },
381
435
  ]
382
436
 
383
437
  export function OnboardingPage() {
@@ -427,21 +481,23 @@ export function SignInPage() {
427
481
  <Label htmlFor="password">Password</Label>
428
482
  <Input id="password" type="password" />
429
483
  </div>
430
- <Button type="submit" className="w-full">Sign In</Button>
484
+ <Button type="submit" className="w-full">
485
+ Sign In
486
+ </Button>
431
487
  </form>
432
488
  </LoginBodyTemplate>
433
489
  )
434
490
  }
435
491
  ```
436
492
 
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 |
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 |
445
501
 
446
502
  ---
447
503
 
@@ -449,10 +505,15 @@ export function SignInPage() {
449
505
 
450
506
  ```tsx
451
507
  import {
452
- Avatar, AvatarFallback, AvatarImage,
508
+ Avatar,
509
+ AvatarFallback,
510
+ AvatarImage,
453
511
  Badge,
454
512
  Button,
455
- Card, CardContent, CardHeader, CardTitle,
513
+ Card,
514
+ CardContent,
515
+ CardHeader,
516
+ CardTitle,
456
517
  Checkbox,
457
518
  DropdownMenu,
458
519
  EmptyState,
@@ -461,16 +522,30 @@ import {
461
522
  NavigationMenu,
462
523
  Popover,
463
524
  ScrollArea,
464
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
525
+ Select,
526
+ SelectContent,
527
+ SelectItem,
528
+ SelectTrigger,
529
+ SelectValue,
465
530
  Separator,
466
- Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger,
531
+ Sheet,
532
+ SheetContent,
533
+ SheetHeader,
534
+ SheetTitle,
535
+ SheetTrigger,
467
536
  Sidebar,
468
537
  Skeleton,
469
538
  Slider,
470
539
  Switch,
471
540
  Table,
472
- Tabs, TabsList, TabsTrigger, TabsContent,
473
- Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,
541
+ Tabs,
542
+ TabsList,
543
+ TabsTrigger,
544
+ TabsContent,
545
+ Tooltip,
546
+ TooltipContent,
547
+ TooltipProvider,
548
+ TooltipTrigger,
474
549
  PageTopBar,
475
550
  } from '@loykin/designkit'
476
551
  ```
@@ -482,17 +557,17 @@ Top breadcrumb / action bar placed at the top of a page template via the `topBar
482
557
  ```tsx
483
558
  import { PageTopBar } from '@loykin/designkit'
484
559
 
485
- <PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
560
+ ;<PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
486
561
  ```
487
562
 
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 |
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 |
496
571
 
497
572
  **Mobile sidebar trigger**
498
573
 
@@ -514,7 +589,7 @@ When `PageTopBar` is rendered inside a `SidebarShell` on a mobile viewport, it a
514
589
  import { EmptyState } from '@loykin/designkit'
515
590
  import { Users } from 'lucide-react'
516
591
 
517
- <EmptyState
592
+ ;<EmptyState
518
593
  icon={Users}
519
594
  title="No users yet"
520
595
  description="Add your first user to get started."
@@ -528,18 +603,18 @@ import { Users } from 'lucide-react'
528
603
 
529
604
  Designkit maps shadcn/ui CSS variables onto its own `--designkit-*` tokens. Changing your shadcn theme automatically updates all designkit components.
530
605
 
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 |
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 |
536
611
 
537
612
  ```css
538
613
  :root {
539
- --designkit-density: 1; /* 0.85 compact / 1 default / 1.15 comfortable */
614
+ --designkit-density: 1; /* 0.85 compact / 1 default / 1.15 comfortable */
540
615
  --designkit-page-padding-x: 1.5rem;
541
616
  --designkit-page-padding-y: 1rem;
542
- --designkit-panel-gap: 1rem;
617
+ --designkit-panel-gap: 1rem;
543
618
  }
544
619
  ```
545
620
 
@@ -571,9 +646,9 @@ Or via `theme` prop:
571
646
  If your app also uses `@loykin/gridkit`, import its styles **after** designkit:
572
647
 
573
648
  ```css
574
- @import "tailwindcss";
575
- @import "@loykin/designkit/styles";
576
- @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 */
577
652
  ```
578
653
 
579
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
+ }