@keboola/design 1.3.1 → 2.0.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.
Files changed (98) hide show
  1. package/dist/icons.cjs +32 -0
  2. package/dist/icons.js +1 -1
  3. package/dist/index.cjs +507 -103
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +495 -106
  6. package/dist/index.js.map +1 -1
  7. package/dist/markdown-editor.cjs +78 -42
  8. package/dist/markdown-editor.cjs.map +1 -1
  9. package/dist/markdown-editor.js +78 -42
  10. package/dist/markdown-editor.js.map +1 -1
  11. package/dist/mermaid.cjs +32 -9
  12. package/dist/mermaid.cjs.map +1 -1
  13. package/dist/mermaid.js +32 -9
  14. package/dist/mermaid.js.map +1 -1
  15. package/dist/styles.css +1 -1
  16. package/package.json +8 -6
  17. package/src/blocks/AIKit/PlanApproval.tsx +29 -12
  18. package/src/blocks/AIKit/PromptInput.test.tsx +53 -0
  19. package/src/blocks/AIKit/PromptInput.tsx +1 -1
  20. package/src/blocks/AIKit/Question.tsx +39 -11
  21. package/src/blocks/AIKit/SuggestedPrompt.stories.tsx +2 -8
  22. package/src/blocks/EmptyState/EmptyState.stories.tsx +5 -5
  23. package/src/blocks/EmptyState/EmptyState.tsx +2 -2
  24. package/src/blocks/ErrorState/ErrorState.stories.tsx +6 -6
  25. package/src/blocks/ErrorState/ErrorState.tsx +18 -3
  26. package/src/blocks/InputOtp/InputOtp.stories.tsx +214 -0
  27. package/src/blocks/InputOtp/InputOtp.test.tsx +162 -0
  28. package/src/blocks/InputOtp/InputOtp.tsx +62 -0
  29. package/src/blocks/InputOtp/InputOtpGroup.tsx +13 -0
  30. package/src/blocks/InputOtp/InputOtpSeparator.tsx +23 -0
  31. package/src/blocks/InputOtp/InputOtpSlot.styles.ts +52 -0
  32. package/src/blocks/InputOtp/InputOtpSlot.tsx +44 -0
  33. package/src/blocks/InputOtp/context.ts +23 -0
  34. package/src/blocks/InputOtp/index.ts +23 -0
  35. package/src/blocks/PageHeader/AGENTS.md +1 -1
  36. package/src/blocks/PageHeader/PageHeader.stories.tsx +6 -3
  37. package/src/blocks/PageHeader/PageHeaderIndex.tsx +2 -2
  38. package/src/blocks/PageLayout/AGENTS.md +3 -3
  39. package/src/blocks/PageLayout/PageLayout.stories.tsx +7 -4
  40. package/src/blocks/PageLayout/PageLayout.tsx +1 -18
  41. package/src/blocks/PageToolbar/PageToolbar.stories.tsx +79 -74
  42. package/src/blocks/PageToolbar/PageToolbar.tsx +45 -4
  43. package/src/blocks/PageToolbar/PageToolbarToken.tsx +61 -0
  44. package/src/blocks/SearchInput/SearchInput.tsx +2 -2
  45. package/src/components/Alert/AlertDescription.tsx +5 -0
  46. package/src/components/Alert/AlertIcon.tsx +1 -1
  47. package/src/components/Badge/Badge.stories.tsx +3 -5
  48. package/src/components/Badge/BadgeAddon.tsx +1 -1
  49. package/src/components/Badge/BadgeButton.tsx +1 -1
  50. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +4 -4
  51. package/src/components/Breadcrumb/BreadcrumbLink.tsx +1 -1
  52. package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +1 -1
  53. package/src/components/Button/ButtonInline.stories.tsx +3 -3
  54. package/src/components/Button/ButtonInline.tsx +4 -0
  55. package/src/components/Button/styles.ts +33 -10
  56. package/src/components/Card/Card.stories.tsx +2 -2
  57. package/src/components/Combobox/ComboboxItemIndicator.tsx +1 -1
  58. package/src/components/Command/CommandItem/CommandItem.tsx +1 -1
  59. package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +4 -4
  60. package/src/components/DropdownMenu/ItemIcon.tsx +1 -1
  61. package/src/components/FormGroup/FormGroup.stories.tsx +2 -2
  62. package/src/components/HoverCard/HoverCardContent.tsx +5 -0
  63. package/src/components/Input/Input.stories.tsx +2 -2
  64. package/src/components/InputGroup/InputGroup.stories.tsx +4 -5
  65. package/src/components/InputGroup/InputGroup.tsx +1 -1
  66. package/src/components/InputGroup/InputGroupAddon.tsx +35 -24
  67. package/src/components/Kbd/Kbd.stories.tsx +2 -2
  68. package/src/components/Label/Label.stories.tsx +2 -2
  69. package/src/components/Modal/ModalBody.tsx +5 -0
  70. package/src/components/Modal/ModalIcon.tsx +1 -1
  71. package/src/components/Navbar/Navbar.stories.tsx +4 -5
  72. package/src/components/Navbar/NavbarMenuItemAdornment.tsx +2 -2
  73. package/src/components/Pagination/Pagination.stories.tsx +0 -1
  74. package/src/components/Pagination/PaginationEllipsis.tsx +1 -1
  75. package/src/components/Popover/PopoverContent.tsx +4 -0
  76. package/src/components/Select/SelectContent.tsx +1 -1
  77. package/src/components/Select/SelectTrigger.tsx +1 -1
  78. package/src/components/Separator/Separator.stories.tsx +19 -0
  79. package/src/components/Separator/Separator.test.tsx +39 -0
  80. package/src/components/Separator/Separator.tsx +42 -11
  81. package/src/components/Sidebar/Sidebar.stories.tsx +6 -5
  82. package/src/components/Sidebar/SidebarGroup/SidebarGroupAction.tsx +1 -1
  83. package/src/components/Sidebar/SidebarMenu/SidebarMenuAction.tsx +1 -1
  84. package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +1 -1
  85. package/src/components/Table/Table.stories.tsx +16 -16
  86. package/src/components/Timeline/Timeline.stories.tsx +8 -8
  87. package/src/components/Timeline/TimelineConnector.tsx +5 -1
  88. package/src/components/Toast/ToastDismiss.tsx +1 -1
  89. package/src/components/Toast/ToastRoot.tsx +1 -1
  90. package/src/components/Toggle/Toggle.tsx +1 -1
  91. package/src/components/ToggleGroup/ToggleGroup.stories.tsx +60 -0
  92. package/src/components/ToggleGroup/ToggleGroup.tsx +99 -0
  93. package/src/components/ToggleGroup/index.ts +1 -0
  94. package/src/components/Tooltip/Tooltip.stories.tsx +2 -2
  95. package/src/hooks/useAutosizeInput.stories.tsx +2 -2
  96. package/src/icons/index.ts +8 -0
  97. package/src/index.ts +2 -0
  98. package/src/patterns/FilterToolbar.stories.tsx +248 -44
@@ -1,11 +1,12 @@
1
1
  import { useState } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
 
4
- import { Badge } from '../../components/Badge';
5
4
  import { Button } from '../../components/Button';
6
- import { ButtonGroup } from '../../components/ButtonGroup';
5
+ import { Checkbox } from '../../components/Checkbox';
7
6
  import { Combobox } from '../../components/Combobox';
8
7
  import { Icon } from '../../components/Icon';
8
+ import { ToggleGroup } from '../../components/ToggleGroup';
9
+ import { LayoutGrid, List } from '../../icons';
9
10
  import { PageLayout } from '../PageLayout';
10
11
  import { SearchInput } from '../SearchInput';
11
12
 
@@ -46,7 +47,6 @@ export const Default: Story = {
46
47
  <PageToolbar.Search>
47
48
  <SearchInput
48
49
  variant="argo"
49
- sizing="medium"
50
50
  allowClear
51
51
  placeholder="Search jobs"
52
52
  value={query}
@@ -58,7 +58,6 @@ export const Default: Story = {
58
58
  <PageToolbar.Filters>
59
59
  <Combobox
60
60
  variant="argo"
61
- sizing="medium"
62
61
  allowClear
63
62
  items={['all', 'failed', 'success'] as const}
64
63
  value={status}
@@ -70,7 +69,6 @@ export const Default: Story = {
70
69
  <PageToolbar.Sort>
71
70
  <Combobox
72
71
  variant="argo"
73
- sizing="medium"
74
72
  items={['newest', 'oldest'] as const}
75
73
  value={sort}
76
74
  onValueChange={setSort}
@@ -90,8 +88,7 @@ export const Default: Story = {
90
88
  };
91
89
 
92
90
  // Standard Filters variant — non-editable chips rendered into the Active Filters row,
93
- // with a Clear action self-aligned to the far right. Uses `Badge` as a stand-in until
94
- // the dedicated `FilterChip` primitive ships.
91
+ // with a Clear action self-aligned to the far right.
95
92
  export const WithStandardFilters: Story = {
96
93
  render: () => {
97
94
  type Active = { id: string; label: string; value: string };
@@ -104,14 +101,13 @@ export const WithStandardFilters: Story = {
104
101
  <PageLayout>
105
102
  <PageToolbar>
106
103
  <PageToolbar.Search>
107
- <SearchInput variant="argo" sizing="medium" allowClear placeholder="Search jobs" />
104
+ <SearchInput variant="argo" allowClear placeholder="Search jobs" />
108
105
  </PageToolbar.Search>
109
106
 
110
107
  <PageToolbar.Right>
111
108
  <PageToolbar.Filters>
112
109
  <Combobox
113
110
  variant="argo"
114
- sizing="medium"
115
111
  allowClear
116
112
  items={['Failed', 'Success', 'Running'] as const}
117
113
  placeholder="Status"
@@ -119,7 +115,6 @@ export const WithStandardFilters: Story = {
119
115
  />
120
116
  <Combobox
121
117
  variant="argo"
122
- sizing="medium"
123
118
  allowClear
124
119
  items={['John Doe', 'Jane Doe'] as const}
125
120
  placeholder="Owner"
@@ -131,23 +126,13 @@ export const WithStandardFilters: Story = {
131
126
  {active.length > 0 && (
132
127
  <PageToolbar.ActiveFilters>
133
128
  {active.map((filter) => (
134
- <Badge
129
+ <PageToolbar.Chip
135
130
  key={filter.id}
136
- variant="default"
137
- color="gray"
138
- suffix={
139
- <button
140
- type="button"
141
- aria-label={`Remove ${filter.label} filter`}
142
- onClick={() => setActive((prev) => prev.filter((f) => f.id !== filter.id))}
143
- className="tw-flex tw-items-center"
144
- >
145
- <Icon icon="xmark" />
146
- </button>
147
- }
131
+ onRemove={() => setActive((prev) => prev.filter((f) => f.id !== filter.id))}
132
+ removeAriaLabel={`Remove ${filter.label} filter`}
148
133
  >
149
134
  {filter.label}: {filter.value}
150
- </Badge>
135
+ </PageToolbar.Chip>
151
136
  ))}
152
137
  <PageToolbar.Clear onClick={() => setActive([])} />
153
138
  </PageToolbar.ActiveFilters>
@@ -163,21 +148,46 @@ export const WithStandardFilters: Story = {
163
148
  };
164
149
 
165
150
  // Query Builder variant — editable tokens rendered into the Active Filters row, with
166
- // `AddFilter` between the tokens and Clear. Uses `Badge` with a `chevron-down` suffix
167
- // as a stand-in until the dedicated `FilterToken` primitive ships.
151
+ // `AddFilter` between the tokens and Clear. Each `Token` exposes a `popover` slot the
152
+ // caller fills with the value editor (here, a multi-select checkbox list).
168
153
  export const WithQueryBuilder: Story = {
169
154
  render: () => {
170
- type Token = { id: string; label: string; values: string[] };
155
+ type Token = { id: string; label: string; values: string[]; options: string[] };
171
156
  const [tokens, setTokens] = useState<Token[]>([
172
- { id: 'status', label: 'Status', values: ['Failed', 'Success'] },
173
- { id: 'owner', label: 'Owner', values: ['John'] },
157
+ {
158
+ id: 'status',
159
+ label: 'Status',
160
+ values: ['Failed', 'Success'],
161
+ options: ['Failed', 'Success', 'Running', 'Scheduled'],
162
+ },
163
+ {
164
+ id: 'owner',
165
+ label: 'Owner',
166
+ values: ['Jan Kovar'],
167
+ options: ['Jan Kovar', 'John Doe', 'Jane Doe'],
168
+ },
174
169
  ]);
175
170
 
171
+ const toggleValue = (tokenId: string, value: string) => {
172
+ setTokens((prev) =>
173
+ prev.map((token) =>
174
+ token.id === tokenId
175
+ ? {
176
+ ...token,
177
+ values: token.values.includes(value)
178
+ ? token.values.filter((v) => v !== value)
179
+ : [...token.values, value],
180
+ }
181
+ : token,
182
+ ),
183
+ );
184
+ };
185
+
176
186
  return (
177
187
  <PageLayout>
178
188
  <PageToolbar>
179
189
  <PageToolbar.Search>
180
- <SearchInput variant="argo" sizing="medium" allowClear placeholder="Search jobs" />
190
+ <SearchInput variant="argo" allowClear placeholder="Search jobs" />
181
191
  </PageToolbar.Search>
182
192
 
183
193
  <PageToolbar.Right>
@@ -192,14 +202,27 @@ export const WithQueryBuilder: Story = {
192
202
  {tokens.length > 0 && (
193
203
  <PageToolbar.ActiveFilters>
194
204
  {tokens.map((token) => (
195
- <Badge
205
+ <PageToolbar.Token
196
206
  key={token.id}
197
- variant="default"
198
- color="gray"
199
- suffix={<Icon icon="chevron-down" />}
207
+ onRemove={() => setTokens((prev) => prev.filter((t) => t.id !== token.id))}
208
+ removeAriaLabel={`Remove ${token.label} filter`}
209
+ popover={
210
+ <div className="tw-flex tw-min-w-48 tw-flex-col tw-gap-1 tw-p-2">
211
+ {token.options.map((option) => (
212
+ <Checkbox
213
+ argo
214
+ key={option}
215
+ checked={token.values.includes(option)}
216
+ onCheckedChange={() => toggleValue(token.id, option)}
217
+ >
218
+ {option}
219
+ </Checkbox>
220
+ ))}
221
+ </div>
222
+ }
200
223
  >
201
224
  {token.label}: {token.values.join(', ')}
202
- </Badge>
225
+ </PageToolbar.Token>
203
226
  ))}
204
227
  <PageToolbar.AddFilter onClick={() => {}} />
205
228
  <PageToolbar.Clear onClick={() => setTokens([])} />
@@ -225,14 +248,13 @@ export const WithViewControls: Story = {
225
248
  <PageLayout>
226
249
  <PageToolbar>
227
250
  <PageToolbar.Search>
228
- <SearchInput variant="argo" sizing="medium" allowClear placeholder="Search items" />
251
+ <SearchInput variant="argo" allowClear placeholder="Search items" />
229
252
  </PageToolbar.Search>
230
253
 
231
254
  <PageToolbar.Right>
232
255
  <PageToolbar.Filters>
233
256
  <Combobox
234
257
  variant="argo"
235
- sizing="medium"
236
258
  allowClear
237
259
  items={['Active', 'Archived'] as const}
238
260
  placeholder="Status"
@@ -242,7 +264,6 @@ export const WithViewControls: Story = {
242
264
  <PageToolbar.Sort>
243
265
  <Combobox
244
266
  variant="argo"
245
- sizing="medium"
246
267
  items={['Newest', 'Oldest'] as const}
247
268
  value="Newest"
248
269
  placeholder="Sort"
@@ -256,33 +277,19 @@ export const WithViewControls: Story = {
256
277
  </Button>
257
278
  </PageToolbar.Actions>
258
279
  <PageToolbar.View>
259
- {/* Temporary view toggle until the dedicated `ToggleGroup` primitive ships.
260
- Two plain outline icon buttons with `aria-pressed` for state; selected
261
- styling lives on the consumer until the primitive lands. */}
262
- <ButtonGroup space="extra-small">
263
- <Button
264
- argo
265
- variant="outline"
266
- size="icon"
267
- onClick={() => setView('list')}
268
- aria-label="List view"
269
- aria-pressed={view === 'list'}
270
- className={view === 'list' ? 'tw-bg-accent tw-text-accent-foreground' : undefined}
271
- >
272
- <Icon icon="table-rows" />
273
- </Button>
274
- <Button
275
- argo
276
- variant="outline"
277
- size="icon"
278
- onClick={() => setView('grid')}
279
- aria-label="Grid view"
280
- aria-pressed={view === 'grid'}
281
- className={view === 'grid' ? 'tw-bg-accent tw-text-accent-foreground' : undefined}
282
- >
283
- <Icon icon="table-cells" />
284
- </Button>
285
- </ButtonGroup>
280
+ <ToggleGroup
281
+ type="single"
282
+ value={view}
283
+ onValueChange={(next: 'list' | 'grid' | '') => next && setView(next)}
284
+ aria-label="View mode"
285
+ >
286
+ <ToggleGroup.Item value="list" aria-label="List view">
287
+ <List className="tw-size-4" />
288
+ </ToggleGroup.Item>
289
+ <ToggleGroup.Item value="grid" aria-label="Grid view">
290
+ <LayoutGrid className="tw-size-4" />
291
+ </ToggleGroup.Item>
292
+ </ToggleGroup>
286
293
  </PageToolbar.View>
287
294
  </PageToolbar.Right>
288
295
  </PageToolbar>
@@ -306,14 +313,13 @@ export const Responsive: Story = {
306
313
  <div className="tw-w-[480px] tw-rounded tw-border tw-border-border tw-bg-background tw-p-4">
307
314
  <PageToolbar>
308
315
  <PageToolbar.Search>
309
- <SearchInput variant="argo" sizing="medium" allowClear placeholder="Search jobs" />
316
+ <SearchInput variant="argo" allowClear placeholder="Search jobs" />
310
317
  </PageToolbar.Search>
311
318
 
312
319
  <PageToolbar.Right>
313
320
  <PageToolbar.Filters>
314
321
  <Combobox
315
322
  variant="argo"
316
- sizing="medium"
317
323
  allowClear
318
324
  items={['Failed', 'Success'] as const}
319
325
  placeholder="Status"
@@ -321,7 +327,6 @@ export const Responsive: Story = {
321
327
  />
322
328
  <Combobox
323
329
  variant="argo"
324
- sizing="medium"
325
330
  allowClear
326
331
  items={['John', 'Jane'] as const}
327
332
  placeholder="Owner"
@@ -331,15 +336,15 @@ export const Responsive: Story = {
331
336
  </PageToolbar.Right>
332
337
 
333
338
  <PageToolbar.ActiveFilters>
334
- <Badge variant="default" color="gray" suffix={<Icon icon="xmark" />}>
339
+ <PageToolbar.Chip onRemove={() => {}} removeAriaLabel="Remove Status filter">
335
340
  Status: Failed
336
- </Badge>
337
- <Badge variant="default" color="gray" suffix={<Icon icon="xmark" />}>
341
+ </PageToolbar.Chip>
342
+ <PageToolbar.Chip onRemove={() => {}} removeAriaLabel="Remove Owner filter">
338
343
  Owner: John Doe
339
- </Badge>
340
- <Badge variant="default" color="gray" suffix={<Icon icon="xmark" />}>
344
+ </PageToolbar.Chip>
345
+ <PageToolbar.Chip onRemove={() => {}} removeAriaLabel="Remove Created by filter">
341
346
  Created by: Marketing team
342
- </Badge>
347
+ </PageToolbar.Chip>
343
348
  <PageToolbar.Clear onClick={() => {}} />
344
349
  </PageToolbar.ActiveFilters>
345
350
  </PageToolbar>
@@ -1,9 +1,12 @@
1
- import type { ComponentPropsWithoutRef, ElementRef, Ref } from 'react';
1
+ import type { ComponentPropsWithoutRef, ElementRef, ReactNode, Ref } from 'react';
2
2
 
3
+ import { Badge } from '../../components/Badge';
3
4
  import { Button } from '../../components/Button';
4
- import { Icon } from '../../components/Icon';
5
+ import { Plus, X } from '../../icons';
5
6
  import { cn } from '../../utils';
6
7
 
8
+ import { PageToolbarToken } from './PageToolbarToken';
9
+
7
10
  type DivProps = ComponentPropsWithoutRef<'div'> & { ref?: Ref<ElementRef<'div'>> };
8
11
 
9
12
  const Root = ({ ref, className, children, ...props }: DivProps) => (
@@ -55,11 +58,41 @@ const ActiveFilters = ({ ref, className, children, ...props }: DivProps) => (
55
58
  </div>
56
59
  );
57
60
 
61
+ type ChipProps = Omit<
62
+ ComponentPropsWithoutRef<typeof Badge>,
63
+ 'variant' | 'color' | 'suffix' | 'children'
64
+ > & {
65
+ children: ReactNode;
66
+ onRemove: () => void;
67
+ removeAriaLabel: string;
68
+ };
69
+
70
+ const Chip = ({ children, onRemove, removeAriaLabel, ...props }: ChipProps) => (
71
+ <Badge
72
+ {...props}
73
+ variant="pill"
74
+ color="blue-inverse"
75
+ suffix={
76
+ <Button
77
+ type="button"
78
+ variant="inline"
79
+ size="icon-2xs"
80
+ aria-label={removeAriaLabel}
81
+ onClick={onRemove}
82
+ >
83
+ <X className="tw-size-3" />
84
+ </Button>
85
+ }
86
+ >
87
+ {children}
88
+ </Badge>
89
+ );
90
+
58
91
  type ActionProps = ComponentPropsWithoutRef<typeof Button>;
59
92
 
60
93
  const AddFilter = ({ className, children, ...props }: ActionProps) => (
61
94
  <Button argo variant="invisible" size="small" className={cn('tw-gap-1.5', className)} {...props}>
62
- <Icon icon="plus" />
95
+ <Plus className="tw-size-4" />
63
96
  {children ?? 'Add filter'}
64
97
  </Button>
65
98
  );
@@ -75,7 +108,7 @@ const Clear = ({ className, children, ...props }: ActionProps) => (
75
108
  * controls on the right, an optional Active Filters row below for filter chips or tokens.
76
109
  *
77
110
  * **When to use:** every index page that supports search or filtering. Hosts inside
78
- * `PageLayout` as the search / filter row — replaces the older bare-`<div>` `PageLayout.Toolbar`.
111
+ * `PageLayout` as the search / filter row, between `PageLayout.Header` and `PageLayout.Content`.
79
112
  * **When NOT to use:** detail pages, form pages, or anywhere there is no search / filter
80
113
  * surface. For routing-based tab rows reach for `PageLayout.Tabs`.
81
114
  *
@@ -112,6 +145,14 @@ export const PageToolbar = Object.assign(Root, {
112
145
  * by `AddFilter` / `Clear`.
113
146
  */
114
147
  ActiveFilters,
148
+ /** Non-editable filter chip (Standard variant) — `Badge` pill with a dismiss button. */
149
+ Chip,
150
+ /**
151
+ * Editable filter token (Query Builder variant) — `Badge` pill with a `chevron-down`
152
+ * popover trigger for editing values and an `×` dismiss button. The popover content
153
+ * is supplied by the caller via the `popover` slot (typically a `Combobox` or option list).
154
+ */
155
+ Token: PageToolbarToken,
115
156
  /** "+ Add filter" action — typically rendered after the chips/tokens in `ActiveFilters`. */
116
157
  AddFilter,
117
158
  /** "Clear all" action — self-aligns to the far right of `ActiveFilters`. */
@@ -0,0 +1,61 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+
3
+ import { Badge } from '../../components/Badge';
4
+ import { Button } from '../../components/Button';
5
+ import { Popover } from '../../components/Popover';
6
+ import { ChevronDown, X } from '../../icons';
7
+
8
+ type PageToolbarTokenProps = Omit<
9
+ ComponentPropsWithoutRef<typeof Badge>,
10
+ 'variant' | 'color' | 'suffix' | 'children' | 'asChild' | 'popover'
11
+ > & {
12
+ children: ReactNode;
13
+ /** Popover content rendered when the chevron-down trigger is clicked — typically a value editor. */
14
+ popover: ReactNode;
15
+ onRemove: () => void;
16
+ removeAriaLabel: string;
17
+ };
18
+
19
+ /**
20
+ * Editable filter token (Query Builder variant) — `Badge` pill with a `chevron-down`
21
+ * popover trigger for editing values and an `×` dismiss button. The popover content
22
+ * is supplied by the caller via the `popover` slot (typically a `Combobox` or option list).
23
+ */
24
+ export const PageToolbarToken = ({
25
+ children,
26
+ popover,
27
+ onRemove,
28
+ removeAriaLabel,
29
+ ...props
30
+ }: PageToolbarTokenProps) => (
31
+ <Popover>
32
+ <Badge
33
+ {...props}
34
+ asChild
35
+ variant="pill"
36
+ color="blue-inverse"
37
+ suffix={
38
+ <Button
39
+ type="button"
40
+ variant="inline"
41
+ size="icon-2xs"
42
+ aria-label={removeAriaLabel}
43
+ onClick={onRemove}
44
+ >
45
+ <X className="tw-size-3" />
46
+ </Button>
47
+ }
48
+ >
49
+ <Popover.Trigger
50
+ type="button"
51
+ className="tailwindcss-preflight tw-inline-flex tw-cursor-pointer tw-items-center tw-gap-1 tw-bg-inherit tw-text-inherit"
52
+ >
53
+ {children}
54
+ <ChevronDown className="tw-size-3" />
55
+ </Popover.Trigger>
56
+ </Badge>
57
+ <Popover.Content variant="argo" align="start">
58
+ {popover}
59
+ </Popover.Content>
60
+ </Popover>
61
+ );
@@ -39,12 +39,12 @@ type Props = InputGroupProps &
39
39
  /**
40
40
  * Debounced search input with a leading magnifying-glass icon and optional clear button.
41
41
  *
42
- * **When to use:** the search field inside `PageLayout.Toolbar`, list / table search, combobox-less
42
+ * **When to use:** the search field inside `PageToolbar.Search`, list / table search, combobox-less
43
43
  * free-text filters. `onValueChange` is debounced so callers can wire it directly to fetches without
44
44
  * `useMemo` plumbing.
45
45
  * **When NOT to use:** for a plain string input use `TextInput`; for selecting from a known enumerated
46
46
  * list use `Combobox` (which has its own search built in); for free-form tag entry use `Tagger`.
47
- * **Composition:** typically rendered in `PageLayout.Toolbar` next to `MultiCombobox` filter pills. Use
47
+ * **Composition:** typically rendered in `PageToolbar.Search` next to `MultiCombobox` filter pills. Use
48
48
  * `allowClear` for the canonical x-to-reset affordance.
49
49
  *
50
50
  * @see RECIPES.md#filter-toolbar — for the search + filter chips toolbar pattern
@@ -23,6 +23,11 @@ export const AlertDescription = ({
23
23
  "group-has-[[data-slot=alert-title]]/alert:before:tw-content-['']",
24
24
 
25
25
  'tw-break-anywhere',
26
+
27
+ // Default size for nested Lucide icons; callers can override on the icon.
28
+ "[&_svg:not([class*='tw-size-'])]:tw-size-4",
29
+ '[&_svg]:tw-shrink-0',
30
+
26
31
  className,
27
32
  )}
28
33
  >
@@ -27,7 +27,7 @@ export const AlertIcon = ({ variant, icon, className, ...rest }: Props) => (
27
27
  'tw-justify-center',
28
28
  'tw-self-start',
29
29
  'tw-p-0.5',
30
- '[&_svg]:tw-size-4',
30
+ '[&_svg:not([class*="tw-size-"])]:tw-size-4',
31
31
  'group-data-[variant=info]/alert:tw-text-secondary-500',
32
32
  'group-data-[variant=warning]/alert:tw-text-warning-500',
33
33
  'group-data-[variant=error]/alert:tw-text-error-500',
@@ -1,8 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
- import { Plus, X } from '@keboola/design/icons';
4
-
5
- import { Icon } from '../Icon';
3
+ import { Check, Info, Plus, X } from '@keboola/design/icons';
6
4
 
7
5
  import { Badge } from './Badge';
8
6
 
@@ -118,7 +116,7 @@ export const BothAddons: Story = {
118
116
 
119
117
  export const IconAddon: Story = {
120
118
  args: {
121
- prefix: <Icon icon="circle-info" />,
122
- suffix: <Icon icon="check" />,
119
+ prefix: <Info />,
120
+ suffix: <Check />,
123
121
  },
124
122
  };
@@ -12,7 +12,7 @@ const addonVariants = cva(
12
12
  'tw-self-stretch',
13
13
  'tw-px-1',
14
14
  'has-[button]:tw-px-0',
15
- '[&_svg]:tw-size-3',
15
+ '[&_svg:not([class*="tw-size-"])]:tw-size-3',
16
16
  ],
17
17
  {
18
18
  defaultVariants: {
@@ -31,7 +31,7 @@ export const BadgeButton = ({
31
31
  // Default sizing for Lucide-icon children (which ship at 24×24); keeps
32
32
  // the visual baseline aligned with the previous FA-icon API and other
33
33
  // icon-wrapper components (ItemIcon, SidebarMenuIcon).
34
- '[&_svg]:tw-size-3',
34
+ '[&_svg:not([class*="tw-size-"])]:tw-size-3',
35
35
  className,
36
36
  )}
37
37
  >
@@ -1,7 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
+ import { ChevronDown, Circle } from '../../icons';
3
4
  import { DropdownMenu } from '../DropdownMenu';
4
- import { Icon } from '../Icon';
5
5
 
6
6
  import { Breadcrumb } from './Breadcrumb';
7
7
 
@@ -44,13 +44,13 @@ export const CustomSeparator: Story = {
44
44
  <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
45
45
  </Breadcrumb.Item>
46
46
  <Breadcrumb.Separator>
47
- <Icon icon="dot-circle" />
47
+ <Circle />
48
48
  </Breadcrumb.Separator>
49
49
  <Breadcrumb.Item>
50
50
  <Breadcrumb.Link href="/components">Components</Breadcrumb.Link>
51
51
  </Breadcrumb.Item>
52
52
  <Breadcrumb.Separator>
53
- <Icon icon="dot-circle" />
53
+ <Circle />
54
54
  </Breadcrumb.Separator>
55
55
  <Breadcrumb.Item>
56
56
  <Breadcrumb.Page>Breadcrumb</Breadcrumb.Page>
@@ -74,7 +74,7 @@ export const Dropdown: Story = {
74
74
  <Breadcrumb.Link asChild>
75
75
  <button>
76
76
  Components
77
- <Icon icon="chevron-down" />
77
+ <ChevronDown />
78
78
  </button>
79
79
  </Breadcrumb.Link>
80
80
  </DropdownMenu.Trigger>
@@ -36,7 +36,7 @@ export const BreadcrumbLink = ({
36
36
  'focus-visible:tw-rounded-sm',
37
37
 
38
38
  // Icon
39
- '[&_svg]:tw-size-2.5',
39
+ '[&_svg:not([class*="tw-size-"])]:tw-size-3.5',
40
40
 
41
41
  // transition
42
42
  'tw-transition-colors',
@@ -17,7 +17,7 @@ export const BreadcrumbSeparator = ({ children, className, ...props }: Props) =>
17
17
 
18
18
  'tw-aspect-square',
19
19
  'tw-size-[15px]',
20
- '[&_svg]:tw-size-2.5',
20
+ '[&_svg:not([class*="tw-size-"])]:tw-size-2.5',
21
21
  'tw-text-muted-foreground',
22
22
  className,
23
23
  )}
@@ -1,7 +1,7 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
 
4
- import { Icon } from '../Icon';
4
+ import { Copy } from '../../icons';
5
5
 
6
6
  import { ButtonInline } from './ButtonInline';
7
7
 
@@ -34,7 +34,7 @@ export const WithIcon: Story = {
34
34
  args: {
35
35
  children: (
36
36
  <>
37
- <Icon icon="copy" />
37
+ <Copy className="tw-size-4" />
38
38
  ButtonAsLink
39
39
  </>
40
40
  ),
@@ -47,7 +47,7 @@ export const WithIcon: Story = {
47
47
  export const CustomVariant: Story = {
48
48
  render: (args) => (
49
49
  <ButtonInline {...args} variant="custom" className="tw-text-error-500">
50
- <Icon icon="copy" className="tw-text-primary-500" />
50
+ <Copy className="tw-size-4 tw-text-primary-500" />
51
51
  Create Configuration
52
52
  </ButtonInline>
53
53
  ),
@@ -18,6 +18,10 @@ const buttonInlineVariants = cva(
18
18
  'disabled:tw-text-neutral-500',
19
19
  'disabled:tw-no-underline',
20
20
  '[&_svg]:disabled:tw-text-neutral-400',
21
+
22
+ // Default size for nested Lucide icons; callers can override on the icon.
23
+ "[&_svg:not([class*='tw-size-'])]:tw-size-4",
24
+ '[&_svg]:tw-shrink-0',
21
25
  ],
22
26
  {
23
27
  defaultVariants: {