@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,9 +1,10 @@
1
- import type { ComponentProps, ElementRef, PointerEventHandler, Ref } from 'react';
1
+ import type { ComponentProps, ElementRef, PointerEventHandler, ReactNode, Ref } from 'react';
2
2
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
3
  import { Root, Slottable } from '@radix-ui/react-slot';
4
4
 
5
+ import { Loader2 } from '../../icons';
5
6
  import { cn } from '../../utils';
6
- import type { Icon } from '../Icon';
7
+ import { renderLegacyIcon } from '../../utils/legacyIcon';
7
8
 
8
9
  import { ItemIcon } from './ItemIcon';
9
10
  import { itemVariants } from './styles';
@@ -15,7 +16,12 @@ type Props = ComponentProps<typeof DropdownMenuPrimitive.Item> & {
15
16
  * https://github.com/radix-ui/primitives/issues/2193#issuecomment-2758461323
16
17
  */
17
18
  asModalTrigger?: boolean;
18
- icon?: ComponentProps<typeof Icon>['icon'];
19
+ /**
20
+ * Pass a Lucide icon node as a leading adornment. Legacy FontAwesome
21
+ * `"name"` / `[prefix, name]` string values are still accepted during the
22
+ * C2 deprecation window and rendered via the legacy `<Icon>` wrapper.
23
+ */
24
+ icon?: ReactNode;
19
25
  isLoading?: boolean;
20
26
  iconClassName?: string;
21
27
  };
@@ -29,13 +35,13 @@ export const DropdownMenuItem = ({
29
35
  onPointerMove,
30
36
  children,
31
37
  asChild,
32
- icon: iconProp,
38
+ icon,
33
39
  iconClassName,
34
40
  isLoading = false,
35
41
  ...props
36
42
  }: Props & { ref?: Ref<ElementRef<'div'>> }) => {
37
43
  const Comp = asChild ? Root : 'div';
38
- const icon = isLoading ? 'spinner' : iconProp;
44
+ const adornment = isLoading ? <Loader2 className="tw-animate-spin" /> : renderLegacyIcon(icon);
39
45
 
40
46
  const handleOnSelect = (event: Event) => {
41
47
  // prevents dropdown menu from closing to prevent modal unmounting from dom
@@ -66,7 +72,7 @@ export const DropdownMenuItem = ({
66
72
  asChild
67
73
  >
68
74
  <Comp>
69
- {icon && <ItemIcon spin={isLoading} icon={icon} className={iconClassName} />}
75
+ {adornment && <ItemIcon className={iconClassName}>{adornment}</ItemIcon>}
70
76
  <Slottable>{children}</Slottable>
71
77
  </Comp>
72
78
  </DropdownMenuPrimitive.Item>
@@ -3,7 +3,7 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
3
  import type { VariantProps } from 'class-variance-authority';
4
4
  import { cva } from 'class-variance-authority';
5
5
 
6
- import { Icon } from '../Icon';
6
+ import { Check, Circle, ToggleRight } from '../../icons';
7
7
 
8
8
  const variants = cva(
9
9
  [
@@ -33,6 +33,9 @@ const variants = cva(
33
33
 
34
34
  '[&_svg]:tw-size-2',
35
35
  '[&_svg]:data-[state=unchecked]:tw-hidden',
36
+ // Lucide Circle is stroke-only — force fill to match the stroke
37
+ // (currentColor) so the indicator reads as a solid dot.
38
+ '[&_svg]:tw-fill-current',
36
39
  'group-data-[variant=argo]/dropdown-menu-content:[&_svg]:tw-text-primary',
37
40
  'group-data-[variant=default]/dropdown-menu-content:[&_svg]:tw-text-secondary-500',
38
41
  ],
@@ -53,6 +56,9 @@ const variants = cva(
53
56
  ],
54
57
  switch: [
55
58
  'tw-size-5',
59
+ // Lucide SVGs default to 24×24 — constrain the rendered ToggleRight
60
+ // to the indicator slot so it doesn't overflow.
61
+ '[&_svg]:tw-size-5',
56
62
  'tw-text-neutral-400',
57
63
  'data-[state=unchecked]:tw-rotate-180',
58
64
  'group-data-[variant=default]/dropdown-menu-content:data-[state=checked]:tw-text-secondary-500',
@@ -72,10 +78,10 @@ export const DropdownMenuItemIndicator = ({
72
78
  forceMount: forceMountProp,
73
79
  ...rest
74
80
  }: Props) => {
75
- const icon: ComponentProps<typeof Icon>['icon'] = (() => {
76
- if (variant === 'switch') return 'toggle-large-on';
77
- if (variant === 'radio') return 'circle';
78
- return 'check';
81
+ const IconComp = (() => {
82
+ if (variant === 'switch') return ToggleRight;
83
+ if (variant === 'radio') return Circle;
84
+ return Check;
79
85
  })();
80
86
 
81
87
  const forceMount = (() => {
@@ -92,7 +98,7 @@ export const DropdownMenuItemIndicator = ({
92
98
  forceMount={forceMount}
93
99
  className={variants({ variant, className })}
94
100
  >
95
- <Icon icon={icon} />
101
+ <IconComp />
96
102
  </DropdownMenuPrimitive.ItemIndicator>
97
103
  );
98
104
  };
@@ -1,8 +1,9 @@
1
- import type { ComponentProps } from 'react';
1
+ import type { ComponentProps, ReactNode } from 'react';
2
2
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
3
 
4
+ import { Loader2 } from '../../../icons';
4
5
  import { cn } from '../../../utils';
5
- import type { Icon } from '../../Icon';
6
+ import { renderLegacyIcon } from '../../../utils/legacyIcon';
6
7
  import { DropdownMenuItemIndicator } from '../DropdownMenuItemIndicator';
7
8
  import { ItemIcon } from '../ItemIcon';
8
9
  import { itemVariants } from '../styles';
@@ -11,7 +12,7 @@ type Props = ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
11
12
  closeOnSelect?: boolean;
12
13
  indicatorAlign?: 'start' | 'end';
13
14
  indicator?: 'radio' | 'check' | 'none';
14
- icon?: ComponentProps<typeof Icon>['icon'];
15
+ icon?: ReactNode;
15
16
  iconClassName?: string;
16
17
  isLoading?: boolean;
17
18
  };
@@ -30,7 +31,9 @@ export const DropdownMenuRadioItem = ({
30
31
  }: Props) => {
31
32
  const isAtStart = indicatorAlign === 'start';
32
33
  const indicatorEle = isLoading ? (
33
- <ItemIcon spin icon="spinner" className={iconClassName} />
34
+ <ItemIcon className={iconClassName}>
35
+ <Loader2 className="tw-animate-spin" />
36
+ </ItemIcon>
34
37
  ) : (
35
38
  <DropdownMenuItemIndicator variant={indicator} />
36
39
  );
@@ -56,7 +59,7 @@ export const DropdownMenuRadioItem = ({
56
59
  {...props}
57
60
  >
58
61
  {isAtStart && indicatorEle}
59
- {icon && <ItemIcon icon={icon} className={iconClassName} />}
62
+ {icon && <ItemIcon className={iconClassName}>{renderLegacyIcon(icon)}</ItemIcon>}
60
63
  <span className="tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-2">{children}</span>
61
64
  {!isAtStart && indicatorEle}
62
65
  </DropdownMenuPrimitive.RadioItem>
@@ -1,8 +1,8 @@
1
1
  import type { ComponentProps } from 'react';
2
2
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
3
 
4
+ import { ChevronRight } from '../../../icons';
4
5
  import { cn } from '../../../utils';
5
- import { Icon } from '../../Icon';
6
6
  import { itemVariants } from '../styles';
7
7
 
8
8
  type Props = ComponentProps<typeof DropdownMenuPrimitive.SubTrigger>;
@@ -11,7 +11,7 @@ export const DropdownMenuSubTrigger = ({ className, children, ...props }: Props)
11
11
  return (
12
12
  <DropdownMenuPrimitive.SubTrigger className={cn(itemVariants, className)} {...props}>
13
13
  {children}
14
- <Icon icon="chevron-right" className="tw-ml-auto" />
14
+ <ChevronRight className="tw-ml-auto tw-size-5" />
15
15
  </DropdownMenuPrimitive.SubTrigger>
16
16
  );
17
17
  };
@@ -1,22 +1,30 @@
1
- import type { ComponentProps } from 'react';
1
+ import type { ComponentProps, ReactNode } from 'react';
2
2
 
3
3
  import { cn } from '../../utils';
4
- import { Icon } from '../Icon';
5
4
 
6
- type Props = ComponentProps<typeof Icon>;
5
+ type Props = ComponentProps<'span'> & {
6
+ children: ReactNode;
7
+ };
7
8
 
8
- export const ItemIcon = ({ className, ...props }: Props) => (
9
- <Icon
9
+ export const ItemIcon = ({ className, children, ...props }: Props) => (
10
+ <span
10
11
  {...props}
11
12
  className={cn(
13
+ 'tw-inline-flex',
14
+ 'tw-items-center',
15
+ 'tw-justify-center',
12
16
  'tw-aspect-square',
13
- 'tw-p-0.5',
14
- 'tw-size-4',
17
+ // 20px container fits the 16px Lucide glyph plus 2px breathing room
18
+ // each side — the previous tw-size-4 + tw-p-0.5 clipped the 16px child.
19
+ 'tw-size-5',
20
+ '[&_svg]:tw-size-4',
15
21
  'group-data-[variant=default]/dropdown-menu-content:tw-text-neutral-400',
16
22
  'group-data-[variant=argo]/dropdown-menu-content:tw-text-primary',
17
23
  'group-data-[variant=argo]/dropdown-menu-content:group-aria-disabled:tw-text-muted-foreground',
18
24
  'group-data-[variant=default]/dropdown-menu-content:group-hover:tw-text-secondary-500',
19
25
  className,
20
26
  )}
21
- />
27
+ >
28
+ {children}
29
+ </span>
22
30
  );
@@ -10,12 +10,27 @@ import { FormGroupTextarea } from './FormGroupTextarea';
10
10
  import { FormGroupTextInput } from './FormGroupTextInput';
11
11
  import type { FormGroupContext } from './types';
12
12
 
13
+ /**
14
+ * Props for `FormGroup`. `state` from `FormGroupContext` ("default" | "error") propagates to nested inputs
15
+ * and help text via context.
16
+ */
13
17
  type FormGroupProps = {
14
18
  children: ReactNode;
15
19
  className?: string;
16
20
  testId?: string;
17
21
  } & FormGroupContext;
18
22
 
23
+ /**
24
+ * Label + input + help-text grouping with shared validation state. Provides the chrome for a single field.
25
+ *
26
+ * **When to use:** rendering a label + bare input + help/error text outside `useAppForm` (e.g. one-off
27
+ * filter fields, settings panes). The `state="error"` flips both `FormGroup.TextInput` and `FormGroup.Help`
28
+ * to error styling without per-component wiring.
29
+ * **When NOT to use:** for a full form with validation and submit state, use `useAppForm` from `blocks/Form` —
30
+ * it composes `FormGroup` internally via the typed field connectors.
31
+ * **Composition:** nest a `Label`, then one of `FormGroup.TextInput` / `FormGroup.NumberInput` /
32
+ * `FormGroup.ColorInput` / `FormGroup.Textarea`, optionally followed by `FormGroup.Help`.
33
+ */
19
34
  export const FormGroup = ({ className, children, state, testId }: FormGroupProps) => {
20
35
  return (
21
36
  <FormGroupProvider value={{ state }}>
@@ -1,6 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import type { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
3
3
 
4
+ import { ChevronLeft } from '../../icons';
4
5
  import { cn } from '../../utils';
5
6
  import { Button } from '../Button';
6
7
  import { Truncated } from '../Truncated';
@@ -48,7 +49,9 @@ export const FullScreenModalHeader = ({
48
49
  >
49
50
  <div className={cn('tw-flex tw-flex-1 tw-items-center', titleWrapperClassName)}>
50
51
  {previousTitle && onClose && (
51
- <Button icon="angle-left" onClick={onClose} size="icon" className="tw-mr-6" />
52
+ <Button onClick={onClose} size="icon" className="tw-mr-6">
53
+ <ChevronLeft className="tw-size-4" />
54
+ </Button>
52
55
  )}
53
56
 
54
57
  <div className="tw-w-full">
@@ -2,6 +2,7 @@ import { useState } from 'react';
2
2
  import type { ChangeEventHandler, ComponentProps, ElementRef, FocusEventHandler, Ref } from 'react';
3
3
  import type { Matcher } from 'react-day-picker';
4
4
 
5
+ import { Calendar as CalendarIcon } from '../../../icons';
5
6
  import { Button } from '../../Button';
6
7
  import { DatePicker } from '../../DatePicker';
7
8
  import { Input } from '../Input';
@@ -130,7 +131,9 @@ export const DateInput = ({
130
131
  selected={currentValue ?? undefined}
131
132
  startMonth={startMonth}
132
133
  >
133
- <Button aria-label="Select Date" icon="calendar" variant="inline" size="icon-3xs" />
134
+ <Button aria-label="Select Date" variant="inline" size="icon-3xs">
135
+ <CalendarIcon className="tw-size-4" />
136
+ </Button>
134
137
  </DatePicker>
135
138
  ) : undefined
136
139
  }
@@ -2,6 +2,7 @@ import { useState } from 'react';
2
2
  import type { ChangeEventHandler, ComponentProps, ElementRef, FocusEventHandler, Ref } from 'react';
3
3
  import type { DateRange } from 'react-day-picker';
4
4
 
5
+ import { Calendar as CalendarIcon } from '../../../icons';
5
6
  import { Button } from '../../Button';
6
7
  import { DatePicker } from '../../DatePicker';
7
8
  import { Input } from '../Input';
@@ -126,12 +127,9 @@ export const DateRangeInput = ({
126
127
  startMonth={startMonth}
127
128
  selected={currentValue ?? undefined}
128
129
  >
129
- <Button
130
- aria-label="Select Date Range"
131
- icon="calendar"
132
- variant="inline"
133
- size="icon-3xs"
134
- />
130
+ <Button aria-label="Select Date Range" variant="inline" size="icon-3xs">
131
+ <CalendarIcon className="tw-size-4" />
132
+ </Button>
135
133
  </DatePicker>
136
134
  ) : undefined
137
135
  }
@@ -9,6 +9,7 @@ import type {
9
9
  Ref,
10
10
  } from 'react';
11
11
 
12
+ import { X } from '../../icons';
12
13
  import { cn, createChangeInputEvent, mergeRefs } from '../../utils';
13
14
  import { InputGroup } from '../InputGroup/InputGroup';
14
15
 
@@ -31,10 +32,23 @@ type OwnProps = {
31
32
  defaultValue?: ComponentProps<'input'>['defaultValue'] | null;
32
33
  };
33
34
 
35
+ /**
36
+ * Props for `Input`. Combines `InputGroup` chrome (variant / sizing / state / focusVariant) with
37
+ * input-specific slots (`prefix`, `leftSuffix`, `rightSuffix`, `allowClear`).
38
+ */
34
39
  type Props = InputGroupProps &
35
40
  OwnProps &
36
41
  Omit<ComponentProps<'input'>, 'prefix' | 'value' | 'defaultValue'>;
37
42
 
43
+ /**
44
+ * Low-level text input with `InputGroup` chrome — addons, clear button, sizing, focus variants.
45
+ *
46
+ * **When to use:** when you need an input with prefix / suffix slots or a clear button and don't need the
47
+ * `onValueChange` convenience callback. Rare on its own — most call sites should use `TextInput`.
48
+ * **When NOT to use:** for plain text fields use `TextInput`; for fields inside a `FormGroup` use
49
+ * `FormGroup.TextInput`; for search use `SearchInput`; for forms with validation use `useAppForm`.
50
+ * **Composition:** wrap in `FormGroup` for shared error state, or render directly as a controlled input.
51
+ */
38
52
  export const Input = ({
39
53
  ref: forwardedRef,
40
54
  state = 'default',
@@ -124,11 +138,13 @@ export const Input = ({
124
138
  {leftSuffix}
125
139
  {allowClear && isClearButtonVisible && (
126
140
  <InputGroup.Button
127
- icon="xmark"
141
+ aria-label="Clear"
128
142
  onMouseDown={(e) => e.preventDefault()}
129
143
  onClick={handleClear}
130
144
  data-testid={testId.clearButton}
131
- />
145
+ >
146
+ <X className="tw-size-4" />
147
+ </InputGroup.Button>
132
148
  )}
133
149
  {rightSuffix && (
134
150
  <div
@@ -10,11 +10,17 @@ type NativeProps = Omit<
10
10
  type OwnProps = {
11
11
  value?: string | null;
12
12
  defaultValue?: string | null;
13
+ /** Called with the unwrapped string value on every change. For the raw event use `onChange`. */
13
14
  onValueChange?: (value: string) => void;
15
+ /** Called with the unwrapped string value on blur. For the raw event use `onBlur`. */
14
16
  onValueBlur?: (value: string) => void;
15
17
  type?: 'text' | 'email' | 'password' | 'datetime-local' | 'date';
16
18
  };
17
19
 
20
+ /**
21
+ * Props for `TextInput`. Extends `Input` with string-typed `value` / `defaultValue` and convenience
22
+ * `onValueChange` / `onValueBlur` callbacks. `null` / `undefined` are coerced to empty strings.
23
+ */
18
24
  export type Props = OwnProps & NativeProps;
19
25
 
20
26
  const sanitizeTextInputValue = (value: string | null | undefined) => {
@@ -22,6 +28,16 @@ const sanitizeTextInputValue = (value: string | null | undefined) => {
22
28
  return value;
23
29
  };
24
30
 
31
+ /**
32
+ * Standard text input with a string-only API. The default choice for a bare text input.
33
+ *
34
+ * **When to use:** any plain text / email / password / date-input field outside `useAppForm`. Pair with
35
+ * `onValueChange` instead of unwrapping `event.target.value` from `onChange`.
36
+ * **When NOT to use:** for fields inside a labelled group use `FormGroup.TextInput`; for forms with
37
+ * validation use `useAppForm` + `field.TextField`; for search use `SearchInput`.
38
+ * **Composition:** wrap in a `FormGroup` for shared error / help-text state, or render directly inside
39
+ * page layouts. In kbc-ui forms prefer `variant="secondary"` (gray background).
40
+ */
25
41
  export const TextInput = ({
26
42
  ref,
27
43
  value,
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import type { ChangeEventHandler, ComponentProps, ElementRef, FocusEventHandler, Ref } from 'react';
3
3
 
4
+ import { Clock } from '../../../icons';
4
5
  import { cn } from '../../../utils';
5
6
  import { Button } from '../../Button';
6
7
  import { TimePicker } from '../../TimePicker';
@@ -94,7 +95,9 @@ export const TimeInput = ({
94
95
  onSelect={handleTimePickerSelect}
95
96
  selected={currentValue}
96
97
  >
97
- <Button aria-label="Select Time" icon="clock" variant="inline" size="icon-3xs" />
98
+ <Button aria-label="Select Time" variant="inline" size="icon-3xs">
99
+ <Clock className="tw-size-4" />
100
+ </Button>
98
101
  </TimePicker>
99
102
  ) : undefined
100
103
  }
@@ -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 { Upload, X } from '@keboola/design/icons';
5
+
4
6
  import { Button } from '../Button/Button';
5
7
  import { Icon } from '../Icon';
6
8
 
@@ -69,7 +71,9 @@ export const WithButton: Story = {
69
71
  <InputGroup {...args}>
70
72
  <InputGroup.Input placeholder={placeholder} disabled={disabled} readOnly={readOnly} />
71
73
  <InputGroup.Addon align="inline-end">
72
- <InputGroup.Button icon="xmark" aria-label="Clear" />
74
+ <InputGroup.Button aria-label="Clear">
75
+ <X className="tw-size-4" />
76
+ </InputGroup.Button>
73
77
  </InputGroup.Addon>
74
78
  </InputGroup>
75
79
  ),
@@ -139,7 +143,9 @@ export const BlockStartAddon: Story = {
139
143
  <Icon icon="copy" />
140
144
  script.js
141
145
  </InputGroup.Text>
142
- <InputGroup.Button icon="upload" aria-label="Send" />
146
+ <InputGroup.Button aria-label="Send">
147
+ <Upload className="tw-size-4" />
148
+ </InputGroup.Button>
143
149
  </InputGroup.Addon>
144
150
 
145
151
  <InputGroup.TextArea placeholder={placeholder} disabled={disabled} readOnly={readOnly} />
@@ -187,7 +193,9 @@ export const BothBlockAddon: Story = {
187
193
  <Icon icon="copy" />
188
194
  script.js
189
195
  </InputGroup.Text>
190
- <InputGroup.Button icon="upload" aria-label="Send" />
196
+ <InputGroup.Button aria-label="Send">
197
+ <Upload className="tw-size-4" />
198
+ </InputGroup.Button>
191
199
  </InputGroup.Addon>
192
200
 
193
201
  <InputGroup.TextArea
@@ -98,7 +98,6 @@ const ControlledDemo = () => {
98
98
  onChange={setValue}
99
99
  placeholder="Type something…"
100
100
  onSave={() => alert('Saved!')}
101
- onCancel={() => alert('Cancelled!')}
102
101
  onImageUpload={mockImageUpload}
103
102
  showToolbar
104
103
  autoFocus
@@ -118,3 +117,66 @@ const ControlledDemo = () => {
118
117
  export const Controlled: Story = {
119
118
  render: ControlledDemo,
120
119
  };
120
+
121
+ const InlineDemo = () => {
122
+ // Two-state pattern (per the inline-editable-text pattern spec):
123
+ // `committed` is what's persisted; `draft` tracks in-progress edits.
124
+ // onSave commits draft → committed. onCancel (Esc) reverts draft ← committed.
125
+ const [committed, setCommitted] = useState(
126
+ 'A multi-line description that overflows two lines so you can see the bottom fade in display state. Click to edit — it expands to full height. Press Esc to revert.',
127
+ );
128
+ const [draft, setDraft] = useState(committed);
129
+
130
+ return (
131
+ <div className="tw-max-w-prose tw-rounded tw-border tw-border-border tw-bg-background tw-p-6">
132
+ <p className="tw-mb-2 tw-text-sm tw-text-muted-foreground">
133
+ Click to edit. Blur or Cmd+Enter commits; Esc reverts. Committed:{' '}
134
+ <code className="tw-text-xs">{committed}</code>
135
+ </p>
136
+ <MarkdownEditor
137
+ compact
138
+ saveOnBlur
139
+ clampDisplay
140
+ value={draft}
141
+ onChange={setDraft}
142
+ onSave={() => setCommitted(draft)}
143
+ onCancel={() => setDraft(committed)}
144
+ placeholder="Add a description…"
145
+ className="tw-text-sm tw-leading-5 tw-text-foreground"
146
+ />
147
+ </div>
148
+ );
149
+ };
150
+
151
+ export const Inline: Story = {
152
+ render: InlineDemo,
153
+ };
154
+
155
+ const InlineTitleDemo = () => {
156
+ const [committed, setCommitted] = useState('Untitled page');
157
+ const [draft, setDraft] = useState(committed);
158
+
159
+ return (
160
+ <div className="tw-max-w-prose tw-rounded tw-border tw-border-border tw-bg-background tw-p-6">
161
+ <p className="tw-mb-2 tw-text-sm tw-text-muted-foreground">
162
+ Click the heading. Enter, blur, or Cmd+Enter commits; Esc reverts. Committed:{' '}
163
+ <code className="tw-text-xs">{committed}</code>
164
+ </p>
165
+ <MarkdownEditor
166
+ compact
167
+ singleLine
168
+ saveOnBlur
169
+ value={draft}
170
+ onChange={setDraft}
171
+ onSave={() => setCommitted(draft)}
172
+ onCancel={() => setDraft(committed)}
173
+ placeholder="Untitled"
174
+ className="tw-break-words tw-text-3xl tw-font-semibold tw-leading-9 tw-text-foreground"
175
+ />
176
+ </div>
177
+ );
178
+ };
179
+
180
+ export const InlineTitle: Story = {
181
+ render: InlineTitleDemo,
182
+ };