@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 +200 -125
- package/cli/designkit.mjs +78 -0
- package/dist/index.cjs +65 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +65 -5
- package/dist/index.js.map +1 -1
- package/docs/guides/README.md +19 -0
- package/docs/guides/commerce-workflow.md +47 -0
- package/docs/guides/managed-table.md +435 -0
- package/docs/guides/manifest.json +39 -0
- package/docs/guides/publishing-workflow.md +53 -0
- package/package.json +17 -5
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
27
|
-
@import
|
|
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
|
|
81
|
-
|
|
82
|
-
| `topBar`
|
|
83
|
-
| `title`
|
|
84
|
-
| `description`
|
|
85
|
-
| `status`
|
|
86
|
-
| `actions`
|
|
87
|
-
| `toolbarLeft` / `toolbarRight` | `ReactNode`
|
|
88
|
-
| `theme`
|
|
89
|
-
| `className`
|
|
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">
|
|
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
|
|
146
|
-
|
|
178
|
+
| layout | Use case |
|
|
179
|
+
| ------------ | --------------------------------------------- |
|
|
147
180
|
| `horizontal` | Label on left, input on right (settings form) |
|
|
148
|
-
| `stacked`
|
|
149
|
-
| `inline`
|
|
150
|
-
| `split`
|
|
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
|
|
155
|
-
|
|
156
|
-
| `layout`
|
|
157
|
-
| `variant`
|
|
158
|
-
| `title`
|
|
159
|
-
| `description` | `ReactNode`
|
|
160
|
-
| `actions`
|
|
161
|
-
| `danger`
|
|
162
|
-
| `className`
|
|
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"
|
|
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"
|
|
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(
|
|
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(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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(
|
|
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
|
|
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}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
303
|
-
|
|
304
|
-
| `topBar`
|
|
305
|
-
| `variableBar`
|
|
306
|
-
| `title`
|
|
307
|
-
| `description`
|
|
308
|
-
| `toolbar`
|
|
309
|
-
| `theme`
|
|
310
|
-
| `className`
|
|
311
|
-
| `contentClassName` | `string`
|
|
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
|
|
318
|
-
|
|
319
|
-
| `title`
|
|
320
|
-
| `description` | `string`
|
|
321
|
-
| `loading`
|
|
322
|
-
| `error`
|
|
323
|
-
| `editable`
|
|
324
|
-
| `headerRight` | `ReactNode` | Action slot in panel header — hidden until hover
|
|
325
|
-
| `transparent` | `boolean`
|
|
326
|
-
| `ref`
|
|
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={
|
|
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
|
|
355
|
-
|
|
356
|
-
| `topBar`
|
|
357
|
-
| `title` / `description`
|
|
358
|
-
| `status`
|
|
359
|
-
| `headerRight` / `actions`
|
|
360
|
-
| `leftPane` / `rightPane`
|
|
361
|
-
| `mainPane`
|
|
362
|
-
| `bottomPane`
|
|
363
|
-
| `resizable`
|
|
364
|
-
| `leftPaneCollapsed` / `rightPaneCollapsed` / `bottomPaneCollapsed` | `boolean`
|
|
365
|
-
| `leftPaneWidth` / `rightPaneWidth` / `bottomPaneHeight`
|
|
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',
|
|
379
|
-
{ key: 'config', title: 'Configuration', content: <ConfigForm />
|
|
380
|
-
{ key: 'review', title: 'Review',
|
|
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">
|
|
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
|
|
438
|
-
|
|
439
|
-
| `layout`
|
|
440
|
-
| `card`
|
|
441
|
-
| `cardWidth` | `'sm' \| 'md' \| 'lg'`
|
|
442
|
-
| `bg`
|
|
443
|
-
| `side`
|
|
444
|
-
| `brand`
|
|
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,
|
|
508
|
+
Avatar,
|
|
509
|
+
AvatarFallback,
|
|
510
|
+
AvatarImage,
|
|
453
511
|
Badge,
|
|
454
512
|
Button,
|
|
455
|
-
Card,
|
|
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,
|
|
525
|
+
Select,
|
|
526
|
+
SelectContent,
|
|
527
|
+
SelectItem,
|
|
528
|
+
SelectTrigger,
|
|
529
|
+
SelectValue,
|
|
465
530
|
Separator,
|
|
466
|
-
Sheet,
|
|
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,
|
|
473
|
-
|
|
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
|
-
|
|
560
|
+
;<PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
|
|
486
561
|
```
|
|
487
562
|
|
|
488
|
-
| Prop
|
|
489
|
-
|
|
490
|
-
| `left`
|
|
491
|
-
| `right`
|
|
492
|
-
| `variant`
|
|
493
|
-
| `sidebarTrigger` | `false \| ReactNode`
|
|
494
|
-
| `height`
|
|
495
|
-
| `className`
|
|
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
|
-
|
|
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
|
|
532
|
-
|
|
533
|
-
| Colors, radius, typography | shadcn/ui theme variables (`--primary`, `--radius`, etc.)
|
|
534
|
-
| Spacing, density, padding
|
|
535
|
-
| Per-page overrides
|
|
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:
|
|
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:
|
|
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
|
|
575
|
-
@import
|
|
576
|
-
@import
|
|
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
|
+
}
|