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