@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 +208 -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 +20 -0
- package/docs/guides/commerce-workflow.md +47 -0
- package/docs/guides/form-workflow.md +212 -0
- package/docs/guides/managed-table.md +435 -0
- package/docs/guides/manifest.json +48 -0
- package/docs/guides/publishing-workflow.md +53 -0
- package/package.json +17 -5
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
|
-
|
|
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
|
+
| 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
|
|
27
|
-
@import
|
|
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
|
|
81
|
-
|
|
82
|
-
| `topBar`
|
|
83
|
-
| `title`
|
|
84
|
-
| `description`
|
|
85
|
-
| `status`
|
|
86
|
-
| `actions`
|
|
87
|
-
| `toolbarLeft` / `toolbarRight` | `ReactNode`
|
|
88
|
-
| `theme`
|
|
89
|
-
| `className`
|
|
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">
|
|
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
|
|
146
|
-
|
|
186
|
+
| layout | Use case |
|
|
187
|
+
| ------------ | --------------------------------------------- |
|
|
147
188
|
| `horizontal` | Label on left, input on right (settings form) |
|
|
148
|
-
| `stacked`
|
|
149
|
-
| `inline`
|
|
150
|
-
| `split`
|
|
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
|
|
155
|
-
|
|
156
|
-
| `layout`
|
|
157
|
-
| `variant`
|
|
158
|
-
| `title`
|
|
159
|
-
| `description` | `ReactNode`
|
|
160
|
-
| `actions`
|
|
161
|
-
| `danger`
|
|
162
|
-
| `className`
|
|
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"
|
|
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"
|
|
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(
|
|
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(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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(
|
|
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
|
|
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}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
303
|
-
|
|
304
|
-
| `topBar`
|
|
305
|
-
| `variableBar`
|
|
306
|
-
| `title`
|
|
307
|
-
| `description`
|
|
308
|
-
| `toolbar`
|
|
309
|
-
| `theme`
|
|
310
|
-
| `className`
|
|
311
|
-
| `contentClassName` | `string`
|
|
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
|
|
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`
|
|
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={
|
|
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
|
|
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`
|
|
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',
|
|
379
|
-
{ key: 'config', title: 'Configuration', content: <ConfigForm />
|
|
380
|
-
{ key: 'review', title: 'Review',
|
|
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">
|
|
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
|
|
438
|
-
|
|
439
|
-
| `layout`
|
|
440
|
-
| `card`
|
|
441
|
-
| `cardWidth` | `'sm' \| 'md' \| 'lg'`
|
|
442
|
-
| `bg`
|
|
443
|
-
| `side`
|
|
444
|
-
| `brand`
|
|
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,
|
|
516
|
+
Avatar,
|
|
517
|
+
AvatarFallback,
|
|
518
|
+
AvatarImage,
|
|
453
519
|
Badge,
|
|
454
520
|
Button,
|
|
455
|
-
Card,
|
|
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,
|
|
533
|
+
Select,
|
|
534
|
+
SelectContent,
|
|
535
|
+
SelectItem,
|
|
536
|
+
SelectTrigger,
|
|
537
|
+
SelectValue,
|
|
465
538
|
Separator,
|
|
466
|
-
Sheet,
|
|
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,
|
|
473
|
-
|
|
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
|
-
|
|
568
|
+
;<PageTopBar left="Admin / Users" right={<Button size="sm">Add</Button>} />
|
|
486
569
|
```
|
|
487
570
|
|
|
488
|
-
| Prop
|
|
489
|
-
|
|
490
|
-
| `left`
|
|
491
|
-
| `right`
|
|
492
|
-
| `variant`
|
|
493
|
-
| `sidebarTrigger` | `false \| ReactNode`
|
|
494
|
-
| `height`
|
|
495
|
-
| `className`
|
|
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
|
-
|
|
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
|
|
532
|
-
|
|
533
|
-
| Colors, radius, typography | shadcn/ui theme variables (`--primary`, `--radius`, etc.)
|
|
534
|
-
| Spacing, density, padding
|
|
535
|
-
| Per-page overrides
|
|
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:
|
|
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:
|
|
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
|
|
575
|
-
@import
|
|
576
|
-
@import
|
|
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
|
+
}
|