@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,8 +1,11 @@
1
1
  import type { ComponentProps, ElementRef, ReactNode, Ref } from 'react';
2
2
 
3
+ import { ChevronDown } from '@keboola/design/icons';
4
+
3
5
  import { Button } from '../../components/Button';
4
6
  import { ButtonGroup } from '../../components/ButtonGroup';
5
7
  import { DropdownMenu } from '../../components/DropdownMenu';
8
+ import { renderLegacyIcon } from '../../utils/legacyIcon';
6
9
 
7
10
  type Variant = ComponentProps<typeof Button>['variant'];
8
11
  type Size = NonNullable<ComponentProps<typeof Button>['size']>;
@@ -15,10 +18,12 @@ type Props = {
15
18
  actions?: ReactNode;
16
19
  isActionsOpen?: boolean;
17
20
  onActionsOpenChange?: (open: boolean) => void;
21
+ /** Optional leading-icon slot — pass a Lucide icon node. */
22
+ icon?: ReactNode;
18
23
  // inline and invisible aren't supported in SplitButton
19
24
  variant?: Exclude<Variant, 'inline' | 'invisible'>;
20
25
  size?: SpitButtonSize;
21
- } & Omit<ComponentProps<typeof Button>, 'variant' | 'size'>;
26
+ } & Omit<ComponentProps<typeof Button>, 'variant' | 'size' | 'icon'>;
22
27
 
23
28
  const iconSizeMap: Record<SpitButtonSize, Size> = {
24
29
  medium: 'icon',
@@ -35,10 +40,17 @@ const SplitButtonBase = ({
35
40
  isActionsOpen,
36
41
  onActionsOpenChange,
37
42
  argo = false,
43
+ icon,
44
+ children,
38
45
  ...rest
39
46
  }: Props & { ref?: Ref<ElementRef<'button'>> }) => (
40
47
  <ButtonGroup space="compact">
41
- <Button {...rest} ref={ref} disabled={disabled} variant={variant} size={size} argo={argo} />
48
+ <Button {...rest} ref={ref} disabled={disabled} variant={variant} size={size} argo={argo}>
49
+ {/* Suppress the leading icon while Button renders its spinner so the two
50
+ glyphs don't double-stack. */}
51
+ {!rest.isLoading && renderLegacyIcon(icon)}
52
+ {children}
53
+ </Button>
42
54
 
43
55
  {actions && (
44
56
  <DropdownMenu
@@ -52,9 +64,10 @@ const SplitButtonBase = ({
52
64
  argo={argo}
53
65
  size={iconSizeMap[size]}
54
66
  variant={variant}
55
- icon="caret-down"
56
67
  aria-label="Additional actions"
57
- />
68
+ >
69
+ <ChevronDown className="tw-size-5" />
70
+ </Button>
58
71
  </DropdownMenu.Trigger>
59
72
  <DropdownMenu.Content align="end">{actions}</DropdownMenu.Content>
60
73
  </DropdownMenu>
@@ -1,4 +1,5 @@
1
1
  import { Badge } from '../../components/Badge';
2
+ import { X } from '../../icons';
2
3
 
3
4
  import type { TagOption } from './types';
4
5
 
@@ -21,7 +22,9 @@ export const TagItem = ({ tag, onRemove, disabled = false }: Props) => {
21
22
 
22
23
  {onRemove && !isDisabled && (
23
24
  <Badge.Addon variant="floating" align="inline-end">
24
- <Badge.Button onClick={handleRemove} icon="xmark" aria-label="Remove Tag" />
25
+ <Badge.Button onClick={handleRemove} aria-label="Remove Tag">
26
+ <X className="tw-size-3" />
27
+ </Badge.Button>
25
28
  </Badge.Addon>
26
29
  )}
27
30
  </Badge.Root>
@@ -1,6 +1,8 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import { useCommandState } from 'cmdk';
3
3
 
4
+ import { Plus } from '@keboola/design/icons';
5
+
4
6
  import { Command } from '../../../components/Command';
5
7
  import { cn } from '../../../utils';
6
8
 
@@ -26,7 +28,7 @@ export const TagSelectCreateItem = ({ onCreate, value }: Props) => {
26
28
  className={cn('[&_svg]:tw-text-primary-500', {
27
29
  'tw-hidden': !isCreateItemVisible,
28
30
  })}
29
- icon="plus"
31
+ icon={<Plus className="tw-size-4" />}
30
32
  >
31
33
  Create{' '}
32
34
  {isSearched ? (
@@ -2,7 +2,7 @@ import { type ComponentProps, useState } from 'react';
2
2
  import { defaultFilter } from 'cmdk';
3
3
 
4
4
  import { Command } from '../../../components/Command';
5
- import { Icon } from '../../../components/Icon';
5
+ import { Search } from '../../../icons';
6
6
  import type { TagOption } from '../types';
7
7
 
8
8
  import { TagSelectCreateItem } from './TagSelectCreateItem';
@@ -41,7 +41,7 @@ export const TagSelectList = ({ options = defaultOptions, onAdd, onCreate }: Pro
41
41
  <div className="tw-border-b tw-border-neutral-100 tw-p-3">
42
42
  <Command.Input
43
43
  variant="secondary"
44
- prefix={<Icon icon="search" className="tw-size-4 tw-text-neutral-400" />}
44
+ prefix={<Search className="tw-size-4 tw-text-neutral-400" />}
45
45
  placeholder="Search tags"
46
46
  allowClear
47
47
  />
@@ -2,8 +2,8 @@ import { useMemo } from 'react';
2
2
  import type { ComponentProps } from 'react';
3
3
 
4
4
  import { Badge } from '../../components/Badge';
5
- import { Icon } from '../../components/Icon';
6
5
  import { TooltipProvider } from '../../components/Tooltip';
6
+ import { Plus } from '../../icons';
7
7
  import { cn } from '../../utils';
8
8
 
9
9
  import { TagItem } from './TagItem';
@@ -82,7 +82,7 @@ export const Tagger = ({
82
82
  <TagSelectPopover options={tagsToAdd} onAdd={handleAdd} onCreate={handleCreate}>
83
83
  <Badge color="gray-inverse" asChild>
84
84
  <button type="button" className="tw-flex tw-items-center tw-gap-1">
85
- <Icon icon="plus" />
85
+ <Plus className="tw-size-4" />
86
86
  <span
87
87
  className={cn({
88
88
  'tw-sr-only': isAnySelected,
@@ -1,6 +1,8 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
 
4
+ import { Eye, Info, X } from '@keboola/design/icons';
5
+
4
6
  import { withTooltipProvider } from '../../utils';
5
7
  import { Button } from '../Button';
6
8
 
@@ -47,9 +49,15 @@ export const WithActions: Story = {
47
49
  args: {
48
50
  actions: (
49
51
  <>
50
- <Button variant="inline" icon="info-circle" size="icon-3xs" aria-label="Info" />
51
- <Button variant="inline" icon="eye" size="icon-3xs" aria-label="Seen" />
52
- <Button variant="inline" icon="xmark" size="icon-3xs" aria-label="Close" />
52
+ <Button variant="inline" size="icon-3xs" aria-label="Info">
53
+ <Info className="tw-size-4" />
54
+ </Button>
55
+ <Button variant="inline" size="icon-3xs" aria-label="Seen">
56
+ <Eye className="tw-size-4" />
57
+ </Button>
58
+ <Button variant="inline" size="icon-3xs" aria-label="Close">
59
+ <X className="tw-size-4" />
60
+ </Button>
53
61
  </>
54
62
  ),
55
63
  },
@@ -13,7 +13,7 @@ import { AlertTitle } from './AlertTitle';
13
13
 
14
14
  type CommonProps = {
15
15
  hideIcon?: boolean;
16
- icon?: ComponentProps<typeof AlertIcon>['icon'];
16
+ icon?: ReactNode;
17
17
  } & Pick<ComponentProps<typeof Collapsible>, 'open' | 'onOpenChange' | 'defaultOpen'> &
18
18
  Omit<ComponentProps<typeof AlertRoot>, 'children'>;
19
19
 
@@ -31,8 +31,26 @@ type DefaultProps = {
31
31
  actions?: ReactNode;
32
32
  };
33
33
 
34
+ /**
35
+ * Props for `Alert`. Discriminated by `mode` — `"default"` renders `title` / `children` / `actions`,
36
+ * `"collapsible"` requires `title` + `children` (no `actions`) and renders a collapsible disclosure.
37
+ * `variant` (`info` | `success` | `warning` | `error`) is a categorical colour contract.
38
+ */
34
39
  type Props = CommonProps & (CollapsibleProps | DefaultProps);
35
40
 
41
+ /**
42
+ * Persistent inline banner for contextual messages tied to the page layout.
43
+ *
44
+ * **When to use:** a message that lives in the page (info banner, warning about a misconfiguration,
45
+ * post-save validation summary). Use `variant` to encode meaning; pass `actions` for inline buttons.
46
+ * `mode="collapsible"` collapses long error details behind a toggle.
47
+ * **When NOT to use:** for transient feedback (save succeeded, job started) use `toast()`; for
48
+ * field-level validation errors use `FormGroup state="error"`; for a confirmation popover use
49
+ * `Popconfirm`.
50
+ * **Composition:** typically rendered at the top of `PageLayout.Content` or above a form section.
51
+ *
52
+ * @see RECIPES.md#alert-with-action — for action-bearing alert composition
53
+ */
36
54
  export const Alert = ({
37
55
  title,
38
56
  variant = 'info',
@@ -1,5 +1,5 @@
1
+ import { ChevronDown } from '../../icons';
1
2
  import { cn } from '../../utils';
2
- import { Icon } from '../Icon';
3
3
  import { Tooltip } from '../Tooltip';
4
4
 
5
5
  type Props = {
@@ -8,11 +8,10 @@ type Props = {
8
8
 
9
9
  export const AlertCollapsibleIcon = ({ open }: Props) => (
10
10
  <Tooltip placement="top" tooltip={open ? 'Collapse alert' : 'Expand alert'} triggerAsChild>
11
- <Icon
12
- icon="chevron-down"
11
+ <ChevronDown
13
12
  className={cn(
14
13
  'tw-mt-px',
15
- 'tw-size-4',
14
+ 'tw-size-5',
16
15
  'tw-text-neutral-400',
17
16
  'group-data-[state=open]/alert:tw-rotate-180',
18
17
 
@@ -1,43 +1,40 @@
1
- import type { ComponentProps, ElementRef, Ref } from 'react';
1
+ import type { ComponentProps, ReactNode } from 'react';
2
2
 
3
+ import { AlertTriangle, CircleCheck, Info } from '../../icons';
3
4
  import { cn } from '../../utils';
4
- import { Icon } from '../Icon';
5
5
 
6
6
  import type { Variant } from './AlertRoot';
7
7
 
8
- const ICONS: Record<Variant, ComponentProps<typeof Icon>['icon']> = {
9
- info: 'circle-info',
10
- warning: 'triangle-exclamation',
11
- error: 'triangle-exclamation',
12
- success: 'circle-check',
8
+ const VARIANT_ICONS: Record<Variant, ReactNode> = {
9
+ info: <Info />,
10
+ warning: <AlertTriangle />,
11
+ error: <AlertTriangle />,
12
+ success: <CircleCheck />,
13
13
  };
14
14
 
15
15
  type Props = {
16
16
  variant: Variant;
17
- icon?: ComponentProps<typeof Icon>['icon'];
18
- } & Omit<ComponentProps<typeof Icon>, 'icon'>;
17
+ icon?: ReactNode;
18
+ } & Omit<ComponentProps<'span'>, 'children'>;
19
19
 
20
- export const AlertIcon = ({
21
- ref,
22
- variant,
23
- icon,
24
- className,
25
- ...rest
26
- }: Props & { ref?: Ref<ElementRef<'svg'>> }) => (
27
- <Icon
20
+ export const AlertIcon = ({ variant, icon, className, ...rest }: Props) => (
21
+ <span
28
22
  {...rest}
29
- ref={ref}
30
23
  data-slot="alert-icon"
31
- icon={icon ?? ICONS[variant]}
32
24
  className={cn(
25
+ 'tw-inline-flex',
26
+ 'tw-items-center',
27
+ 'tw-justify-center',
33
28
  'tw-self-start',
34
29
  'tw-p-0.5',
35
- 'tw-text-base',
30
+ '[&_svg]:tw-size-4',
36
31
  'group-data-[variant=info]/alert:tw-text-secondary-500',
37
32
  'group-data-[variant=warning]/alert:tw-text-warning-500',
38
33
  'group-data-[variant=error]/alert:tw-text-error-500',
39
34
  'group-data-[variant=success]/alert:tw-text-primary-500',
40
35
  className,
41
36
  )}
42
- />
37
+ >
38
+ {icon ?? VARIANT_ICONS[variant]}
39
+ </span>
43
40
  );
@@ -1,5 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
+ import { Plus, X } from '@keboola/design/icons';
4
+
3
5
  import { Icon } from '../Icon';
4
6
 
5
7
  import { Badge } from './Badge';
@@ -80,21 +82,37 @@ export const AsButton: Story = {
80
82
 
81
83
  export const StartAddon: Story = {
82
84
  args: {
83
- prefix: <Badge.Button icon="plus" onClick={() => alert('Removed')} aria-label="Add" />,
85
+ prefix: (
86
+ <Badge.Button onClick={() => alert('Removed')} aria-label="Add">
87
+ <Plus className="tw-size-4" />
88
+ </Badge.Button>
89
+ ),
84
90
  },
85
91
  };
86
92
 
87
93
  export const EndAddon: Story = {
88
94
  args: {
89
- suffix: <Badge.Button icon="xmark" onClick={() => alert('Removed')} aria-label="Remove" />,
95
+ suffix: (
96
+ <Badge.Button onClick={() => alert('Removed')} aria-label="Remove">
97
+ <X className="tw-size-4" />
98
+ </Badge.Button>
99
+ ),
90
100
  prefixVariant: 'floating',
91
101
  },
92
102
  };
93
103
 
94
104
  export const BothAddons: Story = {
95
105
  args: {
96
- prefix: <Badge.Button icon="plus" onClick={() => alert('Removed')} aria-label="Add" />,
97
- suffix: <Badge.Button icon="xmark" onClick={() => alert('Removed')} aria-label="Remove" />,
106
+ prefix: (
107
+ <Badge.Button onClick={() => alert('Removed')} aria-label="Add">
108
+ <Plus className="tw-size-4" />
109
+ </Badge.Button>
110
+ ),
111
+ suffix: (
112
+ <Badge.Button onClick={() => alert('Removed')} aria-label="Remove">
113
+ <X className="tw-size-4" />
114
+ </Badge.Button>
115
+ ),
98
116
  },
99
117
  };
100
118
 
@@ -9,6 +9,11 @@ type AddonVariant = ComponentProps<typeof BadgeAddon>['variant'];
9
9
  type BadgeRootProps = ComponentProps<typeof BadgeRoot>;
10
10
  type BadgeLabelProps = ComponentProps<typeof BadgeLabel>;
11
11
 
12
+ /**
13
+ * Props for `Badge`. `color` is a categorical contract (`green` = success, `red` = error, `yellow` = warning,
14
+ * `purple/cyan/teal/pink/blue/gray` for decorative tags) — colours stay fixed across brand themes.
15
+ * Pass `prefix` / `suffix` for icon-style addons.
16
+ */
12
17
  type Props = Pick<BadgeRootProps, 'variant' | 'color'> &
13
18
  Omit<BadgeLabelProps, 'prefix'> & {
14
19
  prefix?: ReactNode;
@@ -18,6 +23,17 @@ type Props = Pick<BadgeRootProps, 'variant' | 'color'> &
18
23
  labelClassName?: string;
19
24
  };
20
25
 
26
+ /**
27
+ * Compact status / category label rendered as an inline pill.
28
+ *
29
+ * **When to use:** encoding status (active, deprecated, beta), counts, or decorative tags next to a title
30
+ * or in a table cell. The `color` prop is a semantic contract — `green` means success, `red` means error
31
+ * etc., and colours are intentionally stable across customer brand themes.
32
+ * **When NOT to use:** for an inline action use `Button` / `ButtonInline`; for a persistent contextual
33
+ * banner use `Alert`; for a counter-only chip prefer `Badge` with just a numeric label.
34
+ * **Composition:** drop into `PageHeader.Detail` `badges` slot, `Table.Cell`, or alongside titles in cards.
35
+ * Use `Badge.Button` for an interactive (clickable) badge variant.
36
+ */
21
37
  const BadgeInner = ({
22
38
  ref,
23
39
  // root props
@@ -1,17 +1,14 @@
1
1
  import type { ComponentProps, ElementRef, Ref } from 'react';
2
2
 
3
3
  import { cn } from '../../utils';
4
- import { Icon } from '../Icon';
5
4
 
6
- type Props = ComponentProps<'button'> & {
7
- icon: ComponentProps<typeof Icon>['icon'];
8
- };
5
+ type Props = ComponentProps<'button'>;
9
6
 
10
7
  export const BadgeButton = ({
11
8
  ref,
12
9
  className,
13
- icon,
14
10
  type = 'button',
11
+ children,
15
12
  ...rest
16
13
  }: Props & { ref?: Ref<ElementRef<'button'>> }) => (
17
14
  <button
@@ -31,9 +28,13 @@ export const BadgeButton = ({
31
28
 
32
29
  'hover:tw-brightness-95',
33
30
  'tw-px-1',
31
+ // Default sizing for Lucide-icon children (which ship at 24×24); keeps
32
+ // the visual baseline aligned with the previous FA-icon API and other
33
+ // icon-wrapper components (ItemIcon, SidebarMenuIcon).
34
+ '[&_svg]:tw-size-3',
34
35
  className,
35
36
  )}
36
37
  >
37
- <Icon icon={icon} />
38
+ {children}
38
39
  </button>
39
40
  );
@@ -1,7 +1,7 @@
1
1
  import type { ComponentProps, ElementRef, Ref } 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<'button'>;
7
7
 
@@ -37,7 +37,7 @@ export const BreadcrumbEllipsis = ({
37
37
  )}
38
38
  {...props}
39
39
  >
40
- <Icon icon={['far', 'ellipsis']} className="tw-size-4" />
40
+ <Ellipsis className="tw-size-4" />
41
41
  <span className="tw-sr-only">More</span>
42
42
  </button>
43
43
  );
@@ -1,7 +1,7 @@
1
1
  import type { ComponentProps } from 'react';
2
2
 
3
+ import { ChevronRight } from '../../icons';
3
4
  import { cn } from '../../utils';
4
- import { Icon } from '../Icon';
5
5
 
6
6
  type Props = ComponentProps<'li'>;
7
7
 
@@ -23,6 +23,6 @@ export const BreadcrumbSeparator = ({ children, className, ...props }: Props) =>
23
23
  )}
24
24
  {...props}
25
25
  >
26
- {children ?? <Icon icon="chevron-right" />}
26
+ {children ?? <ChevronRight className="!tw-size-4" />}
27
27
  </li>
28
28
  );
@@ -1,6 +1,8 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
 
4
+ import { CircleCheck, X } from '../../icons';
5
+
4
6
  import { Button } from './Button';
5
7
 
6
8
  type Props = ComponentProps<typeof Button>;
@@ -43,11 +45,21 @@ export const Default: Story = {
43
45
  };
44
46
 
45
47
  export const WithIcon: Story = {
46
- args: { children: 'Button', icon: 'circle-check' },
48
+ args: {
49
+ children: (
50
+ <>
51
+ <CircleCheck className="tw-size-4" /> Button
52
+ </>
53
+ ),
54
+ },
47
55
  };
48
56
 
49
57
  export const IconOnly: Story = {
50
- args: { size: 'icon', children: null, icon: 'xmark', 'aria-label': 'Delete' },
58
+ args: {
59
+ size: 'icon',
60
+ children: <X className="tw-size-4" />,
61
+ 'aria-label': 'Delete',
62
+ },
51
63
  argTypes: {
52
64
  size: {
53
65
  control: 'radio',
@@ -2,26 +2,57 @@ import type { ComponentProps, ElementRef, Ref } from 'react';
2
2
  import { Root, Slottable } from '@radix-ui/react-slot';
3
3
  import type { VariantProps } from 'class-variance-authority';
4
4
 
5
+ import { Loader2 } from '../../icons';
5
6
  import { cn } from '../../utils';
6
7
  import { Icon } from '../Icon';
7
8
 
8
9
  import { argoVariants, variants } from './styles';
9
10
 
11
+ /**
12
+ * Props for the `Button` component.
13
+ *
14
+ * Pass `variant` for primary/secondary/danger/outline/invisible looks, `size`
15
+ * for height (including `icon-*` sizes when rendering icon-only). Icons are
16
+ * composed as children — `<Button><Trash2 className="tw-size-4" /> Delete</Button>`.
17
+ * `argo` opts into the Argo redesign styling (PageHeader / sidebar contexts) —
18
+ * required when nested inside `PageHeader` or `PageLayout.Footer` slots.
19
+ */
10
20
  type Props = {
11
- icon?: ComponentProps<typeof Icon>['icon'];
21
+ /** Replaces the leading content with a spinner and disables the button visually. The caller still controls `disabled`. */
12
22
  isLoading?: boolean;
23
+ /** Render as a Radix `Slot` so the parent JSX (e.g. a `react-router` `<Link>`) becomes the button element. */
13
24
  asChild?: boolean;
25
+ /** Opt into the Argo redesign variant. Required inside `PageHeader` slots and other Argo surfaces. */
14
26
  argo?: boolean;
27
+ /**
28
+ * @deprecated FA-name icon prop kept as a shim during the kbc-ui consumer
29
+ * migration sweep (UT-4109 C2). New code should compose icons as children
30
+ * via direct Lucide imports — `<Button><Trash2 className="tw-size-4" /> Delete</Button>`.
31
+ * Removed in the final C2 cleanup once every consumer is migrated.
32
+ */
33
+ icon?: ComponentProps<typeof Icon>['icon'];
15
34
  } & ComponentProps<'button'> &
16
35
  VariantProps<typeof variants>;
17
36
 
37
+ /**
38
+ * Primary action surface for the design system — covers all clickable controls except inline text links.
39
+ *
40
+ * **When to use:** any user-initiated action (submit, save, open modal, run job). Use `size="icon-*"` with no
41
+ * children for an icon-only button; there is no separate `IconButton` component.
42
+ * **When NOT to use:** for inline text-link actions inside body copy use `ButtonInline`; for navigation that
43
+ * opens a URL use `Link`; for "primary action + dropdown of related actions" use `SplitButton`.
44
+ * **Composition:** wrap in `Tooltip` when icon-only or when disabled state needs an explanation; pair with
45
+ * `Popconfirm` for destructive confirmations; pass through `Modal.Trigger` / `DropdownMenu.Trigger` via `asChild`.
46
+ *
47
+ * @see RECIPES.md#disabled-button-tooltip — for the disabled-button + tooltip pattern
48
+ * @see RECIPES.md#confirm-dialog — for destructive-action confirmation
49
+ */
18
50
  export const Button = ({
19
51
  ref,
20
52
  children,
21
53
  disabled = false,
22
54
  className,
23
55
  type = 'button',
24
- icon: iconProp,
25
56
  isLoading = false,
26
57
  asChild = false,
27
58
  onClick,
@@ -29,10 +60,10 @@ export const Button = ({
29
60
  variant = 'primary',
30
61
  size = 'medium',
31
62
  block = false,
63
+ icon,
32
64
  ...buttonProps
33
65
  }: Props & { ref?: Ref<ElementRef<'button'>> }) => {
34
66
  const Comp = asChild ? Root : 'button';
35
- const icon = isLoading ? 'spinner' : iconProp;
36
67
  const classNames = argo
37
68
  ? argoVariants({ variant: variant, size, block, className })
38
69
  : variants({
@@ -61,12 +92,18 @@ export const Button = ({
61
92
  disabled={disabled}
62
93
  aria-disabled={disabled}
63
94
  data-variant={variant}
64
- data-icon-only={!children}
95
+ // `size="icon"` / `size="icon-*"` flags the button as icon-only — needed
96
+ // by `ButtonGroup space="block"` which stretches non-icon children to
97
+ // equal width. Detection by `size` works for both the legacy shim path
98
+ // (no children, FA icon prop) and shadcn composition (icon as the only
99
+ // child).
100
+ data-icon-only={String(size).startsWith('icon')}
65
101
  type={type}
66
102
  onClick={handleClick}
67
103
  className={cn(classNames)}
68
104
  >
69
- {icon && <Icon spin={isLoading} icon={icon} />}
105
+ {isLoading && <Loader2 className="tw-size-4 tw-shrink-0 tw-animate-spin" />}
106
+ {!isLoading && icon && <Icon icon={icon} />}
70
107
  <Slottable>{children}</Slottable>
71
108
  </Comp>
72
109
  );
@@ -222,13 +222,11 @@ export const argoVariants = cva(
222
222
  'focus-visible:tw-ring-primary/50',
223
223
  ],
224
224
  secondary: [
225
- 'tw-bg-secondary',
226
- 'tw-text-secondary-foreground',
227
- 'hover:tw-bg-secondary/80',
228
- 'hover:tw-text-secondary-foreground',
229
- 'focus:tw-text-secondary-foreground',
225
+ 'tw-bg-primary/10',
226
+ 'tw-text-primary',
227
+ 'hover:not-disabled:tw-bg-primary/20',
230
228
 
231
- 'focus-visible:tw-border-secondary',
229
+ 'focus-visible:tw-border-primary',
232
230
  'focus-visible:tw-ring-primary/50',
233
231
  ],
234
232