@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.
Files changed (177) hide show
  1. package/README.md +25 -0
  2. package/dist/icon-names.cjs +18 -0
  3. package/dist/icon-names.cjs.map +1 -0
  4. package/dist/icon-names.js +16 -0
  5. package/dist/icon-names.js.map +1 -0
  6. package/dist/icons.cjs +731 -0
  7. package/dist/icons.cjs.map +1 -0
  8. package/dist/icons.js +29 -0
  9. package/dist/icons.js.map +1 -0
  10. package/dist/index.cjs +14728 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.js +14553 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/markdown-editor.cjs +4440 -0
  15. package/dist/markdown-editor.cjs.map +1 -0
  16. package/dist/markdown-editor.js +4413 -0
  17. package/dist/markdown-editor.js.map +1 -0
  18. package/dist/mermaid.cjs +1011 -0
  19. package/dist/mermaid.cjs.map +1 -0
  20. package/dist/mermaid.js +1005 -0
  21. package/dist/mermaid.js.map +1 -0
  22. package/dist/styles.css +1 -0
  23. package/package.json +10 -3
  24. package/src/blocks/AIKit/CollapsibleStepHeader.tsx +7 -8
  25. package/src/blocks/AIKit/InlineStepLabel.tsx +7 -7
  26. package/src/blocks/AIKit/MessageBubble.stories.tsx +11 -3
  27. package/src/blocks/AIKit/MessageBubble.tsx +1 -1
  28. package/src/blocks/AIKit/PlanApproval.tsx +9 -8
  29. package/src/blocks/AIKit/PlanRewritingIndicator.tsx +2 -2
  30. package/src/blocks/AIKit/PromptInput.stories.tsx +10 -2
  31. package/src/blocks/AIKit/PromptInputSubmit.tsx +12 -8
  32. package/src/blocks/AIKit/Question.tsx +8 -9
  33. package/src/blocks/AIKit/SuggestedPrompt.tsx +12 -5
  34. package/src/blocks/AIKit/ThinkingIndicator.tsx +2 -2
  35. package/src/blocks/AutoSaveTextarea/AutoSaveTextarea.tsx +4 -4
  36. package/src/blocks/ClipboardButton/ClipboardButton.tsx +7 -5
  37. package/src/blocks/ClipboardButton/ClipboardInline.tsx +2 -3
  38. package/src/blocks/ColorInput/ColorInput.tsx +4 -2
  39. package/src/blocks/ColorInput/ColorPicker.tsx +5 -2
  40. package/src/blocks/EmptyState/EmptyState.tsx +16 -0
  41. package/src/blocks/ErrorState/ErrorState.tsx +15 -0
  42. package/src/blocks/Form/formHook.ts +15 -0
  43. package/src/blocks/MultiActionButtonGroup/MultiActionButton.stories.tsx +12 -11
  44. package/src/blocks/MultiActionButtonGroup/MultiActionButton.tsx +5 -4
  45. package/src/blocks/MultiCombobox/MultiCombobox.tsx +28 -12
  46. package/src/blocks/PageHeader/AGENTS.md +63 -3
  47. package/src/blocks/PageHeader/PageHeader.stories.tsx +124 -28
  48. package/src/blocks/PageHeader/PageHeader.tsx +15 -0
  49. package/src/blocks/PageHeader/PageHeaderDetail.tsx +23 -1
  50. package/src/blocks/PageHeader/PageHeaderIndex.tsx +20 -1
  51. package/src/blocks/PageLayout/PageLayout.tsx +26 -0
  52. package/src/blocks/PageToolbar/PageToolbar.stories.tsx +348 -0
  53. package/src/blocks/PageToolbar/PageToolbar.tsx +119 -0
  54. package/src/blocks/PageToolbar/index.ts +1 -0
  55. package/src/blocks/PasswordInput/PasswordInput.tsx +10 -10
  56. package/src/blocks/SearchInput/SearchInput.tsx +30 -4
  57. package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.tsx +9 -6
  58. package/src/blocks/SplitButton/SplitButton.stories.tsx +7 -4
  59. package/src/blocks/SplitButton/SplitButton.tsx +17 -4
  60. package/src/blocks/Tagger/TagItem.tsx +4 -1
  61. package/src/blocks/Tagger/TagSelectPopover/TagSelectCreateItem.tsx +3 -1
  62. package/src/blocks/Tagger/TagSelectPopover/TagSelectList.tsx +2 -2
  63. package/src/blocks/Tagger/Tagger.tsx +2 -2
  64. package/src/components/Alert/Alert.stories.tsx +11 -3
  65. package/src/components/Alert/Alert.tsx +19 -1
  66. package/src/components/Alert/AlertCollapsibleIcon.tsx +3 -4
  67. package/src/components/Alert/AlertIcon.tsx +18 -21
  68. package/src/components/Badge/Badge.stories.tsx +22 -4
  69. package/src/components/Badge/Badge.tsx +16 -0
  70. package/src/components/Badge/BadgeButton.tsx +7 -6
  71. package/src/components/Breadcrumb/BreadcrumbEllipsis.tsx +2 -2
  72. package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +2 -2
  73. package/src/components/Button/Button.stories.tsx +14 -2
  74. package/src/components/Button/Button.tsx +42 -5
  75. package/src/components/Button/styles.ts +4 -6
  76. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +32 -10
  77. package/src/components/Card/Card.tsx +17 -0
  78. package/src/components/Checkbox/Checkbox.tsx +3 -6
  79. package/src/components/CodeEditor/highlighting.ts +1 -1
  80. package/src/components/CollapsiblePanel.tsx +2 -5
  81. package/src/components/Combobox/Combobox.tsx +12 -14
  82. package/src/components/Combobox/ComboboxChips.tsx +6 -1
  83. package/src/components/Combobox/ComboboxItem.tsx +3 -4
  84. package/src/components/Combobox/ComboboxItemIndicator.tsx +6 -4
  85. package/src/components/Combobox/types.ts +2 -6
  86. package/src/components/Command/Command.stories.tsx +47 -56
  87. package/src/components/Command/CommandItem/CheckIndicator.tsx +26 -14
  88. package/src/components/Command/CommandItem/CommandItem.tsx +6 -3
  89. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +8 -4
  90. package/src/components/DropdownMenu/DropdownMenuCheckboxItem.tsx +4 -1
  91. package/src/components/DropdownMenu/DropdownMenuItem.tsx +12 -6
  92. package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +12 -6
  93. package/src/components/DropdownMenu/DropdownMenuRadioGroup/DropdownMenuRadioItem.tsx +8 -5
  94. package/src/components/DropdownMenu/DropdownMenuSub/DropdownMenuSubTrigger.tsx +2 -2
  95. package/src/components/DropdownMenu/ItemIcon.tsx +16 -8
  96. package/src/components/FormGroup/FormGroup.tsx +15 -0
  97. package/src/components/FullScreenModal/FullScreenModalHeader.tsx +4 -1
  98. package/src/components/Input/DateInput/DateInput.tsx +4 -1
  99. package/src/components/Input/DateRangeInput/DateRangeInput.tsx +4 -6
  100. package/src/components/Input/Input.tsx +18 -2
  101. package/src/components/Input/TextInput/TextInput.tsx +16 -0
  102. package/src/components/Input/TimeInput/TimeInput.tsx +4 -1
  103. package/src/components/InputGroup/InputGroup.stories.tsx +11 -3
  104. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +63 -1
  105. package/src/components/MarkdownEditor/MarkdownEditor.tsx +105 -15
  106. package/src/components/MarkdownEditor/nodes/ImageNode.tsx +10 -10
  107. package/src/components/MarkdownEditor/nodes/MermaidNode.tsx +8 -8
  108. package/src/components/MarkdownEditor/plugins/KeyboardPlugin.tsx +47 -3
  109. package/src/components/MarkdownEditor/plugins/MarkdownSyncPlugin.tsx +12 -3
  110. package/src/components/MarkdownEditor/plugins/ToolbarPlugin.tsx +20 -10
  111. package/src/components/MarkdownEditor/plugins/toolbar/AlignmentDropdown.tsx +20 -14
  112. package/src/components/MarkdownEditor/plugins/toolbar/BlockTypeDropdown.tsx +25 -16
  113. package/src/components/MarkdownEditor/plugins/toolbar/LinkPanel.tsx +3 -3
  114. package/src/components/MarkdownEditor/transformers.ts +10 -0
  115. package/src/components/MergeEditor/AcceptControls/AcceptButton.tsx +9 -4
  116. package/src/components/MergeEditor/AcceptControls/AcceptControls.tsx +25 -14
  117. package/src/components/MergeEditor/ConflictStatus/ConflictStatusIndicator.tsx +2 -2
  118. package/src/components/MergeEditor/MergeEditor.stories.tsx +15 -0
  119. package/src/components/MergeEditor/MergeEditor.tsx +9 -0
  120. package/src/components/MergeEditor/MergePane.tsx +22 -4
  121. package/src/components/MergeEditor/decorations.ts +2 -2
  122. package/src/components/Modal/Modal.stories.tsx +6 -4
  123. package/src/components/Modal/Modal.tsx +26 -0
  124. package/src/components/Modal/ModalBody.tsx +5 -0
  125. package/src/components/Modal/ModalCloseIcon.tsx +2 -3
  126. package/src/components/Modal/ModalContent.tsx +8 -0
  127. package/src/components/Modal/ModalFooter.tsx +5 -0
  128. package/src/components/Modal/ModalHeader.tsx +11 -0
  129. package/src/components/Modal/ModalIcon.tsx +45 -19
  130. package/src/components/Navbar/Navbar.stories.tsx +26 -13
  131. package/src/components/Navbar/NavbarMenuButton.tsx +11 -10
  132. package/src/components/Pagination/PaginationEllipsis.tsx +2 -2
  133. package/src/components/Pagination/PaginationNext.tsx +2 -1
  134. package/src/components/Pagination/PaginationPrevious.tsx +4 -1
  135. package/src/components/PanelWithDetails.tsx +3 -4
  136. package/src/components/Popconfirm/Popconfirm.stories.tsx +13 -5
  137. package/src/components/RadioGroup/Radio.tsx +6 -12
  138. package/src/components/Search/ClearButton.tsx +2 -2
  139. package/src/components/Search/Search.tsx +3 -3
  140. package/src/components/Select/Select.tsx +16 -5
  141. package/src/components/Select/SelectContent.tsx +13 -4
  142. package/src/components/Select/SelectItem.tsx +6 -5
  143. package/src/components/Select/SelectTrigger.tsx +2 -2
  144. package/src/components/Sidebar/Sidebar.stories.tsx +55 -19
  145. package/src/components/Sidebar/SidebarGroup/SidebarGroupAction.tsx +8 -4
  146. package/src/components/Sidebar/SidebarMenu/SidebarMenuButton.tsx +4 -2
  147. package/src/components/Sidebar/SidebarMenu/SidebarMenuCard.tsx +3 -2
  148. package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +12 -12
  149. package/src/components/Sidebar/SidebarMenu/SidebarMenuSub.tsx +3 -7
  150. package/src/components/Sidebar/SidebarMenu/SidebarSearchInput.tsx +3 -5
  151. package/src/components/Skeleton/Skeleton.stories.tsx +52 -0
  152. package/src/components/Skeleton/Skeleton.tsx +24 -1
  153. package/src/components/Table/Table.stories.tsx +223 -1
  154. package/src/components/Table/Table.tsx +17 -0
  155. package/src/components/Tabs.tsx +25 -2
  156. package/src/components/Toast/ToastDismiss.tsx +3 -3
  157. package/src/components/Toast/ToastRoot.tsx +24 -19
  158. package/src/components/Toggle/Toggle.tsx +11 -6
  159. package/src/components/Tooltip/Tooltip.tsx +23 -0
  160. package/src/components/ZoomableSvg/ZoomableSvg.tsx +8 -6
  161. package/src/icons/Keboola.tsx +28 -0
  162. package/src/icons/icons.stories.tsx +174 -0
  163. package/src/icons/index.ts +180 -0
  164. package/src/index.ts +1 -0
  165. package/src/patterns/AlertWithAction.stories.tsx +30 -0
  166. package/src/patterns/CardWithEmptyState.stories.tsx +66 -0
  167. package/src/patterns/ConfirmDialog.stories.tsx +72 -0
  168. package/src/patterns/CreatableMultiCombobox.stories.tsx +33 -0
  169. package/src/patterns/DataCardWithSkeleton.stories.tsx +48 -0
  170. package/src/patterns/DisabledButtonTooltip.stories.tsx +30 -0
  171. package/src/patterns/FilterToolbar.stories.tsx +71 -0
  172. package/src/patterns/InfoTooltip.stories.tsx +56 -0
  173. package/src/patterns/ListDetail.stories.tsx +65 -0
  174. package/src/patterns/ModalForm.stories.tsx +59 -0
  175. package/src/patterns/PageHeaderTabs.stories.tsx +48 -0
  176. package/src/styles.css +16 -8
  177. 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: ['far', 'font'] as IconProp, selectable: true },
25
- h1: { label: 'Heading 1', icon: ['far', 'h1'] as IconProp, selectable: true },
26
- h2: { label: 'Heading 2', icon: ['far', 'h2'] as IconProp, selectable: true },
27
- h3: { label: 'Heading 3', icon: ['far', 'h3'] as IconProp, selectable: true },
28
- bullet: { label: 'Bullet List', icon: ['far', 'list-ul'] as IconProp, selectable: false },
29
- number: { label: 'Numbered List', icon: ['far', 'list-ol'] as IconProp, selectable: false },
30
- quote: { label: 'Quote', icon: ['far', 'block-quote'] as IconProp, selectable: false },
31
- code: { label: 'Code Block', icon: 'square-code' as IconProp, selectable: false },
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
- <Icon icon={currentMeta?.icon ?? ['far', 'font']} fixedWidth />
119
+ <CurrentBlockIcon className="tw-size-4" />
111
120
  {currentMeta?.label ?? 'Normal'}
112
- <Icon icon={['fas', 'caret-down']} className="tw-text-[8px]" />
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
- <Icon icon={icon} fixedWidth />
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
- <Icon icon={['far', 'link']} />
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
- <Icon icon={['far', 'link']} />
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
- // 40px column when there are conflicts to render — wide enough to fit the
18
- // accept chevron + dismiss icon without overflowing into the adjacent panes
19
- // (which use `tw-overflow-hidden` and would clip absolutely-positioned
20
- // children that crossed the boundary). Collapses to 0 when idle so empty
21
- // merges don't reserve a sliver of layout. Inner wrapper allows horizontal
22
- // overflow so the icon buttons' focus-visible ring (4px) stays visible past
23
- // the column edge. Buttons stay aligned with their source-pane chunk via the
24
- // parent's `translateY(-scrollTop)` follower applied to `contentRef`.
25
- export const AcceptControls = ({ conflicts, side, onAccept, onDismiss, contentRef }: Props) => (
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-10' : 'tw-w-0',
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
- <Icon icon={allDone ? 'circle-check' : 'triangle-exclamation'} />
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 [&_>div]:tw-overflow-auto">
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 icon="rocket" color="red" />
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 icon="rocket" color="red" />
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 icon="rocket" color="red" />
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 icon="rocket" color="blue" />
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
- <Icon
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,