@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 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
- The per-call-site `argo` / `variant="argo"` prop is a **transitional escape hatch** (force argo on one component regardless of brand), **not** the primary switch — prefer driving the whole tree from the brand. Two escape-hatch forms exist:
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 `yarn workspace @keboola/design generate:api` (extracts prop signatures from the TypeScript types via `react-docgen-typescript`).
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 (`yarn dev --filter=@keboola/design`).
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
- | `argo` | `boolean` | no | — | @internal Transitional opt-in; removed once consumers wrap argo sections in `<ThemeProvider>`. |
57
- | `hideIcon` | `boolean` | no | `false` | |
58
- | `icon` | `ReactNode` | no | — | |
59
- | `mode` | `"collapsible" \| "default"` | no | `default` | |
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" \| "argo" \| null` | no | — | |
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" \| "argo" \| null` | no | — | |
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" \| "argo" \| null` | no | — | |
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" \| "argo" \| null` | no | — | |
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" \| "argo" \| null` | no | — | |
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" \| "argo" \| null` | no | — | |
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 | Required | Default | Description |
235
- | -------------- | -------------------------------------------- | -------- | ------- | ----------- |
236
- | `className` | `string` | no | — | |
237
- | `defaultValue` | `number \| null` | no | — | |
238
- | `focusVariant` | `"off" \| "on" \| "default" \| null` | no | — | |
239
- | `help` | `ReactNode` | no | — | |
240
- | `label` | `ReactNode` | no | — | |
241
- | `onValueBlur` | `((value: number \| null) => void)` | no | — | |
242
- | `prefix` | `ReactNode` | no | — | |
243
- | `required` | `boolean` | no | — | |
244
- | `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
245
- | `suffix` | `ReactNode` | no | — | |
246
- | `variant` | `"primary" \| "secondary" \| "argo" \| null` | no | — | |
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
- > `options` is required. Forwards `variant === "argo"` to `Select.Content`.
253
-
254
- | Prop | Type | Required | Default | Description |
255
- | ------------------ | -------------------------------------------- | -------- | ------- | ----------- |
256
- | `options` | `readonly Option[]` | yes | — | |
257
- | `autoComplete` | `string` | no | — | |
258
- | `className` | `string` | no | — | |
259
- | `defaultOpen` | `boolean` | no | — | |
260
- | `defaultValue` | `string` | no | — | |
261
- | `dir` | `"rtl" \| "ltr"` | no | — | |
262
- | `disabled` | `boolean` | no | — | |
263
- | `form` | `string` | no | — | |
264
- | `help` | `ReactNode` | no | — | |
265
- | `label` | `ReactNode` | no | — | |
266
- | `name` | `string` | no | — | |
267
- | `onOpenChange` | `((open: boolean) => void)` | no | — | |
268
- | `open` | `boolean` | no | — | |
269
- | `placeholder` | `string` | no | — | |
270
- | `required` | `boolean` | no | — | |
271
- | `sizing` | `"small" \| "medium" \| "default" \| null` | no | — | |
272
- | `state` | `"warning" \| "error" \| "default" \| null` | no | — | |
273
- | `triggerClassName` | `string` | no | — | |
274
- | `variant` | `"primary" \| "secondary" \| "argo" \| null` | no | — | |
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" \| "argo" \| null` | no | — | |
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
- | `argo` | `boolean` | no | — | @internal Transitional opt-in; removed once consumers wrap argo sections in `<ThemeProvider>`. |
302
- | `asChild` | `boolean` | no | — | |
303
- | `className` | `string` | no | — | |
304
- | `description` | `ReactNode` | no | — | |
305
- | `help` | `ReactNode` | no | — | |
306
- | `label` | `ReactNode` | no | — | |
307
- | `position` | `"start" \| "end"` | no | — | |
308
- | `tooltip` | `ReactNode` | no | — | |
309
- | `tooltipPlacement` | `"top" \| "right" \| "bottom" \| "left"` | no | — | |
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
- | `argo` | `boolean` | no | — | @internal Transitional opt-in; removed once consumers wrap argo sections in `<ThemeProvider>`. |
319
- | `className` | `string` | no | — | |
320
- | `description` | `ReactNode` | no | — | |
321
- | `help` | `ReactNode` | no | — | |
322
- | `label` | `ReactNode` | no | — | |
323
- | `position` | `"start" \| "end"` | no | — | |
324
- | `size` | `"small" \| "medium" \| "large" \| null` | no | — | |
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
- | `argo` | `boolean` | no | `false` | @internal Transitional opt-in; removed once consumers wrap argo sections in `<ThemeProvider>`. |
338
- | `bordered` | `boolean \| null` | no | `false` | |
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
- yarn add @keboola/design @keboola/tailwind-config
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
- $ yarn dev
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
- $ yarn test
49
+ $ pnpm test
50
50
  ```
51
51
 
52
52
  Or in UI mode:
53
53
 
54
54
  ```
55
- $ yarn test:ui
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
- yarn workspace @keboola/design test:stories
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 `yarn 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.
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 (`yarn build --filter='@keboola/design^...'`) and install the browser (`yarn workspace @keboola/design exec playwright install chromium`).
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
- yarn workspace @keboola/design test:stories:coverage
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 };