@keboola/design 1.3.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/icons.cjs +32 -0
- package/dist/icons.js +1 -1
- package/dist/index.cjs +507 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +495 -106
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.cjs +78 -42
- package/dist/markdown-editor.cjs.map +1 -1
- package/dist/markdown-editor.js +78 -42
- package/dist/markdown-editor.js.map +1 -1
- package/dist/mermaid.cjs +32 -9
- package/dist/mermaid.cjs.map +1 -1
- package/dist/mermaid.js +32 -9
- package/dist/mermaid.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +8 -6
- package/src/blocks/AIKit/PlanApproval.tsx +29 -12
- package/src/blocks/AIKit/PromptInput.test.tsx +53 -0
- package/src/blocks/AIKit/PromptInput.tsx +1 -1
- package/src/blocks/AIKit/Question.tsx +39 -11
- package/src/blocks/AIKit/SuggestedPrompt.stories.tsx +2 -8
- package/src/blocks/EmptyState/EmptyState.stories.tsx +5 -5
- package/src/blocks/EmptyState/EmptyState.tsx +2 -2
- package/src/blocks/ErrorState/ErrorState.stories.tsx +6 -6
- package/src/blocks/ErrorState/ErrorState.tsx +18 -3
- package/src/blocks/InputOtp/InputOtp.stories.tsx +214 -0
- package/src/blocks/InputOtp/InputOtp.test.tsx +162 -0
- package/src/blocks/InputOtp/InputOtp.tsx +62 -0
- package/src/blocks/InputOtp/InputOtpGroup.tsx +13 -0
- package/src/blocks/InputOtp/InputOtpSeparator.tsx +23 -0
- package/src/blocks/InputOtp/InputOtpSlot.styles.ts +52 -0
- package/src/blocks/InputOtp/InputOtpSlot.tsx +44 -0
- package/src/blocks/InputOtp/context.ts +23 -0
- package/src/blocks/InputOtp/index.ts +23 -0
- package/src/blocks/PageHeader/AGENTS.md +1 -1
- package/src/blocks/PageHeader/PageHeader.stories.tsx +6 -3
- package/src/blocks/PageHeader/PageHeaderIndex.tsx +2 -2
- package/src/blocks/PageLayout/AGENTS.md +3 -3
- package/src/blocks/PageLayout/PageLayout.stories.tsx +7 -4
- package/src/blocks/PageLayout/PageLayout.tsx +1 -18
- package/src/blocks/PageToolbar/PageToolbar.stories.tsx +79 -74
- package/src/blocks/PageToolbar/PageToolbar.tsx +45 -4
- package/src/blocks/PageToolbar/PageToolbarToken.tsx +61 -0
- package/src/blocks/SearchInput/SearchInput.tsx +2 -2
- package/src/components/Alert/AlertDescription.tsx +5 -0
- package/src/components/Alert/AlertIcon.tsx +1 -1
- package/src/components/Badge/Badge.stories.tsx +3 -5
- package/src/components/Badge/BadgeAddon.tsx +1 -1
- package/src/components/Badge/BadgeButton.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +4 -4
- package/src/components/Breadcrumb/BreadcrumbLink.tsx +1 -1
- package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +1 -1
- package/src/components/Button/ButtonInline.stories.tsx +3 -3
- package/src/components/Button/ButtonInline.tsx +4 -0
- package/src/components/Button/styles.ts +33 -10
- package/src/components/Card/Card.stories.tsx +2 -2
- package/src/components/Combobox/ComboboxItemIndicator.tsx +1 -1
- package/src/components/Command/CommandItem/CommandItem.tsx +1 -1
- package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +4 -4
- package/src/components/DropdownMenu/ItemIcon.tsx +1 -1
- package/src/components/FormGroup/FormGroup.stories.tsx +2 -2
- package/src/components/HoverCard/HoverCardContent.tsx +5 -0
- package/src/components/Input/Input.stories.tsx +2 -2
- package/src/components/InputGroup/InputGroup.stories.tsx +4 -5
- package/src/components/InputGroup/InputGroup.tsx +1 -1
- package/src/components/InputGroup/InputGroupAddon.tsx +35 -24
- package/src/components/Kbd/Kbd.stories.tsx +2 -2
- package/src/components/Label/Label.stories.tsx +2 -2
- package/src/components/Modal/ModalBody.tsx +5 -0
- package/src/components/Modal/ModalIcon.tsx +1 -1
- package/src/components/Navbar/Navbar.stories.tsx +4 -5
- package/src/components/Navbar/NavbarMenuItemAdornment.tsx +2 -2
- package/src/components/Pagination/Pagination.stories.tsx +0 -1
- package/src/components/Pagination/PaginationEllipsis.tsx +1 -1
- package/src/components/Popover/PopoverContent.tsx +4 -0
- package/src/components/Select/SelectContent.tsx +1 -1
- package/src/components/Select/SelectTrigger.tsx +1 -1
- package/src/components/Separator/Separator.stories.tsx +19 -0
- package/src/components/Separator/Separator.test.tsx +39 -0
- package/src/components/Separator/Separator.tsx +42 -11
- package/src/components/Sidebar/Sidebar.stories.tsx +6 -5
- package/src/components/Sidebar/SidebarGroup/SidebarGroupAction.tsx +1 -1
- package/src/components/Sidebar/SidebarMenu/SidebarMenuAction.tsx +1 -1
- package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +1 -1
- package/src/components/Table/Table.stories.tsx +16 -16
- package/src/components/Timeline/Timeline.stories.tsx +8 -8
- package/src/components/Timeline/TimelineConnector.tsx +5 -1
- package/src/components/Toast/ToastDismiss.tsx +1 -1
- package/src/components/Toast/ToastRoot.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/ToggleGroup/ToggleGroup.stories.tsx +60 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +99 -0
- package/src/components/ToggleGroup/index.ts +1 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +2 -2
- package/src/hooks/useAutosizeInput.stories.tsx +2 -2
- package/src/icons/index.ts +8 -0
- package/src/index.ts +2 -0
- package/src/patterns/FilterToolbar.stories.tsx +248 -44
|
@@ -143,16 +143,39 @@ export const variants = cva(
|
|
|
143
143
|
],
|
|
144
144
|
},
|
|
145
145
|
size: {
|
|
146
|
-
medium: [
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
medium: [
|
|
147
|
+
'tw-max-h-10',
|
|
148
|
+
'tw-min-h-10',
|
|
149
|
+
'tw-px-4',
|
|
150
|
+
'tw-py-2.5',
|
|
151
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-4',
|
|
152
|
+
],
|
|
153
|
+
small: [
|
|
154
|
+
'tw-max-h-8',
|
|
155
|
+
'tw-min-h-8',
|
|
156
|
+
'tw-px-4',
|
|
157
|
+
'tw-py-1.5',
|
|
158
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-4',
|
|
159
|
+
],
|
|
160
|
+
large: [
|
|
161
|
+
'tw-max-h-12',
|
|
162
|
+
'tw-min-h-12',
|
|
163
|
+
'tw-px-4',
|
|
164
|
+
'tw-py-3.5',
|
|
165
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-4',
|
|
166
|
+
],
|
|
167
|
+
|
|
168
|
+
'icon-lg': ['tw-size-12', 'tw-p-0', '[&_svg:not([class*="tw-size-"])]:tw-size-6'],
|
|
169
|
+
icon: ['tw-size-10', 'tw-p-0', '[&_svg:not([class*="tw-size-"])]:tw-size-4'],
|
|
170
|
+
'icon-sm': ['tw-size-8', 'tw-p-0', '[&_svg:not([class*="tw-size-"])]:tw-size-4'],
|
|
171
|
+
'icon-xs': ['tw-size-6', 'tw-p-0', '[&_svg:not([class*="tw-size-"])]:tw-size-4'],
|
|
172
|
+
'icon-2xs': [
|
|
173
|
+
'!tw-inline-flex',
|
|
174
|
+
'tw-size-5',
|
|
175
|
+
'tw-max-h-5',
|
|
176
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-3',
|
|
177
|
+
],
|
|
178
|
+
'icon-3xs': ['tw-h-fit', 'tw-w-fit', '[&_svg:not([class*="tw-size-"])]:tw-size-4'],
|
|
156
179
|
},
|
|
157
180
|
},
|
|
158
181
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
|
|
3
|
+
import { Plus } from '../../icons';
|
|
3
4
|
import { ButtonInline } from '../Button';
|
|
4
|
-
import { Icon } from '../Icon';
|
|
5
5
|
|
|
6
6
|
import { Card } from './Card';
|
|
7
7
|
|
|
@@ -44,7 +44,7 @@ export const WithActions: Story = {
|
|
|
44
44
|
<Card.Header className="tw-flex tw-justify-between">
|
|
45
45
|
<Card.Title>Secrets</Card.Title>
|
|
46
46
|
<ButtonInline color="dark">
|
|
47
|
-
<
|
|
47
|
+
<Plus className="tw-size-4" />
|
|
48
48
|
New Secret
|
|
49
49
|
</ButtonInline>
|
|
50
50
|
</Card.Header>
|
|
@@ -67,7 +67,7 @@ export const ComboboxItemIndicator = ({
|
|
|
67
67
|
'group-data-[variant=default]/combobox-content:tw-right-4',
|
|
68
68
|
// Lucide SVGs default to 24×24 — constrain the rendered child so the
|
|
69
69
|
// 16px indicator slot owns the actual size.
|
|
70
|
-
'[&_svg]:tw-size-4',
|
|
70
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-4',
|
|
71
71
|
'group-data-[highlighted]/combobox-item:tw-text-accent-foreground',
|
|
72
72
|
'group-data-[variant=default]/combobox-content:group-data-[highlighted]/combobox-item:tw-text-foreground',
|
|
73
73
|
className,
|
|
@@ -69,7 +69,7 @@ export const CommandItem = ({
|
|
|
69
69
|
>
|
|
70
70
|
<CheckIndicator variant={variant} />
|
|
71
71
|
{icon && (
|
|
72
|
-
<span className="tw-inline-flex tw-size-4 tw-shrink-0 tw-items-center tw-justify-center tw-text-neutral-400 [&_svg]:tw-size-4">
|
|
72
|
+
<span className="tw-inline-flex tw-size-4 tw-shrink-0 tw-items-center tw-justify-center tw-text-neutral-400 [&_svg:not([class*='tw-size-'])]:tw-size-4">
|
|
73
73
|
{icon}
|
|
74
74
|
</span>
|
|
75
75
|
)}
|
|
@@ -31,7 +31,7 @@ const variants = cva(
|
|
|
31
31
|
'tw-rounded-full',
|
|
32
32
|
'tw-bg-white',
|
|
33
33
|
|
|
34
|
-
'[&_svg]:tw-size-2',
|
|
34
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-2',
|
|
35
35
|
'[&_svg]:data-[state=unchecked]:tw-hidden',
|
|
36
36
|
// Lucide Circle is stroke-only — force fill to match the stroke
|
|
37
37
|
// (currentColor) so the indicator reads as a solid dot.
|
|
@@ -39,7 +39,7 @@ const variants = cva(
|
|
|
39
39
|
'group-data-[variant=argo]/dropdown-menu-content:[&_svg]:tw-text-primary',
|
|
40
40
|
'group-data-[variant=default]/dropdown-menu-content:[&_svg]:tw-text-secondary-500',
|
|
41
41
|
],
|
|
42
|
-
check: ['[&_svg]:tw-size-3.5', '[&_svg]:tw-text-primary'],
|
|
42
|
+
check: ['[&_svg:not([class*="tw-size-"])]:tw-size-3.5', '[&_svg]:tw-text-primary'],
|
|
43
43
|
checkbox: [
|
|
44
44
|
'[&_svg]:tw-text-white',
|
|
45
45
|
'tw-border',
|
|
@@ -47,7 +47,7 @@ const variants = cva(
|
|
|
47
47
|
'tw-border-neutral-200',
|
|
48
48
|
'tw-rounded',
|
|
49
49
|
|
|
50
|
-
'[&_svg]:tw-size-3',
|
|
50
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-3',
|
|
51
51
|
'[&_svg]:data-[state=unchecked]:tw-hidden',
|
|
52
52
|
'group-data-[variant=argo]/dropdown-menu-content:data-[state=checked]:tw-border-primary',
|
|
53
53
|
'group-data-[variant=argo]/dropdown-menu-content:data-[state=checked]:tw-bg-primary',
|
|
@@ -58,7 +58,7 @@ const variants = cva(
|
|
|
58
58
|
'tw-size-5',
|
|
59
59
|
// Lucide SVGs default to 24×24 — constrain the rendered ToggleRight
|
|
60
60
|
// to the indicator slot so it doesn't overflow.
|
|
61
|
-
'[&_svg]:tw-size-5',
|
|
61
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-5',
|
|
62
62
|
'tw-text-neutral-400',
|
|
63
63
|
'data-[state=unchecked]:tw-rotate-180',
|
|
64
64
|
'group-data-[variant=default]/dropdown-menu-content:data-[state=checked]:tw-text-secondary-500',
|
|
@@ -17,7 +17,7 @@ export const ItemIcon = ({ className, children, ...props }: Props) => (
|
|
|
17
17
|
// 20px container fits the 16px Lucide glyph plus 2px breathing room
|
|
18
18
|
// each side — the previous tw-size-4 + tw-p-0.5 clipped the 16px child.
|
|
19
19
|
'tw-size-5',
|
|
20
|
-
'[&_svg]:tw-size-4',
|
|
20
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-4',
|
|
21
21
|
'group-data-[variant=default]/dropdown-menu-content:tw-text-neutral-400',
|
|
22
22
|
'group-data-[variant=argo]/dropdown-menu-content:tw-text-primary',
|
|
23
23
|
'group-data-[variant=argo]/dropdown-menu-content:group-aria-disabled:tw-text-muted-foreground',
|
|
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
import type { ComponentProps } from 'react';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Info } from '../../icons';
|
|
6
6
|
import { Label } from '../Label';
|
|
7
7
|
|
|
8
8
|
import { FormGroup } from './FormGroup';
|
|
@@ -39,7 +39,7 @@ const FormGroupDemo = ({ variant = 'primary', ...formGroupProps }: Props) => {
|
|
|
39
39
|
<form className="tw-flex tw-flex-col tw-gap-4">
|
|
40
40
|
<FormGroup {...formGroupProps}>
|
|
41
41
|
<Label>
|
|
42
|
-
Text Input <
|
|
42
|
+
Text Input <Info className="tw-size-4 tw-text-neutral-400" />
|
|
43
43
|
</Label>
|
|
44
44
|
<FormGroup.TextInput
|
|
45
45
|
placeholder="e.g John"
|
|
@@ -53,6 +53,11 @@ export const HoverCardContent = ({
|
|
|
53
53
|
'data-[state=closed]:tw-animate-out',
|
|
54
54
|
'data-[state=closed]:tw-fade-out-0',
|
|
55
55
|
'data-[state=closed]:tw-zoom-out-95',
|
|
56
|
+
|
|
57
|
+
// Default size for nested Lucide icons; callers can override on the icon.
|
|
58
|
+
"[&_svg:not([class*='tw-size-'])]:tw-size-4",
|
|
59
|
+
'[&_svg]:tw-shrink-0',
|
|
60
|
+
|
|
56
61
|
className,
|
|
57
62
|
)}
|
|
58
63
|
{...props}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Search } from '../../icons';
|
|
5
5
|
|
|
6
6
|
import { Input } from './Input';
|
|
7
7
|
|
|
@@ -68,7 +68,7 @@ export const Basic: Story = {
|
|
|
68
68
|
|
|
69
69
|
export const WithPrefix: Story = {
|
|
70
70
|
args: {
|
|
71
|
-
prefix: <
|
|
71
|
+
prefix: <Search className="tw-size-4 tw-text-neutral-400" />,
|
|
72
72
|
},
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -1,10 +1,9 @@
|
|
|
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';
|
|
4
|
+
import { Copy, Search, Upload, X } from '@keboola/design/icons';
|
|
5
5
|
|
|
6
6
|
import { Button } from '../Button/Button';
|
|
7
|
-
import { Icon } from '../Icon';
|
|
8
7
|
|
|
9
8
|
import { InputGroup } from './InputGroup';
|
|
10
9
|
|
|
@@ -92,7 +91,7 @@ export const InlineStartAddon: Story = {
|
|
|
92
91
|
render: ({ disabled, readOnly, placeholder, ...args }) => (
|
|
93
92
|
<InputGroup {...args}>
|
|
94
93
|
<InputGroup.Addon align="inline-start">
|
|
95
|
-
<
|
|
94
|
+
<Search className="tw-size-4 tw-text-neutral-400" />
|
|
96
95
|
</InputGroup.Addon>
|
|
97
96
|
<InputGroup.Input placeholder={placeholder} disabled={disabled} readOnly={readOnly} />
|
|
98
97
|
</InputGroup>
|
|
@@ -140,7 +139,7 @@ export const BlockStartAddon: Story = {
|
|
|
140
139
|
<InputGroup {...args}>
|
|
141
140
|
<InputGroup.Addon align="block-start" className="tw-justify-between">
|
|
142
141
|
<InputGroup.Text>
|
|
143
|
-
<
|
|
142
|
+
<Copy />
|
|
144
143
|
script.js
|
|
145
144
|
</InputGroup.Text>
|
|
146
145
|
<InputGroup.Button aria-label="Send">
|
|
@@ -190,7 +189,7 @@ export const BothBlockAddon: Story = {
|
|
|
190
189
|
<InputGroup {...args}>
|
|
191
190
|
<InputGroup.Addon align="block-start" className="tw-justify-between tw-border-b">
|
|
192
191
|
<InputGroup.Text>
|
|
193
|
-
<
|
|
192
|
+
<Copy />
|
|
194
193
|
script.js
|
|
195
194
|
</InputGroup.Text>
|
|
196
195
|
<InputGroup.Button aria-label="Send">
|
|
@@ -5,32 +5,43 @@ import { cn } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
// Padding only fires when the addon actually has content — keeps an empty
|
|
7
7
|
// `<Addon>{maybeNull}</Addon>` from carving 16px out of the input row.
|
|
8
|
-
const addonVariants = cva(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
const addonVariants = cva(
|
|
9
|
+
[
|
|
10
|
+
'tw-flex',
|
|
11
|
+
'tw-items-center',
|
|
12
|
+
'tw-gap-1',
|
|
13
|
+
|
|
14
|
+
// Default size for nested Lucide icons in prefix/suffix slots; callers can override on the icon.
|
|
15
|
+
"[&_svg:not([class*='tw-size-'])]:tw-size-4",
|
|
16
|
+
'[&_svg]:tw-shrink-0',
|
|
17
|
+
],
|
|
18
|
+
{
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
align: 'inline-start',
|
|
21
|
+
},
|
|
22
|
+
variants: {
|
|
23
|
+
align: {
|
|
24
|
+
'inline-start': ['tw-order-first', '[&:not(:empty)]:tw-pl-4'],
|
|
25
|
+
'inline-end': ['tw-order-last', '[&:not(:empty)]:tw-pr-4'],
|
|
26
|
+
'block-start': [
|
|
27
|
+
'tw-order-first',
|
|
28
|
+
// `tw-w-full` needs to be empty-conditional too — when the addon
|
|
29
|
+
// has no content the root stays in flex-row, and a 100%-wide
|
|
30
|
+
// empty addon would claim half of the input row beside the input.
|
|
31
|
+
'[&:not(:empty)]:tw-w-full',
|
|
32
|
+
'[&:not(:empty)]:tw-px-4',
|
|
33
|
+
'[&:not(:empty)]:tw-py-1.5',
|
|
34
|
+
],
|
|
35
|
+
'block-end': [
|
|
36
|
+
'tw-order-last',
|
|
37
|
+
'[&:not(:empty)]:tw-w-full',
|
|
38
|
+
'[&:not(:empty)]:tw-px-4',
|
|
39
|
+
'[&:not(:empty)]:tw-py-1.5',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
31
42
|
},
|
|
32
43
|
},
|
|
33
|
-
|
|
44
|
+
);
|
|
34
45
|
|
|
35
46
|
type Props = ComponentProps<'div'> & VariantProps<typeof addonVariants>;
|
|
36
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Copy } from '../../icons';
|
|
4
4
|
|
|
5
5
|
import { Kbd } from './Kbd';
|
|
6
6
|
import { keys } from './keyboardKeys';
|
|
@@ -44,7 +44,7 @@ export const AsSymbol: Story = {
|
|
|
44
44
|
|
|
45
45
|
export const AsIcon: Story = {
|
|
46
46
|
args: {
|
|
47
|
-
children: <
|
|
47
|
+
children: <Copy />,
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
|
|
3
|
+
import { Info } from '../../icons';
|
|
3
4
|
import { FormGroup } from '../FormGroup';
|
|
4
|
-
import { Icon } from '../Icon';
|
|
5
5
|
import { TextInput } from '../Input';
|
|
6
6
|
|
|
7
7
|
import { Label } from './Label';
|
|
@@ -46,7 +46,7 @@ export const WithIcon: Story = {
|
|
|
46
46
|
className: 'tw-flex tw-items-center tw-gap-1',
|
|
47
47
|
children: (
|
|
48
48
|
<>
|
|
49
|
-
Label <
|
|
49
|
+
Label <Info className="tw-size-4 tw-text-neutral-400" />
|
|
50
50
|
</>
|
|
51
51
|
),
|
|
52
52
|
},
|
|
@@ -21,6 +21,11 @@ export const ModalBody = ({ className, ...props }: Props) => (
|
|
|
21
21
|
|
|
22
22
|
'tw-px-1', // 4 px to complement ModalContent and still keep input focus outline visible
|
|
23
23
|
'tw-py-1',
|
|
24
|
+
|
|
25
|
+
// Default size for nested Lucide icons; callers can override on the icon.
|
|
26
|
+
"[&_svg:not([class*='tw-size-'])]:tw-size-4",
|
|
27
|
+
'[&_svg]:tw-shrink-0',
|
|
28
|
+
|
|
24
29
|
className,
|
|
25
30
|
)}
|
|
26
31
|
{...props}
|
|
@@ -96,7 +96,7 @@ export const ModalIcon = (props: Props) => {
|
|
|
96
96
|
'tw-text-white': props.color !== 'violet',
|
|
97
97
|
'tw-text-purple-500': props.color === 'violet',
|
|
98
98
|
},
|
|
99
|
-
'[&_svg]:tw-size-6',
|
|
99
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-6',
|
|
100
100
|
'[&_svg]:tw-aspect-square',
|
|
101
101
|
)}
|
|
102
102
|
>
|
|
@@ -1,8 +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 '
|
|
5
|
-
import { Icon } from '../Icon';
|
|
4
|
+
import { Bell, ChevronsUpDown, Diamond, Keboola, Search } from '@keboola/design/icons';
|
|
6
5
|
|
|
7
6
|
import { Navbar } from './Navbar';
|
|
8
7
|
|
|
@@ -54,13 +53,13 @@ export const Default: Story = {
|
|
|
54
53
|
<Navbar.MenuItem>
|
|
55
54
|
<Navbar.MenuButton variant="emphasized" icon={<Diamond className="tw-size-4" />}>
|
|
56
55
|
Project
|
|
57
|
-
<
|
|
56
|
+
<ChevronsUpDown className="tw-size-5" />
|
|
58
57
|
</Navbar.MenuButton>
|
|
59
58
|
</Navbar.MenuItem>
|
|
60
59
|
<Navbar.MenuItem>
|
|
61
60
|
<Navbar.MenuButton icon={<Diamond className="tw-size-4" />}>
|
|
62
61
|
Branch
|
|
63
|
-
<
|
|
62
|
+
<ChevronsUpDown className="tw-size-5" />
|
|
64
63
|
</Navbar.MenuButton>
|
|
65
64
|
</Navbar.MenuItem>
|
|
66
65
|
</Navbar.Menu>
|
|
@@ -109,7 +108,7 @@ export const ContentScrolling: Story = {
|
|
|
109
108
|
<Navbar.MenuItem key={item}>
|
|
110
109
|
<Navbar.MenuButton icon={<Diamond className="tw-size-4" />}>
|
|
111
110
|
{item}
|
|
112
|
-
<
|
|
111
|
+
<ChevronsUpDown className="tw-size-5" />
|
|
113
112
|
</Navbar.MenuButton>
|
|
114
113
|
</Navbar.MenuItem>
|
|
115
114
|
))}
|
|
@@ -9,7 +9,7 @@ const variants = cva(
|
|
|
9
9
|
'tw-justify-center',
|
|
10
10
|
'tw-items-center',
|
|
11
11
|
|
|
12
|
-
'[&_svg]:tw-size-3',
|
|
12
|
+
'[&_svg:not([class*="tw-size-"])]:tw-size-3',
|
|
13
13
|
'[&:not(svg)]:tw-h-5',
|
|
14
14
|
'[&:not(svg)]:tw-min-w-5',
|
|
15
15
|
|
|
@@ -43,7 +43,7 @@ const variants = cva(
|
|
|
43
43
|
'group-data-[accent=orange]/navbar:tw-bg-warning-500',
|
|
44
44
|
'group-data-[accent=green]/navbar:tw-bg-primary-500',
|
|
45
45
|
],
|
|
46
|
-
'icon-button': ['tw-size-4', 'tw-
|
|
46
|
+
'icon-button': ['tw-size-4', '!tw-p-0', '[&_svg:not([class*="tw-size-"])]:tw-size-4'],
|
|
47
47
|
shortcut: ['tw-bg-muted', 'tw-text-sidebar-foreground', 'tw-ml-auto'],
|
|
48
48
|
},
|
|
49
49
|
},
|
|
@@ -48,6 +48,10 @@ const variants = cva(
|
|
|
48
48
|
'data-[side=left]:tw-slide-in-from-right-2',
|
|
49
49
|
'data-[side=right]:tw-slide-in-from-left-2',
|
|
50
50
|
'data-[side=top]:tw-slide-in-from-bottom-2',
|
|
51
|
+
|
|
52
|
+
// Default size for nested Lucide icons; callers can override on the icon.
|
|
53
|
+
"[&_svg:not([class*='tw-size-'])]:tw-size-4",
|
|
54
|
+
'[&_svg]:tw-shrink-0',
|
|
51
55
|
],
|
|
52
56
|
{
|
|
53
57
|
defaultVariants: {
|
|
@@ -39,3 +39,22 @@ const meta: Meta<typeof Separator> = {
|
|
|
39
39
|
export default meta;
|
|
40
40
|
|
|
41
41
|
export const Basic: Story = {};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pass `label` to render text centered between two line segments — the typical
|
|
45
|
+
* "OR" divider used between auth options. Always rendered horizontally.
|
|
46
|
+
*/
|
|
47
|
+
export const WithLabel: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
label: 'OR',
|
|
50
|
+
},
|
|
51
|
+
render: (props) => (
|
|
52
|
+
<Card>
|
|
53
|
+
<Card.Content className="tw-flex tw-w-80 tw-flex-col tw-gap-4">
|
|
54
|
+
<span>Sign in with email</span>
|
|
55
|
+
<Separator {...props} />
|
|
56
|
+
<span>Sign in with SSO</span>
|
|
57
|
+
</Card.Content>
|
|
58
|
+
</Card>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { Separator } from './Separator';
|
|
5
|
+
|
|
6
|
+
describe('<Separator />', () => {
|
|
7
|
+
it('renders the plain decorative separator without a label', () => {
|
|
8
|
+
const { container } = render(<Separator orientation="horizontal" />);
|
|
9
|
+
expect(container.querySelector('[data-orientation="horizontal"]')).not.toBeNull();
|
|
10
|
+
expect(screen.queryByText('OR')).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders the label between two line segments when label is provided', () => {
|
|
14
|
+
const { container } = render(<Separator label="OR" />);
|
|
15
|
+
expect(screen.getByText('OR')).not.toBeNull();
|
|
16
|
+
// Decorative line segments are drawn with before:/after: pseudo-elements,
|
|
17
|
+
// so the label is the only DOM child of the separator root.
|
|
18
|
+
const root = container.querySelector('[data-orientation="horizontal"]');
|
|
19
|
+
expect(root?.children).toHaveLength(0);
|
|
20
|
+
expect(root?.textContent).toBe('OR');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('forces horizontal orientation when a label is provided', () => {
|
|
24
|
+
const { container } = render(<Separator orientation="vertical" label="OR" />);
|
|
25
|
+
expect(container.querySelector('[data-orientation="horizontal"]')).not.toBeNull();
|
|
26
|
+
expect(container.querySelector('[data-orientation="vertical"]')).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('treats label={false} as no label (conditional rendering pattern)', () => {
|
|
30
|
+
const condition = false;
|
|
31
|
+
render(<Separator label={condition && 'OR'} />);
|
|
32
|
+
expect(screen.queryByText('OR')).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('treats label={null} as no label', () => {
|
|
36
|
+
render(<Separator label={null} />);
|
|
37
|
+
expect(screen.queryByText('OR')).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { ComponentProps } from 'react';
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
2
|
import * as RSeparator from '@radix-ui/react-separator';
|
|
3
3
|
import type { VariantProps } from 'class-variance-authority';
|
|
4
4
|
import { cva } from 'class-variance-authority';
|
|
5
5
|
|
|
6
|
+
import { cn } from '../../utils';
|
|
7
|
+
|
|
6
8
|
const separatorVariants = cva(
|
|
7
9
|
[
|
|
8
10
|
// position
|
|
@@ -13,7 +15,7 @@ const separatorVariants = cva(
|
|
|
13
15
|
|
|
14
16
|
// before
|
|
15
17
|
"before:tw-content-['']",
|
|
16
|
-
'before:tw-bg-
|
|
18
|
+
'before:tw-bg-border',
|
|
17
19
|
],
|
|
18
20
|
{
|
|
19
21
|
defaultVariants: {
|
|
@@ -67,18 +69,47 @@ const separatorVariants = cva(
|
|
|
67
69
|
);
|
|
68
70
|
|
|
69
71
|
type Props = VariantProps<typeof separatorVariants> &
|
|
70
|
-
Omit<ComponentProps<typeof RSeparator.Root>, 'decorative'
|
|
72
|
+
Omit<ComponentProps<typeof RSeparator.Root>, 'decorative'> & {
|
|
73
|
+
/**
|
|
74
|
+
* Optional label rendered centered between two line segments
|
|
75
|
+
* (typical "OR" divider). Implies `orientation="horizontal"`.
|
|
76
|
+
* Overrides any `children` passed to the component.
|
|
77
|
+
*/
|
|
78
|
+
label?: ReactNode;
|
|
79
|
+
};
|
|
71
80
|
|
|
72
81
|
export const Separator = ({
|
|
73
82
|
className,
|
|
74
83
|
orientation = 'vertical',
|
|
75
84
|
size = 'full',
|
|
85
|
+
label,
|
|
76
86
|
...props
|
|
77
|
-
}: Props) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
}: Props) => {
|
|
88
|
+
if (label != null && label !== false) {
|
|
89
|
+
return (
|
|
90
|
+
<RSeparator.Root
|
|
91
|
+
{...props}
|
|
92
|
+
decorative
|
|
93
|
+
orientation="horizontal"
|
|
94
|
+
className={cn(
|
|
95
|
+
'tw-flex tw-w-full tw-items-center tw-gap-3 tw-self-stretch',
|
|
96
|
+
'tw-text-xs tw-font-medium tw-text-muted-foreground',
|
|
97
|
+
"before:tw-content-[''] before:tw-h-px before:tw-flex-1 before:tw-bg-border",
|
|
98
|
+
"after:tw-content-[''] after:tw-h-px after:tw-flex-1 after:tw-bg-border",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{label}
|
|
103
|
+
</RSeparator.Root>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<RSeparator.Root
|
|
109
|
+
{...props}
|
|
110
|
+
decorative
|
|
111
|
+
orientation={orientation}
|
|
112
|
+
className={separatorVariants({ size, orientation, className })}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
};
|