@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.
- package/dist/icons.cjs +32 -0
- package/dist/icons.js +1 -1
- package/dist/index.cjs +507 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +495 -106
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.cjs +78 -42
- package/dist/markdown-editor.cjs.map +1 -1
- package/dist/markdown-editor.js +78 -42
- package/dist/markdown-editor.js.map +1 -1
- package/dist/mermaid.cjs +32 -9
- package/dist/mermaid.cjs.map +1 -1
- package/dist/mermaid.js +32 -9
- package/dist/mermaid.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +8 -6
- 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 +18 -3
- package/src/blocks/InputOtp/InputOtp.stories.tsx +214 -0
- package/src/blocks/InputOtp/InputOtp.test.tsx +162 -0
- package/src/blocks/InputOtp/InputOtp.tsx +62 -0
- package/src/blocks/InputOtp/InputOtpGroup.tsx +13 -0
- package/src/blocks/InputOtp/InputOtpSeparator.tsx +23 -0
- package/src/blocks/InputOtp/InputOtpSlot.styles.ts +52 -0
- package/src/blocks/InputOtp/InputOtpSlot.tsx +44 -0
- package/src/blocks/InputOtp/context.ts +23 -0
- package/src/blocks/InputOtp/index.ts +23 -0
- 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 +2 -2
- package/src/components/Alert/AlertDescription.tsx +5 -0
- package/src/components/Alert/AlertIcon.tsx +1 -1
- package/src/components/Badge/Badge.stories.tsx +3 -5
- package/src/components/Badge/BadgeAddon.tsx +1 -1
- package/src/components/Badge/BadgeButton.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +4 -4
- package/src/components/Breadcrumb/BreadcrumbLink.tsx +1 -1
- package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +1 -1
- package/src/components/Button/ButtonInline.stories.tsx +3 -3
- package/src/components/Button/ButtonInline.tsx +4 -0
- package/src/components/Button/styles.ts +33 -10
- package/src/components/Card/Card.stories.tsx +2 -2
- package/src/components/Combobox/ComboboxItemIndicator.tsx +1 -1
- package/src/components/Command/CommandItem/CommandItem.tsx +1 -1
- package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +4 -4
- package/src/components/DropdownMenu/ItemIcon.tsx +1 -1
- 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/Modal/ModalIcon.tsx +1 -1
- package/src/components/Navbar/Navbar.stories.tsx +4 -5
- package/src/components/Navbar/NavbarMenuItemAdornment.tsx +2 -2
- package/src/components/Pagination/Pagination.stories.tsx +0 -1
- package/src/components/Pagination/PaginationEllipsis.tsx +1 -1
- package/src/components/Popover/PopoverContent.tsx +4 -0
- package/src/components/Select/SelectContent.tsx +1 -1
- 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/Sidebar/SidebarGroup/SidebarGroupAction.tsx +1 -1
- package/src/components/Sidebar/SidebarMenu/SidebarMenuAction.tsx +1 -1
- package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +1 -1
- package/src/components/Table/Table.stories.tsx +16 -16
- package/src/components/Timeline/Timeline.stories.tsx +8 -8
- package/src/components/Timeline/TimelineConnector.tsx +5 -1
- package/src/components/Toast/ToastDismiss.tsx +1 -1
- package/src/components/Toast/ToastRoot.tsx +1 -1
- package/src/components/Toggle/Toggle.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 +8 -0
- package/src/index.ts +2 -0
- package/src/patterns/FilterToolbar.stories.tsx +248 -44
|
@@ -1,48 +1,232 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Badge,
|
|
6
|
+
Button,
|
|
7
|
+
InputGroup,
|
|
8
|
+
PageLayout,
|
|
9
|
+
PageToolbar,
|
|
10
|
+
Select,
|
|
11
|
+
Separator,
|
|
12
|
+
ToggleGroup,
|
|
13
|
+
Tooltip,
|
|
14
|
+
} from '@keboola/design';
|
|
15
|
+
import {
|
|
16
|
+
ChevronsUpDown,
|
|
17
|
+
Info,
|
|
18
|
+
LayoutGrid,
|
|
19
|
+
List,
|
|
20
|
+
ListFilter,
|
|
21
|
+
Plus,
|
|
22
|
+
RefreshCw,
|
|
23
|
+
Search,
|
|
24
|
+
X,
|
|
25
|
+
} from '@keboola/design/icons';
|
|
5
26
|
|
|
6
|
-
type Row = {
|
|
27
|
+
type Row = {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
type: 'table' | 'file';
|
|
31
|
+
status: 'scheduled' | 'failed' | 'succeeded';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type FilterKey = 'scheduled' | 'failed' | 'succeeded';
|
|
35
|
+
|
|
36
|
+
type ViewMode = 'list' | 'grid';
|
|
37
|
+
|
|
38
|
+
type SortKey = 'name-asc' | 'name-desc' | 'recent';
|
|
39
|
+
|
|
40
|
+
const SORT_LABEL: Record<SortKey, string> = {
|
|
41
|
+
'name-asc': 'Name A to Z',
|
|
42
|
+
'name-desc': 'Name Z to A',
|
|
43
|
+
recent: 'Recently updated',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const FILTER_LABEL: Record<FilterKey, string> = {
|
|
47
|
+
scheduled: 'Scheduled',
|
|
48
|
+
failed: 'Failed',
|
|
49
|
+
succeeded: 'Succeeded',
|
|
50
|
+
};
|
|
7
51
|
|
|
8
|
-
const
|
|
52
|
+
const ALL_FILTERS: FilterKey[] = ['scheduled', 'failed', 'succeeded'];
|
|
53
|
+
|
|
54
|
+
const SearchField = ({
|
|
55
|
+
value,
|
|
56
|
+
onValueChange,
|
|
57
|
+
}: {
|
|
58
|
+
value: string;
|
|
59
|
+
onValueChange: (next: string) => void;
|
|
60
|
+
}) => (
|
|
61
|
+
<InputGroup className="tw-w-[300px]">
|
|
62
|
+
<InputGroup.Addon align="inline-start">
|
|
63
|
+
<Search className="tw-size-4 tw-text-muted-foreground" />
|
|
64
|
+
</InputGroup.Addon>
|
|
65
|
+
<InputGroup.Input
|
|
66
|
+
placeholder="Search"
|
|
67
|
+
value={value}
|
|
68
|
+
onChange={(event) => onValueChange(event.target.value)}
|
|
69
|
+
/>
|
|
70
|
+
<InputGroup.Addon align="inline-end">
|
|
71
|
+
<Tooltip tooltip="Searches by name and description." triggerAsChild>
|
|
72
|
+
<InputGroup.Button tabIndex={0} aria-label="Search help">
|
|
73
|
+
<Info className="tw-size-4 tw-text-muted-foreground" />
|
|
74
|
+
</InputGroup.Button>
|
|
75
|
+
</Tooltip>
|
|
76
|
+
</InputGroup.Addon>
|
|
77
|
+
</InputGroup>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const ActiveFiltersRow = ({
|
|
81
|
+
active,
|
|
82
|
+
onRemove,
|
|
83
|
+
onAdd,
|
|
84
|
+
onClear,
|
|
85
|
+
}: {
|
|
86
|
+
active: FilterKey[];
|
|
87
|
+
onRemove: (key: FilterKey) => void;
|
|
88
|
+
onAdd: () => void;
|
|
89
|
+
onClear: () => void;
|
|
90
|
+
}) => (
|
|
91
|
+
<div className="tw-flex tw-w-full tw-items-center tw-gap-2 tw-rounded-xl tw-bg-muted tw-py-1 tw-pl-2 tw-pr-1">
|
|
92
|
+
<div className="tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-2">
|
|
93
|
+
{active.map((key) => (
|
|
94
|
+
<Badge
|
|
95
|
+
key={key}
|
|
96
|
+
variant="pill"
|
|
97
|
+
color="blue-inverse"
|
|
98
|
+
suffix={
|
|
99
|
+
<Button
|
|
100
|
+
variant="inline"
|
|
101
|
+
size="icon-2xs"
|
|
102
|
+
aria-label={`Remove ${FILTER_LABEL[key]} filter`}
|
|
103
|
+
onClick={() => onRemove(key)}
|
|
104
|
+
>
|
|
105
|
+
<X className="tw-size-3" />
|
|
106
|
+
</Button>
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
{FILTER_LABEL[key]}
|
|
110
|
+
</Badge>
|
|
111
|
+
))}
|
|
112
|
+
<Button argo variant="invisible" size="icon-sm" aria-label="Add filter" onClick={onAdd}>
|
|
113
|
+
<Plus className="tw-size-4" />
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
<Button argo variant="invisible" size="small" onClick={onClear}>
|
|
117
|
+
Clear
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const FilteredList = ({
|
|
123
|
+
rows,
|
|
124
|
+
showSort,
|
|
125
|
+
showViewToggle,
|
|
126
|
+
showRefresh,
|
|
127
|
+
showReorder,
|
|
128
|
+
}: {
|
|
129
|
+
rows: Row[];
|
|
130
|
+
showSort: boolean;
|
|
131
|
+
showViewToggle: boolean;
|
|
132
|
+
showRefresh: boolean;
|
|
133
|
+
showReorder: boolean;
|
|
134
|
+
}) => {
|
|
9
135
|
const [query, setQuery] = useState('');
|
|
10
|
-
const [
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
136
|
+
const [sort, setSort] = useState<SortKey>('name-desc');
|
|
137
|
+
const [view, setView] = useState<ViewMode>('list');
|
|
138
|
+
const [active, setActive] = useState<FilterKey[]>(['scheduled', 'failed']);
|
|
139
|
+
|
|
140
|
+
const filtered = useMemo(() => {
|
|
141
|
+
const byQuery = rows.filter((row) => row.name.toLowerCase().includes(query.toLowerCase()));
|
|
142
|
+
const byStatus = active.length ? byQuery.filter((row) => active.includes(row.status)) : byQuery;
|
|
143
|
+
return [...byStatus].sort((a, b) => {
|
|
144
|
+
if (sort === 'name-asc') return a.name.localeCompare(b.name);
|
|
145
|
+
if (sort === 'name-desc') return b.name.localeCompare(a.name);
|
|
146
|
+
return b.id.localeCompare(a.id);
|
|
147
|
+
});
|
|
148
|
+
}, [rows, query, active, sort]);
|
|
149
|
+
|
|
150
|
+
const addFilter = () => {
|
|
151
|
+
setActive((prev) => {
|
|
152
|
+
const next = ALL_FILTERS.find((key) => !prev.includes(key));
|
|
153
|
+
return next ? [...prev, next] : prev;
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const showRightCluster = showSort || showViewToggle || showRefresh || showReorder;
|
|
158
|
+
const showUtilities = showRefresh || showReorder;
|
|
159
|
+
const showDivider = showSort && (showUtilities || showViewToggle);
|
|
21
160
|
|
|
22
161
|
return (
|
|
23
162
|
<PageLayout>
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
163
|
+
<PageToolbar className="tw-flex-col tw-flex-nowrap tw-items-stretch tw-gap-2">
|
|
164
|
+
<div className="tw-flex tw-w-full tw-items-center tw-gap-6">
|
|
165
|
+
<div className="tw-flex tw-min-w-0 tw-flex-1">
|
|
166
|
+
<SearchField value={query} onValueChange={setQuery} />
|
|
167
|
+
</div>
|
|
168
|
+
{showRightCluster && (
|
|
169
|
+
<div className="tw-flex tw-items-center tw-gap-2">
|
|
170
|
+
<Button argo variant="outline" size="icon" aria-label="Open filters">
|
|
171
|
+
<ListFilter className="tw-size-4" />
|
|
172
|
+
</Button>
|
|
173
|
+
{showSort && (
|
|
174
|
+
<Select
|
|
175
|
+
variant="argo"
|
|
176
|
+
className="tw-w-[200px]"
|
|
177
|
+
value={sort}
|
|
178
|
+
onValueChange={(next: SortKey) => setSort(next)}
|
|
179
|
+
items={(Object.keys(SORT_LABEL) as SortKey[]).map((key) => ({
|
|
180
|
+
value: key,
|
|
181
|
+
label: SORT_LABEL[key],
|
|
182
|
+
}))}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
{showDivider && <Separator orientation="vertical" className="tw-h-9" />}
|
|
186
|
+
{showReorder && (
|
|
187
|
+
<Button argo variant="outline" size="icon" aria-label="Reorder">
|
|
188
|
+
<ChevronsUpDown className="tw-size-4" />
|
|
189
|
+
</Button>
|
|
190
|
+
)}
|
|
191
|
+
{showRefresh && (
|
|
192
|
+
<Button argo variant="outline" size="icon" aria-label="Refresh">
|
|
193
|
+
<RefreshCw className="tw-size-4" />
|
|
194
|
+
</Button>
|
|
195
|
+
)}
|
|
196
|
+
{showViewToggle && (
|
|
197
|
+
<ToggleGroup
|
|
198
|
+
type="single"
|
|
199
|
+
value={view}
|
|
200
|
+
onValueChange={(next: ViewMode | '') => next && setView(next)}
|
|
201
|
+
aria-label="View mode"
|
|
202
|
+
>
|
|
203
|
+
<ToggleGroup.Item value="list" aria-label="List view">
|
|
204
|
+
<List className="tw-size-4" />
|
|
205
|
+
</ToggleGroup.Item>
|
|
206
|
+
<ToggleGroup.Item value="grid" aria-label="Grid view">
|
|
207
|
+
<LayoutGrid className="tw-size-4" />
|
|
208
|
+
</ToggleGroup.Item>
|
|
209
|
+
</ToggleGroup>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
{active.length > 0 && (
|
|
215
|
+
<ActiveFiltersRow
|
|
216
|
+
active={active}
|
|
217
|
+
onRemove={(key) => setActive((prev) => prev.filter((k) => k !== key))}
|
|
218
|
+
onAdd={addFilter}
|
|
219
|
+
onClear={() => setActive([])}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
222
|
+
</PageToolbar>
|
|
42
223
|
<PageLayout.Content>
|
|
43
|
-
<ul className="tw-flex tw-flex-col tw-gap-1">
|
|
224
|
+
<ul className="tw-flex tw-flex-col tw-gap-1 tw-text-sm">
|
|
44
225
|
{filtered.map((row) => (
|
|
45
|
-
<li key={row.id}>
|
|
226
|
+
<li key={row.id} className="tw-flex tw-items-center tw-gap-2">
|
|
227
|
+
<span className="tw-text-muted-foreground">[{row.status}]</span>
|
|
228
|
+
{row.name}
|
|
229
|
+
</li>
|
|
46
230
|
))}
|
|
47
231
|
</ul>
|
|
48
232
|
</PageLayout.Content>
|
|
@@ -53,19 +237,39 @@ const FilteredList = ({ rows }: { rows: Row[] }) => {
|
|
|
53
237
|
const meta = {
|
|
54
238
|
title: 'Patterns/FilterToolbar',
|
|
55
239
|
component: FilteredList,
|
|
240
|
+
parameters: { layout: 'padded' },
|
|
241
|
+
args: {
|
|
242
|
+
showSort: true,
|
|
243
|
+
showViewToggle: true,
|
|
244
|
+
showRefresh: false,
|
|
245
|
+
showReorder: false,
|
|
246
|
+
rows: [
|
|
247
|
+
{ id: '1', name: 'marketing.users', type: 'table', status: 'succeeded' },
|
|
248
|
+
{ id: '2', name: 'sales.leads', type: 'table', status: 'scheduled' },
|
|
249
|
+
{ id: '3', name: 'export.csv', type: 'file', status: 'failed' },
|
|
250
|
+
{ id: '4', name: 'snapshot.json', type: 'file', status: 'scheduled' },
|
|
251
|
+
{ id: '5', name: 'events.parquet', type: 'file', status: 'failed' },
|
|
252
|
+
],
|
|
253
|
+
},
|
|
56
254
|
} satisfies Meta<typeof FilteredList>;
|
|
57
255
|
|
|
58
256
|
export default meta;
|
|
59
|
-
|
|
60
257
|
type Story = StoryObj<typeof meta>;
|
|
61
258
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
259
|
+
/** Full toolbar — search, filter, sort, view-mode toggle, and the active-filters row. */
|
|
260
|
+
export const Default: Story = {};
|
|
261
|
+
|
|
262
|
+
/** Minimal variant: only the search field and a filter button. */
|
|
263
|
+
export const SearchAndFilterOnly: Story = {
|
|
264
|
+
args: { showSort: false, showViewToggle: false },
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/** Adds the refresh utility next to the sort, separated by a vertical divider. */
|
|
268
|
+
export const WithRefresh: Story = {
|
|
269
|
+
args: { showRefresh: true },
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/** Reorder + refresh utilities. View toggle hidden, mirrors a sortable list page. */
|
|
273
|
+
export const WithReorderUtilities: Story = {
|
|
274
|
+
args: { showViewToggle: false, showRefresh: true, showReorder: true },
|
|
71
275
|
};
|