@keboola/design 1.3.0 → 2.0.0
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/dist/icons.cjs +24 -0
- package/dist/icons.js +1 -1
- package/dist/index.cjs +278 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +278 -78
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.cjs +39 -26
- package/dist/markdown-editor.cjs.map +1 -1
- package/dist/markdown-editor.js +39 -26
- package/dist/markdown-editor.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -3
- package/src/blocks/AIKit/PlanApproval.tsx +29 -12
- package/src/blocks/AIKit/PromptInput.test.tsx +53 -0
- package/src/blocks/AIKit/PromptInput.tsx +1 -1
- package/src/blocks/AIKit/Question.tsx +39 -11
- package/src/blocks/AIKit/SuggestedPrompt.stories.tsx +2 -8
- package/src/blocks/EmptyState/EmptyState.stories.tsx +5 -5
- package/src/blocks/EmptyState/EmptyState.tsx +2 -2
- package/src/blocks/ErrorState/ErrorState.stories.tsx +6 -6
- package/src/blocks/ErrorState/ErrorState.tsx +3 -3
- package/src/blocks/MultiActionButtonGroup/MultiActionButton.tsx +2 -1
- package/src/blocks/PageHeader/AGENTS.md +1 -1
- package/src/blocks/PageHeader/PageHeader.stories.tsx +6 -3
- package/src/blocks/PageHeader/PageHeaderIndex.tsx +2 -2
- package/src/blocks/PageLayout/AGENTS.md +3 -3
- package/src/blocks/PageLayout/PageLayout.stories.tsx +7 -4
- package/src/blocks/PageLayout/PageLayout.tsx +1 -18
- package/src/blocks/PageToolbar/PageToolbar.stories.tsx +79 -74
- package/src/blocks/PageToolbar/PageToolbar.tsx +45 -4
- package/src/blocks/PageToolbar/PageToolbarToken.tsx +61 -0
- package/src/blocks/SearchInput/SearchInput.tsx +3 -3
- package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.tsx +1 -1
- package/src/components/Alert/AlertDescription.tsx +5 -0
- package/src/components/Badge/Badge.stories.tsx +3 -5
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +4 -4
- package/src/components/Breadcrumb/BreadcrumbLink.tsx +1 -1
- package/src/components/Button/ButtonInline.stories.tsx +3 -3
- package/src/components/Button/ButtonInline.tsx +4 -0
- package/src/components/Card/Card.stories.tsx +2 -2
- package/src/components/FormGroup/FormGroup.stories.tsx +2 -2
- package/src/components/HoverCard/HoverCardContent.tsx +5 -0
- package/src/components/Input/Input.stories.tsx +2 -2
- package/src/components/InputGroup/InputGroup.stories.tsx +4 -5
- package/src/components/InputGroup/InputGroup.tsx +1 -1
- package/src/components/InputGroup/InputGroupAddon.tsx +35 -24
- package/src/components/Kbd/Kbd.stories.tsx +2 -2
- package/src/components/Label/Label.stories.tsx +2 -2
- package/src/components/Modal/ModalBody.tsx +5 -0
- package/src/components/Navbar/Navbar.stories.tsx +4 -5
- package/src/components/Pagination/Pagination.stories.tsx +0 -1
- package/src/components/Popover/PopoverContent.tsx +4 -0
- package/src/components/Search/Search.tsx +2 -2
- package/src/components/Select/SelectTrigger.tsx +1 -1
- package/src/components/Separator/Separator.stories.tsx +19 -0
- package/src/components/Separator/Separator.test.tsx +39 -0
- package/src/components/Separator/Separator.tsx +42 -11
- package/src/components/Sidebar/Sidebar.stories.tsx +6 -5
- package/src/components/Table/Table.stories.tsx +16 -16
- package/src/components/Timeline/Timeline.stories.tsx +8 -8
- package/src/components/Timeline/TimelineConnector.tsx +1 -1
- package/src/components/ToggleGroup/ToggleGroup.stories.tsx +60 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +99 -0
- package/src/components/ToggleGroup/index.ts +1 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +2 -2
- package/src/hooks/useAutosizeInput.stories.tsx +2 -2
- package/src/icons/index.ts +6 -0
- package/src/index.ts +1 -0
- 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 {
|
|
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.
|
|
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"
|
|
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
|
-
<
|
|
129
|
+
<PageToolbar.Chip
|
|
135
130
|
key={filter.id}
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
</
|
|
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.
|
|
167
|
-
//
|
|
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
|
-
{
|
|
173
|
-
|
|
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"
|
|
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
|
-
<
|
|
205
|
+
<PageToolbar.Token
|
|
196
206
|
key={token.id}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
</
|
|
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"
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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"
|
|
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
|
-
<
|
|
339
|
+
<PageToolbar.Chip onRemove={() => {}} removeAriaLabel="Remove Status filter">
|
|
335
340
|
Status: Failed
|
|
336
|
-
</
|
|
337
|
-
<
|
|
341
|
+
</PageToolbar.Chip>
|
|
342
|
+
<PageToolbar.Chip onRemove={() => {}} removeAriaLabel="Remove Owner filter">
|
|
338
343
|
Owner: John Doe
|
|
339
|
-
</
|
|
340
|
-
<
|
|
344
|
+
</PageToolbar.Chip>
|
|
345
|
+
<PageToolbar.Chip onRemove={() => {}} removeAriaLabel="Remove Created by filter">
|
|
341
346
|
Created by: Marketing team
|
|
342
|
-
</
|
|
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 {
|
|
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
|
-
<
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|
|
@@ -143,7 +143,7 @@ export const SearchInput = ({
|
|
|
143
143
|
className={className}
|
|
144
144
|
>
|
|
145
145
|
<InputGroup.Addon align="inline-start">
|
|
146
|
-
<Search className="tw-size-4 tw-text-muted-foreground" />
|
|
146
|
+
<Search className="tw-size-4 tw-shrink-0 tw-text-muted-foreground" />
|
|
147
147
|
</InputGroup.Addon>
|
|
148
148
|
|
|
149
149
|
<InputGroup.Input
|
|
@@ -52,7 +52,7 @@ export const SearchInputWithNavigation = ({
|
|
|
52
52
|
placeholder={placeholder}
|
|
53
53
|
onValueChange={onValueChange}
|
|
54
54
|
onClear={onClear}
|
|
55
|
-
prefix={<Search className="tw-size-4 tw-text-neutral-400" />}
|
|
55
|
+
prefix={<Search className="tw-size-4 tw-shrink-0 tw-text-neutral-400" />}
|
|
56
56
|
focusVariant={focusVariant}
|
|
57
57
|
onKeyDown={handleKeyDown}
|
|
58
58
|
variant={variant}
|
|
@@ -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
|
>
|
|
@@ -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: <
|
|
122
|
-
suffix: <
|
|
119
|
+
prefix: <Info />,
|
|
120
|
+
suffix: <Check />,
|
|
123
121
|
},
|
|
124
122
|
};
|
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
77
|
+
<ChevronDown />
|
|
78
78
|
</button>
|
|
79
79
|
</Breadcrumb.Link>
|
|
80
80
|
</DropdownMenu.Trigger>
|
|
@@ -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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
|
|
3
|
+
import { Plus } from '../../icons';
|
|
3
4
|
import { ButtonInline } from '../Button';
|
|
4
|
-
import { Icon } from '../Icon';
|
|
5
5
|
|
|
6
6
|
import { Card } from './Card';
|
|
7
7
|
|
|
@@ -44,7 +44,7 @@ export const WithActions: Story = {
|
|
|
44
44
|
<Card.Header className="tw-flex tw-justify-between">
|
|
45
45
|
<Card.Title>Secrets</Card.Title>
|
|
46
46
|
<ButtonInline color="dark">
|
|
47
|
-
<
|
|
47
|
+
<Plus className="tw-size-4" />
|
|
48
48
|
New Secret
|
|
49
49
|
</ButtonInline>
|
|
50
50
|
</Card.Header>
|
|
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
import type { ComponentProps } from 'react';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Info } from '../../icons';
|
|
6
6
|
import { Label } from '../Label';
|
|
7
7
|
|
|
8
8
|
import { FormGroup } from './FormGroup';
|
|
@@ -39,7 +39,7 @@ const FormGroupDemo = ({ variant = 'primary', ...formGroupProps }: Props) => {
|
|
|
39
39
|
<form className="tw-flex tw-flex-col tw-gap-4">
|
|
40
40
|
<FormGroup {...formGroupProps}>
|
|
41
41
|
<Label>
|
|
42
|
-
Text Input <
|
|
42
|
+
Text Input <Info className="tw-size-4 tw-text-neutral-400" />
|
|
43
43
|
</Label>
|
|
44
44
|
<FormGroup.TextInput
|
|
45
45
|
placeholder="e.g John"
|
|
@@ -53,6 +53,11 @@ export const HoverCardContent = ({
|
|
|
53
53
|
'data-[state=closed]:tw-animate-out',
|
|
54
54
|
'data-[state=closed]:tw-fade-out-0',
|
|
55
55
|
'data-[state=closed]:tw-zoom-out-95',
|
|
56
|
+
|
|
57
|
+
// Default size for nested Lucide icons; callers can override on the icon.
|
|
58
|
+
"[&_svg:not([class*='tw-size-'])]:tw-size-4",
|
|
59
|
+
'[&_svg]:tw-shrink-0',
|
|
60
|
+
|
|
56
61
|
className,
|
|
57
62
|
)}
|
|
58
63
|
{...props}
|
|
@@ -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 {
|
|
4
|
+
import { Search } from '../../icons';
|
|
5
5
|
|
|
6
6
|
import { Input } from './Input';
|
|
7
7
|
|
|
@@ -68,7 +68,7 @@ export const Basic: Story = {
|
|
|
68
68
|
|
|
69
69
|
export const WithPrefix: Story = {
|
|
70
70
|
args: {
|
|
71
|
-
prefix: <
|
|
71
|
+
prefix: <Search className="tw-size-4 tw-text-neutral-400" />,
|
|
72
72
|
},
|
|
73
73
|
};
|
|
74
74
|
|