@keboola/design 3.2.3 → 4.1.1
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/AGENTS.md +68 -5
- package/COMPONENT-API.md +77 -81
- package/README.md +8 -8
- package/dist/constants-D3gaMY-W.d.cts +39 -0
- package/dist/constants-D3gaMY-W.d.ts +39 -0
- package/dist/editors.cjs +1950 -0
- package/dist/editors.cjs.map +1 -0
- package/dist/editors.d.cts +111 -0
- package/dist/editors.d.ts +111 -0
- package/dist/editors.js +1922 -0
- package/dist/editors.js.map +1 -0
- package/dist/icons.cjs.map +1 -1
- package/dist/icons.js.map +1 -1
- package/dist/index.cjs +3522 -1994
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +414 -510
- package/dist/index.d.ts +414 -510
- package/dist/index.js +3312 -1783
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.cjs +21 -35
- package/dist/markdown-editor.cjs.map +1 -1
- package/dist/markdown-editor.d.cts +4 -3
- package/dist/markdown-editor.d.ts +4 -3
- package/dist/markdown-editor.js +21 -35
- package/dist/markdown-editor.js.map +1 -1
- package/dist/mermaid.cjs +4 -18
- package/dist/mermaid.cjs.map +1 -1
- package/dist/mermaid.js +4 -18
- package/dist/mermaid.js.map +1 -1
- package/dist/styles.css +2 -2
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/package.json +81 -65
- package/src/styles.css +70 -13
package/AGENTS.md
CHANGED
|
@@ -13,6 +13,35 @@ Working **on** the package (commands, layer boundary, token surface, the FA→Lu
|
|
|
13
13
|
- `import '@keboola/design/styles'` — Tailwind-extending consumers. Your PostCSS+Tailwind processes `@tailwind` directives plus design's `dist/` in your content scan. Avoids duplicate preflights/utilities. **Default for any Keboola app.**
|
|
14
14
|
- `import '@keboola/design/styles/compiled'` — drop-in pre-compiled CSS (~176 KB minified). No Tailwind setup required. Use only when you genuinely cannot run Tailwind.
|
|
15
15
|
- Icons: `import { ChevronDown } from '@keboola/design/icons'`.
|
|
16
|
+
- Code editors: `import { CodeEditor, SqlEditor, MergeEditor } from '@keboola/design/editors'`. These pull the full CodeMirror bundle (~1.3 MB), so they ship from a dedicated subpath — keeping the root barrel CodeMirror-free for apps that don't edit code.
|
|
17
|
+
|
|
18
|
+
### Subpath entry index
|
|
19
|
+
|
|
20
|
+
The root barrel (`@keboola/design`) is **not** the whole package. These live only at their subpath — an agent scanning the barrel (or `dist/index.d.ts`) misses them and wrongly concludes the capability is absent. The "always import from the package root" rule above holds for everything in the barrel; the rows below are the **deliberate exceptions** — import them from the subpath, not the root.
|
|
21
|
+
|
|
22
|
+
| Subpath | Exports | Reach for it when |
|
|
23
|
+
| --------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------- |
|
|
24
|
+
| `@keboola/design` | Components, blocks, hooks, `ThemeProvider`, forms | Default — everything below the listed exceptions |
|
|
25
|
+
| `@keboola/design/icons` | Lucide re-export + Keboola-custom SVG icons | Any icon (`import { ChevronDown } from '@keboola/design/icons'`) |
|
|
26
|
+
| `@keboola/design/editors` | `CodeEditor`, `SqlEditor`, `MergeEditor` | Real code editing — pulls the ~1.3 MB CodeMirror bundle |
|
|
27
|
+
| `@keboola/design/mermaid` | `MermaidDiagram` (`{ code }`) | Render a Mermaid diagram — **not** in the root barrel |
|
|
28
|
+
| `@keboola/design/markdown-editor` | `MarkdownEditor` | Markdown editing — **not** in the root barrel |
|
|
29
|
+
| `@keboola/design/testing` | `renderWithTheme` | Unit tests that need a component wrapped in `ThemeProvider` |
|
|
30
|
+
| `@keboola/design/styles` | Tailwind-extending stylesheet | App entry, Tailwind consumers (**default**) |
|
|
31
|
+
| `@keboola/design/styles/compiled` | Pre-compiled CSS (~176 KB) | App entry, when you cannot run Tailwind |
|
|
32
|
+
|
|
33
|
+
### design ≠ shadcn — API deltas
|
|
34
|
+
|
|
35
|
+
The API is "shadcn-shaped" enough to invite copy-paste but different enough to break it — copied shadcn code fails type-check. Translate these before emitting:
|
|
36
|
+
|
|
37
|
+
| Component | shadcn shape (wrong here) | `@keboola/design` shape |
|
|
38
|
+
| --------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `DropdownMenu` | named parts `DropdownMenuTrigger` / `DropdownMenuContent` / `DropdownMenuItem` | **compound** — only `DropdownMenu` exports; use `DropdownMenu.Trigger` / `.Content` / `.Item` / `.Label` / `.Separator` |
|
|
40
|
+
| `Tabs` | `<Tabs><TabsList><TabsTrigger/></TabsList><TabsContent/>` | data-driven — `<Tabs triggers={[{ value, title }]}>` + `TabsContent` children. **No** `TabsList` / `TabsTrigger` export |
|
|
41
|
+
| `Select` | composed children `<Select><SelectTrigger/><SelectContent><SelectItem/></SelectContent></Select>` | data-driven — `Select` takes **`items`**; the form connector `SelectField` also takes **`items`** (canonical), with `options` as a deprecated alias. Same `{ value, label }` shape |
|
|
42
|
+
| `Sidebar.SearchInput` | `asChild`-wrappable trigger | concrete `<input>` (`ComponentProps<'input'>`), **no `asChild`** — put a custom search affordance in a plain element inside `Sidebar.Header` |
|
|
43
|
+
|
|
44
|
+
Other compound (not named-export) components in the barrel: `Modal.*`, `Sheet.*`, `Card.*`, `Sidebar.*`, `PageHeader.Index` / `.Detail`, `EmptyState.*`, `Pagination.*`, `Popover.*` — reach for the dotted sub-parts, not flat `XContent` / `XTrigger` named imports. (The root `SearchInput` block is distinct from `Sidebar.SearchInput` and is fully composable.)
|
|
16
45
|
|
|
17
46
|
## Decision tree
|
|
18
47
|
|
|
@@ -112,6 +141,14 @@ This file answers **which** component to use; [`COMPONENT-API.md`](./COMPONENT-A
|
|
|
112
141
|
|
|
113
142
|
Vertical rhythm: prefer `tw-gap-*` on a flex column parent over `tw-mt-*` on children — avoids margin-collapse. Page-level max width: use `PageLayout`'s `maxWidth` prop (`narrow` / `default` / `wide` / `full`), not `tw-max-w-[…]` literals.
|
|
114
143
|
|
|
144
|
+
**Page gutter is consumer-owned.** `AppShell.Main` and `PageLayout` impose **no** page gutter by design — content renders flush against the sidebar/header unless you add one. Put the padding on the content region yourself (the `AppShell` story wraps Main content in `tw-p-6`):
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<AppShell.Main>
|
|
148
|
+
<div className="tw-flex tw-flex-col tw-gap-4 tw-px-6 tw-py-6">{/* page content */}</div>
|
|
149
|
+
</AppShell.Main>
|
|
150
|
+
```
|
|
151
|
+
|
|
115
152
|
### Navigation / flow
|
|
116
153
|
|
|
117
154
|
| Use | When |
|
|
@@ -148,7 +185,7 @@ The `label` prop implies `orientation="horizontal"` — labels are not rendered
|
|
|
148
185
|
| `CodeEditor` | Generic code editing (JSON, YAML, shell) |
|
|
149
186
|
| `SqlEditor` | SQL editing (syntax + autocomplete) |
|
|
150
187
|
|
|
151
|
-
`Select` is wrong above ~10 items — use `Combobox`. `Combobox` is wrong for free-form values — use `Tagger`. `CodeEditor`/`SqlEditor` load the full CodeMirror bundle; reserve for real code editing.
|
|
188
|
+
`Select` is wrong above ~10 items — use `Combobox`. `Combobox` is wrong for free-form values — use `Tagger`. `CodeEditor`/`SqlEditor` load the full CodeMirror bundle and ship from the `@keboola/design/editors` subpath (not the root); reserve for real code editing.
|
|
152
189
|
|
|
153
190
|
## Composition recipes
|
|
154
191
|
|
|
@@ -269,6 +306,8 @@ const form = useAppForm({
|
|
|
269
306
|
</div>
|
|
270
307
|
```
|
|
271
308
|
|
|
309
|
+
**Editable data grid** — `Table` is presentational (no inline edit / selection / virtualization). For an editable grid (budget / forecast spreadsheet) bring [TanStack Table v8](https://tanstack.com/table/latest) (headless) for the column model + row state, render it through `Table` with `flexRender`, and use `NumberInput` / `TextInput` as the cell. Own the grid model in `useState`, write edits back from the cell renderer, and compute totals from the source data. Canonical example: the `Editable data grid (TanStack Table)` story in `Table.DataGridRecipe.stories.tsx`. Do **not** add a `DataGrid` primitive — this is a recipe.
|
|
310
|
+
|
|
272
311
|
**Data card with skeleton** — render `Skeleton` / `SkeletonList` in the exact slot the real content will occupy:
|
|
273
312
|
|
|
274
313
|
```tsx
|
|
@@ -357,6 +396,17 @@ const form = useAppForm({
|
|
|
357
396
|
/>
|
|
358
397
|
```
|
|
359
398
|
|
|
399
|
+
**Sheet** — use `Sheet.Header title="…" description="…"` for a styled title + close icon; do **not** hand-roll with the raw `Sheet.Title`, which renders an unstyled `<h2>`. `Sheet.Body` provides horizontal padding only (`tw-px-4`) — add your own vertical rhythm/gap:
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
403
|
+
<Sheet.Content>
|
|
404
|
+
<Sheet.Header title="Edit table" description="Adjust the table metadata." />
|
|
405
|
+
<Sheet.Body className="tw-flex tw-flex-col tw-gap-4 tw-py-4">{/* fields */}</Sheet.Body>
|
|
406
|
+
</Sheet.Content>
|
|
407
|
+
</Sheet>
|
|
408
|
+
```
|
|
409
|
+
|
|
360
410
|
## Icons
|
|
361
411
|
|
|
362
412
|
Canonical icon surface is `@keboola/design/icons` — a wildcard re-export of [lucide-react](https://lucide.dev) plus Keboola-custom inline-SVG icons. Follow **shadcn convention**: import named icons directly, no wrapper component. Every Lucide icon is available without any package edit.
|
|
@@ -410,13 +460,26 @@ import { ThemeProvider } from '@keboola/design';
|
|
|
410
460
|
- **`brand`** — the active brand decides the chrome. **argo styling is brand-driven**: any non-`keboola-legacy` brand renders argo chrome automatically (`renderArgo = !isLegacy`), so a default `keboola` / `example-customer` brand needs no per-component flags. The `keboola-legacy` brand renders the pre-modernization chrome. With no `brand` prop, `ThemeProvider` defaults to the canonical `keboola` brand.
|
|
411
461
|
- **`colorScheme`** — `"light"` (default) / `"dark"` / `"system"`. Dark mode is set **here, on the root provider** — not per component. `"system"` follows the OS preference.
|
|
412
462
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
- **Boolean prop:** `<Button argo>`, `<Alert argo>` — swaps the entire variant family.
|
|
416
|
-
- **String variant:** `<Select variant="argo">`, `<MultiCombobox variant="argo">`, `<DropdownMenu variant="argo">` — for components whose `variant` union already encodes `'argo'`.
|
|
463
|
+
Chrome is driven **entirely** by the active brand — there is no per-call-site `argo` prop or `variant="argo"` escape hatch. To render a subtree under a different brand (e.g. an argo section inside a `keboola-legacy` app), wrap it in a nested `<ThemeProvider brand={…}>`; `useIsLegacyBrand()` reads the nearest provider, so the override is scoped to that subtree.
|
|
417
464
|
|
|
418
465
|
Define a custom brand with `@keboola/brand-registry`'s `brandSchema` (Zod) and pass it to `ThemeProvider`; or override the `--color-*` / `--radius-*` CSS variables directly on `:root`.
|
|
419
466
|
|
|
467
|
+
## Testing (Vitest)
|
|
468
|
+
|
|
469
|
+
The published ESM barrel is plain-Node resolvable — `import('@keboola/design')` works under raw Node, so **no special Vitest config is required** to render a component. A default jsdom project is enough:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
import { defineConfig } from 'vitest/config';
|
|
473
|
+
import react from '@vitejs/plugin-react';
|
|
474
|
+
|
|
475
|
+
export default defineConfig({
|
|
476
|
+
plugins: [react()],
|
|
477
|
+
test: { environment: 'jsdom', globals: true },
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
You do **not** need `test.css: true` or `server.deps.inline: [/@keboola\/design/, /react-day-picker/]`. The design system's structural CSS ships in the stylesheet (`@keboola/design/styles`), not as JS-side `.css` side-effect imports, so the barrel carries no raw `.css` import for Node to choke on. Wrap components in the brand context with `renderWithTheme` from `@keboola/design/testing`.
|
|
482
|
+
|
|
420
483
|
## Common Tailwind patterns
|
|
421
484
|
|
|
422
485
|
```
|
package/COMPONENT-API.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @keboola/design — component API reference
|
|
2
2
|
|
|
3
|
-
> **Generated file — do not edit by hand.** Regenerate with `
|
|
3
|
+
> **Generated file — do not edit by hand.** Regenerate with `pnpm --filter @keboola/design generate:api` (extracts prop signatures from the TypeScript types via `react-docgen-typescript`).
|
|
4
4
|
|
|
5
|
-
This is the prop-signature contract for the most-used components. For _which_ component to reach for, see [`AGENTS.md`](./AGENTS.md) (the decision tree); for live worked examples, open the colocated `*.stories.tsx` in Storybook (`
|
|
5
|
+
This is the prop-signature contract for the most-used components. For _which_ component to reach for, see [`AGENTS.md`](./AGENTS.md) (the decision tree); for live worked examples, open the colocated `*.stories.tsx` in Storybook (`pnpm dev --filter=@keboola/design`).
|
|
6
6
|
|
|
7
7
|
Props listed are the ones each component **declares itself**. Components built on a native element (`Button` on `<button>`, `Link` on `<a>`, `Card` on `<div>`, …) also accept that element's standard attributes — called out in each entry's note.
|
|
8
8
|
|
|
@@ -16,7 +16,6 @@ Source: `src/components/Button/Button.tsx`
|
|
|
16
16
|
|
|
17
17
|
| Prop | Type | Required | Default | Description |
|
|
18
18
|
| ----------- | --------------------------------------------------------------------------------------------------------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
-
| `argo` | `boolean` | no | `false` | @internal Transitional opt-in; removed once consumers wrap argo sections in `<ThemeProvider>`. |
|
|
20
19
|
| `asChild` | `boolean` | no | `false` | Render as a Radix `Slot` so the parent JSX (e.g. a `react-router` `<Link>`) becomes the button element. |
|
|
21
20
|
| `block` | `boolean \| null` | no | `false` | |
|
|
22
21
|
| `isLoading` | `boolean` | no | `false` | Replaces the leading content with a spinner and disables the button visually. The caller still controls `disabled`. |
|
|
@@ -50,14 +49,13 @@ Source: `src/components/Alert/Alert.tsx`
|
|
|
50
49
|
|
|
51
50
|
> Discriminated by `mode`: `"default"` renders `title`/`children`/`actions`; `"collapsible"` requires `title`+`children` and forbids `actions`. `variant` is a categorical colour contract (`info`/`success`/`warning`/`error`).
|
|
52
51
|
|
|
53
|
-
| Prop | Type | Required | Default | Description
|
|
54
|
-
| ---------- | ---------------------------- | -------- | --------- |
|
|
55
|
-
| `actions` | `ReactNode` | no | — |
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `variant` | `Variant` | no | `info` | |
|
|
52
|
+
| Prop | Type | Required | Default | Description |
|
|
53
|
+
| ---------- | ---------------------------- | -------- | --------- | ----------- |
|
|
54
|
+
| `actions` | `ReactNode` | no | — | |
|
|
55
|
+
| `hideIcon` | `boolean` | no | `false` | |
|
|
56
|
+
| `icon` | `ReactNode` | no | — | |
|
|
57
|
+
| `mode` | `"collapsible" \| "default"` | no | `default` | |
|
|
58
|
+
| `variant` | `Variant` | no | `info` | |
|
|
61
59
|
|
|
62
60
|
### `Toaster`
|
|
63
61
|
|
|
@@ -92,7 +90,7 @@ Source: `src/components/Input/TextInput/TextInput.tsx`
|
|
|
92
90
|
| `testId` | `string` | no | — | |
|
|
93
91
|
| `type` | `"text" \| "email" \| "date" \| "datetime-local" \| "password"` | no | `text` | |
|
|
94
92
|
| `value` | `string \| null` | no | — | |
|
|
95
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
93
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
96
94
|
|
|
97
95
|
### `PasswordInput`
|
|
98
96
|
|
|
@@ -111,7 +109,7 @@ Source: `src/blocks/PasswordInput/PasswordInput.tsx`
|
|
|
111
109
|
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
112
110
|
| `state` | `"warning" \| "error" \| "default" \| null` | no | — | |
|
|
113
111
|
| `value` | `string \| null` | no | — | |
|
|
114
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
112
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
115
113
|
|
|
116
114
|
### `Textarea`
|
|
117
115
|
|
|
@@ -129,7 +127,7 @@ Source: `src/blocks/Textarea/Textarea.tsx`
|
|
|
129
127
|
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
130
128
|
| `state` | `"warning" \| "error" \| "default" \| null` | no | `default` | |
|
|
131
129
|
| `value` | `string \| null` | no | — | |
|
|
132
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
130
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
133
131
|
|
|
134
132
|
## Inputs — FormGroup (label + input + help, shared validation state)
|
|
135
133
|
|
|
@@ -167,7 +165,7 @@ Source: `src/components/FormGroup/FormGroupTextInput.tsx`
|
|
|
167
165
|
| `testId` | `string` | no | — | |
|
|
168
166
|
| `type` | `"text" \| "email" \| "date" \| "datetime-local" \| "password"` | no | — | |
|
|
169
167
|
| `value` | `string \| null` | no | — | |
|
|
170
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
168
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
171
169
|
|
|
172
170
|
### `FormGroup.Help`
|
|
173
171
|
|
|
@@ -204,7 +202,7 @@ Source: `src/blocks/Form/connectors/TextField.tsx`
|
|
|
204
202
|
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
205
203
|
| `testId` | `string` | no | — | |
|
|
206
204
|
| `type` | `"text" \| "email" \| "date" \| "datetime-local" \| "password"` | no | — | |
|
|
207
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
205
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
208
206
|
|
|
209
207
|
### `field.PasswordField`
|
|
210
208
|
|
|
@@ -225,53 +223,54 @@ Source: `src/blocks/Form/connectors/PasswordField.tsx`
|
|
|
225
223
|
| `required` | `boolean` | no | — | |
|
|
226
224
|
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
227
225
|
| `state` | `"warning" \| "error" \| "default" \| null` | no | — | |
|
|
228
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
226
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
229
227
|
|
|
230
228
|
### `field.NumberField`
|
|
231
229
|
|
|
232
230
|
Source: `src/blocks/Form/connectors/NumberField.tsx`
|
|
233
231
|
|
|
234
|
-
| Prop | Type
|
|
235
|
-
| -------------- |
|
|
236
|
-
| `className` | `string`
|
|
237
|
-
| `defaultValue` | `number \| null`
|
|
238
|
-
| `focusVariant` | `"off" \| "on" \| "default" \| null`
|
|
239
|
-
| `help` | `ReactNode`
|
|
240
|
-
| `label` | `ReactNode`
|
|
241
|
-
| `onValueBlur` | `((value: number \| null) => void)`
|
|
242
|
-
| `prefix` | `ReactNode`
|
|
243
|
-
| `required` | `boolean`
|
|
244
|
-
| `sizing` | `"small" \| "medium" \| "default" \| null`
|
|
245
|
-
| `suffix` | `ReactNode`
|
|
246
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
232
|
+
| Prop | Type | Required | Default | Description |
|
|
233
|
+
| -------------- | ------------------------------------------ | -------- | ------- | ----------- |
|
|
234
|
+
| `className` | `string` | no | — | |
|
|
235
|
+
| `defaultValue` | `number \| null` | no | — | |
|
|
236
|
+
| `focusVariant` | `"off" \| "on" \| "default" \| null` | no | — | |
|
|
237
|
+
| `help` | `ReactNode` | no | — | |
|
|
238
|
+
| `label` | `ReactNode` | no | — | |
|
|
239
|
+
| `onValueBlur` | `((value: number \| null) => void)` | no | — | |
|
|
240
|
+
| `prefix` | `ReactNode` | no | — | |
|
|
241
|
+
| `required` | `boolean` | no | — | |
|
|
242
|
+
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
243
|
+
| `suffix` | `ReactNode` | no | — | |
|
|
244
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
247
245
|
|
|
248
246
|
### `field.SelectField`
|
|
249
247
|
|
|
250
248
|
Source: `src/blocks/Form/connectors/SelectField.tsx`
|
|
251
249
|
|
|
252
|
-
> `
|
|
253
|
-
|
|
254
|
-
| Prop | Type
|
|
255
|
-
| ------------------ |
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `label` | `ReactNode`
|
|
266
|
-
| `name` | `string`
|
|
267
|
-
| `onOpenChange` | `((open: boolean) => void)`
|
|
268
|
-
| `open` | `boolean`
|
|
269
|
-
| `
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
250
|
+
> Pass `items` (canonical, matches the base `Select`) or the deprecated `options` alias — one is required.
|
|
251
|
+
|
|
252
|
+
| Prop | Type | Required | Default | Description |
|
|
253
|
+
| ------------------ | ------------------------------------------- | -------- | ------- | ----------------------- |
|
|
254
|
+
| `autoComplete` | `string` | no | — | |
|
|
255
|
+
| `className` | `string` | no | — | |
|
|
256
|
+
| `defaultOpen` | `boolean` | no | — | |
|
|
257
|
+
| `defaultValue` | `string` | no | — | |
|
|
258
|
+
| `dir` | `"rtl" \| "ltr"` | no | — | |
|
|
259
|
+
| `disabled` | `boolean` | no | — | |
|
|
260
|
+
| `form` | `string` | no | — | |
|
|
261
|
+
| `help` | `ReactNode` | no | — | |
|
|
262
|
+
| `items` | `readonly Option[]` | no | — | |
|
|
263
|
+
| `label` | `ReactNode` | no | — | |
|
|
264
|
+
| `name` | `string` | no | — | |
|
|
265
|
+
| `onOpenChange` | `((open: boolean) => void)` | no | — | |
|
|
266
|
+
| `open` | `boolean` | no | — | |
|
|
267
|
+
| `options` | `readonly Option[]` | no | — | @deprecated use `items` |
|
|
268
|
+
| `placeholder` | `string` | no | — | |
|
|
269
|
+
| `required` | `boolean` | no | — | |
|
|
270
|
+
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
271
|
+
| `state` | `"warning" \| "error" \| "default" \| null` | no | — | |
|
|
272
|
+
| `triggerClassName` | `string` | no | — | |
|
|
273
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
275
274
|
|
|
276
275
|
### `field.TextAreaField`
|
|
277
276
|
|
|
@@ -290,39 +289,37 @@ Source: `src/blocks/Form/connectors/TextAreaField.tsx`
|
|
|
290
289
|
| `onValueBlur` | `(((value: string) => void) & ((value: string) => void))` | no | — | |
|
|
291
290
|
| `required` | `boolean` | no | — | |
|
|
292
291
|
| `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
|
|
293
|
-
| `variant` | `"primary" \| "secondary" \|
|
|
292
|
+
| `variant` | `"primary" \| "secondary" \| null` | no | — | |
|
|
294
293
|
|
|
295
294
|
### `field.CheckboxField`
|
|
296
295
|
|
|
297
296
|
Source: `src/blocks/Form/connectors/CheckboxField.tsx`
|
|
298
297
|
|
|
299
|
-
| Prop | Type | Required | Default | Description
|
|
300
|
-
| ------------------ | ---------------------------------------- | -------- | ------- |
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
304
|
-
| `
|
|
305
|
-
| `
|
|
306
|
-
| `
|
|
307
|
-
| `
|
|
308
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `variant` | `"default" \| "boxed" \| null` | no | — | |
|
|
298
|
+
| Prop | Type | Required | Default | Description |
|
|
299
|
+
| ------------------ | ---------------------------------------- | -------- | ------- | ----------- |
|
|
300
|
+
| `asChild` | `boolean` | no | — | |
|
|
301
|
+
| `className` | `string` | no | — | |
|
|
302
|
+
| `description` | `ReactNode` | no | — | |
|
|
303
|
+
| `help` | `ReactNode` | no | — | |
|
|
304
|
+
| `label` | `ReactNode` | no | — | |
|
|
305
|
+
| `position` | `"start" \| "end"` | no | — | |
|
|
306
|
+
| `tooltip` | `ReactNode` | no | — | |
|
|
307
|
+
| `tooltipPlacement` | `"top" \| "right" \| "bottom" \| "left"` | no | — | |
|
|
308
|
+
| `variant` | `"default" \| "boxed" \| null` | no | — | |
|
|
311
309
|
|
|
312
310
|
### `field.SwitchField`
|
|
313
311
|
|
|
314
312
|
Source: `src/blocks/Form/connectors/SwitchField.tsx`
|
|
315
313
|
|
|
316
|
-
| Prop | Type | Required | Default | Description
|
|
317
|
-
| ------------- | ---------------------------------------- | -------- | ------- |
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `variant` | `"default" \| "boxed" \| null` | no | — | |
|
|
314
|
+
| Prop | Type | Required | Default | Description |
|
|
315
|
+
| ------------- | ---------------------------------------- | -------- | ------- | ----------- |
|
|
316
|
+
| `className` | `string` | no | — | |
|
|
317
|
+
| `description` | `ReactNode` | no | — | |
|
|
318
|
+
| `help` | `ReactNode` | no | — | |
|
|
319
|
+
| `label` | `ReactNode` | no | — | |
|
|
320
|
+
| `position` | `"start" \| "end"` | no | — | |
|
|
321
|
+
| `size` | `"small" \| "medium" \| "large" \| null` | no | — | |
|
|
322
|
+
| `variant` | `"default" \| "boxed" \| null` | no | — | |
|
|
326
323
|
|
|
327
324
|
## Data display — Card (compound)
|
|
328
325
|
|
|
@@ -332,11 +329,10 @@ Source: `src/components/Card/Card.tsx`
|
|
|
332
329
|
|
|
333
330
|
> Subcomponents that EXIST: `Card.Header`, `Card.Title`, `Card.Content`. There is NO `Card.Description` and NO `Card.Footer`. Plus native `<div>` attributes.
|
|
334
331
|
|
|
335
|
-
| Prop | Type | Required | Default | Description
|
|
336
|
-
| ---------- | -------------------------------------------------- | -------- | ------- |
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `padding` | `"small" \| "medium" \| "large" \| "none" \| null` | no | `small` | |
|
|
332
|
+
| Prop | Type | Required | Default | Description |
|
|
333
|
+
| ---------- | -------------------------------------------------- | -------- | ------- | ----------- |
|
|
334
|
+
| `bordered` | `boolean \| null` | no | `false` | |
|
|
335
|
+
| `padding` | `"small" \| "medium" \| "large" \| "none" \| null` | no | `small` | |
|
|
340
336
|
|
|
341
337
|
### `Card.Header`
|
|
342
338
|
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ It is version-pinned to your lockfile, so it always matches the components you a
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
|
|
26
|
+
pnpm add @keboola/design @keboola/tailwind-config
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## Use
|
|
@@ -38,7 +38,7 @@ import '@keboola/design/styles';
|
|
|
38
38
|
### Documentation
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
$
|
|
41
|
+
$ pnpm dev
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
See [Storybook's documentation](https://storybook.js.org/docs/react/writing-stories/introduction) for an introduction to writing stories
|
|
@@ -46,13 +46,13 @@ See [Storybook's documentation](https://storybook.js.org/docs/react/writing-stor
|
|
|
46
46
|
### Testing
|
|
47
47
|
|
|
48
48
|
```
|
|
49
|
-
$
|
|
49
|
+
$ pnpm test
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
Or in UI mode:
|
|
53
53
|
|
|
54
54
|
```
|
|
55
|
-
$
|
|
55
|
+
$ pnpm test:ui
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
For more information, read [Vitest's documentation](https://vitest.dev/guide/)
|
|
@@ -65,18 +65,18 @@ Every story is also run as a test via the [Storybook Vitest addon](https://story
|
|
|
65
65
|
- **interaction** — each story's `play()` function runs as a test.
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
|
|
68
|
+
pnpm --filter @keboola/design test:stories
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
This is the cheap, low-flakiness base layer for testing stories. Screenshot/visual regression is a separate, complementary layer (UT-4060) — the Vitest addon does **not** do screenshot comparison.
|
|
72
72
|
|
|
73
73
|
**How it's wired:**
|
|
74
74
|
|
|
75
|
-
- `vitest.config.ts` — a separate Vitest config (browser mode, Playwright Chromium provider) so `
|
|
75
|
+
- `vitest.config.ts` — a separate Vitest config (browser mode, Playwright Chromium provider) so `pnpm test` stays jsdom-only and browser-free; story tests are opt-in via `test:stories`. It is named `vitest.config.ts` (not `vitest.storybook.config.ts`) so the Storybook "Run tests" button (`@storybook/addon-vitest`) can discover it — the addon only scans `vitest.workspace.*` / `vitest.config.*` / `vite.config.*` names; under any other name it falls back to `vite.config.ts` and crashes the dev server (UT-4243). The jsdom `test` script therefore pins `--config vite.config.ts` explicitly.
|
|
76
76
|
- `.storybook/vitest.setup.ts` — applies the Storybook `preview` annotations (decorators, globals) so tests render exactly like Storybook.
|
|
77
77
|
- `optimizeDeps.entries` crawls every story up front so Vite optimizes all story dependencies in one pass — without it, a heavy dep (mermaid, codemirror, …) discovered mid-run makes Vite re-optimize and 404 in-flight imports.
|
|
78
78
|
|
|
79
|
-
**Prerequisites:** build the workspace deps once (`
|
|
79
|
+
**Prerequisites:** build the workspace deps once (`pnpm build --filter='@keboola/design^...'`) and install the browser (`pnpm --filter @keboola/design exec playwright install chromium`).
|
|
80
80
|
|
|
81
81
|
**Adding coverage:** nothing to do — every CSF `*.stories.tsx` is picked up automatically. Add a `play()` to a story to turn it into an interaction test. (MDX docs pages — `src/**/*.mdx` — are not executed as tests; the addon only runs CSF stories.)
|
|
82
82
|
|
|
@@ -127,7 +127,7 @@ Worked examples: `src/components/Button/Button.stories.tsx` (click + disabled),
|
|
|
127
127
|
Coverage is **measured, not gated** — it gives a baseline for the story suite. The default `test:stories` run stays coverage-free and fast; coverage only loads when you opt in:
|
|
128
128
|
|
|
129
129
|
```bash
|
|
130
|
-
|
|
130
|
+
pnpm --filter @keboola/design test:stories:coverage
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
Same prerequisites as `test:stories` (build workspace deps, install Chromium). Reports land in `packages/design/coverage/` (gitignored): a `text` summary in the console and a browsable `html` report at `coverage/index.html`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
declare const CHUNK_TYPE: {
|
|
2
|
+
readonly UNCHANGED: "unchanged";
|
|
3
|
+
readonly ADDED: "added";
|
|
4
|
+
readonly REMOVED: "removed";
|
|
5
|
+
};
|
|
6
|
+
type ChunkType = (typeof CHUNK_TYPE)[keyof typeof CHUNK_TYPE];
|
|
7
|
+
type DiffChunk = {
|
|
8
|
+
type: ChunkType;
|
|
9
|
+
value: string;
|
|
10
|
+
/** 1-indexed start line in the old (left) text */
|
|
11
|
+
oldStart: number;
|
|
12
|
+
/** 1-indexed end line in the old (left) text */
|
|
13
|
+
oldEnd: number;
|
|
14
|
+
/** 1-indexed start line in the new (right) text */
|
|
15
|
+
newStart: number;
|
|
16
|
+
/** 1-indexed end line in the new (right) text */
|
|
17
|
+
newEnd: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A paired conflict: adjacent REMOVED + ADDED chunks from diffLines,
|
|
21
|
+
* representing a section that was modified. If only one side exists,
|
|
22
|
+
* the other is empty (pure insertion or deletion).
|
|
23
|
+
*/
|
|
24
|
+
type MergeConflict = {
|
|
25
|
+
id: string;
|
|
26
|
+
oldValue: string;
|
|
27
|
+
oldStartLine: number;
|
|
28
|
+
oldEndLine: number;
|
|
29
|
+
newValue: string;
|
|
30
|
+
newStartLine: number;
|
|
31
|
+
newEndLine: number;
|
|
32
|
+
};
|
|
33
|
+
declare const CHANGE_CLASS: {
|
|
34
|
+
readonly SAFE: "safe";
|
|
35
|
+
readonly CONFLICT: "conflict";
|
|
36
|
+
};
|
|
37
|
+
type ChangeClassification = (typeof CHANGE_CLASS)[keyof typeof CHANGE_CLASS];
|
|
38
|
+
|
|
39
|
+
export type { ChangeClassification as C, DiffChunk as D, MergeConflict as M };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
declare const CHUNK_TYPE: {
|
|
2
|
+
readonly UNCHANGED: "unchanged";
|
|
3
|
+
readonly ADDED: "added";
|
|
4
|
+
readonly REMOVED: "removed";
|
|
5
|
+
};
|
|
6
|
+
type ChunkType = (typeof CHUNK_TYPE)[keyof typeof CHUNK_TYPE];
|
|
7
|
+
type DiffChunk = {
|
|
8
|
+
type: ChunkType;
|
|
9
|
+
value: string;
|
|
10
|
+
/** 1-indexed start line in the old (left) text */
|
|
11
|
+
oldStart: number;
|
|
12
|
+
/** 1-indexed end line in the old (left) text */
|
|
13
|
+
oldEnd: number;
|
|
14
|
+
/** 1-indexed start line in the new (right) text */
|
|
15
|
+
newStart: number;
|
|
16
|
+
/** 1-indexed end line in the new (right) text */
|
|
17
|
+
newEnd: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A paired conflict: adjacent REMOVED + ADDED chunks from diffLines,
|
|
21
|
+
* representing a section that was modified. If only one side exists,
|
|
22
|
+
* the other is empty (pure insertion or deletion).
|
|
23
|
+
*/
|
|
24
|
+
type MergeConflict = {
|
|
25
|
+
id: string;
|
|
26
|
+
oldValue: string;
|
|
27
|
+
oldStartLine: number;
|
|
28
|
+
oldEndLine: number;
|
|
29
|
+
newValue: string;
|
|
30
|
+
newStartLine: number;
|
|
31
|
+
newEndLine: number;
|
|
32
|
+
};
|
|
33
|
+
declare const CHANGE_CLASS: {
|
|
34
|
+
readonly SAFE: "safe";
|
|
35
|
+
readonly CONFLICT: "conflict";
|
|
36
|
+
};
|
|
37
|
+
type ChangeClassification = (typeof CHANGE_CLASS)[keyof typeof CHANGE_CLASS];
|
|
38
|
+
|
|
39
|
+
export type { ChangeClassification as C, DiffChunk as D, MergeConflict as M };
|