@keboola/design 1.2.0 → 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/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 +429 -333
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +426 -331
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.cjs +270 -135
- package/dist/markdown-editor.cjs.map +1 -1
- package/dist/markdown-editor.js +270 -135
- package/dist/markdown-editor.js.map +1 -1
- package/dist/mermaid.cjs +24 -15
- package/dist/mermaid.cjs.map +1 -1
- package/dist/mermaid.js +24 -15
- package/dist/mermaid.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +12 -5
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keboola/design",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Keboola UI platform design system — shadcn-aligned React primitives, Keboola-branded blocks, and the form bindings built on TanStack Form.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"./styles": "./src/styles.css",
|
|
14
14
|
"./mermaid": "./src/blocks/MermaidDiagram/index.ts",
|
|
15
15
|
"./markdown-editor": "./src/components/MarkdownEditor/index.ts",
|
|
16
|
+
"./icons": "./src/icons/index.ts",
|
|
16
17
|
"./icon-names": "./src/icon-names.ts"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"build:js": "tsup",
|
|
49
50
|
"build:css": "tailwindcss --postcss -c tailwind.config.js -i src/styles.css -o dist/styles.css --minify",
|
|
50
51
|
"test": "vitest run --passWithNoTests --reporter=dot",
|
|
52
|
+
"test:stories": "vitest run --config vitest.storybook.config.ts",
|
|
51
53
|
"lint": "yarn lint:format && yarn lint:oxlint",
|
|
52
54
|
"lint:fix": "oxlint --fix . && oxfmt",
|
|
53
55
|
"knip:check": "yarn --cwd ../.. exec knip --workspace packages/design",
|
|
@@ -65,15 +67,19 @@
|
|
|
65
67
|
"@keboola/tsconfig": "workspace:^",
|
|
66
68
|
"@storybook/addon-docs": "^10.2.19",
|
|
67
69
|
"@storybook/addon-links": "^10.2.19",
|
|
70
|
+
"@storybook/addon-vitest": "10.2.19",
|
|
68
71
|
"@storybook/react-vite": "^10.2.19",
|
|
69
72
|
"@testing-library/dom": "^10.4.1",
|
|
70
73
|
"@testing-library/react": "^16.3.2",
|
|
71
74
|
"@testing-library/user-event": "^14.6.1",
|
|
72
75
|
"@types/react": "^19.2.15",
|
|
73
76
|
"@vitejs/plugin-react": "^6.0.0",
|
|
77
|
+
"@vitest/browser": "4.1.2",
|
|
78
|
+
"@vitest/browser-playwright": "4.1.2",
|
|
74
79
|
"eslint-plugin-storybook": "^10.3.5",
|
|
75
80
|
"oxfmt": "^0.44.0",
|
|
76
81
|
"oxlint": "^1.56.0",
|
|
82
|
+
"playwright": "1.59.1",
|
|
77
83
|
"storybook": "^10.2.19",
|
|
78
84
|
"tailwindcss": "^3.4.17",
|
|
79
85
|
"tsup": "^8.5.1",
|
|
@@ -82,7 +88,7 @@
|
|
|
82
88
|
"vitest": "^4.1.2"
|
|
83
89
|
},
|
|
84
90
|
"dependencies": {
|
|
85
|
-
"@awesome.me/kit-be8bcf7e5e": "
|
|
91
|
+
"@awesome.me/kit-be8bcf7e5e": "1.0.220",
|
|
86
92
|
"@base-ui/react": "1.5.0",
|
|
87
93
|
"@codemirror/autocomplete": "^6.20.1",
|
|
88
94
|
"@codemirror/commands": "^6.10.3",
|
|
@@ -98,9 +104,9 @@
|
|
|
98
104
|
"@codemirror/view": "^6.41.0",
|
|
99
105
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
|
100
106
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
|
101
|
-
"@keboola/codemirror-lang-sfsql": "
|
|
102
|
-
"@keboola/codemirror-lang-sql": "
|
|
103
|
-
"@keboola/tailwind-config": "
|
|
107
|
+
"@keboola/codemirror-lang-sfsql": "^1.2.0",
|
|
108
|
+
"@keboola/codemirror-lang-sql": "^0.2.0",
|
|
109
|
+
"@keboola/tailwind-config": "^0.1.1",
|
|
104
110
|
"@lexical/code": "^0.42.0",
|
|
105
111
|
"@lexical/link": "^0.42.0",
|
|
106
112
|
"@lexical/list": "^0.42.0",
|
|
@@ -138,6 +144,7 @@
|
|
|
138
144
|
"cmdk": "^1.1.1",
|
|
139
145
|
"diff": "^9.0.0",
|
|
140
146
|
"lexical": "^0.42.0",
|
|
147
|
+
"lucide-react": "^1.17.0",
|
|
141
148
|
"mermaid": "^11.15.0",
|
|
142
149
|
"node-diff3": "^3.2.0",
|
|
143
150
|
"react": "^19.2.6",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ChevronDown, CircleCheck, Loader2, XCircle } from '../../icons';
|
|
4
4
|
import { cn } from '../../utils';
|
|
5
5
|
|
|
6
6
|
type Variant = 'success' | 'error' | 'processing' | 'dismissed';
|
|
@@ -25,15 +25,15 @@ const variantClasses: Record<Variant, string> = {
|
|
|
25
25
|
|
|
26
26
|
const renderVariantIcon = (variant: Variant) => {
|
|
27
27
|
if (variant === 'processing') {
|
|
28
|
-
return <
|
|
28
|
+
return <Loader2 className="tw-size-4 tw-animate-spin" />;
|
|
29
29
|
}
|
|
30
30
|
if (variant === 'error') {
|
|
31
|
-
return <
|
|
31
|
+
return <XCircle className="tw-size-4" />;
|
|
32
32
|
}
|
|
33
33
|
if (variant === 'dismissed') {
|
|
34
|
-
return <
|
|
34
|
+
return <XCircle className="tw-size-4 tw-text-neutral-500" />;
|
|
35
35
|
}
|
|
36
|
-
return <
|
|
36
|
+
return <CircleCheck className="tw-size-4" />;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
export const CollapsibleStepHeader = ({
|
|
@@ -56,9 +56,8 @@ export const CollapsibleStepHeader = ({
|
|
|
56
56
|
{renderVariantIcon(variant)}
|
|
57
57
|
<span>{label}</span>
|
|
58
58
|
</div>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
className={cn('tw-size-3 tw-transition-transform', isExpanded && 'tw-rotate-180')}
|
|
59
|
+
<ChevronDown
|
|
60
|
+
className={cn('tw-size-4 tw-transition-transform', isExpanded && 'tw-rotate-180')}
|
|
62
61
|
/>
|
|
63
62
|
</button>
|
|
64
63
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CircleCheck, Loader2, XCircle } from '../../icons';
|
|
4
4
|
|
|
5
5
|
type Variant = 'processing' | 'success' | 'error' | 'dismissed';
|
|
6
6
|
|
|
@@ -11,23 +11,23 @@ type Props = {
|
|
|
11
11
|
|
|
12
12
|
const renderVariantIcon = (variant: Variant) => {
|
|
13
13
|
if (variant === 'processing') {
|
|
14
|
-
return <
|
|
14
|
+
return <Loader2 className="tw-size-4 tw-animate-spin tw-text-neutral-200" />;
|
|
15
15
|
}
|
|
16
16
|
if (variant === 'error') {
|
|
17
|
-
return <
|
|
17
|
+
return <XCircle className="tw-size-4 tw-text-error-500" />;
|
|
18
18
|
}
|
|
19
19
|
if (variant === 'dismissed') {
|
|
20
|
-
return <
|
|
20
|
+
return <XCircle className="tw-size-4 tw-text-neutral-200" />;
|
|
21
21
|
}
|
|
22
|
-
return <
|
|
22
|
+
return <CircleCheck className="tw-size-4 tw-text-neutral-200" />;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export const InlineStepLabel = ({ variant, children }: Props) => (
|
|
26
|
-
<div className="tw-flex tw-items-center tw-gap-1">
|
|
26
|
+
<div className="tw-flex tw-min-w-0 tw-items-center tw-gap-1">
|
|
27
27
|
<div className="tw-flex tw-size-5 tw-shrink-0 tw-items-center tw-justify-center">
|
|
28
28
|
{renderVariantIcon(variant)}
|
|
29
29
|
</div>
|
|
30
|
-
<div className="tw-flex tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-1 tw-text-sm tw-font-medium tw-italic tw-text-neutral-800">
|
|
30
|
+
<div className="tw-flex tw-min-w-0 tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-1 tw-text-sm tw-font-medium tw-italic tw-text-neutral-800">
|
|
31
31
|
{children}
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
|
|
3
|
+
import { Copy, ThumbsDown, ThumbsUp } from '@keboola/design/icons';
|
|
4
|
+
|
|
3
5
|
import { Button } from '../../components/Button';
|
|
4
6
|
|
|
5
7
|
import { MessageBubble } from './MessageBubble';
|
|
@@ -66,9 +68,15 @@ export const AssistantWithActions: Story = {
|
|
|
66
68
|
),
|
|
67
69
|
actions: (
|
|
68
70
|
<div className="tw-flex tw-gap-2">
|
|
69
|
-
<Button size="icon-sm" variant="outline"
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
<Button size="icon-sm" variant="outline" aria-label="Copy">
|
|
72
|
+
<Copy className="tw-size-4" />
|
|
73
|
+
</Button>
|
|
74
|
+
<Button size="icon-sm" variant="outline" aria-label="Helpful">
|
|
75
|
+
<ThumbsUp className="tw-size-4" />
|
|
76
|
+
</Button>
|
|
77
|
+
<Button size="icon-sm" variant="outline" aria-label="Not helpful">
|
|
78
|
+
<ThumbsDown className="tw-size-4" />
|
|
79
|
+
</Button>
|
|
72
80
|
</div>
|
|
73
81
|
),
|
|
74
82
|
},
|
|
@@ -2,7 +2,7 @@ import { type FormEvent, type KeyboardEvent, type ReactNode, useState } from 're
|
|
|
2
2
|
|
|
3
3
|
import { Badge } from '../../components/Badge';
|
|
4
4
|
import { Button } from '../../components/Button';
|
|
5
|
-
import {
|
|
5
|
+
import { ChevronDown, Send, X } from '../../icons';
|
|
6
6
|
import { cn } from '../../utils';
|
|
7
7
|
import { Textarea } from '../Textarea';
|
|
8
8
|
|
|
@@ -102,9 +102,8 @@ const ExpandToggle = ({
|
|
|
102
102
|
className="tailwindcss-preflight tw-inline-flex tw-cursor-pointer tw-items-center tw-gap-1 tw-bg-transparent tw-p-0 tw-text-sm tw-font-medium tw-text-secondary-500 hover:tw-text-secondary-600 focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-offset-2 focus-visible:tw-outline-secondary-500"
|
|
103
103
|
>
|
|
104
104
|
<span>{isExpanded ? showLessLabel : showMoreLabel}</span>
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
className={cn('tw-size-3 tw-transition-transform', isExpanded && 'tw-rotate-180')}
|
|
105
|
+
<ChevronDown
|
|
106
|
+
className={cn('tw-size-5 tw-transition-transform', isExpanded && 'tw-rotate-180')}
|
|
108
107
|
/>
|
|
109
108
|
</button>
|
|
110
109
|
);
|
|
@@ -221,12 +220,13 @@ export const PlanApproval = ({
|
|
|
221
220
|
argo
|
|
222
221
|
variant="outline"
|
|
223
222
|
size="icon-sm"
|
|
224
|
-
icon={['far', 'xmark']}
|
|
225
223
|
aria-label={closeLabel}
|
|
226
224
|
onClick={onClose}
|
|
227
225
|
disabled={isSubmitting}
|
|
228
226
|
data-testid="plan-close-button"
|
|
229
|
-
|
|
227
|
+
>
|
|
228
|
+
<X className="tw-size-4" />
|
|
229
|
+
</Button>
|
|
230
230
|
)}
|
|
231
231
|
</div>
|
|
232
232
|
|
|
@@ -271,12 +271,13 @@ export const PlanApproval = ({
|
|
|
271
271
|
type="submit"
|
|
272
272
|
variant="invisible"
|
|
273
273
|
size="icon-sm"
|
|
274
|
-
icon="paper-plane-top"
|
|
275
274
|
aria-label={changeRequestSubmitLabel}
|
|
276
275
|
disabled={!canSubmitChangeRequest}
|
|
277
276
|
className="tw-absolute tw-right-2 tw-top-1/2 tw--translate-y-1/2 [&_svg]:tw-text-secondary-500 not-disabled:hover:[&_svg]:tw-text-secondary-600"
|
|
278
277
|
data-testid="plan-change-request-submit"
|
|
279
|
-
|
|
278
|
+
>
|
|
279
|
+
<Send className="tw-size-4" />
|
|
280
|
+
</Button>
|
|
280
281
|
</div>
|
|
281
282
|
</form>
|
|
282
283
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Loader2 } from '../../icons';
|
|
2
2
|
|
|
3
3
|
type PlanRewritingIndicatorProps = {
|
|
4
4
|
label?: string;
|
|
@@ -12,7 +12,7 @@ export const PlanRewritingIndicator = ({
|
|
|
12
12
|
role="status"
|
|
13
13
|
data-testid="plan-rewriting-indicator"
|
|
14
14
|
>
|
|
15
|
-
<
|
|
15
|
+
<Loader2 className="tw-size-4 tw-animate-spin tw-text-secondary-500" />
|
|
16
16
|
<span className="tw-text-sm tw-italic tw-text-secondary-500">{label}</span>
|
|
17
17
|
</div>
|
|
18
18
|
);
|
|
@@ -2,6 +2,8 @@ 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 { Upload } from '@keboola/design/icons';
|
|
6
|
+
|
|
5
7
|
import { Button } from '../../components/Button';
|
|
6
8
|
import { DropdownMenu } from '../../components/DropdownMenu';
|
|
7
9
|
|
|
@@ -47,7 +49,9 @@ export default {
|
|
|
47
49
|
disabled: false,
|
|
48
50
|
suffix: (
|
|
49
51
|
<PromptInput.Action>
|
|
50
|
-
<Button size="icon-sm" variant="invisible"
|
|
52
|
+
<Button size="icon-sm" variant="invisible">
|
|
53
|
+
<Upload className="tw-size-4" />
|
|
54
|
+
</Button>
|
|
51
55
|
</PromptInput.Action>
|
|
52
56
|
),
|
|
53
57
|
|
|
@@ -87,7 +91,11 @@ export const Inline: Story = {
|
|
|
87
91
|
|
|
88
92
|
export const Actions: Story = {
|
|
89
93
|
args: {
|
|
90
|
-
suffix:
|
|
94
|
+
suffix: (
|
|
95
|
+
<Button size="icon-3xs" variant="inline">
|
|
96
|
+
<Upload className="tw-size-4" />
|
|
97
|
+
</Button>
|
|
98
|
+
),
|
|
91
99
|
},
|
|
92
100
|
};
|
|
93
101
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ComponentProps, ElementRef, Ref } from 'react';
|
|
1
|
+
import type { ComponentProps, ElementRef, ReactNode, Ref } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Button } from '../../components/Button';
|
|
4
4
|
import { Tooltip } from '../../components/Tooltip';
|
|
5
|
+
import { CirclePause, Play, Send } from '../../icons';
|
|
5
6
|
|
|
6
7
|
import { PromptInputAction } from './PromptInputAction';
|
|
7
8
|
|
|
@@ -16,11 +17,11 @@ export const PromptInputSubmit = ({
|
|
|
16
17
|
disabled,
|
|
17
18
|
status = 'ready',
|
|
18
19
|
}: Props & { ref?: Ref<ElementRef<'button'>> }) => {
|
|
19
|
-
const
|
|
20
|
-
if (status === 'streaming') return
|
|
21
|
-
if (status === 'interrupted') return
|
|
22
|
-
return
|
|
23
|
-
};
|
|
20
|
+
const icon: ReactNode = (() => {
|
|
21
|
+
if (status === 'streaming') return <CirclePause className="tw-size-4" />;
|
|
22
|
+
if (status === 'interrupted') return <Play className="tw-size-4" />;
|
|
23
|
+
return <Send className="tw-size-4" />;
|
|
24
|
+
})();
|
|
24
25
|
|
|
25
26
|
const getTooltipText = () => {
|
|
26
27
|
if (status === 'streaming') return 'Stop';
|
|
@@ -39,9 +40,12 @@ export const PromptInputSubmit = ({
|
|
|
39
40
|
size="icon-sm"
|
|
40
41
|
variant="invisible"
|
|
41
42
|
isLoading={status === 'submitted'}
|
|
42
|
-
icon={getIcon()}
|
|
43
43
|
className="tw-ml-auto tw-flex tw-items-center tw-rounded-lg [&_svg]:tw-text-secondary-500 not-disabled:hover:[&_svg]:tw-text-secondary-600"
|
|
44
|
-
|
|
44
|
+
>
|
|
45
|
+
{/* Suppress the child icon while isLoading — Button already renders
|
|
46
|
+
the spinner; rendering both would double-stack glyphs. */}
|
|
47
|
+
{status !== 'submitted' && icon}
|
|
48
|
+
</Button>
|
|
45
49
|
</PromptInputAction>
|
|
46
50
|
</Tooltip.Trigger>
|
|
47
51
|
|
|
@@ -2,9 +2,9 @@ import { type KeyboardEvent, type ReactNode, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Button } from '../../components/Button';
|
|
4
4
|
import { Checkbox } from '../../components/Checkbox';
|
|
5
|
-
import { Icon } from '../../components/Icon';
|
|
6
5
|
import { TextInput } from '../../components/Input';
|
|
7
6
|
import { Progress } from '../../components/Progress';
|
|
7
|
+
import { ChevronLeft, ChevronRight, X } from '../../icons';
|
|
8
8
|
import { cn } from '../../utils';
|
|
9
9
|
|
|
10
10
|
type QuestionOption = {
|
|
@@ -142,11 +142,12 @@ export const Question = ({
|
|
|
142
142
|
argo
|
|
143
143
|
variant="outline"
|
|
144
144
|
size="icon-sm"
|
|
145
|
-
icon={['far', 'xmark']}
|
|
146
145
|
aria-label="Close"
|
|
147
146
|
onClick={onClose}
|
|
148
147
|
data-testid="question-close-button"
|
|
149
|
-
|
|
148
|
+
>
|
|
149
|
+
<X className="tw-size-4" />
|
|
150
|
+
</Button>
|
|
150
151
|
)}
|
|
151
152
|
</div>
|
|
152
153
|
|
|
@@ -264,10 +265,9 @@ export const Question = ({
|
|
|
264
265
|
onClick={onPrevious}
|
|
265
266
|
aria-label="Previous step"
|
|
266
267
|
>
|
|
267
|
-
<
|
|
268
|
-
icon="chevron-left"
|
|
268
|
+
<ChevronLeft
|
|
269
269
|
className={cn(
|
|
270
|
-
'tw-size-
|
|
270
|
+
'tw-size-5',
|
|
271
271
|
isFirstStep ? 'tw-text-primary/25' : 'tw-text-primary',
|
|
272
272
|
)}
|
|
273
273
|
/>
|
|
@@ -282,10 +282,9 @@ export const Question = ({
|
|
|
282
282
|
onClick={onNext}
|
|
283
283
|
aria-label="Next step"
|
|
284
284
|
>
|
|
285
|
-
<
|
|
286
|
-
icon="chevron-right"
|
|
285
|
+
<ChevronRight
|
|
287
286
|
className={cn(
|
|
288
|
-
'tw-size-
|
|
287
|
+
'tw-size-5',
|
|
289
288
|
currentStep === totalSteps ? 'tw-text-primary/25' : 'tw-text-primary',
|
|
290
289
|
)}
|
|
291
290
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { FileText } from '../../icons';
|
|
4
4
|
import { cn } from '../../utils';
|
|
5
5
|
|
|
6
6
|
type Props = {
|
|
@@ -23,10 +23,17 @@ export const SuggestedPrompt = ({ label, disabled, onClick, icon, className }: P
|
|
|
23
23
|
onClick={onClick}
|
|
24
24
|
>
|
|
25
25
|
{icon ?? (
|
|
26
|
-
<
|
|
27
|
-
className=
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
<FileText
|
|
27
|
+
className={cn(
|
|
28
|
+
'tw-size-4 tw-shrink-0 tw-basis-4',
|
|
29
|
+
// Lucide icons are stroke-based — paint the visible line with the
|
|
30
|
+
// AI gradient (no fill, so the glyph keeps its outline silhouette
|
|
31
|
+
// instead of becoming a solid block). The `*` selector catches
|
|
32
|
+
// every shape element (path, circle, polyline, …) so multi-part
|
|
33
|
+
// icons don't lose sub-shapes the way `path`-only selectors do.
|
|
34
|
+
'[&_*]:tw-stroke-aigradient-600',
|
|
35
|
+
'group-not-disabled:group-hover:[&_*]:tw-stroke-secondary-600',
|
|
36
|
+
)}
|
|
30
37
|
/>
|
|
31
38
|
)}
|
|
32
39
|
<div className="tw-whitespace-normal tw-text-left">{label}</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Loader2 } from '../../icons';
|
|
2
2
|
|
|
3
3
|
type ThinkingIndicatorProps = {
|
|
4
4
|
label?: string;
|
|
@@ -7,7 +7,7 @@ type ThinkingIndicatorProps = {
|
|
|
7
7
|
export const ThinkingIndicator = ({ label = 'Thinking...' }: ThinkingIndicatorProps) => {
|
|
8
8
|
return (
|
|
9
9
|
<div className="tw-flex tw-items-center tw-gap-2" data-testid="chat-thinking-indicator">
|
|
10
|
-
<
|
|
10
|
+
<Loader2 className="tw-size-4 tw-animate-spin tw-text-secondary-500" />
|
|
11
11
|
<span className="tw-text-sm tw-italic tw-text-secondary-500">{label}</span>
|
|
12
12
|
</div>
|
|
13
13
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ComponentProps, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CircleCheck, Loader2 } from '../../icons';
|
|
4
4
|
import { cn } from '../../utils';
|
|
5
5
|
import { Textarea } from '../Textarea';
|
|
6
6
|
|
|
@@ -83,17 +83,17 @@ export const AutoSaveTextarea = ({
|
|
|
83
83
|
{currentValue.length} / {maxLength}
|
|
84
84
|
</span>
|
|
85
85
|
)}
|
|
86
|
-
<span className="tw-text-neutral-400">
|
|
86
|
+
<span className="tw-flex tw-items-center tw-gap-1 tw-text-neutral-400">
|
|
87
87
|
{isOverLimit && 'Character limit exceeded'}
|
|
88
88
|
{!isOverLimit && isChanged && !isSaving && 'Unsaved'}
|
|
89
89
|
{!isOverLimit && isChanged && isSaving && (
|
|
90
90
|
<>
|
|
91
|
-
<
|
|
91
|
+
<Loader2 className="tw-size-4 tw-animate-spin" /> Saving
|
|
92
92
|
</>
|
|
93
93
|
)}
|
|
94
94
|
{!isOverLimit && value !== null && !isChanged && !isSaving && (
|
|
95
95
|
<>
|
|
96
|
-
<
|
|
96
|
+
<CircleCheck className="tw-size-4 tw-text-primary-500" /> Saved
|
|
97
97
|
</>
|
|
98
98
|
)}
|
|
99
99
|
</span>
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import type { ComponentProps } from 'react';
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Button } from '../../components/Button';
|
|
4
4
|
import { Clipboard } from '../../components/Clipboard';
|
|
5
|
+
import { Copy } from '../../icons';
|
|
5
6
|
|
|
6
7
|
type ClipboardProps = Pick<
|
|
7
8
|
ComponentProps<typeof Clipboard>,
|
|
8
9
|
'text' | 'tooltip' | 'tooltipPlacement' | 'tooltipVariant'
|
|
9
10
|
>;
|
|
10
11
|
|
|
11
|
-
type ButtonProps = Omit<ComponentProps<typeof Button>, 'disabled'>;
|
|
12
|
+
type ButtonProps = Omit<ComponentProps<typeof Button>, 'disabled' | 'icon'>;
|
|
12
13
|
|
|
13
|
-
type Props = ClipboardProps & ButtonProps;
|
|
14
|
+
type Props = ClipboardProps & ButtonProps & { icon?: ReactNode };
|
|
14
15
|
|
|
15
|
-
const DEFAULT_ICON:
|
|
16
|
+
const DEFAULT_ICON: ReactNode = <Copy />;
|
|
16
17
|
|
|
17
18
|
export const ClipboardButton = ({
|
|
18
19
|
// clipboard props
|
|
@@ -34,7 +35,8 @@ export const ClipboardButton = ({
|
|
|
34
35
|
tooltipVariant={tooltipVariant}
|
|
35
36
|
text={text}
|
|
36
37
|
>
|
|
37
|
-
<Button {...rest}
|
|
38
|
+
<Button {...rest} variant={variant} disabled={!text}>
|
|
39
|
+
{icon}
|
|
38
40
|
{children}
|
|
39
41
|
</Button>
|
|
40
42
|
</Clipboard>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
|
|
3
|
-
import { Icon } from '@keboola/design';
|
|
4
|
-
|
|
5
3
|
import { ButtonInline } from '../../components/Button';
|
|
6
4
|
import { Clipboard } from '../../components/Clipboard';
|
|
5
|
+
import { Copy } from '../../icons';
|
|
7
6
|
|
|
8
7
|
type ClipboardProps = Pick<
|
|
9
8
|
ComponentProps<typeof Clipboard>,
|
|
@@ -34,7 +33,7 @@ export const ClipboardInline = ({
|
|
|
34
33
|
tooltipVariant={tooltipVariant}
|
|
35
34
|
>
|
|
36
35
|
<ButtonInline {...rest} disabled={!text}>
|
|
37
|
-
{!hideIcon && <
|
|
36
|
+
{!hideIcon && <Copy className="tw-size-4" />}
|
|
38
37
|
{children}
|
|
39
38
|
</ButtonInline>
|
|
40
39
|
</Clipboard>
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from 'react';
|
|
10
10
|
|
|
11
11
|
import { InputGroup } from '../../components/InputGroup';
|
|
12
|
+
import { X } from '../../icons';
|
|
12
13
|
import { cn, createChangeInputEvent, mergeRefs } from '../../utils';
|
|
13
14
|
|
|
14
15
|
import { ColorPicker } from './ColorPicker';
|
|
@@ -150,12 +151,13 @@ export const ColorInput = ({
|
|
|
150
151
|
<InputGroup.Addon align="inline-end">
|
|
151
152
|
{canClear && (
|
|
152
153
|
<InputGroup.Button
|
|
153
|
-
icon="xmark"
|
|
154
154
|
// prevents input focus stealing
|
|
155
155
|
onMouseDown={(event) => event.preventDefault()}
|
|
156
156
|
onClick={handleClear}
|
|
157
157
|
aria-label="Clear color"
|
|
158
|
-
|
|
158
|
+
>
|
|
159
|
+
<X className="tw-size-4" />
|
|
160
|
+
</InputGroup.Button>
|
|
159
161
|
)}
|
|
160
162
|
<ColorPicker
|
|
161
163
|
disabled={disabled}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ComponentProps, type ElementRef, useRef } from 'react';
|
|
2
2
|
|
|
3
|
+
import { Pipette } from '@keboola/design/icons';
|
|
4
|
+
|
|
3
5
|
import { InputGroup } from '../../components/InputGroup';
|
|
4
6
|
|
|
5
7
|
import { coerceColorPickerValue, showPicker } from './utils';
|
|
@@ -30,10 +32,11 @@ export const ColorPicker = ({ value, disabled, readOnly, ...props }: Props) => {
|
|
|
30
32
|
<InputGroup.Button
|
|
31
33
|
onMouseDown={(event) => event.preventDefault()}
|
|
32
34
|
disabled={disabled || readOnly}
|
|
33
|
-
icon="eye-dropper"
|
|
34
35
|
onClick={handleOpenPicker}
|
|
35
36
|
aria-label="Pick color"
|
|
36
|
-
|
|
37
|
+
>
|
|
38
|
+
<Pipette className="tw-size-4" />
|
|
39
|
+
</InputGroup.Button>
|
|
37
40
|
</div>
|
|
38
41
|
);
|
|
39
42
|
};
|
|
@@ -67,11 +67,27 @@ const emptyStateActionsClassnames = cva(['tw-flex', 'tw-items-center', 'tw-flex-
|
|
|
67
67
|
|
|
68
68
|
type Variant = VariantProps<typeof emptyStateClassnames>['variant'];
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Props for `EmptyState`. `variant` chooses spacing — `"default"` for full-page empty states,
|
|
72
|
+
* `"compact"` for inside cards / small surfaces.
|
|
73
|
+
*/
|
|
70
74
|
type RootProps = ComponentProps<'div'> &
|
|
71
75
|
VariantProps<typeof emptyStateClassnames> & {
|
|
72
76
|
variant?: Variant;
|
|
73
77
|
};
|
|
74
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Friendly "nothing here yet" placeholder with icon, title, description, and actions slots.
|
|
81
|
+
*
|
|
82
|
+
* **When to use:** the user reached a page or surface that is intentionally empty (no items created
|
|
83
|
+
* yet, an empty list after filtering, an empty inbox). The goal is to guide the user to the next action.
|
|
84
|
+
* **When NOT to use:** for an error or failure state use `ErrorState`; for inline loading placeholders
|
|
85
|
+
* use `Skeleton`; for "no matches found after search" inside a search/combobox use `EmptySearchContent`.
|
|
86
|
+
* **Composition:** nest `EmptyState.Icon`, `EmptyState.Title`, `EmptyState.Description`, and
|
|
87
|
+
* `EmptyState.Actions` (with one or two `Button`s). Use `variant="compact"` inside a `Card`.
|
|
88
|
+
*
|
|
89
|
+
* @see RECIPES.md#card-with-empty-state — for the empty-card pattern
|
|
90
|
+
*/
|
|
75
91
|
const EmptyStateRoot = ({ className, variant = 'default', children, ...rest }: RootProps) => (
|
|
76
92
|
<div role="status" className={emptyStateClassnames({ className, variant })} {...rest}>
|
|
77
93
|
{children}
|
|
@@ -69,11 +69,26 @@ const errorStateActionsClassnames = cva(['tw-flex', 'tw-items-center', 'tw-flex-
|
|
|
69
69
|
|
|
70
70
|
type Variant = VariantProps<typeof errorStateClassnames>['variant'];
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Props for `ErrorState`. `variant` chooses look — `"default"` for non-blocking errors, `"compact"` for
|
|
74
|
+
* inside cards, `"destructive"` for blocking failures (red title + icon).
|
|
75
|
+
*/
|
|
72
76
|
type RootProps = ComponentProps<'div'> &
|
|
73
77
|
VariantProps<typeof errorStateClassnames> & {
|
|
74
78
|
variant?: Variant;
|
|
75
79
|
};
|
|
76
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Page-level error placeholder with icon, title, description, and recovery-action slots.
|
|
83
|
+
*
|
|
84
|
+
* **When to use:** the page can't render its content because a request failed, the user lacks permission,
|
|
85
|
+
* or the entity doesn't exist. Show a recovery action (retry, go back, request access) in
|
|
86
|
+
* `ErrorState.Actions`.
|
|
87
|
+
* **When NOT to use:** for transient operation failures use `toast()`; for inline form validation use
|
|
88
|
+
* `FormGroup state="error"`; for contextual warning banners use `Alert variant="error"`.
|
|
89
|
+
* **Composition:** nest `ErrorState.Icon`, `ErrorState.Title`, `ErrorState.Description`, and
|
|
90
|
+
* `ErrorState.Actions`. Use `variant="destructive"` for full-page critical failures.
|
|
91
|
+
*/
|
|
77
92
|
const ErrorStateRoot = ({ className, variant = 'default', children, ...rest }: RootProps) => (
|
|
78
93
|
<div role="alert" className={errorStateClassnames({ className, variant })} {...rest}>
|
|
79
94
|
{children}
|
|
@@ -11,6 +11,21 @@ import { TextAreaField } from './connectors/TextAreaField';
|
|
|
11
11
|
import { TextField } from './connectors/TextField';
|
|
12
12
|
import { fieldContext, formContext } from './contexts';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Typed form hook + `withForm` binding for the design system.
|
|
16
|
+
*
|
|
17
|
+
* **When to use:** any form layout that has validation, server-side errors, or a submit state — pages,
|
|
18
|
+
* modals, drawers, multi-step wizards. Use `useAppForm({ defaultValues, validators, onSubmit })` at the
|
|
19
|
+
* top of the form component, then render the typed `field.TextField` / `field.NumberField` /
|
|
20
|
+
* `field.PasswordField` / `field.TextAreaField` / `field.SelectField` / `field.CheckboxField` /
|
|
21
|
+
* `field.SwitchField` connectors inside `form.AppForm` → `form.FormBody`. Submit via `form.SubmitButton`.
|
|
22
|
+
* **When NOT to use:** for a single uncontrolled `<input>` use `TextInput` directly; for a bare label + input
|
|
23
|
+
* pair without validation use `FormGroup` + `FormGroup.TextInput`.
|
|
24
|
+
* **Composition:** schema-library agnostic — pass any [Standard Schema](https://standardschema.dev/) validator
|
|
25
|
+
* (zod, valibot, …) in the consuming app. Server errors come from `setServerFieldErrors` (also re-exported).
|
|
26
|
+
*
|
|
27
|
+
* @see RECIPES.md#modal-form — for the canonical form-in-modal composition
|
|
28
|
+
*/
|
|
14
29
|
export const { useAppForm, withForm } = createFormHook({
|
|
15
30
|
fieldContext,
|
|
16
31
|
formContext,
|