@keboola/design 1.2.1 → 1.3.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/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 +5 -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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
|
|
4
|
+
import { Combobox, PageLayout, SearchInput } from '@keboola/design';
|
|
5
|
+
|
|
6
|
+
type Row = { id: string; name: string; type: 'table' | 'file' };
|
|
7
|
+
|
|
8
|
+
const FilteredList = ({ rows }: { rows: Row[] }) => {
|
|
9
|
+
const [query, setQuery] = useState('');
|
|
10
|
+
const [type, setType] = useState<Row['type'] | null>(null);
|
|
11
|
+
|
|
12
|
+
const filtered = useMemo(
|
|
13
|
+
() =>
|
|
14
|
+
rows.filter((row) => {
|
|
15
|
+
const matchesQuery = row.name.toLowerCase().includes(query.toLowerCase());
|
|
16
|
+
const matchesType = type ? row.type === type : true;
|
|
17
|
+
return matchesQuery && matchesType;
|
|
18
|
+
}),
|
|
19
|
+
[rows, query, type],
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<PageLayout>
|
|
24
|
+
<PageLayout.Toolbar>
|
|
25
|
+
<SearchInput
|
|
26
|
+
allowClear
|
|
27
|
+
placeholder="Search by name"
|
|
28
|
+
value={query}
|
|
29
|
+
onValueChange={setQuery}
|
|
30
|
+
className="tw-w-64"
|
|
31
|
+
/>
|
|
32
|
+
<Combobox
|
|
33
|
+
allowClear
|
|
34
|
+
items={['table', 'file'] as const}
|
|
35
|
+
value={type}
|
|
36
|
+
onValueChange={setType}
|
|
37
|
+
placeholder="Type"
|
|
38
|
+
className="tw-w-40"
|
|
39
|
+
/>
|
|
40
|
+
<span className="tw-text-sm tw-text-muted-foreground">{filtered.length} results</span>
|
|
41
|
+
</PageLayout.Toolbar>
|
|
42
|
+
<PageLayout.Content>
|
|
43
|
+
<ul className="tw-flex tw-flex-col tw-gap-1">
|
|
44
|
+
{filtered.map((row) => (
|
|
45
|
+
<li key={row.id}>{row.name}</li>
|
|
46
|
+
))}
|
|
47
|
+
</ul>
|
|
48
|
+
</PageLayout.Content>
|
|
49
|
+
</PageLayout>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const meta = {
|
|
54
|
+
title: 'Patterns/FilterToolbar',
|
|
55
|
+
component: FilteredList,
|
|
56
|
+
} satisfies Meta<typeof FilteredList>;
|
|
57
|
+
|
|
58
|
+
export default meta;
|
|
59
|
+
|
|
60
|
+
type Story = StoryObj<typeof meta>;
|
|
61
|
+
|
|
62
|
+
export const Default: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
rows: [
|
|
65
|
+
{ id: '1', name: 'marketing.users', type: 'table' },
|
|
66
|
+
{ id: '2', name: 'sales.leads', type: 'table' },
|
|
67
|
+
{ id: '3', name: 'export.csv', type: 'file' },
|
|
68
|
+
{ id: '4', name: 'snapshot.json', type: 'file' },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Button, FormGroup, Label, TextInput, Tooltip } from '@keboola/design';
|
|
4
|
+
import { Info } from '@keboola/design/icons';
|
|
5
|
+
|
|
6
|
+
/** Info icon next to a label with a hover/focus tooltip — `Label` + icon-only `Button` + `Tooltip`. */
|
|
7
|
+
const FieldWithInfo = ({ id, label, hint }: { id: string; label: string; hint: string }) => (
|
|
8
|
+
<FormGroup>
|
|
9
|
+
<div className="tw-flex tw-items-center tw-gap-1">
|
|
10
|
+
<Label htmlFor={id}>{label}</Label>
|
|
11
|
+
<Tooltip tooltip={hint} placement="right" triggerAsChild>
|
|
12
|
+
<Button
|
|
13
|
+
variant="inline"
|
|
14
|
+
size="icon-3xs"
|
|
15
|
+
aria-label={`More info about ${label}`}
|
|
16
|
+
className="tw-text-muted-foreground"
|
|
17
|
+
>
|
|
18
|
+
<Info className="tw-size-3" />
|
|
19
|
+
</Button>
|
|
20
|
+
</Tooltip>
|
|
21
|
+
</div>
|
|
22
|
+
<TextInput id={id} />
|
|
23
|
+
</FormGroup>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const meta = {
|
|
27
|
+
title: 'Patterns/InfoTooltip',
|
|
28
|
+
component: FieldWithInfo,
|
|
29
|
+
parameters: {
|
|
30
|
+
docs: {
|
|
31
|
+
description: {
|
|
32
|
+
component: 'Info icon next to a label with a hover/focus tooltip.',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} satisfies Meta<typeof FieldWithInfo>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
type Story = StoryObj<typeof meta>;
|
|
41
|
+
|
|
42
|
+
export const Default: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
id: 'account-name',
|
|
45
|
+
label: 'Account name',
|
|
46
|
+
hint: 'Used to identify your project in URLs.',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const LongHint: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
id: 'webhook-url',
|
|
53
|
+
label: 'Webhook URL',
|
|
54
|
+
hint: 'Endpoint that Keboola will POST to whenever this job finishes. Must respond with 2xx within 5 seconds or the call is retried.',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
|
|
4
|
+
import { Card, PageLayout, Sidebar, SidebarProvider } from '@keboola/design';
|
|
5
|
+
|
|
6
|
+
type Item = { id: string; name: string };
|
|
7
|
+
|
|
8
|
+
const ListDetailPage = ({ items }: { items: Item[] }) => {
|
|
9
|
+
const [selectedId, setSelectedId] = useState(items[0]?.id ?? '');
|
|
10
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<SidebarProvider defaultLeftMode="expanded" leftVariant="inset" leftCollapsible="icon">
|
|
14
|
+
<div className="tw-flex tw-flex-1">
|
|
15
|
+
<Sidebar side="left">
|
|
16
|
+
<Sidebar.Content>
|
|
17
|
+
<Sidebar.Menu>
|
|
18
|
+
{items.map((item) => (
|
|
19
|
+
<Sidebar.MenuItem key={item.id}>
|
|
20
|
+
<Sidebar.MenuButton
|
|
21
|
+
tooltip={item.name}
|
|
22
|
+
aria-selected={item.id === selectedId}
|
|
23
|
+
onClick={() => setSelectedId(item.id)}
|
|
24
|
+
>
|
|
25
|
+
{item.name}
|
|
26
|
+
</Sidebar.MenuButton>
|
|
27
|
+
</Sidebar.MenuItem>
|
|
28
|
+
))}
|
|
29
|
+
</Sidebar.Menu>
|
|
30
|
+
</Sidebar.Content>
|
|
31
|
+
</Sidebar>
|
|
32
|
+
<PageLayout>
|
|
33
|
+
<PageLayout.Content>
|
|
34
|
+
<Card bordered>
|
|
35
|
+
<Card.Header>
|
|
36
|
+
<Card.Title>{selected?.name ?? 'Select an item'}</Card.Title>
|
|
37
|
+
</Card.Header>
|
|
38
|
+
<Card.Content>Detail for {selected?.id}.</Card.Content>
|
|
39
|
+
</Card>
|
|
40
|
+
</PageLayout.Content>
|
|
41
|
+
</PageLayout>
|
|
42
|
+
</div>
|
|
43
|
+
</SidebarProvider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const meta = {
|
|
48
|
+
title: 'Patterns/ListDetail',
|
|
49
|
+
component: ListDetailPage,
|
|
50
|
+
parameters: { layout: 'fullscreen' },
|
|
51
|
+
} satisfies Meta<typeof ListDetailPage>;
|
|
52
|
+
|
|
53
|
+
export default meta;
|
|
54
|
+
|
|
55
|
+
type Story = StoryObj<typeof meta>;
|
|
56
|
+
|
|
57
|
+
export const Default: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
items: [
|
|
60
|
+
{ id: 'marketing', name: 'Marketing' },
|
|
61
|
+
{ id: 'sales', name: 'Sales' },
|
|
62
|
+
{ id: 'analytics', name: 'Analytics' },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
|
|
4
|
+
import { Button, Modal, useAppForm } from '@keboola/design';
|
|
5
|
+
|
|
6
|
+
const NewBucketModal = () => {
|
|
7
|
+
const [open, setOpen] = useState(false);
|
|
8
|
+
|
|
9
|
+
const form = useAppForm({
|
|
10
|
+
defaultValues: { name: '', description: '' },
|
|
11
|
+
onSubmit: async ({ value }) => {
|
|
12
|
+
await Promise.resolve(value);
|
|
13
|
+
setOpen(false);
|
|
14
|
+
form.reset();
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Modal open={open} onOpenChange={setOpen}>
|
|
20
|
+
<Modal.Trigger asChild>
|
|
21
|
+
<Button>New bucket</Button>
|
|
22
|
+
</Modal.Trigger>
|
|
23
|
+
<Modal.Content size="medium">
|
|
24
|
+
<Modal.Header title="New bucket" description="Create an empty storage bucket." />
|
|
25
|
+
<Modal.Body>
|
|
26
|
+
<form.FormBody>
|
|
27
|
+
<form.AppField name="name">
|
|
28
|
+
{(field) => <field.TextField label="Name" required variant="secondary" />}
|
|
29
|
+
</form.AppField>
|
|
30
|
+
<form.AppField name="description">
|
|
31
|
+
{(field) => <field.TextAreaField label="Description" variant="secondary" />}
|
|
32
|
+
</form.AppField>
|
|
33
|
+
</form.FormBody>
|
|
34
|
+
</Modal.Body>
|
|
35
|
+
<Modal.Footer>
|
|
36
|
+
<Modal.Close asChild>
|
|
37
|
+
<Button type="button" variant="secondary">
|
|
38
|
+
Cancel
|
|
39
|
+
</Button>
|
|
40
|
+
</Modal.Close>
|
|
41
|
+
<form.AppForm>
|
|
42
|
+
<form.SubmitButton>Create</form.SubmitButton>
|
|
43
|
+
</form.AppForm>
|
|
44
|
+
</Modal.Footer>
|
|
45
|
+
</Modal.Content>
|
|
46
|
+
</Modal>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const meta = {
|
|
51
|
+
title: 'Patterns/ModalForm',
|
|
52
|
+
component: NewBucketModal,
|
|
53
|
+
} satisfies Meta<typeof NewBucketModal>;
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
|
|
57
|
+
type Story = StoryObj<typeof meta>;
|
|
58
|
+
|
|
59
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { PageHeader, PageLayout, Tabs, TabsContent, type Trigger } from '@keboola/design';
|
|
6
|
+
|
|
7
|
+
const TABS: Trigger<string>[] = [
|
|
8
|
+
{ value: '/storage', title: 'Tables & Buckets' },
|
|
9
|
+
{ value: '/storage/files', title: 'Files' },
|
|
10
|
+
{ value: '/storage/jobs', title: 'Storage Jobs' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Story uses local state so the composition renders standalone in Storybook.
|
|
14
|
+
// See RECIPES.md for the router-driven version (TanStack Router).
|
|
15
|
+
const StoragePage = ({ children }: { children: ReactNode }) => {
|
|
16
|
+
const [activeTab, setActiveTab] = useState(TABS[0]!.value);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<PageLayout>
|
|
20
|
+
<PageLayout.Header>
|
|
21
|
+
<PageHeader.Index
|
|
22
|
+
title="Storage"
|
|
23
|
+
metadata="3 buckets and 39 tables"
|
|
24
|
+
headerNav={
|
|
25
|
+
<Tabs triggers={TABS} defaultValue={activeTab} onValueChange={setActiveTab}>
|
|
26
|
+
<TabsContent value={activeTab}>{children}</TabsContent>
|
|
27
|
+
</Tabs>
|
|
28
|
+
}
|
|
29
|
+
/>
|
|
30
|
+
</PageLayout.Header>
|
|
31
|
+
</PageLayout>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const meta = {
|
|
36
|
+
title: 'Patterns/PageHeaderTabs',
|
|
37
|
+
component: StoragePage,
|
|
38
|
+
} satisfies Meta<typeof StoragePage>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
|
|
42
|
+
type Story = StoryObj<typeof meta>;
|
|
43
|
+
|
|
44
|
+
export const Default: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
children: <div className="tw-p-4 tw-text-muted-foreground">Tab content</div>,
|
|
47
|
+
},
|
|
48
|
+
};
|
package/src/styles.css
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
--color-background: 249 250 251; /* #f9fafb */
|
|
14
14
|
--color-foreground: 30 41 59; /* #1e293b */
|
|
15
15
|
|
|
16
|
+
/* Primary — Keboola blue (matches secondary-500 / sidebar-primary). */
|
|
17
|
+
--color-primary: 31 143 255; /* #1f8fff */
|
|
18
|
+
--color-primary-foreground: 255 255 255; /* #ffffff */
|
|
19
|
+
|
|
16
20
|
/* Popover */
|
|
17
21
|
--color-popover: 255 255 255; /* #ffffff */
|
|
18
22
|
--color-popover-foreground: 30 41 59; /* #1e293b */
|
|
@@ -54,6 +58,10 @@
|
|
|
54
58
|
--color-background: 30 41 59; /* #1e293b */
|
|
55
59
|
--color-foreground: 241 245 249; /* #f1f5f9 */
|
|
56
60
|
|
|
61
|
+
/* Primary — Keboola blue (kept consistent across themes; tune in UT-3936 follow-up). */
|
|
62
|
+
--color-primary: 31 143 255; /* #1f8fff */
|
|
63
|
+
--color-primary-foreground: 255 255 255; /* #ffffff */
|
|
64
|
+
|
|
57
65
|
/* Popover */
|
|
58
66
|
--color-popover: 30 41 59; /* #1e293b */
|
|
59
67
|
--color-popover-foreground: 241 245 249; /* #f1f5f9 */
|
|
@@ -142,14 +150,14 @@
|
|
|
142
150
|
h2 {
|
|
143
151
|
font-size: 22px;
|
|
144
152
|
line-height: 32px;
|
|
145
|
-
@apply tw-text-
|
|
153
|
+
@apply tw-text-foreground;
|
|
146
154
|
font-weight: 500;
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
h3 {
|
|
150
158
|
font-size: 16px;
|
|
151
159
|
line-height: 24px;
|
|
152
|
-
@apply tw-text-
|
|
160
|
+
@apply tw-text-foreground;
|
|
153
161
|
font-weight: 500;
|
|
154
162
|
margin: 0;
|
|
155
163
|
}
|
|
@@ -158,7 +166,7 @@
|
|
|
158
166
|
font-size: 14px;
|
|
159
167
|
line-height: 20px;
|
|
160
168
|
font-weight: 500;
|
|
161
|
-
@apply tw-text-
|
|
169
|
+
@apply tw-text-foreground;
|
|
162
170
|
margin: 0;
|
|
163
171
|
}
|
|
164
172
|
|
|
@@ -166,7 +174,7 @@
|
|
|
166
174
|
font-size: 12px;
|
|
167
175
|
line-height: 20px;
|
|
168
176
|
font-weight: 500;
|
|
169
|
-
@apply tw-text-
|
|
177
|
+
@apply tw-text-foreground;
|
|
170
178
|
margin: 0;
|
|
171
179
|
}
|
|
172
180
|
|
|
@@ -176,7 +184,7 @@
|
|
|
176
184
|
h4 small,
|
|
177
185
|
h5 small,
|
|
178
186
|
h6 small {
|
|
179
|
-
@apply tw-text-
|
|
187
|
+
@apply tw-text-muted-foreground;
|
|
180
188
|
font-size: 75%;
|
|
181
189
|
}
|
|
182
190
|
|
|
@@ -192,11 +200,11 @@
|
|
|
192
200
|
}
|
|
193
201
|
|
|
194
202
|
.tw-prose blockquote {
|
|
195
|
-
border-left: 4px solid theme('colors.
|
|
196
|
-
background-color: theme('colors.
|
|
203
|
+
border-left: 4px solid theme('colors.border');
|
|
204
|
+
background-color: theme('colors.muted.DEFAULT');
|
|
197
205
|
padding: 12px 16px;
|
|
198
206
|
border-radius: 2px;
|
|
199
|
-
color: theme('colors.
|
|
207
|
+
color: theme('colors.foreground');
|
|
200
208
|
font-size: 14px;
|
|
201
209
|
font-style: italic;
|
|
202
210
|
font-weight: normal;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Icon } from '../components/Icon';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shim helpers for wrappers whose `icon` prop transitioned from a FontAwesome
|
|
7
|
+
* name / `[prefix, name]` tuple to a `ReactNode` (Lucide-composition) during the
|
|
8
|
+
* UT-4109 C2 sweep. New code should pass a Lucide icon node directly — these
|
|
9
|
+
* helpers exist only so existing `<Wrapper icon="cog">` callsites keep
|
|
10
|
+
* rendering an icon instead of the raw string while consumer-migration PRs
|
|
11
|
+
* (C2b–C2j) land. Removed by the final C2 cleanup PR.
|
|
12
|
+
*/
|
|
13
|
+
type FAIconValue = ComponentProps<typeof Icon>['icon'];
|
|
14
|
+
|
|
15
|
+
export const isFAIconValue = (value: unknown): value is FAIconValue =>
|
|
16
|
+
typeof value === 'string' ||
|
|
17
|
+
(Array.isArray(value) &&
|
|
18
|
+
value.length === 2 &&
|
|
19
|
+
typeof value[0] === 'string' &&
|
|
20
|
+
typeof value[1] === 'string');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Render a node, transparently dispatching legacy FontAwesome icon values
|
|
24
|
+
* (string / `[prefix, name]` tuple) through the legacy `<Icon>` wrapper.
|
|
25
|
+
* For ReactNode inputs (the new composition path), returns the node as-is.
|
|
26
|
+
*/
|
|
27
|
+
export const renderLegacyIcon = (value: ReactNode | FAIconValue): ReactNode =>
|
|
28
|
+
isFAIconValue(value) ? <Icon icon={value} /> : value;
|