@keboola/design 1.3.0 → 2.0.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.
- package/dist/icons.cjs +24 -0
- package/dist/icons.js +1 -1
- package/dist/index.cjs +278 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +278 -78
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.cjs +39 -26
- package/dist/markdown-editor.cjs.map +1 -1
- package/dist/markdown-editor.js +39 -26
- package/dist/markdown-editor.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -3
- 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 +3 -3
- package/src/blocks/MultiActionButtonGroup/MultiActionButton.tsx +2 -1
- 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 +3 -3
- package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.tsx +1 -1
- package/src/components/Alert/AlertDescription.tsx +5 -0
- package/src/components/Badge/Badge.stories.tsx +3 -5
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +4 -4
- package/src/components/Breadcrumb/BreadcrumbLink.tsx +1 -1
- package/src/components/Button/ButtonInline.stories.tsx +3 -3
- package/src/components/Button/ButtonInline.tsx +4 -0
- package/src/components/Card/Card.stories.tsx +2 -2
- 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/Navbar/Navbar.stories.tsx +4 -5
- package/src/components/Pagination/Pagination.stories.tsx +0 -1
- package/src/components/Popover/PopoverContent.tsx +4 -0
- package/src/components/Search/Search.tsx +2 -2
- 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/Table/Table.stories.tsx +16 -16
- package/src/components/Timeline/Timeline.stories.tsx +8 -8
- package/src/components/Timeline/TimelineConnector.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 +6 -0
- package/src/index.ts +1 -0
- package/src/patterns/FilterToolbar.stories.tsx +248 -44
|
@@ -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}
|
|
@@ -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
|
))}
|
|
@@ -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: {
|
|
@@ -112,9 +112,9 @@ export const Search = ({
|
|
|
112
112
|
data-testid={testId.wrapper}
|
|
113
113
|
>
|
|
114
114
|
{variant === 'ai' ? (
|
|
115
|
-
<Sparkles className="tw-size-4 [&_*]:tw-stroke-aigradient-600" />
|
|
115
|
+
<Sparkles className="tw-size-4 tw-shrink-0 [&_*]:tw-stroke-aigradient-600" />
|
|
116
116
|
) : (
|
|
117
|
-
<SearchIcon className="tw-size-4 tw-text-neutral-400" />
|
|
117
|
+
<SearchIcon className="tw-size-4 tw-shrink-0 tw-text-neutral-400" />
|
|
118
118
|
)}
|
|
119
119
|
|
|
120
120
|
<input
|
|
@@ -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
|
+
};
|
|
@@ -4,6 +4,8 @@ import type { Decorator, Meta, StoryObj } from '@storybook/react-vite';
|
|
|
4
4
|
import type { InputType } from 'storybook/internal/types';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
AlertTriangle,
|
|
8
|
+
Ellipsis,
|
|
7
9
|
Expand,
|
|
8
10
|
Gauge,
|
|
9
11
|
Keboola,
|
|
@@ -21,7 +23,6 @@ import { cn } from '../../utils';
|
|
|
21
23
|
import { Button } from '../Button';
|
|
22
24
|
import { ButtonGroup } from '../ButtonGroup';
|
|
23
25
|
import { DropdownMenu } from '../DropdownMenu';
|
|
24
|
-
import { Icon } from '../Icon';
|
|
25
26
|
import { Link } from '../Link';
|
|
26
27
|
import { Modal } from '../Modal';
|
|
27
28
|
|
|
@@ -547,7 +548,7 @@ export const MenuAction: Story = {
|
|
|
547
548
|
<DropdownMenu>
|
|
548
549
|
<DropdownMenu.Trigger asChild>
|
|
549
550
|
<Sidebar.MenuAction showOnHover>
|
|
550
|
-
<
|
|
551
|
+
<Ellipsis />
|
|
551
552
|
<span className="tw-sr-only">Actions</span>
|
|
552
553
|
</Sidebar.MenuAction>
|
|
553
554
|
</DropdownMenu.Trigger>
|
|
@@ -571,7 +572,7 @@ export const MenuAction: Story = {
|
|
|
571
572
|
</Sidebar.MenuButton>
|
|
572
573
|
|
|
573
574
|
<Sidebar.MenuAction asChild>
|
|
574
|
-
<
|
|
575
|
+
<AlertTriangle className="tw-text-warning-600" />
|
|
575
576
|
</Sidebar.MenuAction>
|
|
576
577
|
</Sidebar.MenuItem>
|
|
577
578
|
<Sidebar.MenuItem>
|
|
@@ -579,7 +580,7 @@ export const MenuAction: Story = {
|
|
|
579
580
|
<span className="tw-truncate">As Non-actionable with Very long text</span>
|
|
580
581
|
</Sidebar.MenuButton>
|
|
581
582
|
<Sidebar.MenuAction asChild>
|
|
582
|
-
<
|
|
583
|
+
<AlertTriangle className="tw-text-warning-600" />
|
|
583
584
|
</Sidebar.MenuAction>
|
|
584
585
|
</Sidebar.MenuItem>
|
|
585
586
|
<Sidebar.MenuItem>
|
|
@@ -589,7 +590,7 @@ export const MenuAction: Story = {
|
|
|
589
590
|
</Sidebar.MenuButton>
|
|
590
591
|
<Sidebar.MenuAction asChild>
|
|
591
592
|
<Link href="#">
|
|
592
|
-
<
|
|
593
|
+
<Gauge className="tw-text-warning-600" />
|
|
593
594
|
<span className="tw-sr-only">Dashboard</span>
|
|
594
595
|
</Link>
|
|
595
596
|
</Sidebar.MenuAction>
|
|
@@ -6,8 +6,8 @@ import { EmptyState } from '../../blocks/EmptyState';
|
|
|
6
6
|
import { ErrorState } from '../../blocks/ErrorState';
|
|
7
7
|
import { SearchInput } from '../../blocks/SearchInput';
|
|
8
8
|
import { Button } from '../../components/Button';
|
|
9
|
-
import { Icon } from '../../components/Icon';
|
|
10
9
|
import { Pagination } from '../../components/Pagination';
|
|
10
|
+
import { ChevronDown, ChevronsUpDown, ChevronUp, CircleAlert, Search } from '../../icons';
|
|
11
11
|
|
|
12
12
|
import { Table } from './Table';
|
|
13
13
|
|
|
@@ -141,19 +141,19 @@ const SortHeader = ({
|
|
|
141
141
|
direction: SortDir;
|
|
142
142
|
onClick: () => void;
|
|
143
143
|
children: ReactNode;
|
|
144
|
-
}) =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
className="tw-size-3 tw-text-muted-foreground"
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
}) => {
|
|
145
|
+
const SortIcon = !active ? ChevronsUpDown : direction === 'asc' ? ChevronUp : ChevronDown;
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={onClick}
|
|
150
|
+
className="tw-group tw-flex tw-cursor-pointer tw-items-center tw-gap-1 tw-border-0 tw-bg-transparent tw-p-0 tw-font-medium tw-text-inherit"
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
<SortIcon className="tw-size-3 tw-text-muted-foreground" />
|
|
154
|
+
</button>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
159
|
* Canonical entity-list pattern: `Table` (presentational) composed with `SearchInput`, `Pagination`,
|
|
@@ -213,7 +213,7 @@ export const AsEntityList: Story = {
|
|
|
213
213
|
{filtered.length === 0 ? (
|
|
214
214
|
<EmptyState>
|
|
215
215
|
<EmptyState.Icon>
|
|
216
|
-
<
|
|
216
|
+
<Search />
|
|
217
217
|
</EmptyState.Icon>
|
|
218
218
|
<EmptyState.Title>No tokens match your search</EmptyState.Title>
|
|
219
219
|
<EmptyState.Description>
|
|
@@ -313,7 +313,7 @@ export const AsEntityListError: Story = {
|
|
|
313
313
|
<SearchInput allowClear placeholder="Search tokens…" disabled className="tw-w-72" />
|
|
314
314
|
<ErrorState>
|
|
315
315
|
<ErrorState.Icon>
|
|
316
|
-
<
|
|
316
|
+
<CircleAlert />
|
|
317
317
|
</ErrorState.Icon>
|
|
318
318
|
<ErrorState.Title>Couldn't load tokens</ErrorState.Title>
|
|
319
319
|
<ErrorState.Description>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
3
|
|
|
4
|
+
import { Check, Circle, Search, Warehouse } from '../../icons';
|
|
4
5
|
import { Alert } from '../Alert';
|
|
5
6
|
import { ButtonInline } from '../Button';
|
|
6
|
-
import { Icon } from '../Icon';
|
|
7
7
|
|
|
8
8
|
import { Timeline } from './Timeline';
|
|
9
9
|
|
|
@@ -94,21 +94,21 @@ export const Icons: Story = {
|
|
|
94
94
|
<Timeline {...props}>
|
|
95
95
|
<Timeline.Item>
|
|
96
96
|
<Timeline.Connector variant="icon">
|
|
97
|
-
<
|
|
97
|
+
<Check />
|
|
98
98
|
</Timeline.Connector>
|
|
99
99
|
<Timeline.Content>Checked</Timeline.Content>
|
|
100
100
|
</Timeline.Item>
|
|
101
101
|
|
|
102
102
|
<Timeline.Item>
|
|
103
103
|
<Timeline.Connector variant="icon">
|
|
104
|
-
<
|
|
104
|
+
<Circle />
|
|
105
105
|
</Timeline.Connector>
|
|
106
106
|
<Timeline.Content>Dot</Timeline.Content>
|
|
107
107
|
</Timeline.Item>
|
|
108
108
|
|
|
109
109
|
<Timeline.Item>
|
|
110
110
|
<Timeline.Connector variant="icon">
|
|
111
|
-
<
|
|
111
|
+
<Search />
|
|
112
112
|
</Timeline.Connector>
|
|
113
113
|
<Timeline.Content>Search</Timeline.Content>
|
|
114
114
|
</Timeline.Item>
|
|
@@ -184,7 +184,7 @@ export const Nested: Story = {
|
|
|
184
184
|
<Timeline variant="solid">
|
|
185
185
|
<Timeline.Item>
|
|
186
186
|
<Timeline.Connector variant="icon">
|
|
187
|
-
<
|
|
187
|
+
<Warehouse />
|
|
188
188
|
</Timeline.Connector>
|
|
189
189
|
<Timeline.Content className="tw-gap-2">
|
|
190
190
|
<h4>Create SQL Transformation</h4>
|
|
@@ -194,7 +194,7 @@ export const Nested: Story = {
|
|
|
194
194
|
<Timeline.Item>
|
|
195
195
|
<Timeline.Connector variant="line" />
|
|
196
196
|
<Timeline.Content className="tw-gap-2">
|
|
197
|
-
<
|
|
197
|
+
<Warehouse className="tw-size-4 tw-text-neutral-400" />
|
|
198
198
|
<div className="tw-flex tw-flex-col">
|
|
199
199
|
<h4>Documentation Query</h4>
|
|
200
200
|
<div className="tw-flex tw-gap-2">
|
|
@@ -207,7 +207,7 @@ export const Nested: Story = {
|
|
|
207
207
|
<Timeline.Item>
|
|
208
208
|
<Timeline.Connector variant="line" />
|
|
209
209
|
<Timeline.Content className="tw-gap-2">
|
|
210
|
-
<
|
|
210
|
+
<Warehouse className="tw-size-4 tw-text-neutral-400" />
|
|
211
211
|
<div className="tw-flex tw-flex-col">
|
|
212
212
|
<h4>Documentation Query</h4>
|
|
213
213
|
<div className="tw-flex tw-gap-2">
|
|
@@ -220,7 +220,7 @@ export const Nested: Story = {
|
|
|
220
220
|
<Timeline.Item>
|
|
221
221
|
<Timeline.Connector align="start" variant="line" />
|
|
222
222
|
<Timeline.Content className="tw-gap-2">
|
|
223
|
-
<
|
|
223
|
+
<Warehouse className="tw-size-4 tw-text-neutral-400" />
|
|
224
224
|
<div className="tw-flex tw-flex-col">
|
|
225
225
|
<h4>Documentation Query</h4>
|
|
226
226
|
<div className="tw-flex tw-gap-2">
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
|
|
4
|
+
import { Bold, Italic, LayoutGrid, List, Underline } from '../../icons';
|
|
5
|
+
|
|
6
|
+
import { ToggleGroup } from './ToggleGroup';
|
|
7
|
+
|
|
8
|
+
const ViewSwitcherStory = () => {
|
|
9
|
+
const [value, setValue] = useState<'list' | 'grid'>('list');
|
|
10
|
+
return (
|
|
11
|
+
<ToggleGroup
|
|
12
|
+
type="single"
|
|
13
|
+
value={value}
|
|
14
|
+
onValueChange={(next) => next && setValue(next as 'list' | 'grid')}
|
|
15
|
+
aria-label="View mode"
|
|
16
|
+
>
|
|
17
|
+
<ToggleGroup.Item value="list" aria-label="List view">
|
|
18
|
+
<List className="tw-size-4" />
|
|
19
|
+
</ToggleGroup.Item>
|
|
20
|
+
<ToggleGroup.Item value="grid" aria-label="Grid view">
|
|
21
|
+
<LayoutGrid className="tw-size-4" />
|
|
22
|
+
</ToggleGroup.Item>
|
|
23
|
+
</ToggleGroup>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const WithLabelsStory = () => (
|
|
28
|
+
<ToggleGroup type="single" defaultValue="month" aria-label="Range">
|
|
29
|
+
<ToggleGroup.Item value="day">Day</ToggleGroup.Item>
|
|
30
|
+
<ToggleGroup.Item value="week">Week</ToggleGroup.Item>
|
|
31
|
+
<ToggleGroup.Item value="month">Month</ToggleGroup.Item>
|
|
32
|
+
</ToggleGroup>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const MultipleStory = () => (
|
|
36
|
+
<ToggleGroup type="multiple" defaultValue={['bold']} aria-label="Text formatting">
|
|
37
|
+
<ToggleGroup.Item value="bold" aria-label="Bold">
|
|
38
|
+
<Bold className="tw-size-4" />
|
|
39
|
+
</ToggleGroup.Item>
|
|
40
|
+
<ToggleGroup.Item value="italic" aria-label="Italic">
|
|
41
|
+
<Italic className="tw-size-4" />
|
|
42
|
+
</ToggleGroup.Item>
|
|
43
|
+
<ToggleGroup.Item value="underline" aria-label="Underline">
|
|
44
|
+
<Underline className="tw-size-4" />
|
|
45
|
+
</ToggleGroup.Item>
|
|
46
|
+
</ToggleGroup>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const meta = {
|
|
50
|
+
title: 'Components/ToggleGroup',
|
|
51
|
+
component: ToggleGroup,
|
|
52
|
+
args: { type: 'single' },
|
|
53
|
+
} satisfies Meta<typeof ToggleGroup>;
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
type Story = StoryObj<typeof meta>;
|
|
57
|
+
|
|
58
|
+
export const ViewSwitcher: Story = { render: () => <ViewSwitcherStory /> };
|
|
59
|
+
export const WithLabels: Story = { render: () => <WithLabelsStory /> };
|
|
60
|
+
export const Multiple: Story = { render: () => <MultipleStory /> };
|