@keboola/design 1.2.1 → 1.3.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/README.md +25 -0
- package/dist/icon-names.cjs +18 -0
- package/dist/icon-names.cjs.map +1 -0
- package/dist/icon-names.js +16 -0
- package/dist/icon-names.js.map +1 -0
- package/dist/icons.cjs +731 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.js +29 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.cjs +14728 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +14553 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown-editor.cjs +4440 -0
- package/dist/markdown-editor.cjs.map +1 -0
- package/dist/markdown-editor.js +4413 -0
- package/dist/markdown-editor.js.map +1 -0
- package/dist/mermaid.cjs +1011 -0
- package/dist/mermaid.cjs.map +1 -0
- package/dist/mermaid.js +1005 -0
- package/dist/mermaid.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +10 -3
- package/src/blocks/AIKit/CollapsibleStepHeader.tsx +7 -8
- package/src/blocks/AIKit/InlineStepLabel.tsx +7 -7
- package/src/blocks/AIKit/MessageBubble.stories.tsx +11 -3
- package/src/blocks/AIKit/MessageBubble.tsx +1 -1
- package/src/blocks/AIKit/PlanApproval.tsx +9 -8
- package/src/blocks/AIKit/PlanRewritingIndicator.tsx +2 -2
- package/src/blocks/AIKit/PromptInput.stories.tsx +10 -2
- package/src/blocks/AIKit/PromptInputSubmit.tsx +12 -8
- package/src/blocks/AIKit/Question.tsx +8 -9
- package/src/blocks/AIKit/SuggestedPrompt.tsx +12 -5
- package/src/blocks/AIKit/ThinkingIndicator.tsx +2 -2
- package/src/blocks/AutoSaveTextarea/AutoSaveTextarea.tsx +4 -4
- package/src/blocks/ClipboardButton/ClipboardButton.tsx +7 -5
- package/src/blocks/ClipboardButton/ClipboardInline.tsx +2 -3
- package/src/blocks/ColorInput/ColorInput.tsx +4 -2
- package/src/blocks/ColorInput/ColorPicker.tsx +5 -2
- package/src/blocks/EmptyState/EmptyState.tsx +16 -0
- package/src/blocks/ErrorState/ErrorState.tsx +15 -0
- package/src/blocks/Form/formHook.ts +15 -0
- package/src/blocks/MultiActionButtonGroup/MultiActionButton.stories.tsx +12 -11
- package/src/blocks/MultiActionButtonGroup/MultiActionButton.tsx +4 -4
- package/src/blocks/MultiCombobox/MultiCombobox.tsx +28 -12
- package/src/blocks/PageHeader/AGENTS.md +63 -3
- package/src/blocks/PageHeader/PageHeader.stories.tsx +124 -28
- package/src/blocks/PageHeader/PageHeader.tsx +15 -0
- package/src/blocks/PageHeader/PageHeaderDetail.tsx +23 -1
- package/src/blocks/PageHeader/PageHeaderIndex.tsx +20 -1
- package/src/blocks/PageLayout/PageLayout.tsx +26 -0
- package/src/blocks/PageToolbar/PageToolbar.stories.tsx +348 -0
- package/src/blocks/PageToolbar/PageToolbar.tsx +119 -0
- package/src/blocks/PageToolbar/index.ts +1 -0
- package/src/blocks/PasswordInput/PasswordInput.tsx +10 -10
- package/src/blocks/SearchInput/SearchInput.tsx +30 -4
- package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.tsx +9 -6
- package/src/blocks/SplitButton/SplitButton.stories.tsx +7 -4
- package/src/blocks/SplitButton/SplitButton.tsx +17 -4
- package/src/blocks/Tagger/TagItem.tsx +4 -1
- package/src/blocks/Tagger/TagSelectPopover/TagSelectCreateItem.tsx +3 -1
- package/src/blocks/Tagger/TagSelectPopover/TagSelectList.tsx +2 -2
- package/src/blocks/Tagger/Tagger.tsx +2 -2
- package/src/components/Alert/Alert.stories.tsx +11 -3
- package/src/components/Alert/Alert.tsx +19 -1
- package/src/components/Alert/AlertCollapsibleIcon.tsx +3 -4
- package/src/components/Alert/AlertIcon.tsx +18 -21
- package/src/components/Badge/Badge.stories.tsx +22 -4
- package/src/components/Badge/Badge.tsx +16 -0
- package/src/components/Badge/BadgeButton.tsx +7 -6
- package/src/components/Breadcrumb/BreadcrumbEllipsis.tsx +2 -2
- package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +2 -2
- package/src/components/Button/Button.stories.tsx +14 -2
- package/src/components/Button/Button.tsx +42 -5
- package/src/components/Button/styles.ts +4 -6
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +32 -10
- package/src/components/Card/Card.tsx +17 -0
- package/src/components/Checkbox/Checkbox.tsx +3 -6
- package/src/components/CodeEditor/highlighting.ts +1 -1
- package/src/components/CollapsiblePanel.tsx +2 -5
- package/src/components/Combobox/Combobox.tsx +12 -14
- package/src/components/Combobox/ComboboxChips.tsx +6 -1
- package/src/components/Combobox/ComboboxItem.tsx +3 -4
- package/src/components/Combobox/ComboboxItemIndicator.tsx +6 -4
- package/src/components/Combobox/types.ts +2 -6
- package/src/components/Command/Command.stories.tsx +47 -56
- package/src/components/Command/CommandItem/CheckIndicator.tsx +26 -14
- package/src/components/Command/CommandItem/CommandItem.tsx +6 -3
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +8 -4
- package/src/components/DropdownMenu/DropdownMenuCheckboxItem.tsx +4 -1
- package/src/components/DropdownMenu/DropdownMenuItem.tsx +12 -6
- package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +12 -6
- package/src/components/DropdownMenu/DropdownMenuRadioGroup/DropdownMenuRadioItem.tsx +8 -5
- package/src/components/DropdownMenu/DropdownMenuSub/DropdownMenuSubTrigger.tsx +2 -2
- package/src/components/DropdownMenu/ItemIcon.tsx +16 -8
- package/src/components/FormGroup/FormGroup.tsx +15 -0
- package/src/components/FullScreenModal/FullScreenModalHeader.tsx +4 -1
- package/src/components/Input/DateInput/DateInput.tsx +4 -1
- package/src/components/Input/DateRangeInput/DateRangeInput.tsx +4 -6
- package/src/components/Input/Input.tsx +18 -2
- package/src/components/Input/TextInput/TextInput.tsx +16 -0
- package/src/components/Input/TimeInput/TimeInput.tsx +4 -1
- package/src/components/InputGroup/InputGroup.stories.tsx +11 -3
- package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +63 -1
- package/src/components/MarkdownEditor/MarkdownEditor.tsx +105 -15
- package/src/components/MarkdownEditor/nodes/ImageNode.tsx +10 -10
- package/src/components/MarkdownEditor/nodes/MermaidNode.tsx +8 -8
- package/src/components/MarkdownEditor/plugins/KeyboardPlugin.tsx +47 -3
- package/src/components/MarkdownEditor/plugins/MarkdownSyncPlugin.tsx +12 -3
- package/src/components/MarkdownEditor/plugins/ToolbarPlugin.tsx +20 -10
- package/src/components/MarkdownEditor/plugins/toolbar/AlignmentDropdown.tsx +20 -14
- package/src/components/MarkdownEditor/plugins/toolbar/BlockTypeDropdown.tsx +25 -16
- package/src/components/MarkdownEditor/plugins/toolbar/LinkPanel.tsx +3 -3
- package/src/components/MarkdownEditor/transformers.ts +10 -0
- package/src/components/MergeEditor/AcceptControls/AcceptButton.tsx +9 -4
- package/src/components/MergeEditor/AcceptControls/AcceptControls.tsx +25 -14
- package/src/components/MergeEditor/ConflictStatus/ConflictStatusIndicator.tsx +2 -2
- package/src/components/MergeEditor/MergeEditor.stories.tsx +15 -0
- package/src/components/MergeEditor/MergeEditor.tsx +9 -0
- package/src/components/MergeEditor/MergePane.tsx +22 -4
- package/src/components/MergeEditor/decorations.ts +2 -2
- package/src/components/Modal/Modal.stories.tsx +6 -4
- package/src/components/Modal/Modal.tsx +26 -0
- package/src/components/Modal/ModalBody.tsx +5 -0
- package/src/components/Modal/ModalCloseIcon.tsx +2 -3
- package/src/components/Modal/ModalContent.tsx +8 -0
- package/src/components/Modal/ModalFooter.tsx +5 -0
- package/src/components/Modal/ModalHeader.tsx +11 -0
- package/src/components/Modal/ModalIcon.tsx +45 -19
- package/src/components/Navbar/Navbar.stories.tsx +26 -13
- package/src/components/Navbar/NavbarMenuButton.tsx +11 -10
- package/src/components/Pagination/PaginationEllipsis.tsx +2 -2
- package/src/components/Pagination/PaginationNext.tsx +2 -1
- package/src/components/Pagination/PaginationPrevious.tsx +4 -1
- package/src/components/PanelWithDetails.tsx +3 -4
- package/src/components/Popconfirm/Popconfirm.stories.tsx +13 -5
- package/src/components/RadioGroup/Radio.tsx +6 -12
- package/src/components/Search/ClearButton.tsx +2 -2
- package/src/components/Search/Search.tsx +3 -3
- package/src/components/Select/Select.tsx +16 -5
- package/src/components/Select/SelectContent.tsx +13 -4
- package/src/components/Select/SelectItem.tsx +6 -5
- package/src/components/Select/SelectTrigger.tsx +2 -2
- package/src/components/Sidebar/Sidebar.stories.tsx +55 -19
- package/src/components/Sidebar/SidebarGroup/SidebarGroupAction.tsx +8 -4
- package/src/components/Sidebar/SidebarMenu/SidebarMenuButton.tsx +4 -2
- package/src/components/Sidebar/SidebarMenu/SidebarMenuCard.tsx +3 -2
- package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +12 -12
- package/src/components/Sidebar/SidebarMenu/SidebarMenuSub.tsx +3 -7
- package/src/components/Sidebar/SidebarMenu/SidebarSearchInput.tsx +3 -5
- package/src/components/Skeleton/Skeleton.stories.tsx +52 -0
- package/src/components/Skeleton/Skeleton.tsx +24 -1
- package/src/components/Table/Table.stories.tsx +223 -1
- package/src/components/Table/Table.tsx +17 -0
- package/src/components/Tabs.tsx +25 -2
- package/src/components/Toast/ToastDismiss.tsx +3 -3
- package/src/components/Toast/ToastRoot.tsx +24 -19
- package/src/components/Toggle/Toggle.tsx +11 -6
- package/src/components/Tooltip/Tooltip.tsx +23 -0
- package/src/components/ZoomableSvg/ZoomableSvg.tsx +8 -6
- package/src/icons/Keboola.tsx +28 -0
- package/src/icons/icons.stories.tsx +174 -0
- package/src/icons/index.ts +180 -0
- package/src/index.ts +1 -0
- package/src/patterns/AlertWithAction.stories.tsx +30 -0
- package/src/patterns/CardWithEmptyState.stories.tsx +66 -0
- package/src/patterns/ConfirmDialog.stories.tsx +72 -0
- package/src/patterns/CreatableMultiCombobox.stories.tsx +33 -0
- package/src/patterns/DataCardWithSkeleton.stories.tsx +48 -0
- package/src/patterns/DisabledButtonTooltip.stories.tsx +30 -0
- package/src/patterns/FilterToolbar.stories.tsx +71 -0
- package/src/patterns/InfoTooltip.stories.tsx +56 -0
- package/src/patterns/ListDetail.stories.tsx +65 -0
- package/src/patterns/ModalForm.stories.tsx +59 -0
- package/src/patterns/PageHeaderTabs.stories.tsx +48 -0
- package/src/styles.css +16 -8
- package/src/utils/legacyIcon.tsx +28 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentProps, type ReactNode, useMemo, useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { action } from 'storybook/actions';
|
|
4
|
+
|
|
5
|
+
import { EmptyState } from '../../blocks/EmptyState';
|
|
6
|
+
import { ErrorState } from '../../blocks/ErrorState';
|
|
7
|
+
import { SearchInput } from '../../blocks/SearchInput';
|
|
8
|
+
import { Button } from '../../components/Button';
|
|
9
|
+
import { Icon } from '../../components/Icon';
|
|
10
|
+
import { Pagination } from '../../components/Pagination';
|
|
3
11
|
|
|
4
12
|
import { Table } from './Table';
|
|
5
13
|
|
|
@@ -104,3 +112,217 @@ export const Overflow: Story = {
|
|
|
104
112
|
export const Bordered: Story = {
|
|
105
113
|
render: (props) => <Component {...props} bordered />,
|
|
106
114
|
};
|
|
115
|
+
|
|
116
|
+
type Token = { id: string; name: string; createdAt: string; lastUsed: string };
|
|
117
|
+
|
|
118
|
+
const pad2 = (n: number) => String(n).padStart(2, '0');
|
|
119
|
+
|
|
120
|
+
const tokens: Token[] = Array.from({ length: 23 }, (_, i) => ({
|
|
121
|
+
id: `tok_${String(i + 1).padStart(3, '0')}`,
|
|
122
|
+
name: `Token ${i + 1}${i % 3 === 0 ? ' (production)' : i % 3 === 1 ? ' (staging)' : ''}`,
|
|
123
|
+
createdAt: `2026-01-${pad2((i % 28) + 1)}`,
|
|
124
|
+
lastUsed: `2026-05-${pad2((i % 28) + 1)}`,
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
type SortKey = keyof Token;
|
|
128
|
+
type SortDir = 'asc' | 'desc';
|
|
129
|
+
const PAGE_SIZE = 5;
|
|
130
|
+
|
|
131
|
+
const sortAttr = (active: boolean, direction: SortDir) =>
|
|
132
|
+
!active ? 'none' : direction === 'asc' ? 'ascending' : 'descending';
|
|
133
|
+
|
|
134
|
+
const SortHeader = ({
|
|
135
|
+
active,
|
|
136
|
+
direction,
|
|
137
|
+
onClick,
|
|
138
|
+
children,
|
|
139
|
+
}: {
|
|
140
|
+
active: boolean;
|
|
141
|
+
direction: SortDir;
|
|
142
|
+
onClick: () => void;
|
|
143
|
+
children: ReactNode;
|
|
144
|
+
}) => (
|
|
145
|
+
<button
|
|
146
|
+
type="button"
|
|
147
|
+
onClick={onClick}
|
|
148
|
+
className="tw-group tw-flex tw-cursor-pointer tw-items-center tw-gap-1 tw-border-0 tw-bg-transparent tw-p-0 tw-font-medium tw-text-inherit"
|
|
149
|
+
>
|
|
150
|
+
{children}
|
|
151
|
+
<Icon
|
|
152
|
+
icon={!active ? 'sort' : direction === 'asc' ? 'angle-up' : 'angle-down'}
|
|
153
|
+
className="tw-size-3 tw-text-muted-foreground"
|
|
154
|
+
/>
|
|
155
|
+
</button>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Canonical entity-list pattern: `Table` (presentational) composed with `SearchInput`, `Pagination`,
|
|
160
|
+
* and `EmptyState` / `ErrorState`. State (sort / name search / page) is plain React `useState` —
|
|
161
|
+
* scales up to TanStack Table's headless model (`@tanstack/react-table`,
|
|
162
|
+
* https://tanstack.com/table/latest) when the consumer needs server-side pagination, multi-column
|
|
163
|
+
* sort, row selection, or a true global filter that scans every column.
|
|
164
|
+
*
|
|
165
|
+
* Pattern reference: TanStack "headless UI" — the state library is separable from the presentational
|
|
166
|
+
* layer (https://tanstack.com/table/latest/docs/introduction#what-is-headless-ui).
|
|
167
|
+
*/
|
|
168
|
+
export const AsEntityList: Story = {
|
|
169
|
+
name: 'As entity list',
|
|
170
|
+
parameters: { layout: 'padded' },
|
|
171
|
+
render: () => {
|
|
172
|
+
const [query, setQuery] = useState('');
|
|
173
|
+
const [sortKey, setSortKey] = useState<SortKey>('name');
|
|
174
|
+
const [sortDir, setSortDir] = useState<SortDir>('asc');
|
|
175
|
+
const [page, setPage] = useState(0);
|
|
176
|
+
|
|
177
|
+
const filtered = useMemo(() => {
|
|
178
|
+
const q = query.trim().toLowerCase();
|
|
179
|
+
const matched = q ? tokens.filter((t) => t.name.toLowerCase().includes(q)) : tokens;
|
|
180
|
+
return [...matched].sort((a, b) => {
|
|
181
|
+
const av = a[sortKey];
|
|
182
|
+
const bv = b[sortKey];
|
|
183
|
+
return sortDir === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av);
|
|
184
|
+
});
|
|
185
|
+
}, [query, sortKey, sortDir]);
|
|
186
|
+
|
|
187
|
+
const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
|
188
|
+
const safePage = Math.min(page, pageCount - 1);
|
|
189
|
+
const pageRows = filtered.slice(safePage * PAGE_SIZE, safePage * PAGE_SIZE + PAGE_SIZE);
|
|
190
|
+
|
|
191
|
+
const toggleSort = (key: SortKey) => {
|
|
192
|
+
if (key === sortKey) {
|
|
193
|
+
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
|
|
194
|
+
} else {
|
|
195
|
+
setSortKey(key);
|
|
196
|
+
setSortDir('asc');
|
|
197
|
+
}
|
|
198
|
+
setPage(0);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div className="tw-flex tw-w-[720px] tw-flex-col tw-gap-4">
|
|
203
|
+
<SearchInput
|
|
204
|
+
allowClear
|
|
205
|
+
placeholder="Search tokens…"
|
|
206
|
+
value={query}
|
|
207
|
+
onValueChange={(v) => {
|
|
208
|
+
setQuery(v);
|
|
209
|
+
setPage(0);
|
|
210
|
+
}}
|
|
211
|
+
className="tw-w-72"
|
|
212
|
+
/>
|
|
213
|
+
{filtered.length === 0 ? (
|
|
214
|
+
<EmptyState>
|
|
215
|
+
<EmptyState.Icon>
|
|
216
|
+
<Icon icon="search" />
|
|
217
|
+
</EmptyState.Icon>
|
|
218
|
+
<EmptyState.Title>No tokens match your search</EmptyState.Title>
|
|
219
|
+
<EmptyState.Description>
|
|
220
|
+
Try a different query or clear the search to see all tokens.
|
|
221
|
+
</EmptyState.Description>
|
|
222
|
+
<EmptyState.Actions>
|
|
223
|
+
<Button variant="outline" onClick={() => setQuery('')}>
|
|
224
|
+
Clear search
|
|
225
|
+
</Button>
|
|
226
|
+
</EmptyState.Actions>
|
|
227
|
+
</EmptyState>
|
|
228
|
+
) : (
|
|
229
|
+
<>
|
|
230
|
+
<Table bordered>
|
|
231
|
+
<Table.Header>
|
|
232
|
+
<Table.Row>
|
|
233
|
+
<Table.Head aria-sort={sortAttr(sortKey === 'name', sortDir)}>
|
|
234
|
+
<SortHeader
|
|
235
|
+
active={sortKey === 'name'}
|
|
236
|
+
direction={sortDir}
|
|
237
|
+
onClick={() => toggleSort('name')}
|
|
238
|
+
>
|
|
239
|
+
Name
|
|
240
|
+
</SortHeader>
|
|
241
|
+
</Table.Head>
|
|
242
|
+
<Table.Head aria-sort={sortAttr(sortKey === 'createdAt', sortDir)}>
|
|
243
|
+
<SortHeader
|
|
244
|
+
active={sortKey === 'createdAt'}
|
|
245
|
+
direction={sortDir}
|
|
246
|
+
onClick={() => toggleSort('createdAt')}
|
|
247
|
+
>
|
|
248
|
+
Created
|
|
249
|
+
</SortHeader>
|
|
250
|
+
</Table.Head>
|
|
251
|
+
<Table.Head aria-sort={sortAttr(sortKey === 'lastUsed', sortDir)}>
|
|
252
|
+
<SortHeader
|
|
253
|
+
active={sortKey === 'lastUsed'}
|
|
254
|
+
direction={sortDir}
|
|
255
|
+
onClick={() => toggleSort('lastUsed')}
|
|
256
|
+
>
|
|
257
|
+
Last used
|
|
258
|
+
</SortHeader>
|
|
259
|
+
</Table.Head>
|
|
260
|
+
</Table.Row>
|
|
261
|
+
</Table.Header>
|
|
262
|
+
<Table.Body>
|
|
263
|
+
{pageRows.map((token) => (
|
|
264
|
+
<Table.Row key={token.id}>
|
|
265
|
+
<Table.Cell>{token.name}</Table.Cell>
|
|
266
|
+
<Table.Cell>{token.createdAt}</Table.Cell>
|
|
267
|
+
<Table.Cell>{token.lastUsed}</Table.Cell>
|
|
268
|
+
</Table.Row>
|
|
269
|
+
))}
|
|
270
|
+
</Table.Body>
|
|
271
|
+
</Table>
|
|
272
|
+
<Pagination>
|
|
273
|
+
<Pagination.Content>
|
|
274
|
+
<Pagination.Item>
|
|
275
|
+
<Pagination.Previous
|
|
276
|
+
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
|
277
|
+
disabled={safePage === 0}
|
|
278
|
+
aria-disabled={safePage === 0}
|
|
279
|
+
/>
|
|
280
|
+
</Pagination.Item>
|
|
281
|
+
{Array.from({ length: pageCount }, (_, i) => (
|
|
282
|
+
<Pagination.Item key={i}>
|
|
283
|
+
<Pagination.Button active={i === safePage} onClick={() => setPage(i)}>
|
|
284
|
+
{i + 1}
|
|
285
|
+
</Pagination.Button>
|
|
286
|
+
</Pagination.Item>
|
|
287
|
+
))}
|
|
288
|
+
<Pagination.Item>
|
|
289
|
+
<Pagination.Next
|
|
290
|
+
onClick={() => setPage((p) => Math.min(pageCount - 1, p + 1))}
|
|
291
|
+
disabled={safePage === pageCount - 1}
|
|
292
|
+
aria-disabled={safePage === pageCount - 1}
|
|
293
|
+
/>
|
|
294
|
+
</Pagination.Item>
|
|
295
|
+
</Pagination.Content>
|
|
296
|
+
</Pagination>
|
|
297
|
+
</>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Failure surface for the entity-list pattern. Swap the table body for `ErrorState` when the
|
|
306
|
+
* underlying fetch errors. Mirrors the `AsEntityList` story shape so consumers can copy/paste either.
|
|
307
|
+
*/
|
|
308
|
+
export const AsEntityListError: Story = {
|
|
309
|
+
name: 'As entity list — error',
|
|
310
|
+
parameters: { layout: 'padded' },
|
|
311
|
+
render: () => (
|
|
312
|
+
<div className="tw-flex tw-w-[720px] tw-flex-col tw-gap-4">
|
|
313
|
+
<SearchInput allowClear placeholder="Search tokens…" disabled className="tw-w-72" />
|
|
314
|
+
<ErrorState>
|
|
315
|
+
<ErrorState.Icon>
|
|
316
|
+
<Icon icon="circle-exclamation" />
|
|
317
|
+
</ErrorState.Icon>
|
|
318
|
+
<ErrorState.Title>Couldn't load tokens</ErrorState.Title>
|
|
319
|
+
<ErrorState.Description>
|
|
320
|
+
The request to load tokens failed. Check your connection and try again.
|
|
321
|
+
</ErrorState.Description>
|
|
322
|
+
<ErrorState.Actions>
|
|
323
|
+
<Button onClick={action('retry')}>Retry</Button>
|
|
324
|
+
</ErrorState.Actions>
|
|
325
|
+
</ErrorState>
|
|
326
|
+
</div>
|
|
327
|
+
),
|
|
328
|
+
};
|
|
@@ -9,12 +9,29 @@ import { TableHead } from './TableHead';
|
|
|
9
9
|
import { TableHeader } from './TableHeader';
|
|
10
10
|
import { TableRow } from './TableRow';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Props for `Table`. `borderCollapsed` toggles standard collapsed borders vs separate row spacing;
|
|
14
|
+
* `bordered` wraps the table in a rounded bordered container with overflow scrolling.
|
|
15
|
+
*/
|
|
12
16
|
type Props = {
|
|
13
17
|
borderCollapsed?: boolean;
|
|
14
18
|
bordered?: boolean;
|
|
15
19
|
containerClassName?: string;
|
|
16
20
|
} & ComponentProps<'table'>;
|
|
17
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Tabular data surface with header / body / row / cell compound parts.
|
|
24
|
+
*
|
|
25
|
+
* **When to use:** tabular data with named columns — entity lists with shared schema (jobs, buckets,
|
|
26
|
+
* tables), sortable rows, row-level actions. Compose `Table.Header` + `Table.Body` + `Table.Row` +
|
|
27
|
+
* `Table.Head` / `Table.Cell`.
|
|
28
|
+
* **When NOT to use:** for heterogeneous lists where each row has its own shape use a `Card` list;
|
|
29
|
+
* for tree / hierarchy layouts use a custom recursive list with `Table` per level.
|
|
30
|
+
* **Composition:** wrap in `PageLayout.Content`. For sortable / filterable behaviour bring your own
|
|
31
|
+
* state library (TanStack Table is a common pairing); this component is presentational.
|
|
32
|
+
*
|
|
33
|
+
* @see RECIPES.md#list-detail — for a list-table + row-click-to-detail pattern
|
|
34
|
+
*/
|
|
18
35
|
export const Table = ({
|
|
19
36
|
borderCollapsed = true,
|
|
20
37
|
bordered = false,
|
package/src/components/Tabs.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import * as RadixTabs from '@radix-ui/react-tabs';
|
|
3
3
|
|
|
4
|
+
import { X } from '../icons';
|
|
4
5
|
import { cn } from '../utils';
|
|
5
6
|
|
|
6
7
|
import { Button } from './Button';
|
|
@@ -18,12 +19,29 @@ const TRIGGER_COMMON = {
|
|
|
18
19
|
'hover:enabled:tw-text-secondary-500 hover:enabled:tw-border-secondary-500 hover:enabled:tw-cursor-pointer',
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Tab trigger descriptor. `value` keys the tab; `title` is the visible label; `onClose` makes the tab
|
|
24
|
+
* closable (renders an x button on hover). Generic over `T` so consumers can use string-literal unions.
|
|
25
|
+
*/
|
|
21
26
|
type Trigger<T extends string> = {
|
|
22
27
|
value: T;
|
|
23
28
|
title: ReactNode;
|
|
29
|
+
/** When set, renders an inline close button on the trigger. Called with the tab's `value`. */
|
|
24
30
|
onClose?: (value: T) => void;
|
|
25
31
|
} & Omit<RadixTabs.TabsTriggerProps, 'title'>;
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Tabbed navigation with a single content area. Built on Radix Tabs.
|
|
35
|
+
*
|
|
36
|
+
* **When to use:** switching between sibling views of the same context (overview / runs / settings on a
|
|
37
|
+
* detail page; sections inside a modal body). The triggers row is horizontally scrollable on overflow.
|
|
38
|
+
* **When NOT to use:** for top-level routing prefer `PageHeader` `headerNav` slot or `PageLayout.Tabs`;
|
|
39
|
+
* for accordion-style disclosure use `Collapsible`.
|
|
40
|
+
* **Composition:** pass `triggers` as the declarative list and one or more `TabsContent` children keyed
|
|
41
|
+
* by `value`. Use `compact` for dense in-modal tabs.
|
|
42
|
+
*
|
|
43
|
+
* @see RECIPES.md#page-header-tabs — for the page-header + routed tabs pattern
|
|
44
|
+
*/
|
|
27
45
|
const Tabs = <T extends string>({
|
|
28
46
|
triggers,
|
|
29
47
|
compact = false,
|
|
@@ -76,7 +94,6 @@ const Tabs = <T extends string>({
|
|
|
76
94
|
<div>
|
|
77
95
|
{title}
|
|
78
96
|
<Button
|
|
79
|
-
icon="xmark"
|
|
80
97
|
variant="inline"
|
|
81
98
|
size="icon-3xs"
|
|
82
99
|
disabled={rest.disabled}
|
|
@@ -85,7 +102,9 @@ const Tabs = <T extends string>({
|
|
|
85
102
|
onMouseDown={(e) => e.preventDefault()}
|
|
86
103
|
className="!tw-size-8 tw-opacity-0 tw-duration-200 group-hover/tab-trigger:tw-opacity-100"
|
|
87
104
|
aria-label="Close tab"
|
|
88
|
-
|
|
105
|
+
>
|
|
106
|
+
<X className="tw-size-4" />
|
|
107
|
+
</Button>
|
|
89
108
|
</div>
|
|
90
109
|
</RadixTabs.Trigger>
|
|
91
110
|
);
|
|
@@ -104,6 +123,10 @@ const Tabs = <T extends string>({
|
|
|
104
123
|
);
|
|
105
124
|
};
|
|
106
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Content panel for a single tab. Matched to a `Tabs` trigger by `value`. Receives `tabIndex={-1}` so the
|
|
128
|
+
* panel is focusable for screen readers without being a tab stop.
|
|
129
|
+
*/
|
|
107
130
|
const TabsContent = (props: RadixTabs.TabsContentProps) => (
|
|
108
131
|
<RadixTabs.Content tabIndex={-1} {...props} />
|
|
109
132
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ComponentProps, ElementRef, Ref } from 'react';
|
|
2
2
|
|
|
3
|
+
import { X } from '../../icons';
|
|
3
4
|
import { cn } from '../../utils';
|
|
4
|
-
import { Icon } from '../Icon';
|
|
5
5
|
|
|
6
6
|
type Props = ComponentProps<'button'>;
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ export const ToastDismiss = ({
|
|
|
21
21
|
'tailwindcss-preflight',
|
|
22
22
|
'[&_svg]:tw-aspect-square',
|
|
23
23
|
'[&_svg]:tw-size-4',
|
|
24
|
-
'[&_svg]:tw-
|
|
24
|
+
'[&_svg]:tw-m-1',
|
|
25
25
|
|
|
26
26
|
'tw-text-white',
|
|
27
27
|
'group-data-[variant=default]/toast:[&_svg]:tw-text-popover-foreground',
|
|
@@ -30,7 +30,7 @@ export const ToastDismiss = ({
|
|
|
30
30
|
type="button"
|
|
31
31
|
{...rest}
|
|
32
32
|
>
|
|
33
|
-
<
|
|
33
|
+
<X />
|
|
34
34
|
<span className="tw-sr-only">Dismiss</span>
|
|
35
35
|
</button>
|
|
36
36
|
);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MouseEventHandler, ReactNode } from 'react';
|
|
2
2
|
import type { VariantProps } from 'class-variance-authority';
|
|
3
3
|
import { cva } from 'class-variance-authority';
|
|
4
4
|
import { type Action, toast as sonnerToast } from 'sonner';
|
|
5
5
|
|
|
6
|
+
import { AlertCircle, AlertTriangle, CircleCheck, Info, Loader2 } from '../../icons';
|
|
6
7
|
import { cn } from '../../utils';
|
|
7
|
-
import {
|
|
8
|
+
import { renderLegacyIcon } from '../../utils/legacyIcon';
|
|
8
9
|
|
|
9
10
|
import { ToastAction } from './ToastAction';
|
|
10
11
|
import { ToastDescription } from './ToastDescription';
|
|
@@ -55,12 +56,11 @@ const variants = cva(
|
|
|
55
56
|
type Variants = VariantProps<typeof variants>;
|
|
56
57
|
type Variant = NonNullable<Variants['variant']>;
|
|
57
58
|
type VariantIcon = Exclude<Variant, 'default'>;
|
|
58
|
-
type Icon = ComponentProps<typeof Icon>['icon'];
|
|
59
59
|
|
|
60
60
|
type Props = {
|
|
61
61
|
id: string | number;
|
|
62
62
|
title: ReactNode;
|
|
63
|
-
icon?:
|
|
63
|
+
icon?: ReactNode;
|
|
64
64
|
description?: ReactNode;
|
|
65
65
|
action?: Action;
|
|
66
66
|
loading?: boolean;
|
|
@@ -68,11 +68,11 @@ type Props = {
|
|
|
68
68
|
// offset?: number;
|
|
69
69
|
} & Variants;
|
|
70
70
|
|
|
71
|
-
const DEFAULT_VARIANT_ICON: Record<VariantIcon,
|
|
72
|
-
error:
|
|
73
|
-
info:
|
|
74
|
-
success:
|
|
75
|
-
warning:
|
|
71
|
+
const DEFAULT_VARIANT_ICON: Record<VariantIcon, ReactNode> = {
|
|
72
|
+
error: <AlertTriangle />,
|
|
73
|
+
info: <Info />,
|
|
74
|
+
success: <CircleCheck />,
|
|
75
|
+
warning: <AlertCircle />,
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
export const ToastRoot = ({
|
|
@@ -85,9 +85,9 @@ export const ToastRoot = ({
|
|
|
85
85
|
loading = false,
|
|
86
86
|
icon: iconProp,
|
|
87
87
|
}: Props) => {
|
|
88
|
-
const icon:
|
|
89
|
-
if (loading) return
|
|
90
|
-
if (iconProp) return iconProp;
|
|
88
|
+
const icon: ReactNode = (() => {
|
|
89
|
+
if (loading) return <Loader2 className="tw-animate-spin" />;
|
|
90
|
+
if (iconProp) return renderLegacyIcon(iconProp);
|
|
91
91
|
if (variant === 'default' || variant === null) return null;
|
|
92
92
|
return DEFAULT_VARIANT_ICON[variant];
|
|
93
93
|
})();
|
|
@@ -100,20 +100,25 @@ export const ToastRoot = ({
|
|
|
100
100
|
return (
|
|
101
101
|
<div data-variant={variant} className={cn(variants({ variant }))}>
|
|
102
102
|
{icon && (
|
|
103
|
-
<
|
|
103
|
+
<span
|
|
104
104
|
data-slot="toast-icon"
|
|
105
|
-
spin={loading}
|
|
106
105
|
className={cn(
|
|
107
|
-
|
|
106
|
+
'tw-inline-flex',
|
|
107
|
+
'tw-items-center',
|
|
108
|
+
'tw-justify-center',
|
|
109
|
+
// styles — container at 20px with no padding so the 20px Lucide
|
|
110
|
+
// glyph fills the slot (previous tw-size-4 + tw-p-1 clipped a
|
|
111
|
+
// 16px child into 8px of visible space)
|
|
108
112
|
'tw-aspect-square',
|
|
109
|
-
'tw-size-
|
|
113
|
+
'tw-size-5',
|
|
110
114
|
'tw-shrink-0',
|
|
111
|
-
'tw-
|
|
115
|
+
'[&_svg]:tw-size-5',
|
|
112
116
|
'tw-text-white',
|
|
113
117
|
'group-data-[variant=default]/toast:tw-text-popover-foreground',
|
|
114
118
|
)}
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
>
|
|
120
|
+
{icon}
|
|
121
|
+
</span>
|
|
117
122
|
)}
|
|
118
123
|
|
|
119
124
|
<ToastTitle>{title}</ToastTitle>
|
|
@@ -3,7 +3,7 @@ import { Root } from '@radix-ui/react-toggle';
|
|
|
3
3
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
import { cn } from '../../utils';
|
|
6
|
-
import {
|
|
6
|
+
import { renderLegacyIcon } from '../../utils/legacyIcon';
|
|
7
7
|
|
|
8
8
|
const toggleVariants = cva(
|
|
9
9
|
[
|
|
@@ -72,9 +72,12 @@ const toggleVariants = cva(
|
|
|
72
72
|
'hover:aria-pressed:tw-from-secondary-700',
|
|
73
73
|
'hover:aria-pressed:tw-to-purple-700',
|
|
74
74
|
|
|
75
|
-
// icon color - gradient when off, white when on
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
// icon color - gradient stroke when off, white stroke when on.
|
|
76
|
+
// Lucide icons are stroke-based; targeting every shape (`*`)
|
|
77
|
+
// catches paths, circles, and other non-path sub-shapes so the
|
|
78
|
+
// whole glyph picks up the gradient.
|
|
79
|
+
'[&_*]:tw-stroke-aigradient-600',
|
|
80
|
+
'[&_*]:aria-pressed:tw-stroke-white',
|
|
78
81
|
'*:aria-pressed:tw-text-white',
|
|
79
82
|
],
|
|
80
83
|
'ai-vivid': [
|
|
@@ -150,7 +153,7 @@ const toggleVariants = cva(
|
|
|
150
153
|
);
|
|
151
154
|
|
|
152
155
|
type Props = {
|
|
153
|
-
icon?:
|
|
156
|
+
icon?: ReactNode;
|
|
154
157
|
badge?: ReactNode;
|
|
155
158
|
} & ComponentProps<typeof Root> &
|
|
156
159
|
Omit<VariantProps<typeof toggleVariants>, 'iconOnly'>;
|
|
@@ -182,7 +185,9 @@ export const Toggle = ({
|
|
|
182
185
|
</span>
|
|
183
186
|
)}
|
|
184
187
|
{icon && (
|
|
185
|
-
<
|
|
188
|
+
<span className="tw-inline-flex tw-size-4 tw-shrink-0 tw-items-center tw-justify-center tw-align-middle [&_svg]:tw-size-4">
|
|
189
|
+
{renderLegacyIcon(icon)}
|
|
190
|
+
</span>
|
|
186
191
|
)}
|
|
187
192
|
|
|
188
193
|
{isAiVariant ? (
|
|
@@ -15,19 +15,42 @@ type TriggerProps = {
|
|
|
15
15
|
triggerAsChild?: boolean;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Props for `Tooltip`. `tooltip` is the popup content; `children` is the trigger element.
|
|
20
|
+
* `variant` auto-resolves to `"explanatory"` for strings >30 chars, else `"action"`.
|
|
21
|
+
*/
|
|
18
22
|
export type Props = {
|
|
23
|
+
/** Keep the tooltip visible when the user clicks the trigger (useful for click-to-copy feedback). */
|
|
19
24
|
keepOpenOnTriggerClick?: boolean;
|
|
25
|
+
/** Content rendered inside the tooltip. Plain text only — no interactive elements (use `HoverCard` or `Popover` for that). */
|
|
20
26
|
tooltip: ReactNode;
|
|
21
27
|
children: ReactNode;
|
|
22
28
|
placement?: ComponentProps<typeof Content>['side'];
|
|
29
|
+
/** Forcibly hide the tooltip regardless of hover state. */
|
|
23
30
|
forceHide?: boolean;
|
|
31
|
+
/** Forcibly show the tooltip regardless of hover state. Useful for guided-tour overlays. */
|
|
24
32
|
forceShow?: boolean;
|
|
25
33
|
className?: string;
|
|
26
34
|
} & TriggerProps &
|
|
27
35
|
Pick<ComponentProps<typeof TooltipContent>, 'variant' | 'className'>;
|
|
28
36
|
|
|
37
|
+
/** Re-export of Radix `Tooltip.Provider`. Mount once at the app root so tooltips share delay state. */
|
|
29
38
|
export const TooltipProvider = Provider;
|
|
30
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Short plain-text label that appears on hover or focus.
|
|
42
|
+
*
|
|
43
|
+
* **When to use:** describing an icon-only `Button`, explaining a disabled control, or labelling a
|
|
44
|
+
* truncated value. Plain text only — auto-switches to the longer `"explanatory"` variant when the string
|
|
45
|
+
* exceeds 30 characters.
|
|
46
|
+
* **When NOT to use:** for rich preview content (multiple lines, formatted text, structured data) use
|
|
47
|
+
* `HoverCard`; for tooltips containing links or buttons use `Popover` (interactive elements break a11y
|
|
48
|
+
* inside a `Tooltip`).
|
|
49
|
+
* **Composition:** wrap any focusable element via `triggerAsChild` so the tooltip targets the underlying
|
|
50
|
+
* native element. Mount `TooltipProvider` once at the app root.
|
|
51
|
+
*
|
|
52
|
+
* @see RECIPES.md#disabled-button-tooltip — for the disabled-button + tooltip pattern
|
|
53
|
+
*/
|
|
31
54
|
export const Tooltip = ({
|
|
32
55
|
tooltip,
|
|
33
56
|
children,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { ClipboardButton } from '../../blocks/ClipboardButton';
|
|
4
|
+
import { Maximize2, Minimize2 } from '../../icons';
|
|
4
5
|
import { cn } from '../../utils';
|
|
5
6
|
import { Button } from '../Button';
|
|
6
7
|
import { Tooltip } from '../Tooltip';
|
|
@@ -159,15 +160,16 @@ export const ZoomableSvg = ({ html, copyText, className }: Props) => {
|
|
|
159
160
|
<Button
|
|
160
161
|
size="icon-sm"
|
|
161
162
|
variant="outline"
|
|
162
|
-
icon={
|
|
163
|
-
isFullscreen
|
|
164
|
-
? 'down-left-and-up-right-to-center'
|
|
165
|
-
: 'up-right-and-down-left-from-center'
|
|
166
|
-
}
|
|
167
163
|
aria-label={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
168
164
|
onClick={() => setIsFullscreen((f) => !f)}
|
|
169
165
|
argo
|
|
170
|
-
|
|
166
|
+
>
|
|
167
|
+
{isFullscreen ? (
|
|
168
|
+
<Minimize2 className="tw-size-4" />
|
|
169
|
+
) : (
|
|
170
|
+
<Maximize2 className="tw-size-4" />
|
|
171
|
+
)}
|
|
172
|
+
</Button>
|
|
171
173
|
</Tooltip>
|
|
172
174
|
</div>
|
|
173
175
|
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { LucideIcon, LucideProps } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
// `forwardRef` here is required for type compatibility with `LucideIcon`
|
|
5
|
+
// (a `ForwardRefExoticComponent` from lucide-react). React 19's ref-as-prop
|
|
6
|
+
// pattern produces a plain function component which isn't structurally
|
|
7
|
+
// assignable to `LucideIcon`, so consumers couldn't pass `Keboola`
|
|
8
|
+
// interchangeably with imported Lucide icons (e.g. `<Button><Keboola className="tw-size-4" /></Button>`).
|
|
9
|
+
// oxlint-disable-next-line react-x/no-forward-ref
|
|
10
|
+
export const Keboola: LucideIcon = forwardRef<SVGSVGElement, LucideProps>(
|
|
11
|
+
({ size = 24, color = 'currentColor', className, children, ...rest }, ref) => (
|
|
12
|
+
<svg
|
|
13
|
+
ref={ref}
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
width={size}
|
|
16
|
+
height={size}
|
|
17
|
+
viewBox="0 0 300 512"
|
|
18
|
+
fill={color}
|
|
19
|
+
className={className}
|
|
20
|
+
{...rest}
|
|
21
|
+
>
|
|
22
|
+
<path d="M152 320C79.7 320 24.5 233.9 24.5 124.7C24.5 15.6 70.5 0 152 0s123.6 15.6 123.6 124.7C275.5 233.9 217.7 320 152 320zm-8.7-50.5c0-11.2-9.5-20.3-21.2-20.3s-21.2 9.1-21.2 20.3c0 11.2 9.5 20.3 21.2 20.3s21.2-9.1 21.2-20.3zm55.7 0c0-11.2-9.5-20.3-21.2-20.3s-21.2 9.1-21.2 20.3c0 11.2 9.5 20.3 21.2 20.3s21.2-9.1 21.2-20.3zm68.9 115.1c10.2 9.3 20.2 20.5 29 35c5.9 9.7 2.6 22.1-7.5 27.8c-3.4 1.9-7.1 2.8-10.7 2.8c-7.2 0-14.3-3.6-18.3-10c-6.8-11.1-13.8-18.9-21.7-26.1c-7.3-6.7-15.6-12.7-24.9-19.5c-.7-.5-1.4-1-2.1-1.5c-5-3.6-10-6.8-14.9-9.4c.6 1 1.2 2.1 1.8 3.2c14.4 26.4 29.2 63.1 29.2 104.9c0 11.2-9.5 20.3-21.2 20.3c-11.7 0-21.2-9.1-21.2-20.3c.1-31.8-11.6-62.3-23.8-84.9c-4-7.4-8-13.9-11.6-19.3c-3.4 5.2-7.2 11.3-11 18.3c-12.5 22.7-24.5 53.8-24.4 86c0 11.2-9.5 20.3-21.2 20.3s-21.2-9.1-21.2-20.3c.1-41.8 14.9-78.4 29.2-104.9c.6-1.1 1.2-2.1 1.8-3.2c-5 2.7-9.9 5.8-14.9 9.4c-.6 .5-1.3 .9-1.9 1.4c-9.5 6.9-17.8 12.9-25.1 19.7c-7.9 7.2-14.9 15.1-21.7 26.1c-5.9 9.7-18.9 12.9-29 7.2C.5 441.8-2.9 429.3 3.1 419.7c8.9-14.4 18.8-25.7 29-35c9.2-8.4 18.5-15.1 27.6-21.7c1-.7 2-1.4 3-2.2c4.7-3.5 9.6-6.7 14.6-9.6c-5-2.9-9.8-6.1-14.6-9.6C39.8 325 19.9 302.5 3.1 275.1c-5.9-9.7-2.6-22.1 7.5-27.8c10.1-5.7 23.1-2.5 29 7.2c14.6 23.7 31.1 42 48.7 54.9c18.9 13.7 37.4 20.5 56.5 21.5c1.7-.1 3.5-.1 5.2-.1c1.8 0 3.5 .1 5.2 .1c0 0 0 0 0 0c19.1-1.1 37.6-7.9 56.5-21.5c17.6-12.8 34.2-31.2 48.7-54.9c6-9.7 18.9-12.9 29-7.2c10.1 5.7 13.4 18.1 7.5 27.8c-16.8 27.4-36.7 49.9-59.6 66.6c-4.7 3.5-9.6 6.7-14.5 9.6c4.9 2.9 9.8 6.1 14.5 9.6c.9 .7 1.8 1.3 2.7 2c9.2 6.7 18.5 13.5 27.9 22z" />
|
|
23
|
+
{children}
|
|
24
|
+
</svg>
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
Keboola.displayName = 'Keboola';
|