@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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
|
-
import type { ComponentProps } from 'react';
|
|
3
2
|
import { $createCodeNode } from '@lexical/code';
|
|
4
3
|
import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
|
|
5
4
|
import { $createHeadingNode, $createQuoteNode } from '@lexical/rich-text';
|
|
@@ -13,22 +12,31 @@ import {
|
|
|
13
12
|
type LexicalEditor,
|
|
14
13
|
} from 'lexical';
|
|
15
14
|
|
|
15
|
+
import {
|
|
16
|
+
ChevronDown,
|
|
17
|
+
Heading1,
|
|
18
|
+
Heading2,
|
|
19
|
+
Heading3,
|
|
20
|
+
List as ListIcon,
|
|
21
|
+
ListOrdered,
|
|
22
|
+
type LucideIcon,
|
|
23
|
+
Quote,
|
|
24
|
+
SquareCode,
|
|
25
|
+
Type,
|
|
26
|
+
} from '../../../../icons';
|
|
16
27
|
import { cn } from '../../../../utils';
|
|
17
28
|
import { DropdownMenu } from '../../../DropdownMenu';
|
|
18
|
-
import { Icon } from '../../../Icon';
|
|
19
29
|
import { Tooltip } from '../../../Tooltip';
|
|
20
30
|
|
|
21
|
-
type IconProp = ComponentProps<typeof Icon>['icon'];
|
|
22
|
-
|
|
23
31
|
const BLOCK_TYPE_META = {
|
|
24
|
-
paragraph: { label: 'Normal', icon:
|
|
25
|
-
h1: { label: 'Heading 1', icon:
|
|
26
|
-
h2: { label: 'Heading 2', icon:
|
|
27
|
-
h3: { label: 'Heading 3', icon:
|
|
28
|
-
bullet: { label: 'Bullet List', icon:
|
|
29
|
-
number: { label: 'Numbered List', icon:
|
|
30
|
-
quote: { label: 'Quote', icon:
|
|
31
|
-
code: { label: 'Code Block', icon:
|
|
32
|
+
paragraph: { label: 'Normal', icon: Type satisfies LucideIcon, selectable: true },
|
|
33
|
+
h1: { label: 'Heading 1', icon: Heading1 satisfies LucideIcon, selectable: true },
|
|
34
|
+
h2: { label: 'Heading 2', icon: Heading2 satisfies LucideIcon, selectable: true },
|
|
35
|
+
h3: { label: 'Heading 3', icon: Heading3 satisfies LucideIcon, selectable: true },
|
|
36
|
+
bullet: { label: 'Bullet List', icon: ListIcon satisfies LucideIcon, selectable: false },
|
|
37
|
+
number: { label: 'Numbered List', icon: ListOrdered satisfies LucideIcon, selectable: false },
|
|
38
|
+
quote: { label: 'Quote', icon: Quote satisfies LucideIcon, selectable: false },
|
|
39
|
+
code: { label: 'Code Block', icon: SquareCode satisfies LucideIcon, selectable: false },
|
|
32
40
|
} as const;
|
|
33
41
|
|
|
34
42
|
type BlockType = keyof typeof BLOCK_TYPE_META;
|
|
@@ -51,6 +59,7 @@ export const BlockTypeDropdown = ({
|
|
|
51
59
|
}) => {
|
|
52
60
|
const savedSelectionRef = useRef<BaseSelection | null>(null);
|
|
53
61
|
const currentMeta = isBlockType(blockType) ? BLOCK_TYPE_META[blockType] : null;
|
|
62
|
+
const CurrentBlockIcon = currentMeta?.icon ?? Type;
|
|
54
63
|
|
|
55
64
|
const handleOpenChange = (open: boolean) => {
|
|
56
65
|
if (open) {
|
|
@@ -107,14 +116,14 @@ export const BlockTypeDropdown = ({
|
|
|
107
116
|
'hover:tw-bg-neutral-100',
|
|
108
117
|
)}
|
|
109
118
|
>
|
|
110
|
-
<
|
|
119
|
+
<CurrentBlockIcon className="tw-size-4" />
|
|
111
120
|
{currentMeta?.label ?? 'Normal'}
|
|
112
|
-
<
|
|
121
|
+
<ChevronDown className="tw-size-3" />
|
|
113
122
|
</button>
|
|
114
123
|
</DropdownMenu.Trigger>
|
|
115
124
|
</Tooltip>
|
|
116
125
|
<DropdownMenu.Content align="start" className="tw-min-w-40">
|
|
117
|
-
{BLOCK_TYPE_OPTIONS.map(({ value, label, icon }) => (
|
|
126
|
+
{BLOCK_TYPE_OPTIONS.map(({ value, label, icon: IconComp }) => (
|
|
118
127
|
<DropdownMenu.Item
|
|
119
128
|
key={value}
|
|
120
129
|
onSelect={() => handleSelect(value)}
|
|
@@ -123,7 +132,7 @@ export const BlockTypeDropdown = ({
|
|
|
123
132
|
blockType === value && 'tw-bg-secondary-50 tw-text-secondary-500',
|
|
124
133
|
)}
|
|
125
134
|
>
|
|
126
|
-
<
|
|
135
|
+
<IconComp className="tw-size-4" />
|
|
127
136
|
{label}
|
|
128
137
|
</DropdownMenu.Item>
|
|
129
138
|
))}
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
type LexicalEditor,
|
|
11
11
|
} from 'lexical';
|
|
12
12
|
|
|
13
|
+
import { Link as LinkIcon } from '../../../../icons';
|
|
13
14
|
import { Button } from '../../../Button';
|
|
14
15
|
import { ButtonGroup } from '../../../ButtonGroup';
|
|
15
16
|
import { FormGroup } from '../../../FormGroup';
|
|
16
|
-
import { Icon } from '../../../Icon';
|
|
17
17
|
import { TextInput } from '../../../Input';
|
|
18
18
|
import { Label } from '../../../Label';
|
|
19
19
|
|
|
@@ -101,7 +101,7 @@ export const LinkPanel = ({ editor, isLink }: { editor: LexicalEditor; isLink: b
|
|
|
101
101
|
onClick={() => editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)}
|
|
102
102
|
tooltip="Remove Link"
|
|
103
103
|
>
|
|
104
|
-
<
|
|
104
|
+
<LinkIcon className="tw-size-4" />
|
|
105
105
|
</ToolbarButton>
|
|
106
106
|
);
|
|
107
107
|
}
|
|
@@ -109,7 +109,7 @@ export const LinkPanel = ({ editor, isLink }: { editor: LexicalEditor; isLink: b
|
|
|
109
109
|
return (
|
|
110
110
|
<div className="tw-relative">
|
|
111
111
|
<ToolbarButton active={open} onClick={handleOpen} tooltip="Insert Link">
|
|
112
|
-
<
|
|
112
|
+
<LinkIcon className="tw-size-4" />
|
|
113
113
|
</ToolbarButton>
|
|
114
114
|
{open && (
|
|
115
115
|
<div
|
|
@@ -115,6 +115,16 @@ export const CUSTOM_TRANSFORMERS: Transformer[] = [
|
|
|
115
115
|
...TRANSFORMERS,
|
|
116
116
|
];
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Inline-only subset for `singleLine` mode: keeps text-format (bold/italic/code/strike)
|
|
120
|
+
* and text-match (links) transformers; drops every element / multiline-element transformer
|
|
121
|
+
* (headings, lists, HR, code blocks, mermaid). Used for inline-editable titles where the
|
|
122
|
+
* caller doesn't want block-level markdown syntax to "promote" a line.
|
|
123
|
+
*/
|
|
124
|
+
export const SINGLE_LINE_TRANSFORMERS: Transformer[] = CUSTOM_TRANSFORMERS.filter(
|
|
125
|
+
(t) => t.type === 'text-format' || t.type === 'text-match',
|
|
126
|
+
);
|
|
127
|
+
|
|
118
128
|
// --- Table pre/post-processing ---
|
|
119
129
|
// GFM tables can't round-trip through Lexical's markdown (no built-in table transformer).
|
|
120
130
|
// We wrap them in fenced code blocks on import and unwrap on export to preserve them exactly.
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
+
import { ChevronsLeft, ChevronsRight } from '../../../icons';
|
|
1
2
|
import { Button } from '../../Button';
|
|
2
3
|
|
|
3
4
|
type Props = {
|
|
4
5
|
side: 'left' | 'right';
|
|
5
6
|
onAccept: () => void;
|
|
7
|
+
argo?: boolean;
|
|
6
8
|
};
|
|
7
9
|
|
|
8
|
-
export const AcceptButton = ({ side, onAccept }: Props) => {
|
|
10
|
+
export const AcceptButton = ({ side, onAccept, argo }: Props) => {
|
|
9
11
|
const label = `Accept ${side === 'left' ? 'production' : 'branch'} version`;
|
|
12
|
+
const Chevron = side === 'left' ? ChevronsRight : ChevronsLeft;
|
|
10
13
|
return (
|
|
11
14
|
<Button
|
|
12
|
-
icon={side === 'left' ? ['far', 'chevrons-right'] : ['far', 'chevrons-left']}
|
|
13
15
|
onClick={onAccept}
|
|
14
16
|
variant="outline"
|
|
15
|
-
size="icon"
|
|
17
|
+
size="icon-sm"
|
|
16
18
|
title={label}
|
|
17
19
|
aria-label={label}
|
|
18
|
-
|
|
20
|
+
argo={argo}
|
|
21
|
+
>
|
|
22
|
+
<Chevron className="tw-size-5" />
|
|
23
|
+
</Button>
|
|
19
24
|
);
|
|
20
25
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RefCallback } from 'react';
|
|
2
2
|
|
|
3
|
+
import { X } from '../../../icons';
|
|
3
4
|
import { cn } from '../../../utils';
|
|
4
5
|
import { Button } from '../../Button';
|
|
5
6
|
import { LINE_HEIGHT_PX, type MergeConflict, PANE_HEADER_HEIGHT_PX } from '../constants';
|
|
@@ -12,21 +13,29 @@ type Props = {
|
|
|
12
13
|
onAccept: (conflict: MergeConflict) => void;
|
|
13
14
|
onDismiss?: (conflict: MergeConflict) => void;
|
|
14
15
|
contentRef?: RefCallback<HTMLDivElement>;
|
|
16
|
+
argo?: boolean;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
//
|
|
18
|
-
// accept chevron + dismiss icon without overflowing into the
|
|
19
|
-
// (which use `tw-overflow-hidden` and would clip absolutely-
|
|
20
|
-
// children that crossed the boundary). Collapses to 0 when idle
|
|
21
|
-
// merges don't reserve a sliver of layout. Inner wrapper allows
|
|
22
|
-
// overflow so the icon buttons' focus-visible ring
|
|
23
|
-
// the column edge. Buttons stay aligned with their source-pane chunk
|
|
24
|
-
// parent's `translateY(-scrollTop)` follower applied to `contentRef`.
|
|
25
|
-
export const AcceptControls = ({
|
|
19
|
+
// 28px column when there are conflicts to render — wide enough to fit the
|
|
20
|
+
// 24px icon-xs accept chevron + dismiss icon without overflowing into the
|
|
21
|
+
// adjacent panes (which use `tw-overflow-hidden` and would clip absolutely-
|
|
22
|
+
// positioned children that crossed the boundary). Collapses to 0 when idle
|
|
23
|
+
// so empty merges don't reserve a sliver of layout. Inner wrapper allows
|
|
24
|
+
// horizontal overflow so the icon buttons' focus-visible ring stays visible
|
|
25
|
+
// past the column edge. Buttons stay aligned with their source-pane chunk
|
|
26
|
+
// via the parent's `translateY(-scrollTop)` follower applied to `contentRef`.
|
|
27
|
+
export const AcceptControls = ({
|
|
28
|
+
conflicts,
|
|
29
|
+
side,
|
|
30
|
+
onAccept,
|
|
31
|
+
onDismiss,
|
|
32
|
+
contentRef,
|
|
33
|
+
argo,
|
|
34
|
+
}: Props) => (
|
|
26
35
|
<div
|
|
27
36
|
className={cn(
|
|
28
37
|
'tw-z-10 tw-flex tw-shrink-0 tw-flex-col tw-items-center',
|
|
29
|
-
conflicts.length > 0 ? 'tw-w-
|
|
38
|
+
conflicts.length > 0 ? 'tw-w-7' : 'tw-w-0',
|
|
30
39
|
)}
|
|
31
40
|
>
|
|
32
41
|
{conflicts.length > 0 && (
|
|
@@ -44,16 +53,18 @@ export const AcceptControls = ({ conflicts, side, onAccept, onDismiss, contentRe
|
|
|
44
53
|
className="tw-absolute tw-inset-x-0 tw-flex tw-flex-col tw-items-center tw-gap-0.5"
|
|
45
54
|
style={{ top: `${topOffset}px` }}
|
|
46
55
|
>
|
|
47
|
-
<AcceptButton side={side} onAccept={() => onAccept(conflict)} />
|
|
56
|
+
<AcceptButton side={side} onAccept={() => onAccept(conflict)} argo={argo} />
|
|
48
57
|
{onDismiss && (
|
|
49
58
|
<Button
|
|
50
|
-
icon="xmark"
|
|
51
59
|
onClick={() => onDismiss(conflict)}
|
|
52
60
|
variant="outline"
|
|
53
|
-
size="icon"
|
|
61
|
+
size="icon-sm"
|
|
54
62
|
title="Dismiss"
|
|
55
63
|
aria-label={`Dismiss ${side === 'left' ? 'production' : 'branch'} change`}
|
|
56
|
-
|
|
64
|
+
argo={argo}
|
|
65
|
+
>
|
|
66
|
+
<X className="tw-size-4" />
|
|
67
|
+
</Button>
|
|
57
68
|
)}
|
|
58
69
|
</div>
|
|
59
70
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { AlertTriangle, CircleCheck } from '../../../icons';
|
|
1
2
|
import { cn } from '../../../utils';
|
|
2
|
-
import { Icon } from '../../Icon';
|
|
3
3
|
|
|
4
4
|
type Props = {
|
|
5
5
|
remainingConflicts: number;
|
|
@@ -31,7 +31,7 @@ export const ConflictStatusIndicator = ({ remainingConflicts, remainingSafeChang
|
|
|
31
31
|
colorClass,
|
|
32
32
|
)}
|
|
33
33
|
>
|
|
34
|
-
|
|
34
|
+
{allDone ? <CircleCheck className="tw-size-4" /> : <AlertTriangle className="tw-size-4" />}
|
|
35
35
|
{allDone ? 'All changes resolved' : `${parts.join(', ')} remaining`}
|
|
36
36
|
</p>
|
|
37
37
|
);
|
|
@@ -86,6 +86,7 @@ type MergeEditorHookProps = {
|
|
|
86
86
|
right: string;
|
|
87
87
|
leftLabel?: string;
|
|
88
88
|
rightLabel?: string;
|
|
89
|
+
argo?: boolean;
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
const MergeEditorWithHook = ({
|
|
@@ -94,6 +95,7 @@ const MergeEditorWithHook = ({
|
|
|
94
95
|
right = branchText,
|
|
95
96
|
leftLabel = 'Production',
|
|
96
97
|
rightLabel = 'Branch',
|
|
98
|
+
argo,
|
|
97
99
|
}: Partial<MergeEditorHookProps>) => {
|
|
98
100
|
const {
|
|
99
101
|
mergeValue,
|
|
@@ -148,6 +150,7 @@ const MergeEditorWithHook = ({
|
|
|
148
150
|
middleClassName="tw-border-blue-400"
|
|
149
151
|
rightHeader={<PaneHeader label={rightLabel} sublabel="Version #3" />}
|
|
150
152
|
className="tw-flex-1"
|
|
153
|
+
argo={argo}
|
|
151
154
|
/>
|
|
152
155
|
</div>
|
|
153
156
|
);
|
|
@@ -209,3 +212,15 @@ export const BothSidesAddRows: Story = {
|
|
|
209
212
|
/>
|
|
210
213
|
),
|
|
211
214
|
};
|
|
215
|
+
|
|
216
|
+
export const Argo: Story = {
|
|
217
|
+
render: () => <MergeEditorWithHook argo />,
|
|
218
|
+
parameters: {
|
|
219
|
+
docs: {
|
|
220
|
+
description: {
|
|
221
|
+
story:
|
|
222
|
+
'Same merge editor with the `argo` prop set. Accept and dismiss buttons inside the editor pick up the argo button palette while the layout chrome is unchanged.',
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
@@ -30,6 +30,12 @@ type Props = {
|
|
|
30
30
|
middleClassName?: string;
|
|
31
31
|
rightHeader?: ReactNode;
|
|
32
32
|
className?: string;
|
|
33
|
+
/**
|
|
34
|
+
* When true, the editor's internal accept and dismiss buttons render in the
|
|
35
|
+
* `argo` button variant. Layout chrome (panes, pane headers, scrollers) is
|
|
36
|
+
* unchanged — only consumer-controlled interactive surfaces switch theme.
|
|
37
|
+
*/
|
|
38
|
+
argo?: boolean;
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
export const MergeEditor = ({
|
|
@@ -55,6 +61,7 @@ export const MergeEditor = ({
|
|
|
55
61
|
middleClassName,
|
|
56
62
|
rightHeader,
|
|
57
63
|
className,
|
|
64
|
+
argo,
|
|
58
65
|
}: Props) => {
|
|
59
66
|
const { registerScroller, registerFollower } = useScrollSync();
|
|
60
67
|
|
|
@@ -81,6 +88,7 @@ export const MergeEditor = ({
|
|
|
81
88
|
onAccept={acceptLeft}
|
|
82
89
|
onDismiss={dismissLeft}
|
|
83
90
|
contentRef={registerFollower(0, 'left-controls')}
|
|
91
|
+
argo={argo}
|
|
84
92
|
/>
|
|
85
93
|
|
|
86
94
|
<MergePane
|
|
@@ -100,6 +108,7 @@ export const MergeEditor = ({
|
|
|
100
108
|
onAccept={acceptRight}
|
|
101
109
|
onDismiss={dismissRight}
|
|
102
110
|
contentRef={registerFollower(2, 'right-controls')}
|
|
111
|
+
argo={argo}
|
|
103
112
|
/>
|
|
104
113
|
|
|
105
114
|
<MergePane
|
|
@@ -44,15 +44,19 @@ export const MergePane = ({
|
|
|
44
44
|
backgroundColor: colors.neutral[50],
|
|
45
45
|
outline: 'none',
|
|
46
46
|
},
|
|
47
|
-
'&.cm-editor .cm-scroller': {
|
|
48
|
-
alignItems: 'stretch !important',
|
|
49
|
-
},
|
|
50
47
|
'.cm-gutters': {
|
|
51
48
|
backgroundColor: colors.neutral[100],
|
|
52
49
|
},
|
|
53
50
|
'*': { lineHeight: `${LINE_HEIGHT_PX}px` },
|
|
54
51
|
'.cm-lineNumbers .cm-gutterElement': { padding: '0 0 0 25px' },
|
|
55
52
|
}),
|
|
53
|
+
// Recalc viewport after every doc change so late-settling layouts (alerts
|
|
54
|
+
// appearing above the editor) don't leave trailing lines blank.
|
|
55
|
+
EditorView.updateListener.of((update) => {
|
|
56
|
+
if (update.docChanged) {
|
|
57
|
+
requestAnimationFrame(() => update.view.dispatch({}));
|
|
58
|
+
}
|
|
59
|
+
}),
|
|
56
60
|
],
|
|
57
61
|
[],
|
|
58
62
|
);
|
|
@@ -76,12 +80,26 @@ export const MergePane = ({
|
|
|
76
80
|
return () => onScrollDOMReady?.(null);
|
|
77
81
|
}, [onScrollDOMReady]);
|
|
78
82
|
|
|
83
|
+
// Re-measure on wrapper resize — parent dimensions settle after CM mounts
|
|
84
|
+
// (fullscreen modal, alerts appearing above the editor).
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const view = editorRef.current?.view;
|
|
87
|
+
const wrapper = view?.dom.parentElement;
|
|
88
|
+
if (!view || !wrapper) return;
|
|
89
|
+
|
|
90
|
+
const observer = new ResizeObserver(() => view.dispatch({}));
|
|
91
|
+
observer.observe(wrapper);
|
|
92
|
+
|
|
93
|
+
return () => observer.disconnect();
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
79
96
|
return (
|
|
80
97
|
<div className={cn('tw-flex tw-min-w-0 tw-flex-1 tw-flex-col tw-overflow-hidden', className)}>
|
|
81
98
|
{header && <div className={cn('tw-bg-white tw-px-4 tw-py-3', headerClassName)}>{header}</div>}
|
|
82
|
-
<div className="tw-flex tw-h-0 tw-flex-1 tw-flex-col [&_>div]:tw-h-full
|
|
99
|
+
<div className="tw-flex tw-h-0 tw-flex-1 tw-flex-col [&_>div]:tw-h-full">
|
|
83
100
|
<CodeMirror
|
|
84
101
|
ref={editorRef}
|
|
102
|
+
height="100%"
|
|
85
103
|
value={value}
|
|
86
104
|
onChange={onChange}
|
|
87
105
|
extensions={extensions}
|
|
@@ -29,11 +29,11 @@ const safeDecoration = Decoration.line({ class: 'cm-merge-safe-line' });
|
|
|
29
29
|
const conflictDecoration = Decoration.line({ class: 'cm-merge-conflict-line' });
|
|
30
30
|
|
|
31
31
|
class SafeGutterMarker extends GutterMarker {
|
|
32
|
-
elementClass = 'cm-merge-safe-gutter';
|
|
32
|
+
override elementClass = 'cm-merge-safe-gutter';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
class ConflictGutterMarker extends GutterMarker {
|
|
36
|
-
elementClass = 'cm-merge-conflict-gutter';
|
|
36
|
+
override elementClass = 'cm-merge-conflict-gutter';
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const safeGutterMarker = new SafeGutterMarker();
|
|
@@ -3,6 +3,8 @@ import type { ComponentProps, FormEventHandler } from 'react';
|
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
4
|
import { action } from 'storybook/actions';
|
|
5
5
|
|
|
6
|
+
import { Rocket } from '@keboola/design/icons';
|
|
7
|
+
|
|
6
8
|
import { Alert } from '../Alert';
|
|
7
9
|
import { Button } from '../Button';
|
|
8
10
|
import { ButtonGroup } from '../ButtonGroup';
|
|
@@ -28,7 +30,7 @@ const ModalDefault = (props: ComponentProps<typeof Modal>) => (
|
|
|
28
30
|
</Modal.Trigger>
|
|
29
31
|
|
|
30
32
|
<Modal.Content>
|
|
31
|
-
<Modal.Icon
|
|
33
|
+
<Modal.Icon color="red" icon={<Rocket />} />
|
|
32
34
|
<Modal.Header title="Default Modal" description="Opens default modal">
|
|
33
35
|
<Alert variant="info">Additional Header stuff</Alert>
|
|
34
36
|
</Modal.Header>
|
|
@@ -68,7 +70,7 @@ const ModalLongContent = (props: ComponentProps<typeof Modal>) => (
|
|
|
68
70
|
</Modal.Trigger>
|
|
69
71
|
|
|
70
72
|
<Modal.Content>
|
|
71
|
-
<Modal.Icon
|
|
73
|
+
<Modal.Icon color="red" icon={<Rocket />} />
|
|
72
74
|
<Modal.Header title="Default Modal" description="Opens default modal">
|
|
73
75
|
<Alert variant="info">Addiitonal Header staff</Alert>
|
|
74
76
|
</Modal.Header>
|
|
@@ -232,7 +234,7 @@ const ModalRemoteFormSubmission = (props: ComponentProps<typeof Modal>) => {
|
|
|
232
234
|
</Modal.Trigger>
|
|
233
235
|
|
|
234
236
|
<Modal.Content>
|
|
235
|
-
<Modal.Icon
|
|
237
|
+
<Modal.Icon color="red" icon={<Rocket />} />
|
|
236
238
|
|
|
237
239
|
<Modal.Header title="Crate User" description="Opens modal with remote form submission" />
|
|
238
240
|
|
|
@@ -418,7 +420,7 @@ const ModalMultiStepContent = () => {
|
|
|
418
420
|
|
|
419
421
|
return (
|
|
420
422
|
<>
|
|
421
|
-
<Modal.Icon
|
|
423
|
+
<Modal.Icon color="blue" icon={<Rocket />} />
|
|
422
424
|
<Modal.Header title="Wizard Form" description="Opens modal with multi-step form" />
|
|
423
425
|
|
|
424
426
|
<Modal.Body>
|
|
@@ -8,16 +8,42 @@ import { ModalHeader } from './ModalHeader';
|
|
|
8
8
|
import { ModalIcon } from './ModalIcon';
|
|
9
9
|
import { ModalOverlay } from './ModalOverlay';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Props for the `Modal` root — forwarded to Radix `Dialog.Root` (controls `open`, `onOpenChange`,
|
|
13
|
+
* `defaultOpen`, `modal`).
|
|
14
|
+
*/
|
|
11
15
|
type Props = ComponentProps<typeof Root>;
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Blocking dialog for confirmations, forms, and detail views. Built on Radix Dialog.
|
|
19
|
+
*
|
|
20
|
+
* **When to use:** the user must act before continuing (confirm a destructive action, fill a short form,
|
|
21
|
+
* view a focused detail panel). Use the compound parts `Modal.Trigger`, `Modal.Content`, `Modal.Header`,
|
|
22
|
+
* `Modal.Body`, `Modal.Footer`, and optionally `Modal.Icon` for a status-coloured header glyph.
|
|
23
|
+
* **When NOT to use:** for slide-in side editing use `Sheet`; for an inline destructive confirmation anchored
|
|
24
|
+
* to its trigger use `Popconfirm`; for hover previews use `HoverCard`.
|
|
25
|
+
* **Composition:** wrap `Modal.Content` around `Modal.Header` + `Modal.Body` + `Modal.Footer`. Put `useAppForm`
|
|
26
|
+
* `FormBody` inside `Modal.Body` and the submit / cancel buttons inside `Modal.Footer`.
|
|
27
|
+
*
|
|
28
|
+
* @see RECIPES.md#modal-form — for the canonical form-in-modal composition
|
|
29
|
+
* @see RECIPES.md#confirm-dialog — for destructive-confirm dialogs
|
|
30
|
+
*/
|
|
13
31
|
export const Modal = (props: Props) => <Root {...props} />;
|
|
14
32
|
|
|
33
|
+
/** Radix `Dialog.Close` re-export — closes the nearest `Modal` when clicked. Use `asChild` to wrap a `Button`. */
|
|
15
34
|
Modal.Close = Close;
|
|
35
|
+
/** Radix `Dialog.Trigger` re-export — opens the modal. Use `asChild` to wrap a `Button`. */
|
|
16
36
|
Modal.Trigger = Trigger;
|
|
17
37
|
|
|
38
|
+
/** Modal content container — portals into the overlay, handles sizing (`small` / `medium` / `large`) and open/close animation. */
|
|
18
39
|
Modal.Content = ModalContent;
|
|
40
|
+
/** Scrollable body slot. Place form fields or detail content here; takes available space inside `Modal.Content`. */
|
|
19
41
|
Modal.Body = ModalBody;
|
|
42
|
+
/** Status-coloured circular icon docked to the top-left of `Modal.Content`. Use `color="red"` for destructive, `"orange"` for warning, etc. */
|
|
20
43
|
Modal.Icon = ModalIcon;
|
|
44
|
+
/** Bottom action row. Place the primary + cancel `Button`s here; sits below `Modal.Body`. */
|
|
21
45
|
Modal.Footer = ModalFooter;
|
|
46
|
+
/** Top row with title, optional description, and close icon. Pass `title` / `description` props or nest custom JSX. */
|
|
22
47
|
Modal.Header = ModalHeader;
|
|
48
|
+
/** Backdrop overlay rendered behind `Modal.Content`. Normally implicit — only override for non-standard layouts. */
|
|
23
49
|
Modal.Overlay = ModalOverlay;
|
|
@@ -2,10 +2,15 @@ import type { ComponentProps } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { cn } from '../../utils';
|
|
4
4
|
|
|
5
|
+
/** Props for `Modal.Body`. Accepts all `<div>` attributes; `as="form"` is reserved for future use. */
|
|
5
6
|
type Props = ComponentProps<'div'> & {
|
|
6
7
|
as?: 'form';
|
|
7
8
|
};
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Scrollable body slot for `Modal`. Place form fields, content blocks, or detail panels here.
|
|
12
|
+
* Takes available height inside `Modal.Content` and scrolls when content overflows.
|
|
13
|
+
*/
|
|
9
14
|
export const ModalBody = ({ className, ...props }: Props) => (
|
|
10
15
|
<div
|
|
11
16
|
data-slot="modal-body"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
import { Close } from '@radix-ui/react-dialog';
|
|
3
3
|
|
|
4
|
+
import { X } from '../../icons';
|
|
4
5
|
import { cn } from '../../utils';
|
|
5
|
-
import { Icon } from '../Icon';
|
|
6
6
|
|
|
7
7
|
type Props = ComponentProps<typeof Close>;
|
|
8
8
|
|
|
@@ -18,8 +18,7 @@ export const ModalCloseIcon = ({ className, ...rest }: Props) => (
|
|
|
18
18
|
className,
|
|
19
19
|
)}
|
|
20
20
|
>
|
|
21
|
-
<
|
|
22
|
-
icon="close"
|
|
21
|
+
<X
|
|
23
22
|
className={cn(
|
|
24
23
|
'tw-size-4',
|
|
25
24
|
'tw-text-neutral-400',
|
|
@@ -5,8 +5,16 @@ import { cn } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
import { ModalOverlay } from './ModalOverlay';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Props for `Modal.Content`. `size` controls the max-width (`small` 480px / `medium` default / `large` 4xl).
|
|
10
|
+
* All other Radix `Dialog.Content` props (`onEscapeKeyDown`, `onPointerDownOutside`, …) pass through.
|
|
11
|
+
*/
|
|
8
12
|
type Props = ComponentProps<typeof Content> & { size?: 'small' | 'medium' | 'large' };
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Modal content surface — portals into the overlay and renders the header / body / footer slots.
|
|
16
|
+
* Stops escape-key propagation so a parent `FullScreenModal` does not also close.
|
|
17
|
+
*/
|
|
10
18
|
export const ModalContent = ({
|
|
11
19
|
size = 'medium',
|
|
12
20
|
className,
|
|
@@ -2,8 +2,13 @@ import type { ComponentProps } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { cn } from '../../utils';
|
|
4
4
|
|
|
5
|
+
/** Props for `Modal.Footer`. Accepts all `<div>` attributes. */
|
|
5
6
|
type Props = ComponentProps<'div'>;
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Bottom action row for `Modal`. Layout is up to the caller — typically `tw-flex tw-justify-end tw-gap-2`
|
|
10
|
+
* with cancel + primary `Button`s.
|
|
11
|
+
*/
|
|
7
12
|
export const ModalFooter = ({ className, ...props }: Props) => (
|
|
8
13
|
<div
|
|
9
14
|
className={cn(
|
|
@@ -6,12 +6,23 @@ import { ModalCloseIcon } from './ModalCloseIcon';
|
|
|
6
6
|
import { ModalDescription } from './ModalDescription';
|
|
7
7
|
import { ModalTitle } from './ModalTitle';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Props for `Modal.Header`. Pass `title` + optional `description` for the standard layout, or nest custom JSX
|
|
11
|
+
* as `children` to render below the title row.
|
|
12
|
+
*/
|
|
9
13
|
type Props = {
|
|
14
|
+
/** Title text rendered as `ModalTitle`. Wraps a Radix `Dialog.Title` for a11y. */
|
|
10
15
|
title?: ReactNode;
|
|
16
|
+
/** Subtitle / supporting text rendered below the title. */
|
|
11
17
|
description?: ReactNode;
|
|
18
|
+
/** Hide the top-right close icon. Use when the modal must not be dismissible by the icon (still closable via `Modal.Close`). */
|
|
12
19
|
hideCloseIcon?: boolean;
|
|
13
20
|
} & ComponentProps<'div'>;
|
|
14
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Modal header slot — title + description row with a built-in close icon.
|
|
24
|
+
* Renders a bottom border when followed by a `Modal.Body`; drops the border when body contains `Tabs`.
|
|
25
|
+
*/
|
|
15
26
|
export const ModalHeader = ({
|
|
16
27
|
className,
|
|
17
28
|
title,
|