@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.
Files changed (173) hide show
  1. package/README.md +25 -0
  2. package/dist/icons.cjs +731 -0
  3. package/dist/icons.cjs.map +1 -0
  4. package/dist/icons.js +29 -0
  5. package/dist/icons.js.map +1 -0
  6. package/dist/index.cjs +429 -333
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +426 -331
  9. package/dist/index.js.map +1 -1
  10. package/dist/markdown-editor.cjs +270 -135
  11. package/dist/markdown-editor.cjs.map +1 -1
  12. package/dist/markdown-editor.js +270 -135
  13. package/dist/markdown-editor.js.map +1 -1
  14. package/dist/mermaid.cjs +24 -15
  15. package/dist/mermaid.cjs.map +1 -1
  16. package/dist/mermaid.js +24 -15
  17. package/dist/mermaid.js.map +1 -1
  18. package/dist/styles.css +1 -1
  19. package/package.json +12 -5
  20. package/src/blocks/AIKit/CollapsibleStepHeader.tsx +7 -8
  21. package/src/blocks/AIKit/InlineStepLabel.tsx +7 -7
  22. package/src/blocks/AIKit/MessageBubble.stories.tsx +11 -3
  23. package/src/blocks/AIKit/MessageBubble.tsx +1 -1
  24. package/src/blocks/AIKit/PlanApproval.tsx +9 -8
  25. package/src/blocks/AIKit/PlanRewritingIndicator.tsx +2 -2
  26. package/src/blocks/AIKit/PromptInput.stories.tsx +10 -2
  27. package/src/blocks/AIKit/PromptInputSubmit.tsx +12 -8
  28. package/src/blocks/AIKit/Question.tsx +8 -9
  29. package/src/blocks/AIKit/SuggestedPrompt.tsx +12 -5
  30. package/src/blocks/AIKit/ThinkingIndicator.tsx +2 -2
  31. package/src/blocks/AutoSaveTextarea/AutoSaveTextarea.tsx +4 -4
  32. package/src/blocks/ClipboardButton/ClipboardButton.tsx +7 -5
  33. package/src/blocks/ClipboardButton/ClipboardInline.tsx +2 -3
  34. package/src/blocks/ColorInput/ColorInput.tsx +4 -2
  35. package/src/blocks/ColorInput/ColorPicker.tsx +5 -2
  36. package/src/blocks/EmptyState/EmptyState.tsx +16 -0
  37. package/src/blocks/ErrorState/ErrorState.tsx +15 -0
  38. package/src/blocks/Form/formHook.ts +15 -0
  39. package/src/blocks/MultiActionButtonGroup/MultiActionButton.stories.tsx +12 -11
  40. package/src/blocks/MultiActionButtonGroup/MultiActionButton.tsx +4 -4
  41. package/src/blocks/MultiCombobox/MultiCombobox.tsx +28 -12
  42. package/src/blocks/PageHeader/AGENTS.md +63 -3
  43. package/src/blocks/PageHeader/PageHeader.stories.tsx +124 -28
  44. package/src/blocks/PageHeader/PageHeader.tsx +15 -0
  45. package/src/blocks/PageHeader/PageHeaderDetail.tsx +23 -1
  46. package/src/blocks/PageHeader/PageHeaderIndex.tsx +20 -1
  47. package/src/blocks/PageLayout/PageLayout.tsx +26 -0
  48. package/src/blocks/PageToolbar/PageToolbar.stories.tsx +348 -0
  49. package/src/blocks/PageToolbar/PageToolbar.tsx +119 -0
  50. package/src/blocks/PageToolbar/index.ts +1 -0
  51. package/src/blocks/PasswordInput/PasswordInput.tsx +10 -10
  52. package/src/blocks/SearchInput/SearchInput.tsx +30 -4
  53. package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.tsx +9 -6
  54. package/src/blocks/SplitButton/SplitButton.stories.tsx +7 -4
  55. package/src/blocks/SplitButton/SplitButton.tsx +17 -4
  56. package/src/blocks/Tagger/TagItem.tsx +4 -1
  57. package/src/blocks/Tagger/TagSelectPopover/TagSelectCreateItem.tsx +3 -1
  58. package/src/blocks/Tagger/TagSelectPopover/TagSelectList.tsx +2 -2
  59. package/src/blocks/Tagger/Tagger.tsx +2 -2
  60. package/src/components/Alert/Alert.stories.tsx +11 -3
  61. package/src/components/Alert/Alert.tsx +19 -1
  62. package/src/components/Alert/AlertCollapsibleIcon.tsx +3 -4
  63. package/src/components/Alert/AlertIcon.tsx +18 -21
  64. package/src/components/Badge/Badge.stories.tsx +22 -4
  65. package/src/components/Badge/Badge.tsx +16 -0
  66. package/src/components/Badge/BadgeButton.tsx +7 -6
  67. package/src/components/Breadcrumb/BreadcrumbEllipsis.tsx +2 -2
  68. package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +2 -2
  69. package/src/components/Button/Button.stories.tsx +14 -2
  70. package/src/components/Button/Button.tsx +42 -5
  71. package/src/components/Button/styles.ts +4 -6
  72. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +32 -10
  73. package/src/components/Card/Card.tsx +17 -0
  74. package/src/components/Checkbox/Checkbox.tsx +3 -6
  75. package/src/components/CodeEditor/highlighting.ts +1 -1
  76. package/src/components/CollapsiblePanel.tsx +2 -5
  77. package/src/components/Combobox/Combobox.tsx +12 -14
  78. package/src/components/Combobox/ComboboxChips.tsx +6 -1
  79. package/src/components/Combobox/ComboboxItem.tsx +3 -4
  80. package/src/components/Combobox/ComboboxItemIndicator.tsx +6 -4
  81. package/src/components/Combobox/types.ts +2 -6
  82. package/src/components/Command/Command.stories.tsx +47 -56
  83. package/src/components/Command/CommandItem/CheckIndicator.tsx +26 -14
  84. package/src/components/Command/CommandItem/CommandItem.tsx +6 -3
  85. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +8 -4
  86. package/src/components/DropdownMenu/DropdownMenuCheckboxItem.tsx +4 -1
  87. package/src/components/DropdownMenu/DropdownMenuItem.tsx +12 -6
  88. package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +12 -6
  89. package/src/components/DropdownMenu/DropdownMenuRadioGroup/DropdownMenuRadioItem.tsx +8 -5
  90. package/src/components/DropdownMenu/DropdownMenuSub/DropdownMenuSubTrigger.tsx +2 -2
  91. package/src/components/DropdownMenu/ItemIcon.tsx +16 -8
  92. package/src/components/FormGroup/FormGroup.tsx +15 -0
  93. package/src/components/FullScreenModal/FullScreenModalHeader.tsx +4 -1
  94. package/src/components/Input/DateInput/DateInput.tsx +4 -1
  95. package/src/components/Input/DateRangeInput/DateRangeInput.tsx +4 -6
  96. package/src/components/Input/Input.tsx +18 -2
  97. package/src/components/Input/TextInput/TextInput.tsx +16 -0
  98. package/src/components/Input/TimeInput/TimeInput.tsx +4 -1
  99. package/src/components/InputGroup/InputGroup.stories.tsx +11 -3
  100. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +63 -1
  101. package/src/components/MarkdownEditor/MarkdownEditor.tsx +105 -15
  102. package/src/components/MarkdownEditor/nodes/ImageNode.tsx +10 -10
  103. package/src/components/MarkdownEditor/nodes/MermaidNode.tsx +8 -8
  104. package/src/components/MarkdownEditor/plugins/KeyboardPlugin.tsx +47 -3
  105. package/src/components/MarkdownEditor/plugins/MarkdownSyncPlugin.tsx +12 -3
  106. package/src/components/MarkdownEditor/plugins/ToolbarPlugin.tsx +20 -10
  107. package/src/components/MarkdownEditor/plugins/toolbar/AlignmentDropdown.tsx +20 -14
  108. package/src/components/MarkdownEditor/plugins/toolbar/BlockTypeDropdown.tsx +25 -16
  109. package/src/components/MarkdownEditor/plugins/toolbar/LinkPanel.tsx +3 -3
  110. package/src/components/MarkdownEditor/transformers.ts +10 -0
  111. package/src/components/MergeEditor/AcceptControls/AcceptButton.tsx +9 -4
  112. package/src/components/MergeEditor/AcceptControls/AcceptControls.tsx +25 -14
  113. package/src/components/MergeEditor/ConflictStatus/ConflictStatusIndicator.tsx +2 -2
  114. package/src/components/MergeEditor/MergeEditor.stories.tsx +15 -0
  115. package/src/components/MergeEditor/MergeEditor.tsx +9 -0
  116. package/src/components/MergeEditor/MergePane.tsx +22 -4
  117. package/src/components/MergeEditor/decorations.ts +2 -2
  118. package/src/components/Modal/Modal.stories.tsx +6 -4
  119. package/src/components/Modal/Modal.tsx +26 -0
  120. package/src/components/Modal/ModalBody.tsx +5 -0
  121. package/src/components/Modal/ModalCloseIcon.tsx +2 -3
  122. package/src/components/Modal/ModalContent.tsx +8 -0
  123. package/src/components/Modal/ModalFooter.tsx +5 -0
  124. package/src/components/Modal/ModalHeader.tsx +11 -0
  125. package/src/components/Modal/ModalIcon.tsx +45 -19
  126. package/src/components/Navbar/Navbar.stories.tsx +26 -13
  127. package/src/components/Navbar/NavbarMenuButton.tsx +11 -10
  128. package/src/components/Pagination/PaginationEllipsis.tsx +2 -2
  129. package/src/components/Pagination/PaginationNext.tsx +2 -1
  130. package/src/components/Pagination/PaginationPrevious.tsx +4 -1
  131. package/src/components/PanelWithDetails.tsx +3 -4
  132. package/src/components/Popconfirm/Popconfirm.stories.tsx +13 -5
  133. package/src/components/RadioGroup/Radio.tsx +6 -12
  134. package/src/components/Search/ClearButton.tsx +2 -2
  135. package/src/components/Search/Search.tsx +3 -3
  136. package/src/components/Select/Select.tsx +16 -5
  137. package/src/components/Select/SelectContent.tsx +13 -4
  138. package/src/components/Select/SelectItem.tsx +6 -5
  139. package/src/components/Select/SelectTrigger.tsx +2 -2
  140. package/src/components/Sidebar/Sidebar.stories.tsx +55 -19
  141. package/src/components/Sidebar/SidebarGroup/SidebarGroupAction.tsx +8 -4
  142. package/src/components/Sidebar/SidebarMenu/SidebarMenuButton.tsx +4 -2
  143. package/src/components/Sidebar/SidebarMenu/SidebarMenuCard.tsx +3 -2
  144. package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +12 -12
  145. package/src/components/Sidebar/SidebarMenu/SidebarMenuSub.tsx +3 -7
  146. package/src/components/Sidebar/SidebarMenu/SidebarSearchInput.tsx +3 -5
  147. package/src/components/Skeleton/Skeleton.stories.tsx +52 -0
  148. package/src/components/Skeleton/Skeleton.tsx +24 -1
  149. package/src/components/Table/Table.stories.tsx +223 -1
  150. package/src/components/Table/Table.tsx +17 -0
  151. package/src/components/Tabs.tsx +25 -2
  152. package/src/components/Toast/ToastDismiss.tsx +3 -3
  153. package/src/components/Toast/ToastRoot.tsx +24 -19
  154. package/src/components/Toggle/Toggle.tsx +11 -6
  155. package/src/components/Tooltip/Tooltip.tsx +23 -0
  156. package/src/components/ZoomableSvg/ZoomableSvg.tsx +8 -6
  157. package/src/icons/Keboola.tsx +28 -0
  158. package/src/icons/icons.stories.tsx +174 -0
  159. package/src/icons/index.ts +180 -0
  160. package/src/index.ts +1 -0
  161. package/src/patterns/AlertWithAction.stories.tsx +30 -0
  162. package/src/patterns/CardWithEmptyState.stories.tsx +66 -0
  163. package/src/patterns/ConfirmDialog.stories.tsx +72 -0
  164. package/src/patterns/CreatableMultiCombobox.stories.tsx +33 -0
  165. package/src/patterns/DataCardWithSkeleton.stories.tsx +48 -0
  166. package/src/patterns/DisabledButtonTooltip.stories.tsx +30 -0
  167. package/src/patterns/FilterToolbar.stories.tsx +71 -0
  168. package/src/patterns/InfoTooltip.stories.tsx +56 -0
  169. package/src/patterns/ListDetail.stories.tsx +65 -0
  170. package/src/patterns/ModalForm.stories.tsx +59 -0
  171. package/src/patterns/PageHeaderTabs.stories.tsx +48 -0
  172. package/src/styles.css +16 -8
  173. package/src/utils/legacyIcon.tsx +28 -0
@@ -1,13 +1,26 @@
1
1
  import { useState } from 'react';
2
- import type { ComponentProps } from 'react';
2
+ import type { ReactNode } from 'react';
3
3
 
4
4
  import { cn } from '../../utils';
5
+ import { isFAIconValue } from '../../utils/legacyIcon';
5
6
  import { Icon } from '../Icon';
6
7
 
7
8
  type NamedColor = 'orange' | 'blue' | 'red' | 'green' | 'gray' | 'violet';
8
9
 
9
- type IconProps = Omit<ComponentProps<typeof Icon>, 'className'> & {
10
+ type IconProps = {
10
11
  color: NamedColor | (string & {});
12
+ /**
13
+ * Pass a Lucide icon node. Legacy FontAwesome `"name"` / `[prefix, name]`
14
+ * string values are still accepted during the C2 deprecation window and
15
+ * rendered via the legacy `<Icon>` wrapper.
16
+ */
17
+ icon: ReactNode;
18
+ /**
19
+ * @deprecated FA-icon `spin` pass-through kept only for the legacy
20
+ * `<Modal.Icon icon="spinner" spin />` callsite during the kbc-ui sweep.
21
+ * New code should render an animated Lucide loader as the `icon` node.
22
+ */
23
+ spin?: boolean;
11
24
  iconUrl?: never;
12
25
  iconAlt?: never;
13
26
  };
@@ -17,13 +30,24 @@ type ImageProps = {
17
30
  iconUrl: string;
18
31
  iconAlt?: string;
19
32
  icon?: never;
33
+ spin?: never;
20
34
  };
21
35
 
36
+ /**
37
+ * Discriminated props for `Modal.Icon` — either an `icon` slot (a Lucide icon)
38
+ * with a categorical `color`, or a remote `iconUrl` (renders an `<img>`).
39
+ * The two shapes are mutually exclusive.
40
+ */
22
41
  type Props = IconProps | ImageProps;
23
42
 
24
43
  const NAMED_COLORS = new Set<string>(['orange', 'blue', 'red', 'green', 'gray', 'violet']);
25
44
 
26
- export const ModalIcon = ({ color, ...props }: Props) => {
45
+ /**
46
+ * Status-coloured icon docked to the top-left of `Modal.Content`. Use the categorical `color`
47
+ * (`red` destructive, `orange` warning, `green` success, `blue` info, `gray` neutral, `violet` agent)
48
+ * to encode meaning. The colour palette is categorical and stays fixed across brand themes.
49
+ */
50
+ export const ModalIcon = (props: Props) => {
27
51
  const [hasImageError, setHasImageError] = useState(false);
28
52
 
29
53
  if ('iconUrl' in props && hasImageError) {
@@ -44,16 +68,19 @@ export const ModalIcon = ({ color, ...props }: Props) => {
44
68
  ) : (
45
69
  <div
46
70
  className={cn('tw-relative', 'tw-rounded-full', 'tw-size-12', {
47
- 'tw-bg-warning-500': color === 'orange',
48
- 'tw-bg-secondary-500': color === 'blue',
49
- 'tw-bg-primary-500': color === 'green',
50
- 'tw-bg-error-500': color === 'red',
51
- 'tw-bg-neutral-150': color === 'gray',
52
- 'tw-bg-purple-100': color === 'violet',
71
+ 'tw-bg-warning-500': props.color === 'orange',
72
+ 'tw-bg-secondary-500': props.color === 'blue',
73
+ 'tw-bg-primary-500': props.color === 'green',
74
+ 'tw-bg-error-500': props.color === 'red',
75
+ 'tw-bg-neutral-150': props.color === 'gray',
76
+ 'tw-bg-purple-100': props.color === 'violet',
53
77
  })}
54
- style={color && !NAMED_COLORS.has(color) ? { backgroundColor: color } : undefined}
78
+ style={
79
+ props.color && !NAMED_COLORS.has(props.color)
80
+ ? { backgroundColor: props.color }
81
+ : undefined
82
+ }
55
83
  >
56
- {/* Icon spinning uses CSS transforms, which conflict with center positioning, so a wrapper should be used */}
57
84
  <span
58
85
  className={cn(
59
86
  'tw-absolute',
@@ -65,16 +92,15 @@ export const ModalIcon = ({ color, ...props }: Props) => {
65
92
  'tw-left-1/2',
66
93
  '-tw-translate-x-1/2',
67
94
  '-tw-translate-y-1/2',
95
+ {
96
+ 'tw-text-white': props.color !== 'violet',
97
+ 'tw-text-purple-500': props.color === 'violet',
98
+ },
99
+ '[&_svg]:tw-size-6',
100
+ '[&_svg]:tw-aspect-square',
68
101
  )}
69
102
  >
70
- <Icon
71
- className={cn(
72
- { 'tw-text-white': color !== 'violet', 'tw-text-purple-500': color === 'violet' },
73
- 'tw-size-6',
74
- 'tw-aspect-square',
75
- )}
76
- {...props}
77
- />
103
+ {isFAIconValue(props.icon) ? <Icon icon={props.icon} spin={props.spin} /> : props.icon}
78
104
  </span>
79
105
  </div>
80
106
  )}
@@ -1,6 +1,7 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
 
4
+ import { Bell, Diamond, Keboola, Search } from '../../icons';
4
5
  import { Icon } from '../Icon';
5
6
 
6
7
  import { Navbar } from './Navbar';
@@ -44,20 +45,20 @@ export const Default: Story = {
44
45
  <Navbar {...props}>
45
46
  <Navbar.Header>
46
47
  <Navbar.MenuItemAdornment variant="icon" asChild>
47
- <Icon icon={['fak', 'keboola']} className="!tw-size-8 !tw-p-0" />
48
+ <Keboola className="!tw-size-8 tw-shrink-0 !tw-p-0" />
48
49
  </Navbar.MenuItemAdornment>
49
50
  </Navbar.Header>
50
51
 
51
52
  <Navbar.Content>
52
53
  <Navbar.Menu marker="forward-slash">
53
54
  <Navbar.MenuItem>
54
- <Navbar.MenuButton variant="emphasized" icon={['far', 'diamonds-4']}>
55
+ <Navbar.MenuButton variant="emphasized" icon={<Diamond className="tw-size-4" />}>
55
56
  Project
56
57
  <Icon icon={['far', 'angles-up-down']} />
57
58
  </Navbar.MenuButton>
58
59
  </Navbar.MenuItem>
59
60
  <Navbar.MenuItem>
60
- <Navbar.MenuButton icon={['far', 'diamond']}>
61
+ <Navbar.MenuButton icon={<Diamond className="tw-size-4" />}>
61
62
  Branch
62
63
  <Icon icon={['far', 'angles-up-down']} />
63
64
  </Navbar.MenuButton>
@@ -67,14 +68,18 @@ export const Default: Story = {
67
68
  <Navbar.Footer>
68
69
  <Navbar.Menu>
69
70
  <Navbar.MenuItem>
70
- <Navbar.MenuButton variant="search" icon="search">
71
+ <Navbar.MenuButton variant="search" icon={<Search className="tw-size-4" />}>
71
72
  Search
72
73
  <Navbar.MenuItemAdornment variant="shortcut">/</Navbar.MenuItemAdornment>
73
74
  </Navbar.MenuButton>
74
75
  </Navbar.MenuItem>
75
76
 
76
77
  <Navbar.MenuItem>
77
- <Navbar.MenuButton variant="icon" icon="bell" aria-label="Notifications" />
78
+ <Navbar.MenuButton
79
+ variant="icon"
80
+ aria-label="Notifications"
81
+ icon={<Bell className="tw-size-4" />}
82
+ />
78
83
  </Navbar.MenuItem>
79
84
 
80
85
  <Navbar.MenuItem>
@@ -94,7 +99,7 @@ export const ContentScrolling: Story = {
94
99
  <Navbar.Header>
95
100
  <Navbar.Menu>
96
101
  <Navbar.MenuItem>
97
- <Icon icon={['fak', 'keboola']} className="tw-mx-2 !tw-size-8 tw-text-secondary-500" />
102
+ <Keboola className="tw-mx-2 !tw-size-8 tw-shrink-0 tw-text-secondary-500" />
98
103
  </Navbar.MenuItem>
99
104
  </Navbar.Menu>
100
105
  </Navbar.Header>
@@ -102,7 +107,7 @@ export const ContentScrolling: Story = {
102
107
  <Navbar.Menu marker="forward-slash">
103
108
  {items.map((item) => (
104
109
  <Navbar.MenuItem key={item}>
105
- <Navbar.MenuButton icon={['far', 'diamond']}>
110
+ <Navbar.MenuButton icon={<Diamond className="tw-size-4" />}>
106
111
  {item}
107
112
  <Icon icon={['far', 'angles-up-down']} />
108
113
  </Navbar.MenuButton>
@@ -130,34 +135,42 @@ export const MenuItems: Story = {
130
135
  <Navbar.Content>
131
136
  <Navbar.Menu>
132
137
  <Navbar.MenuItem>
133
- <Navbar.MenuButton icon={['far', 'diamond']}>Default</Navbar.MenuButton>
138
+ <Navbar.MenuButton icon={<Diamond className="tw-size-4" />}>Default</Navbar.MenuButton>
134
139
  </Navbar.MenuItem>
135
140
  <Navbar.MenuItem>
136
- <Navbar.MenuButton icon={['far', 'diamond']} asChild>
141
+ <Navbar.MenuButton asChild icon={<Diamond className="tw-size-4" />}>
137
142
  <a href="https://www.keboola.com">Default - Anchor</a>
138
143
  </Navbar.MenuButton>
139
144
  </Navbar.MenuItem>
140
145
 
141
146
  <Navbar.MenuItem>
142
- <Navbar.MenuButton disabled icon={['far', 'diamond']}>
147
+ <Navbar.MenuButton disabled icon={<Diamond className="tw-size-4" />}>
143
148
  Default - Disabled
144
149
  </Navbar.MenuButton>
145
150
  </Navbar.MenuItem>
146
151
 
147
152
  <Navbar.MenuItem>
148
- <Navbar.MenuButton variant="emphasized" icon={['far', 'diamond']}>
153
+ <Navbar.MenuButton variant="emphasized" icon={<Diamond className="tw-size-4" />}>
149
154
  Emphasized
150
155
  </Navbar.MenuButton>
151
156
  </Navbar.MenuItem>
152
157
 
153
158
  <Navbar.MenuItem>
154
- <Navbar.MenuButton variant="emphasized" icon={['far', 'diamond']} asChild>
159
+ <Navbar.MenuButton
160
+ variant="emphasized"
161
+ asChild
162
+ icon={<Diamond className="tw-size-4" />}
163
+ >
155
164
  <a href="https://www.keboola.com">Emphasized - Anchor</a>
156
165
  </Navbar.MenuButton>
157
166
  </Navbar.MenuItem>
158
167
 
159
168
  <Navbar.MenuItem>
160
- <Navbar.MenuButton disabled variant="emphasized" icon={['far', 'diamond']}>
169
+ <Navbar.MenuButton
170
+ disabled
171
+ variant="emphasized"
172
+ icon={<Diamond className="tw-size-4" />}
173
+ >
161
174
  Emphasized - Disabled
162
175
  </Navbar.MenuButton>
163
176
  </Navbar.MenuItem>
@@ -1,9 +1,12 @@
1
- import type { ComponentProps, ElementRef, Ref } from 'react';
1
+ import type { ComponentProps, ElementRef, ReactNode, Ref } from 'react';
2
2
  import { Root, Slottable } from '@radix-ui/react-slot';
3
3
  import type { VariantProps } from 'class-variance-authority';
4
4
  import { cva } from 'class-variance-authority';
5
5
 
6
- import { cn, Icon } from '@keboola/design';
6
+ import { cn } from '@keboola/design';
7
+ import { Search } from '@keboola/design/icons';
8
+
9
+ import { renderLegacyIcon } from '../../utils/legacyIcon';
7
10
 
8
11
  import { NavbarMenuItemAdornment } from './NavbarMenuItemAdornment';
9
12
 
@@ -87,9 +90,9 @@ const variants = cva(
87
90
  },
88
91
  );
89
92
 
90
- type Icon = ComponentProps<typeof Icon>['icon'];
91
93
  type Props = ComponentProps<'button'> & {
92
- icon?: Icon;
94
+ /** Optional leading-icon slot. Pass a Lucide icon node to render an adornment before children. */
95
+ icon?: ReactNode;
93
96
  asChild?: boolean;
94
97
  } & VariantProps<typeof variants>;
95
98
 
@@ -98,7 +101,7 @@ export const NavbarMenuButton = ({
98
101
  className,
99
102
  type = 'button',
100
103
  children,
101
- icon: iconProp,
104
+ icon,
102
105
  asChild = false,
103
106
  variant = 'default',
104
107
  ...rest
@@ -106,7 +109,7 @@ export const NavbarMenuButton = ({
106
109
  const Comp = asChild ? Root : 'button';
107
110
  const isSearchVariant = variant === 'search';
108
111
  const isEmphasizedVariant = variant === 'emphasized';
109
- const icon: Icon | undefined = isSearchVariant ? 'search' : iconProp;
112
+ const adornment = isSearchVariant ? <Search /> : renderLegacyIcon(icon);
110
113
 
111
114
  const adornmentVariant: ComponentProps<typeof NavbarMenuItemAdornment>['variant'] = (() => {
112
115
  if (isSearchVariant || variant === 'icon') return 'icon-button';
@@ -122,10 +125,8 @@ export const NavbarMenuButton = ({
122
125
  data-variant={variant}
123
126
  className={cn(variants({ className, variant }))}
124
127
  >
125
- {icon && (
126
- <NavbarMenuItemAdornment asChild variant={adornmentVariant}>
127
- <Icon icon={icon} />
128
- </NavbarMenuItemAdornment>
128
+ {adornment && (
129
+ <NavbarMenuItemAdornment variant={adornmentVariant}>{adornment}</NavbarMenuItemAdornment>
129
130
  )}
130
131
 
131
132
  <Slottable>{children}</Slottable>
@@ -1,7 +1,7 @@
1
1
  import type { ComponentProps } from 'react';
2
2
 
3
+ import { Ellipsis } from '../../icons';
3
4
  import { cn } from '../../utils';
4
- import { Icon } from '../Icon';
5
5
 
6
6
  type Props = ComponentProps<'span'>;
7
7
 
@@ -21,6 +21,6 @@ export const PaginationEllipsis = ({ className, ...props }: Props) => (
21
21
  )}
22
22
  {...props}
23
23
  >
24
- <Icon icon={['far', 'ellipsis']} />
24
+ <Ellipsis />
25
25
  </span>
26
26
  );
@@ -1,5 +1,6 @@
1
1
  import type { ComponentProps } from 'react';
2
2
 
3
+ import { ChevronRight } from '../../icons';
3
4
  import { cn } from '../../utils';
4
5
 
5
6
  import { PaginationButton } from './PaginationButton';
@@ -10,10 +11,10 @@ export const PaginationNext = ({ children, className, ...props }: Props) => (
10
11
  <PaginationButton
11
12
  {...props}
12
13
  aria-label="Go to next page"
13
- icon="chevron-right"
14
14
  size="medium"
15
15
  className={cn('tw-flex-row-reverse', className)}
16
16
  >
17
+ <ChevronRight className="tw-size-5" />
17
18
  {children ?? 'Next'}
18
19
  </PaginationButton>
19
20
  );
@@ -1,11 +1,14 @@
1
1
  import type { ComponentProps } from 'react';
2
2
 
3
+ import { ChevronLeft } from '../../icons';
4
+
3
5
  import { PaginationButton } from './PaginationButton';
4
6
 
5
7
  type Props = ComponentProps<typeof PaginationButton>;
6
8
 
7
9
  export const PaginationPrevious = ({ children, ...props }: Props) => (
8
- <PaginationButton {...props} size="medium" aria-label="Go to previous page" icon="chevron-left">
10
+ <PaginationButton {...props} size="medium" aria-label="Go to previous page">
11
+ <ChevronLeft className="tw-size-5" />
9
12
  {children ?? 'Previous'}
10
13
  </PaginationButton>
11
14
  );
@@ -1,8 +1,8 @@
1
1
  import { useState } from 'react';
2
2
  import type { KeyboardEvent, PropsWithChildren } from 'react';
3
3
 
4
- import { Icon } from '../components/Icon';
5
4
  import { keyCodes } from '../constants/keyCodes';
5
+ import { ChevronDown } from '../icons';
6
6
  import { cn } from '../utils';
7
7
 
8
8
  import { Collapse } from './Collapse';
@@ -47,10 +47,9 @@ export const PanelWithDetails = ({
47
47
  onKeyUp={handleKeyUp}
48
48
  >
49
49
  <div className="tw-mr-2 tw-flex tw-shrink-0 tw-items-center">
50
- <Icon
51
- icon="chevron-down"
50
+ <ChevronDown
52
51
  className={cn(
53
- 'tw-text-xs',
52
+ 'tw-size-5',
54
53
  !isOpen ? '-tw-rotate-90' : placement === 'bottom' ? 'tw-rotate-180' : '',
55
54
  )}
56
55
  />
@@ -2,6 +2,8 @@ import { type ComponentProps, useState } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import { action } from 'storybook/actions';
4
4
 
5
+ import { Check, Rocket, Trash2, X } from '@keboola/design/icons';
6
+
5
7
  import { Button } from '../Button/Button';
6
8
  import { DropdownMenu } from '../DropdownMenu';
7
9
  import { Label } from '../Label';
@@ -252,11 +254,15 @@ export const CustomButtons: Story = {
252
254
 
253
255
  <Popconfirm.Footer className="tw-flex tw-justify-end">
254
256
  <Popconfirm.Cancel asChild>
255
- <Button variant="outline" size="icon" icon="cancel" />
257
+ <Button variant="outline" size="icon">
258
+ <X className="tw-size-4" />
259
+ </Button>
256
260
  </Popconfirm.Cancel>
257
261
 
258
262
  <Popconfirm.Confirm asChild>
259
- <Button size="icon" icon="rocket" />
263
+ <Button size="icon">
264
+ <Rocket className="tw-size-4" />
265
+ </Button>
260
266
  </Popconfirm.Confirm>
261
267
  </Popconfirm.Footer>
262
268
  </Popconfirm.Content>
@@ -297,7 +303,9 @@ export const InModal: Story = {
297
303
  <Modal.Body className="tw-flex tw-justify-center">
298
304
  <Popconfirm {...props}>
299
305
  <Popconfirm.Trigger asChild>
300
- <Button icon="trash" variant="danger" size="icon" />
306
+ <Button variant="danger" size="icon">
307
+ <Trash2 className="tw-size-4" />
308
+ </Button>
301
309
  </Popconfirm.Trigger>
302
310
  <Popconfirm.Content>
303
311
  <Popconfirm.Body>Are you sure you want to delete this branch?</Popconfirm.Body>
@@ -331,7 +339,7 @@ export const InDropdownMenu: Story = {
331
339
  <DropdownMenu.Group>
332
340
  <Popconfirm {...props} variant="danger">
333
341
  <Popconfirm.Trigger asChild>
334
- <DropdownMenu.Item icon="trash" asModalTrigger>
342
+ <DropdownMenu.Item asModalTrigger icon={<Trash2 className="tw-size-4" />}>
335
343
  Delete Task
336
344
  </DropdownMenu.Item>
337
345
  </Popconfirm.Trigger>
@@ -346,7 +354,7 @@ export const InDropdownMenu: Story = {
346
354
 
347
355
  <Popconfirm {...props}>
348
356
  <Popconfirm.Trigger asChild>
349
- <DropdownMenu.Item asModalTrigger icon="check">
357
+ <DropdownMenu.Item asModalTrigger icon={<Check className="tw-size-4" />}>
350
358
  Confirm
351
359
  </DropdownMenu.Item>
352
360
  </Popconfirm.Trigger>
@@ -1,6 +1,7 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import { Indicator, Item } from '@radix-ui/react-radio-group';
3
3
 
4
+ import { Circle } from '../../icons';
4
5
  import { cn } from '../../utils';
5
6
 
6
7
  type Props = ComponentProps<typeof Item>;
@@ -51,17 +52,10 @@ export const Radio = ({ className, ...props }: Props) => (
51
52
  className,
52
53
  )}
53
54
  >
54
- <Indicator
55
- className={cn(
56
- 'tw-flex',
57
- 'tw-items-center',
58
- 'tw-justify-center',
59
- 'after:tw-content-[""]',
60
- 'after:tw-block',
61
- 'after:tw-size-2.5',
62
- 'after:tw-rounded-full',
63
- 'after:tw-bg-primary',
64
- )}
65
- />
55
+ <Indicator className={cn('tw-flex', 'tw-items-center', 'tw-justify-center')}>
56
+ {/* Lucide Circle is stroke-only — force fill to match the stroke (currentColor)
57
+ so the indicator reads as a solid dot. */}
58
+ <Circle className="tw-size-2.5 tw-fill-current tw-text-primary" />
59
+ </Indicator>
66
60
  </Item>
67
61
  );
@@ -1,7 +1,7 @@
1
1
  import type { MouseEventHandler } from 'react';
2
2
 
3
+ import { X } from '../../icons';
3
4
  import { cn } from '../../utils';
4
- import { Icon } from '../Icon';
5
5
 
6
6
  type Props = {
7
7
  onClick: MouseEventHandler<HTMLButtonElement>;
@@ -36,7 +36,7 @@ export const ClearButton = ({ onClick, testId }: Props) => {
36
36
  )}
37
37
  data-testid={testId}
38
38
  >
39
- <Icon className="tw-size-4" icon="xmark" />
39
+ <X className="tw-size-4" />
40
40
  </button>
41
41
  );
42
42
  };
@@ -12,8 +12,8 @@ import type {
12
12
  Ref,
13
13
  } from 'react';
14
14
 
15
+ import { Search as SearchIcon, Sparkles } from '../../icons';
15
16
  import { cn, debounce, noop } from '../../utils';
16
- import { Icon } from '../Icon';
17
17
 
18
18
  import { ClearButton } from './ClearButton';
19
19
  import { searchInputClassnames, searchWrapperVariants } from './styles';
@@ -112,9 +112,9 @@ export const Search = ({
112
112
  data-testid={testId.wrapper}
113
113
  >
114
114
  {variant === 'ai' ? (
115
- <Icon icon={['far', 'sparkles']} className="[&_path]:tw-fill-aigradient-600" />
115
+ <Sparkles className="tw-size-4 [&_*]:tw-stroke-aigradient-600" />
116
116
  ) : (
117
- <Icon className="tw-text-base tw-text-neutral-400" icon="magnifying-glass" />
117
+ <SearchIcon className="tw-size-4 tw-text-neutral-400" />
118
118
  )}
119
119
 
120
120
  <input
@@ -1,8 +1,6 @@
1
1
  import { useId } from 'react';
2
2
  import type { ComponentProps, ReactNode } from 'react';
3
3
 
4
- import type { Icon } from '../Icon';
5
-
6
4
  import { SelectContent } from './SelectContent';
7
5
  import { SelectGroup } from './SelectGroup';
8
6
  import { SelectItem as SelectItemComponent } from './SelectItem';
@@ -11,12 +9,10 @@ import { SelectRoot } from './SelectRoot';
11
9
  import { SelectSeparator } from './SelectSeparator';
12
10
  import { SelectTrigger } from './SelectTrigger';
13
11
 
14
- type IconName = ComponentProps<typeof Icon>['icon'];
15
-
16
12
  export type SelectItem = {
17
13
  value: string;
18
14
  label: ReactNode;
19
- icon?: IconName;
15
+ icon?: ReactNode;
20
16
  disabled?: boolean;
21
17
  };
22
18
 
@@ -31,6 +27,10 @@ const isGroup = (itemOrGroup: SelectItem | SelectItemGroup): itemOrGroup is Sele
31
27
  type RootProps = ComponentProps<typeof SelectRoot>;
32
28
  type TriggerProps = ComponentProps<typeof SelectTrigger>;
33
29
 
30
+ /**
31
+ * Props for `Select`. Combines Radix `Select.Root` control props with `Select.Trigger` chrome and the
32
+ * convenience `items` shape (flat list, or grouped lists with optional labels).
33
+ */
34
34
  type Props = Pick<
35
35
  RootProps,
36
36
  | 'value'
@@ -47,6 +47,17 @@ type Props = Pick<
47
47
  items?: readonly (SelectItem | SelectItemGroup)[];
48
48
  };
49
49
 
50
+ /**
51
+ * Single selection from a short enumerated list. Built on Radix Select.
52
+ *
53
+ * **When to use:** picking one value from ~10 or fewer options where search adds no value (status,
54
+ * region, role). Pass a flat `items` array or grouped arrays with optional labels for headings.
55
+ * **When NOT to use:** for longer lists with search use `Combobox`; for multi-select use `MultiCombobox`;
56
+ * for free-form tag entry use `Tagger`.
57
+ * **Composition:** wrap in `FormGroup` for label + help text, or use `useAppForm` + `field.SelectField`
58
+ * for forms with validation. Use the compound parts (`Select.Root`, `Select.Trigger`, `Select.Content`,
59
+ * `Select.Item`) for custom item layouts; the top-level `Select` covers the common cases.
60
+ */
50
61
  export const Select = ({
51
62
  // root
52
63
  value,
@@ -1,8 +1,8 @@
1
1
  import type { ComponentProps, ElementRef, Ref } from 'react';
2
2
  import * as SelectPrimitive from '@radix-ui/react-select';
3
3
 
4
+ import { ChevronDown, ChevronUp } from '../../icons';
4
5
  import { cn } from '../../utils';
5
- import { Icon } from '../Icon';
6
6
 
7
7
  const scrollButtonStyles = cn(
8
8
  'tw-flex',
@@ -10,16 +10,25 @@ const scrollButtonStyles = cn(
10
10
  'tw-justify-center',
11
11
  'tw-rounded-sm',
12
12
  'tw-p-1',
13
- '[&_svg]:tw-size-3',
13
+ '[&_svg]:tw-size-5',
14
14
  );
15
15
 
16
16
  type Variant = 'default' | 'argo';
17
17
 
18
+ /**
19
+ * Props for `Select.Content`. `container` portals the listbox into a custom element (useful inside modals);
20
+ * `variant` switches between default and Argo styling.
21
+ */
18
22
  type Props = ComponentProps<typeof SelectPrimitive.Content> & {
19
23
  container?: HTMLElement | null;
20
24
  variant?: Variant;
21
25
  };
22
26
 
27
+ /**
28
+ * Listbox container for `Select`. Portals items into a popover sized to the trigger.
29
+ * Normally rendered implicitly by `Select`; use directly only when composing custom item layouts via
30
+ * the `Select.Root` / `Select.Trigger` / `Select.Content` compound parts.
31
+ */
23
32
  export const SelectContent = ({
24
33
  ref,
25
34
  className,
@@ -81,7 +90,7 @@ export const SelectContent = ({
81
90
  )}
82
91
  >
83
92
  <SelectPrimitive.ScrollUpButton className={scrollButtonStyles}>
84
- <Icon icon="chevron-up" />
93
+ <ChevronUp />
85
94
  </SelectPrimitive.ScrollUpButton>
86
95
 
87
96
  <SelectPrimitive.Viewport
@@ -94,7 +103,7 @@ export const SelectContent = ({
94
103
  </SelectPrimitive.Viewport>
95
104
 
96
105
  <SelectPrimitive.ScrollDownButton className={scrollButtonStyles}>
97
- <Icon icon="chevron-down" />
106
+ <ChevronDown />
98
107
  </SelectPrimitive.ScrollDownButton>
99
108
  </SelectPrimitive.Content>
100
109
  </SelectPrimitive.Portal>
@@ -1,11 +1,12 @@
1
- import type { ComponentProps, ElementRef, Ref } from 'react';
1
+ import type { ComponentProps, ElementRef, ReactNode, Ref } from 'react';
2
2
  import * as SelectPrimitive from '@radix-ui/react-select';
3
3
 
4
+ import { Check } from '../../icons';
4
5
  import { cn } from '../../utils';
5
- import { Icon } from '../Icon';
6
+ import { renderLegacyIcon } from '../../utils/legacyIcon';
6
7
 
7
8
  type Props = ComponentProps<typeof SelectPrimitive.Item> & {
8
- icon?: ComponentProps<typeof Icon>['icon'];
9
+ icon?: ReactNode;
9
10
  };
10
11
 
11
12
  export const SelectItem = ({
@@ -60,12 +61,12 @@ export const SelectItem = ({
60
61
  className,
61
62
  )}
62
63
  >
63
- {icon && <Icon icon={icon} />}
64
+ {renderLegacyIcon(icon)}
64
65
  <span className="tw-flex-1 tw-truncate">
65
66
  <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
66
67
  </span>
67
68
  <SelectPrimitive.ItemIndicator className="tw-pointer-events-none">
68
- <Icon icon="check" className="tw-size-4" />
69
+ <Check className="tw-size-4" />
69
70
  </SelectPrimitive.ItemIndicator>
70
71
  </SelectPrimitive.Item>
71
72
  );
@@ -2,8 +2,8 @@ import type { ComponentProps, ElementRef, Ref } from 'react';
2
2
  import * as SelectPrimitive from '@radix-ui/react-select';
3
3
  import { cva, type VariantProps } from 'class-variance-authority';
4
4
 
5
+ import { ChevronDown } from '../../icons';
5
6
  import { cn } from '../../utils';
6
- import { Icon } from '../Icon';
7
7
 
8
8
  const variants = cva(
9
9
  [
@@ -155,7 +155,7 @@ export const SelectTrigger = ({
155
155
  >
156
156
  <SelectPrimitive.Value placeholder={placeholder} />
157
157
  <SelectPrimitive.Icon asChild>
158
- <Icon icon="chevron-down" className="tw-size-3.5 tw-text-muted-foreground" />
158
+ <ChevronDown className="tw-size-5 tw-text-muted-foreground" />
159
159
  </SelectPrimitive.Icon>
160
160
  </SelectPrimitive.Trigger>
161
161
  );