@loykin/designkit 0.0.4 → 0.0.5

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.
@@ -2,12 +2,15 @@
2
2
 
3
3
  The Playground's **Guides** group contains complete product flows. Existing template groups remain visual API references; they do not receive an AI guide merely because a workflow uses that template.
4
4
 
5
- | Workflow | Pattern ID | Connected destinations | Contract |
6
- | ------------------- | --------------------- | -------------------------------------- | ----------------------------------------------- |
7
- | Resource Management | `managed-table` | table → detail Sheet; create/edit page | [Managed Table](./managed-table.md) |
8
- | Forms | `form-workflow` | create, edit, and settings pages | [Stacked Form](./form-workflow.md) |
9
- | Publishing | `publishing-workflow` | blog collection article route | [Publishing Workflow](./publishing-workflow.md) |
10
- | Commerce | `commerce-workflow` | filtered catalogproduct route | [Commerce Workflow](./commerce-workflow.md) |
5
+ Read [AI UI Implementation Contract](./ai-ui-implementation-contract.md) before implementing any Guide screen — it defines the mandatory build order (template → slot → component → variant → minimal custom CSS) that every Guide below assumes.
6
+
7
+ | Workflow | Pattern ID | Connected destinations | Contract |
8
+ | ------------------- | ---------------------- | -------------------------------------- | ------------------------------------------------- |
9
+ | Resource Management | `managed-table` | tabledetail SidePanel; create/edit page | [Managed Table](./managed-table.md) |
10
+ | Operations | `kubernetes-workspace` | Pod tabledetail panel → tool dock | [Kubernetes Workspace](./kubernetes-workspace.md) |
11
+ | Forms | `form-workflow` | create, edit, and settings pages | [Stacked Form](./form-workflow.md) |
12
+ | Publishing | `publishing-workflow` | blog collection → article route | [Publishing Workflow](./publishing-workflow.md) |
13
+ | Commerce | `commerce-workflow` | filtered catalog → product route | [Commerce Workflow](./commerce-workflow.md) |
11
14
 
12
15
  ## How to use these guides
13
16
 
@@ -0,0 +1,52 @@
1
+ # AI UI Implementation Contract
2
+
3
+ This contract applies to every Guide in `docs/guides/`. Read it once, then read the specific Guide for the workflow you are building. It exists because an executable Guide demo teaches by example — an AI session copies what the code *does* more strongly than what the prose *says*. If a Guide's TSX is full of one-off Tailwind, that reads as "styling is done ad hoc here," no matter what the Markdown claims.
4
+
5
+ ## Mandatory implementation order
6
+
7
+ 1. Select the closest executable Guide (`docs/guides/manifest.json`).
8
+ 2. Use the page template that Guide documents.
9
+ 3. Fill the template's documented slots before creating wrapper layouts.
10
+ 4. Use existing DesignKit or bundled shadcn components.
11
+ 5. Use an existing named variant for a supported visual difference.
12
+ 6. If a visual difference is reusable, add or extend a named component or variant — see `AGENTS.md`'s Implementation Priority.
13
+ 7. Use custom CSS only when the requirement is content-specific, integration-specific, and cannot be represented by an existing template, slot, component, or variant.
14
+
15
+ Custom CSS is not forbidden. It must not read as the default way to build a screen.
16
+
17
+ ## Forbidden patterns
18
+
19
+ - Reimplementing page-level layout (reading width, article typography, card elevation, section spacing) with raw `<div>` + Tailwind when a DesignKit component or variant already expresses it.
20
+ - Overriding padding, width, typography hierarchy, or responsive behavior that a template already owns.
21
+ - Storing Tailwind utility strings in domain/sample data (e.g. `accent: 'from-violet-500/80 ...'`). Store a semantic value (`accent: 'violet'`) and map it through a component or variant instead — an AI session should never need to invent gradient utility combinations.
22
+ - Copying the same hand-rolled visual pattern into more than one Guide. The second occurrence is the signal to promote it to a component (see `InteractiveCard`, born from the same hover/elevation block appearing in four demos).
23
+
24
+ ## Classifying a `className`/`style` you're about to write
25
+
26
+ - **Promote to a DesignKit template-level component** if the pattern is reusable across pages regardless of domain: reading width, article typography, metadata rows, table of contents, loading skeletons, elevation/hover behavior, cover/hero layout.
27
+ - **Promote to a named variant** if it's a limited, enumerable visual choice on an existing component: tone, density, elevation, content width, presentation mode.
28
+ - **Keep as local custom CSS** only when all of the following hold: it is content- or integration-specific; it cannot be expressed by an existing template, slot, component, or variant; it does not repeat in another Guide; it does not touch template-owned layout. Justify it inline:
29
+
30
+ ```tsx
31
+ /**
32
+ * Guide exception:
33
+ * This third-party renderer requires an explicit container height.
34
+ * Page spacing, typography, and responsive layout remain owned by DesignKit.
35
+ */
36
+ ```
37
+
38
+ Custom styling with no such comment should be removed or promoted the next time it's touched.
39
+
40
+ ## Reconstruction checklist (add to every Guide)
41
+
42
+ - [ ] Existing template slots are used before custom wrapper layouts
43
+ - [ ] Existing DesignKit or shadcn variants/components are used before custom classes
44
+ - [ ] Reusable visual differences are implemented as named components or variants, not repeated `className` blocks
45
+ - [ ] Custom CSS is local, minimal, and explicitly justified with a `Guide exception` comment
46
+ - [ ] No Tailwind utility strings are stored in domain data
47
+ - [ ] Template-owned spacing, width, typography, and responsive behavior are not overridden
48
+
49
+ ## Precedent
50
+
51
+ - `ArticleCover`, `ArticleByline`, `ArticleToc`, `ArticleBody`, `ArticleBodySkeleton` (`@loykin/designkit`) — extracted from the Publishing guide's hand-rolled article markup; a `tone: ArticleTone` prop replaces raw gradient strings in sample data.
52
+ - `InteractiveCard` (`@loykin/designkit`) — the hover-lift-card treatment duplicated across the Publishing, Commerce, Browse, and Blog Feed demos, promoted to one component.
@@ -12,6 +12,8 @@ Use this guide to build a discovery-to-decision commerce experience. The executa
12
12
 
13
13
  The existing **Browse / Catalog** and product detail template demos remain visual references. This Guide defines their behavioral connection.
14
14
 
15
+ Read [AI UI Implementation Contract](./ai-ui-implementation-contract.md) first. Product cards use DesignKit `InteractiveCard` for the clickable, hover-lifting shell — the same component the Publishing guide uses — not a hand-rolled `<div className="... hover:shadow-md ...">`.
16
+
15
17
  ## Route contract
16
18
 
17
19
  ```text
@@ -45,3 +47,4 @@ Selecting a product card navigates to the product route. Browser Back and the pr
45
47
  - [ ] Product breadcrumb returns to catalog
46
48
  - [ ] Purchase action exists only on the product route
47
49
  - [ ] No nested page templates and no product Sheet
50
+ - [ ] Product cards use `InteractiveCard`, not a hand-rolled hover/elevation `className` block
@@ -11,6 +11,8 @@ Use this contract for create, edit, and settings forms. It standardizes the visu
11
11
 
12
12
  The existing **DataBodyTemplate / Form / Horizontal**, **Stacked**, and **Inline** entries are visual API references. When generating a product form, follow this guide instead of selecting among those demos.
13
13
 
14
+ Read [AI UI Implementation Contract](./ai-ui-implementation-contract.md) first.
15
+
14
16
  The executable Playground example uses React Hook Form to demonstrate shared state across modular section components, controlled DesignKit inputs, and field-level errors. React Hook Form is a Playground implementation choice, not a DesignKit dependency or part of this visual contract.
15
17
 
16
18
  ## Non-negotiable visual contract
@@ -20,10 +22,10 @@ The executable Playground example uses React Hook Form to demonstrate shared sta
20
22
  3. Use one `DataBodyTemplate` page root.
21
23
  4. Use the stacked form shape for create, edit, and settings pages. Do not switch to `horizontal` or `inline` because the domain changed.
22
24
  5. Divide the form into one or more `DataBodyTemplate.Group layout="stacked"` sections according to meaning, not visual preference.
23
- 6. Keep field spacing at `space-y-3`; keep each label/control/help block at `space-y-1.5`.
24
- 7. Use DesignKit controls and the established compact control sizing: `h-8 text-sm` for inputs and selects, `size="sm"` with `h-8 text-xs` for actions.
25
- 8. Put Cancel before the primary submit action. Keep the action cluster right-aligned at the bottom of its save boundary.
26
- 9. Show validation text directly below its field. Show form-level failure above the bottom actions, inside the same form boundary.
25
+ 6. Wrap each field in `FormField` (label, control, and `error`/`helperText`) instead of hand-rolling a `space-y-1.5` + `Label` block; keep the fields inside a group at `space-y-3`.
26
+ 7. Use DesignKit controls and the established compact control sizing: `h-8 text-sm` for inputs and selects.
27
+ 8. End the form in `FormActions` (`status`, `submitLabel`, `onCancel`) instead of hand-rolling the bottom action row — it already puts Cancel before the right-aligned primary submit action and renders the divider above it. Do not add a second divider around it.
28
+ 9. Pass validation text to `FormField`'s `error` prop; pass form-level failure to `FormActions`' `status` prop, inside the same form boundary.
27
29
  10. A submitting, refreshing, or validation state must not replace or flash the page header or unrelated groups.
28
30
 
29
31
  ## Module boundary contract
@@ -79,18 +81,14 @@ function IdentitySection() {
79
81
  description="Basic account information."
80
82
  >
81
83
  <div className="space-y-3">
82
- <div className="space-y-1.5">
83
- <Label htmlFor="member-name" className="text-xs">
84
- Name
85
- </Label>
84
+ <FormField label="Name" htmlFor="member-name" error={errors.name?.message}>
86
85
  <Input
87
86
  id="member-name"
88
87
  {...register('name', { required: 'Enter a member name.' })}
89
88
  aria-invalid={Boolean(errors.name)}
90
89
  className="h-8 text-sm"
91
90
  />
92
- {errors.name && <p className="text-xs text-destructive">{errors.name.message}</p>}
93
- </div>
91
+ </FormField>
94
92
  </div>
95
93
  </DataBodyTemplate.Group>
96
94
  )
@@ -106,10 +104,7 @@ function AccessSection() {
106
104
  description="Default workspace permissions."
107
105
  >
108
106
  <div className="space-y-3">
109
- <div className="space-y-1.5">
110
- <Label htmlFor="member-role" className="text-xs">
111
- Role
112
- </Label>
107
+ <FormField label="Role" htmlFor="member-role">
113
108
  <Controller
114
109
  control={control}
115
110
  name="role"
@@ -122,7 +117,7 @@ function AccessSection() {
122
117
  </Select>
123
118
  )}
124
119
  />
125
- </div>
120
+ </FormField>
126
121
  <Controller
127
122
  control={control}
128
123
  name="active"
@@ -152,14 +147,7 @@ function MemberForm() {
152
147
  <form className="contents" onSubmit={form.handleSubmit(submitMember)}>
153
148
  <IdentitySection />
154
149
  <AccessSection />
155
- <div className="flex justify-end gap-2 border-t border-border pt-(--designkit-panel-gap)">
156
- <Button type="button" variant="outline" size="sm" className="h-8 text-xs">
157
- Cancel
158
- </Button>
159
- <Button type="submit" size="sm" className="h-8 text-xs">
160
- Save
161
- </Button>
162
- </div>
150
+ <FormActions submitLabel="Save" />
163
151
  </form>
164
152
  </FormProvider>
165
153
  )
@@ -210,3 +198,5 @@ function ProfileSettingsSection() {
210
198
  - Group count follows the information model; it does not select a different layout.
211
199
  - Form and validation libraries remain application choices.
212
200
  - Bottom actions belong to the form that they submit.
201
+ - Fields use `FormField`, not a hand-rolled `space-y-1.5` + `Label` block.
202
+ - The bottom action row uses `FormActions`, not a hand-rolled Cancel/submit `div`.
@@ -0,0 +1,169 @@
1
+ # Kubernetes Workspace Contract for AI
2
+
3
+ Use this contract for operational resource browsers where people repeatedly inspect a collection, compare resource state, and open logs or a shell without losing list context.
4
+
5
+ The executable reference is available at:
6
+
7
+ - `/sidebar/kubernetes-workspace-guide`
8
+ - `/header/kubernetes-workspace-guide`
9
+
10
+ The `DataBodyTemplate / Kubernetes` entry is a visual API reference backed by the same composition. The **Guides / Operations / Kubernetes Workspace** entry is the normative workflow contract.
11
+
12
+ Read [AI UI Implementation Contract](./ai-ui-implementation-contract.md) first.
13
+
14
+ ## 1. Workflow identity
15
+
16
+ This is one workspace with three distinct interaction layers:
17
+
18
+ 1. The resource table owns discovery, search, filters, refresh, sorting, and selection context.
19
+ 2. A right SidePanel owns concise inspection of the selected resource.
20
+ 3. A bottom Control Dock owns persistent work such as logs, shell sessions, and cluster events.
21
+
22
+ Do not flatten these concerns into one table component. Keep query state, selected-resource inspection, and long-running tools independently replaceable.
23
+
24
+ ## 2. Required composition
25
+
26
+ - Use one `DataBodyTemplate` as the only page-level template.
27
+ - Put GridKit `GlobalSearch` and a BaseKit `FilterInput` (`type: 'select'`, keyed to the namespace column) in the DataGrid header slots. GridKit's own `SelectFilter`/`MultiSelectFilter` do not expose a `size`/`className` prop, so they cannot be sized to match `GlobalSearch`; `FilterInput` can.
28
+ - Use BaseKit `SidePanelProvider` with DesignKit `PanelTemplate` for row inspection. Put `Shell`/`Logs` (and any other panel-opening actions) in `PanelTemplate`'s `actions` slot next to the title, not in `footer` — footer is for closing/committing the panel, not for opening other tools. Render `Close` as a plain icon button (no confirmation) since dismissing a read-only inspection panel is not a destructive action.
29
+ - Use BaseKit `ControlBarProvider` as the tab and persistence model for the bottom dock.
30
+ - Keep Kubernetes API clients, authorization, watches, and mutations in the consuming application.
31
+ - Keep GridKit, `FilterInput`, SidePanel, and Control Bar as application or Playground integrations; they are not DesignKit runtime dependencies.
32
+
33
+ ## 3. Resource collection boundary
34
+
35
+ Create a named resource component such as `PodsResource`. It owns:
36
+
37
+ - collection query parameters and result rendering;
38
+ - search and filter state;
39
+ - column definitions and row actions;
40
+ - opening the selected resource in the SidePanel;
41
+ - opening resource tools in the Control Dock.
42
+
43
+ The route or workspace component owns only cluster context, page hierarchy, providers, and layout. It must not absorb every table and panel implementation detail.
44
+
45
+ For server-backed data, put search, namespace, sorting, pagination or cursor, and refresh inputs into one query model. Preserve the previous successful result during background refresh when possible. Show initial loading, empty, filtered-empty, permission failure, and refresh failure as distinct states.
46
+
47
+ ## 4. Row interaction contract
48
+
49
+ Clicking a Pod row opens a read-oriented detail SidePanel. It must not implicitly open logs or start a shell.
50
+
51
+ The detail panel should show enough information to decide the next action:
52
+
53
+ - status, readiness, restarts, and age;
54
+ - node, owner/controller, and QoS class;
55
+ - namespace and stable resource name;
56
+ - explicit `Logs` and `Shell` actions.
57
+
58
+ Use a full detail route only when the resource has deep navigation, editable configuration, history, or a stable URL that users must share. Use the SidePanel for quick inspection that should preserve table position and filters.
59
+
60
+ Row-level Logs and Shell icon buttons may remain for expert access. They must stop row-click propagation and have accessible names.
61
+
62
+ ## 5. Control Dock contract
63
+
64
+ Logs, shell sessions, and cluster events are persistent workspace tools, not transient popovers.
65
+
66
+ - Keep the dock visible at the bottom even with zero tabs.
67
+ - The empty dock is 36px tall and communicates `No active resource panels`.
68
+ - Opening a tool expands the dock to its working height.
69
+ - The dock participates in the workspace flex layout. It must reduce the table region rather than overlap or cover rows.
70
+ - Closing the last tab returns to the empty dock; it does not remove the dock.
71
+ - Deduplicate tools by tool type and resource identity. Reopening an existing Logs or Shell tool activates its tab.
72
+ - Collapsing preserves tabs and active state.
73
+ - Events are cluster-scoped; Logs and Shell are resource-scoped.
74
+
75
+ The Control Bar package supplies both the tab model (`useControlBar`) and the tab strip chrome (`ControlBar`) — render `<ControlBar />` for the dock header and content instead of hand-building a tab strip. The application still owns domain-specific tool renderers (via `registerTabType`) and any dock action beyond the package's own collapse/expand/fullscreen controls (e.g. an "Events" trigger).
76
+
77
+ Pass `alwaysVisible` and `emptyState` so the dock stays mounted at zero tabs instead of unmounting: `<ControlBar alwaysVisible emptyState="No active resource panels" />`. Do not hand-build an empty-state wrapper around `ControlBar` — the package owns this directly.
78
+
79
+ ## 6. Spacing and resizing
80
+
81
+ - The table region must remain `min-height: 0` so it can shrink when the dock expands.
82
+ - The SidePanel must be mounted inside the workspace provider boundary, not around the app shell.
83
+ - The content area's bottom padding must equal its horizontal page padding. A table border touching the viewport edge reads as clipped even when it technically fits.
84
+ - Do not add a second page shell, header, or navigation sidebar inside the demo.
85
+ - Verify the same composition in both SidebarShell and HeaderShell.
86
+
87
+ ## 7. Actions and state
88
+
89
+ Refresh belongs in the table header because it reloads the collection. Search and namespace filtering belong together without decorative separators that imply unrelated groups.
90
+
91
+ Do not add aggregate status counters merely to fill header space. Add a summary only when it supports an operational decision and is driven by the same query scope as the table.
92
+
93
+ For real clusters:
94
+
95
+ - disable or hide Shell when the user lacks permission;
96
+ - make container selection explicit for multi-container Pods;
97
+ - stream Logs and Shell through cancellable connections;
98
+ - announce connection, reconnecting, and terminal states;
99
+ - clean up watches and streams when their dock tab closes;
100
+ - avoid persisting credentials or log contents in local storage.
101
+
102
+ ## 8. Reference shape
103
+
104
+ ```tsx
105
+ function PodsResource() {
106
+ const controlBar = useControlBar()
107
+ const sidePanel = useSidePanel()
108
+ const query = usePodsQuery()
109
+
110
+ const inspectPod = (pod: Pod) => {
111
+ sidePanel.open(
112
+ <PodDetailPanel
113
+ pod={pod}
114
+ onOpenLogs={() => openPodTool(controlBar, 'logs', pod)}
115
+ onOpenShell={() => openPodTool(controlBar, 'shell', pod)}
116
+ />,
117
+ { side: 'right', size: 420, resizable: true },
118
+ )
119
+ }
120
+
121
+ return (
122
+ <DataGrid
123
+ data={query.data}
124
+ columns={columns}
125
+ onRowClick={inspectPod}
126
+ headerLeft={(table) => (
127
+ <>
128
+ <GlobalSearch table={table} />
129
+ <FilterInput
130
+ config={namespaceFilterConfig}
131
+ value={(table.getColumn('namespace')?.getFilterValue() as string) ?? null}
132
+ onChange={(next) => table.getColumn('namespace')?.setFilterValue(next ?? undefined)}
133
+ />
134
+ </>
135
+ )}
136
+ />
137
+ )
138
+ }
139
+
140
+ function KubernetesWorkspace() {
141
+ return (
142
+ <ControlBarProvider persistKey="kubernetes-workspace">
143
+ <SidePanelProvider className="h-full min-h-0">
144
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
145
+ <DataBodyTemplate>{/* PodsResource */}</DataBodyTemplate>
146
+ <ControlBar alwaysVisible emptyState="No active resource panels" />
147
+ </div>
148
+ </SidePanelProvider>
149
+ </ControlBarProvider>
150
+ )
151
+ }
152
+ ```
153
+
154
+ ## 9. Review checklist
155
+
156
+ - [ ] There is exactly one page-level template and one app shell.
157
+ - [ ] Search is GridKit `GlobalSearch` and the namespace control is a BaseKit `FilterInput`, both in the table header.
158
+ - [ ] `GlobalSearch`, `FilterInput`, and any header `Button`s render at the same height and corner radius — verify computed style, not just class names, since GridKit/BaseKit and DesignKit resolve `--radius` independently.
159
+ - [ ] Row click opens a Pod detail SidePanel.
160
+ - [ ] Logs and Shell are explicit actions, not implicit row-click behavior.
161
+ - [ ] Logs, Shell, and any other panel-opening actions live in `PanelTemplate`'s `actions` slot next to the title; `footer` is reserved for closing/committing, and `Close` is a plain icon button, not a confirmation.
162
+ - [ ] Row action buttons stop propagation and have accessible names.
163
+ - [ ] Detail, collection query, and dock tools have separate component/state boundaries.
164
+ - [ ] Tool tabs deduplicate by type and resource identity.
165
+ - [ ] The empty Control Dock remains visible after the final tab closes.
166
+ - [ ] Expanding the dock reflows the table and never overlays it.
167
+ - [ ] Bottom content padding equals horizontal content padding.
168
+ - [ ] Loading, empty, permission, refresh failure, and stream failure states are defined.
169
+ - [ ] The SidebarShell and HeaderShell routes both show exactly one navigation shell.
@@ -4,6 +4,8 @@ This is the normative implementation contract for a tabbed administrative table
4
4
 
5
5
  The Playground's **Guides / Resource Management / Managed Table** screen is the executable end-to-end reference. Its records are sample data; the pattern is defined by the table responsibilities and template composition below, not by that data domain. If an implementation differs from this contract, revise the implementation rather than inventing a local layout.
6
6
 
7
+ Read [AI UI Implementation Contract](./ai-ui-implementation-contract.md) first.
8
+
7
9
  ## Pattern identity and reference registry
8
10
 
9
11
  - Pattern ID: `managed-table`
@@ -16,10 +18,10 @@ The Playground's **Guides / Resource Management / Managed Table** screen is the
16
18
 
17
19
  | Status | Playground reference | Role in this pattern |
18
20
  | -------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
19
- | Executable pattern | `Guides / Resource Management / Managed Table` | Complete list, tabs, queries, create route, form, pagination, and concise detail Sheet |
21
+ | Executable pattern | `Guides / Resource Management / Managed Table` | Complete list, tabs, queries, create route, form, pagination, and concise detail SidePanel |
20
22
  | Supporting reference | `DataBodyTemplate / Table / Standard` | Base GridKit table composition and table sizing |
21
23
  | Supporting contract | `Guides / Forms / Stacked Form` | Create/edit form spacing, modular Groups, and action alignment |
22
- | Supporting reference | `DetailBodyTemplate / Detail / Record` | Full-page destination when detail outgrows a Sheet |
24
+ | Supporting reference | `DetailBodyTemplate / Detail / Record` | Full-page destination when detail outgrows a SidePanel |
23
25
  | Supporting reference | `FormWizardBodyTemplate / Wizard` | Multi-step destination when create/edit outgrows a stacked form |
24
26
  | Counterexample | `DashboardBodyTemplate / Dashboard` | Monitoring panels are not an administrative table |
25
27
  | Counterexample | `BrowseBodyTemplate / Browse` | Consumer discovery is not an administrative resource list |
@@ -33,7 +35,7 @@ Only the entry marked **Executable pattern** implements this pattern end to end.
33
35
  | Collection list, tabs, search, filters, pagination | `DataBodyTemplate` + `DataBodyTemplate.Resource` | `Guides / Resource Management / Managed Table` |
34
36
  | Base table behavior | GridKit `DataGrid` | `DataBodyTemplate / Table / Standard` |
35
37
  | Simple create/edit route | stacked `DataBodyTemplate.Group` | `Guides / Forms / Stacked Form` |
36
- | Concise read-only inspection | `Sheet` | `Managed Table` row detail |
38
+ | Concise read-only inspection | BaseKit `SidePanel` (`@loykin/side-panel`) | `Managed Table` row detail |
37
39
  | Complex full-page detail route | `DetailBodyTemplate` | `DetailBodyTemplate / Detail / Record` |
38
40
  | Multi-step create/edit route | `FormWizardBodyTemplate` | `FormWizardBodyTemplate / Wizard` |
39
41
  | Destructive confirmation | `AlertDialog` | UI primitive contract |
@@ -72,7 +74,7 @@ This guide covers three destinations for any managed resource:
72
74
 
73
75
  ```text
74
76
  /<resources>
75
- ├─ select row → concise read-only detail Sheet
77
+ ├─ select row → concise read-only detail SidePanel
76
78
  ├─ create action → /<resources>/new create page
77
79
  └─ edit action → /<resources>/:resourceId/edit page
78
80
  ```
@@ -84,12 +86,12 @@ It does not define every `DataBodyTemplate` use case. Settings, dashboards, long
84
86
  | User intent | Default destination | Reason |
85
87
  | ------------------------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------- |
86
88
  | Browse, search, filter, paginate | Page | Stable, linkable working context |
87
- | Inspect concise read-only row information | Sheet | Preserves list context |
89
+ | Inspect concise read-only row information | SidePanel | Preserves list context |
88
90
  | Create or edit an entity | Page with its own URL | Space for validation, permissions, responsive layout, and future fields |
89
- | Inspect long, editable, multi-section, permission-sensitive, or linkable detail | Page with its own URL | Sheet constraints are no longer appropriate |
91
+ | Inspect long, editable, multi-section, permission-sensitive, or linkable detail | Page with its own URL | SidePanel constraints are no longer appropriate |
90
92
  | Confirm a destructive action or collect one narrowly scoped value | Modal | Short, blocking decision |
91
93
 
92
- A Sheet is not the default form container. Do not put general create or edit forms in a Sheet merely because the trigger originates in a table.
94
+ A SidePanel is not the default form container. Do not put general create or edit forms in a SidePanel merely because the trigger originates in a table.
93
95
 
94
96
  ## Route and page hierarchy
95
97
 
@@ -156,7 +158,7 @@ Each tab must be a separate React component and own all state that affects its r
156
158
  - TanStack Query and query key
157
159
  - Search and filters
158
160
  - Pagination
159
- - Row selection and concise detail Sheet
161
+ - Row selection and concise detail SidePanel
160
162
  - Mutations that belong to the list itself
161
163
 
162
164
  Query keys include every server-state input:
@@ -332,43 +334,21 @@ function UserCreatePage() {
332
334
  description="Identity and access settings for the new account."
333
335
  >
334
336
  <form className="space-y-3" onSubmit={handleSubmit}>
335
- <div className="space-y-1.5">
336
- <Label htmlFor="user-name" className="text-xs">
337
- Name
338
- </Label>
337
+ <FormField label="Name" htmlFor="user-name">
339
338
  <Input id="user-name" className="h-8 text-sm" required />
340
- </div>
341
- <div className="space-y-1.5">
342
- <Label htmlFor="user-email" className="text-xs">
343
- Email
344
- </Label>
339
+ </FormField>
340
+ <FormField label="Email" htmlFor="user-email">
345
341
  <Input id="user-email" type="email" className="h-8 text-sm" required />
346
- </div>
347
- <div className="space-y-1.5">
348
- <Label htmlFor="user-role" className="text-xs">
349
- Role
350
- </Label>
342
+ </FormField>
343
+ <FormField label="Role" htmlFor="user-role">
351
344
  <Select defaultValue="Viewer">
352
345
  <SelectTrigger id="user-role" className="h-8 text-sm">
353
346
  <SelectValue />
354
347
  </SelectTrigger>
355
348
  <SelectContent>{/* roles */}</SelectContent>
356
349
  </Select>
357
- </div>
358
- <div className="flex justify-end gap-2">
359
- <Button
360
- type="button"
361
- variant="outline"
362
- size="sm"
363
- className="h-8 text-xs"
364
- onClick={() => navigate('/users')}
365
- >
366
- Cancel
367
- </Button>
368
- <Button type="submit" size="sm" className="h-8 text-xs">
369
- Create user
370
- </Button>
371
- </div>
350
+ </FormField>
351
+ <FormActions onCancel={() => navigate('/users')} submitLabel="Create user" />
372
352
  </form>
373
353
  </DataBodyTemplate.Group>
374
354
  </DataBodyTemplate>
@@ -379,28 +359,57 @@ function UserCreatePage() {
379
359
  Form rules:
380
360
 
381
361
  - Form controls and buttons are `32px` (`h-8`); this differs from the table toolbar's `28px` controls.
382
- - Use `space-y-3` for the form and `space-y-1.5` within each field.
362
+ - Wrap each field in `FormField`; use `space-y-3` between fields inside the form.
363
+ - End the form in `FormActions`; it already renders the divider and Cancel-before-submit order — do not add a second divider around it.
383
364
  - Use the full content width supplied by the stacked group.
384
- - Do not add `mx-auto`, an arbitrary `max-w-*`, extra horizontal padding, a nested card, or a second action divider.
365
+ - Do not add `mx-auto`, an arbitrary `max-w-*`, extra horizontal padding, or a nested card.
385
366
  - The form page owns its mutation. After success, invalidate the narrowest affected queries and navigate to the list or created entity.
386
367
 
387
- ## Concise detail Sheet
368
+ ## Concise detail SidePanel
388
369
 
389
- Keep the list mounted while showing concise, mostly read-only row information:
370
+ Keep the list mounted while showing concise, mostly read-only row information. Use BaseKit `SidePanelProvider`/`useSidePanel` (`@loykin/side-panel`) — the same package the Kubernetes Workspace guide uses — with DesignKit `PanelTemplate` for the panel body: `title`/`eyebrow` for the header, `PanelTemplate.Section` + `PanelTemplate.Row` for read-only fields, and a `Close` button in `footer` calling `close()`. Do not use DesignKit's own `Sheet` primitive for this, and do not hand-roll a label/value grid with raw `div`/`p` markup.
390
371
 
391
372
  ```tsx
392
- <Sheet open={Boolean(selectedUser)} onOpenChange={(open) => !open && clearSelection()}>
393
- <SheetContent>
394
- <SheetHeader>
395
- <SheetTitle>{selectedUser?.name}</SheetTitle>
396
- <SheetDescription>User details</SheetDescription>
397
- </SheetHeader>
398
- {/* concise read-only fields */}
399
- </SheetContent>
400
- </Sheet>
373
+ function UserPanel({ user }: { user: SelectedUser }) {
374
+ const { close } = useSidePanel()
375
+ return (
376
+ <PanelTemplate
377
+ eyebrow="User"
378
+ title={user.name}
379
+ footer={
380
+ <div className="flex justify-end">
381
+ <Button variant="outline" size="sm" onClick={() => void close()}>
382
+ Close
383
+ </Button>
384
+ </div>
385
+ }
386
+ >
387
+ <PanelTemplate.Section title="Details">
388
+ <dl className="space-y-2">
389
+ <PanelTemplate.Row label="Email">{user.email}</PanelTemplate.Row>
390
+ <PanelTemplate.Row label="Role">{user.role}</PanelTemplate.Row>
391
+ </dl>
392
+ </PanelTemplate.Section>
393
+ </PanelTemplate>
394
+ )
395
+ }
396
+
397
+ function UsersListPage() {
398
+ const { open } = useSidePanel()
399
+ return (
400
+ <SidePanelProvider className="h-full min-h-0">
401
+ <DataBodyTemplate>
402
+ <DataGrid
403
+ /* … */
404
+ onRowClick={(user) => open(<UserPanel user={user} />, { side: 'right', size: 420, resizable: true })}
405
+ />
406
+ </DataBodyTemplate>
407
+ </SidePanelProvider>
408
+ )
409
+ }
401
410
  ```
402
411
 
403
- If the detail gains editing, multiple sections, complex permissions, a long history, or a need for a shareable URL, replace the Sheet with a detail page. Do not gradually turn the Sheet into a full page inside an overlay.
412
+ If the detail gains editing, multiple sections, complex permissions, a long history, or a need for a shareable URL, replace the SidePanel with a detail page. Do not gradually turn the SidePanel into a full page inside an overlay.
404
413
 
405
414
  ## Public API boundary
406
415
 
@@ -429,7 +438,9 @@ Do not import package-internal source paths. Do not add TanStack Query, React Ro
429
438
  - [ ] Table toolbar and pagination controls are `28px` high.
430
439
  - [ ] Form controls and buttons are `32px` high.
431
440
  - [ ] The stacked form has no arbitrary max-width wrapper or extra action divider.
432
- - [ ] Create and edit use pages; concise read-only detail uses a Sheet.
441
+ - [ ] The create/edit form uses `FormField` and `FormActions` (see the Stacked Form guide), not hand-rolled label/input or action-row markup.
442
+ - [ ] Create and edit use pages; concise read-only detail uses a SidePanel.
443
+ - [ ] The SidePanel body uses `PanelTemplate` (`title`/`eyebrow` + `PanelTemplate.Section`/`PanelTemplate.Row`), not hand-rolled label/value markup, and DesignKit's own `Sheet` primitive is not used for this.
433
444
  - [ ] Complex detail uses a page.
434
445
  - [ ] Only public package entry points are imported.
435
446
  - [ ] The experience is visually checked in both Sidebar and Header shells.
@@ -14,6 +14,23 @@
14
14
  ],
15
15
  "contract": "managed-table.md"
16
16
  },
17
+ {
18
+ "id": "kubernetes-workspace",
19
+ "title": "Operations / Kubernetes Workspace",
20
+ "summary": "Operational resource collection with quick SidePanel inspection and persistent logs, shell, and events tools in a bottom dock.",
21
+ "useWhen": [
22
+ "Kubernetes operations",
23
+ "resource inspection",
24
+ "logs and shell",
25
+ "persistent tool dock"
26
+ ],
27
+ "templates": ["DataBodyTemplate", "PanelTemplate"],
28
+ "playgroundPaths": [
29
+ "/sidebar/kubernetes-workspace-guide",
30
+ "/header/kubernetes-workspace-guide"
31
+ ],
32
+ "contract": "kubernetes-workspace.md"
33
+ },
17
34
  {
18
35
  "id": "form-workflow",
19
36
  "title": "Forms / Stacked Form",
@@ -12,6 +12,8 @@ Use this guide to build a publishing experience where a content collection leads
12
12
 
13
13
  The existing **Blog Feed** and **Article** template demos are visual references only. This Guide is the supported example of how those page shapes connect.
14
14
 
15
+ Read [AI UI Implementation Contract](./ai-ui-implementation-contract.md) first. It governs how this Guide's card grid and article content are built.
16
+
15
17
  ## Route contract
16
18
 
17
19
  ```text
@@ -37,6 +39,18 @@ Card selection uses React Router navigation. Browser Back returns to the collect
37
39
  - Article actions such as Save or Share belong in the article header.
38
40
  - Render one page-level template per route. Never put `DetailBodyTemplate` inside `DataBodyTemplate.Body`.
39
41
 
42
+ ## Card and article presentation
43
+
44
+ Do not hand-roll the card grid or article body with raw `<div>`/Tailwind. Use these DesignKit components:
45
+
46
+ - `ArticleCardPreview` — the whole grid card: cover, category, title, excerpt, byline, read time. Pass `coverContent` for per-listing decoration (an icon, an overlay); do not reassemble the card shape from `InteractiveCard` + `ArticleCover` + `ArticleByline` by hand — that duplication is exactly why this component exists (it was copy-pasted near-verbatim between this guide and the Blog Feed demo before being promoted).
47
+ - `ArticleCover` / `ArticleByline` — the cover and byline building blocks `ArticleCardPreview` uses internally. Reach for them directly only outside the card shape, e.g. the article page's hero (`ArticleCover`) and header (`ArticleByline`). `tone` is a semantic value (`'violet' | 'emerald' | 'amber' | 'rose' | 'sky' | 'slate'`), not a raw gradient class string — sample data stores `accent: ArticleTone`, never a Tailwind string.
48
+ - `ArticleBody` — reading-width container with article typography for the article's prose.
49
+ - `ArticleBodySkeleton` — loading placeholder matching `ArticleBody`'s width.
50
+ - `ArticleToc` — the article aside's table of contents.
51
+
52
+ Article title, excerpt, and category stay as plain content passed into these components; only the reusable shell moves into DesignKit.
53
+
40
54
  ## Deterministic ordering
41
55
 
42
56
  The mock API returns articles newest first and the GridKit collection declares the same initial published-date sort. In a real API, send sort parameters to the server and include them in the query key. Never sort humanized labels such as “2 hours ago”.
@@ -51,3 +65,6 @@ The mock API returns articles newest first and the GridKit collection declares t
51
65
  - [ ] Initial loading is local to the destination content
52
66
  - [ ] Existing content remains visible during background refetch
53
67
  - [ ] No nested page templates and no article Sheet
68
+ - [ ] The grid card uses `ArticleCardPreview`, not a hand-assembled `InteractiveCard` + `ArticleCover` + `ArticleByline` composition
69
+ - [ ] Article page presentation uses `ArticleBody`/`ArticleBodySkeleton` and `ArticleToc`, not hand-rolled `className` blocks
70
+ - [ ] Sample data stores a semantic `accent`/tone value, never a Tailwind gradient string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loykin/designkit",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "A React UI component library with theming support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -49,6 +49,7 @@
49
49
  "devDependencies": {
50
50
  "@eslint/js": "^10.0.1",
51
51
  "@loykin/gridkit": "^0.2.2",
52
+ "@tanstack/react-table": "^8.21.3",
52
53
  "@typescript/native": "npm:typescript@~7.0.2",
53
54
  "@types/node": "^26.0.0",
54
55
  "@types/react": "^19.2.14",