@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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ComponentProps, ElementRef, Ref } from 'react';
|
|
2
|
+
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils';
|
|
6
|
+
|
|
7
|
+
const rootVariants = cva(
|
|
8
|
+
[
|
|
9
|
+
'tailwindcss-preflight',
|
|
10
|
+
'tw-inline-flex',
|
|
11
|
+
'tw-items-center',
|
|
12
|
+
'tw-shrink-0',
|
|
13
|
+
'tw-bg-muted',
|
|
14
|
+
'tw-rounded-lg',
|
|
15
|
+
'tw-p-[3px]',
|
|
16
|
+
'tw-gap-0',
|
|
17
|
+
],
|
|
18
|
+
{
|
|
19
|
+
defaultVariants: { size: 'medium' },
|
|
20
|
+
variants: {
|
|
21
|
+
size: {
|
|
22
|
+
small: ['tw-h-8'],
|
|
23
|
+
medium: ['tw-h-9'],
|
|
24
|
+
large: ['tw-h-10'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const itemClasses = cn(
|
|
31
|
+
'tailwindcss-preflight',
|
|
32
|
+
'tw-inline-flex',
|
|
33
|
+
'tw-items-center',
|
|
34
|
+
'tw-justify-center',
|
|
35
|
+
'tw-gap-2',
|
|
36
|
+
'tw-shrink-0',
|
|
37
|
+
'tw-whitespace-nowrap',
|
|
38
|
+
|
|
39
|
+
'tw-h-full',
|
|
40
|
+
'tw-rounded-md',
|
|
41
|
+
'tw-px-2',
|
|
42
|
+
'tw-py-1',
|
|
43
|
+
|
|
44
|
+
'tw-bg-transparent',
|
|
45
|
+
'tw-text-foreground',
|
|
46
|
+
'tw-text-sm',
|
|
47
|
+
'tw-font-medium',
|
|
48
|
+
|
|
49
|
+
'tw-transition-colors',
|
|
50
|
+
|
|
51
|
+
'disabled:tw-cursor-not-allowed',
|
|
52
|
+
'disabled:tw-opacity-50',
|
|
53
|
+
|
|
54
|
+
'!tw-outline-none',
|
|
55
|
+
'focus-visible:tw-ring-2',
|
|
56
|
+
'focus-visible:tw-ring-primary/50',
|
|
57
|
+
|
|
58
|
+
// hover on non-pressed
|
|
59
|
+
'not-disabled:[&[data-state=off]]:hover:tw-bg-background/60',
|
|
60
|
+
|
|
61
|
+
// pressed
|
|
62
|
+
'data-[state=on]:tw-bg-background',
|
|
63
|
+
'data-[state=on]:tw-shadow-[0_1px_2px_rgba(0,0,0,0.05),0_1px_1px_rgba(0,0,0,0.1)]',
|
|
64
|
+
|
|
65
|
+
'[&_svg]:tw-shrink-0',
|
|
66
|
+
'[&_svg]:tw-pointer-events-none',
|
|
67
|
+
"[&_svg:not([class*='tw-size-'])]:tw-size-4",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
type RootProps = ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
|
71
|
+
VariantProps<typeof rootVariants>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Segmented row of mutually-exclusive (or multi-select) options. Built on `@radix-ui/react-toggle-group`.
|
|
75
|
+
* For `type="single"`, `onValueChange` receives `""` when the user deselects the active item.
|
|
76
|
+
*/
|
|
77
|
+
const Root = ({
|
|
78
|
+
ref,
|
|
79
|
+
className,
|
|
80
|
+
size = 'medium',
|
|
81
|
+
...props
|
|
82
|
+
}: RootProps & { ref?: Ref<ElementRef<'div'>> }) => (
|
|
83
|
+
<ToggleGroupPrimitive.Root
|
|
84
|
+
ref={ref}
|
|
85
|
+
{...props}
|
|
86
|
+
className={cn(rootVariants({ size, className }))}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
type ItemProps = ComponentProps<typeof ToggleGroupPrimitive.Item>;
|
|
91
|
+
|
|
92
|
+
const Item = ({ ref, className, ...props }: ItemProps & { ref?: Ref<ElementRef<'button'>> }) => (
|
|
93
|
+
<ToggleGroupPrimitive.Item ref={ref} {...props} className={cn(itemClasses, className)} />
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
export const ToggleGroup = Object.assign(Root, {
|
|
97
|
+
/** Individual segment inside a `ToggleGroup`. `value` is required and must be unique within the group. */
|
|
98
|
+
Item,
|
|
99
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ToggleGroup } from './ToggleGroup';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
3
|
|
|
4
|
+
import { CircleHelp } from '../../icons';
|
|
4
5
|
import { Button } from '../Button';
|
|
5
|
-
import { Icon } from '../Icon';
|
|
6
6
|
|
|
7
7
|
import { Tooltip, TooltipProvider } from './Tooltip';
|
|
8
8
|
|
|
@@ -53,7 +53,7 @@ export const StaticExplanatory: Story = {
|
|
|
53
53
|
},
|
|
54
54
|
render: (args) => (
|
|
55
55
|
<Tooltip {...args}>
|
|
56
|
-
<
|
|
56
|
+
<CircleHelp className="tw-size-4 tw-text-neutral-400" />
|
|
57
57
|
</Tooltip>
|
|
58
58
|
),
|
|
59
59
|
parameters: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ChangeEvent, useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
3
|
|
|
4
|
-
import { Icon } from '../components/Icon';
|
|
5
4
|
import { TextInput } from '../components/Input/TextInput/TextInput';
|
|
6
5
|
import { InputGroup } from '../components/InputGroup';
|
|
6
|
+
import { Search } from '../icons';
|
|
7
7
|
|
|
8
8
|
import { useAutosizeInput } from './useAutosizeInput';
|
|
9
9
|
|
|
@@ -102,7 +102,7 @@ export const WithInputGroup: Story = {
|
|
|
102
102
|
return (
|
|
103
103
|
<InputGroup className="!tw-inline-flex">
|
|
104
104
|
<InputGroup.Addon align="inline-start">
|
|
105
|
-
<
|
|
105
|
+
<Search className="tw-size-4 tw-text-neutral-400" />
|
|
106
106
|
</InputGroup.Addon>
|
|
107
107
|
<InputGroup.Input
|
|
108
108
|
ref={inputRef}
|
package/src/icons/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export {
|
|
|
14
14
|
ArrowRight,
|
|
15
15
|
ArrowRightLeft,
|
|
16
16
|
ArrowUp,
|
|
17
|
+
ArrowUpAZ,
|
|
17
18
|
ArrowUpRight,
|
|
18
19
|
Ban,
|
|
19
20
|
Bell,
|
|
@@ -99,6 +100,7 @@ export {
|
|
|
99
100
|
Link,
|
|
100
101
|
List,
|
|
101
102
|
ListChecks,
|
|
103
|
+
ListFilter,
|
|
102
104
|
ListOrdered,
|
|
103
105
|
Loader2,
|
|
104
106
|
Lock,
|
|
@@ -116,7 +118,11 @@ export {
|
|
|
116
118
|
MoveHorizontal,
|
|
117
119
|
MoveVertical,
|
|
118
120
|
OctagonAlert,
|
|
121
|
+
PanelBottomClose,
|
|
122
|
+
PanelBottomOpen,
|
|
119
123
|
PanelLeft,
|
|
124
|
+
PanelLeftClose,
|
|
125
|
+
PanelLeftOpen,
|
|
120
126
|
PartyPopper,
|
|
121
127
|
Pause,
|
|
122
128
|
Pencil,
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from './components/ThemeProvider';
|
|
|
37
37
|
export * from './components/Avatar';
|
|
38
38
|
export * from './components/MarkedText';
|
|
39
39
|
export * from './components/Toggle';
|
|
40
|
+
export * from './components/ToggleGroup';
|
|
40
41
|
export * from './components/Timeline';
|
|
41
42
|
export * from './components/Kbd';
|
|
42
43
|
export * from './components/Collapsible';
|
|
@@ -1,48 +1,232 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Badge,
|
|
6
|
+
Button,
|
|
7
|
+
InputGroup,
|
|
8
|
+
PageLayout,
|
|
9
|
+
PageToolbar,
|
|
10
|
+
Select,
|
|
11
|
+
Separator,
|
|
12
|
+
ToggleGroup,
|
|
13
|
+
Tooltip,
|
|
14
|
+
} from '@keboola/design';
|
|
15
|
+
import {
|
|
16
|
+
ChevronsUpDown,
|
|
17
|
+
Info,
|
|
18
|
+
LayoutGrid,
|
|
19
|
+
List,
|
|
20
|
+
ListFilter,
|
|
21
|
+
Plus,
|
|
22
|
+
RefreshCw,
|
|
23
|
+
Search,
|
|
24
|
+
X,
|
|
25
|
+
} from '@keboola/design/icons';
|
|
5
26
|
|
|
6
|
-
type Row = {
|
|
27
|
+
type Row = {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
type: 'table' | 'file';
|
|
31
|
+
status: 'scheduled' | 'failed' | 'succeeded';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type FilterKey = 'scheduled' | 'failed' | 'succeeded';
|
|
35
|
+
|
|
36
|
+
type ViewMode = 'list' | 'grid';
|
|
37
|
+
|
|
38
|
+
type SortKey = 'name-asc' | 'name-desc' | 'recent';
|
|
39
|
+
|
|
40
|
+
const SORT_LABEL: Record<SortKey, string> = {
|
|
41
|
+
'name-asc': 'Name A to Z',
|
|
42
|
+
'name-desc': 'Name Z to A',
|
|
43
|
+
recent: 'Recently updated',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const FILTER_LABEL: Record<FilterKey, string> = {
|
|
47
|
+
scheduled: 'Scheduled',
|
|
48
|
+
failed: 'Failed',
|
|
49
|
+
succeeded: 'Succeeded',
|
|
50
|
+
};
|
|
7
51
|
|
|
8
|
-
const
|
|
52
|
+
const ALL_FILTERS: FilterKey[] = ['scheduled', 'failed', 'succeeded'];
|
|
53
|
+
|
|
54
|
+
const SearchField = ({
|
|
55
|
+
value,
|
|
56
|
+
onValueChange,
|
|
57
|
+
}: {
|
|
58
|
+
value: string;
|
|
59
|
+
onValueChange: (next: string) => void;
|
|
60
|
+
}) => (
|
|
61
|
+
<InputGroup className="tw-w-[300px]">
|
|
62
|
+
<InputGroup.Addon align="inline-start">
|
|
63
|
+
<Search className="tw-size-4 tw-text-muted-foreground" />
|
|
64
|
+
</InputGroup.Addon>
|
|
65
|
+
<InputGroup.Input
|
|
66
|
+
placeholder="Search"
|
|
67
|
+
value={value}
|
|
68
|
+
onChange={(event) => onValueChange(event.target.value)}
|
|
69
|
+
/>
|
|
70
|
+
<InputGroup.Addon align="inline-end">
|
|
71
|
+
<Tooltip tooltip="Searches by name and description." triggerAsChild>
|
|
72
|
+
<InputGroup.Button tabIndex={0} aria-label="Search help">
|
|
73
|
+
<Info className="tw-size-4 tw-text-muted-foreground" />
|
|
74
|
+
</InputGroup.Button>
|
|
75
|
+
</Tooltip>
|
|
76
|
+
</InputGroup.Addon>
|
|
77
|
+
</InputGroup>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const ActiveFiltersRow = ({
|
|
81
|
+
active,
|
|
82
|
+
onRemove,
|
|
83
|
+
onAdd,
|
|
84
|
+
onClear,
|
|
85
|
+
}: {
|
|
86
|
+
active: FilterKey[];
|
|
87
|
+
onRemove: (key: FilterKey) => void;
|
|
88
|
+
onAdd: () => void;
|
|
89
|
+
onClear: () => void;
|
|
90
|
+
}) => (
|
|
91
|
+
<div className="tw-flex tw-w-full tw-items-center tw-gap-2 tw-rounded-xl tw-bg-muted tw-py-1 tw-pl-2 tw-pr-1">
|
|
92
|
+
<div className="tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-2">
|
|
93
|
+
{active.map((key) => (
|
|
94
|
+
<Badge
|
|
95
|
+
key={key}
|
|
96
|
+
variant="pill"
|
|
97
|
+
color="blue-inverse"
|
|
98
|
+
suffix={
|
|
99
|
+
<Button
|
|
100
|
+
variant="inline"
|
|
101
|
+
size="icon-2xs"
|
|
102
|
+
aria-label={`Remove ${FILTER_LABEL[key]} filter`}
|
|
103
|
+
onClick={() => onRemove(key)}
|
|
104
|
+
>
|
|
105
|
+
<X className="tw-size-3" />
|
|
106
|
+
</Button>
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
{FILTER_LABEL[key]}
|
|
110
|
+
</Badge>
|
|
111
|
+
))}
|
|
112
|
+
<Button argo variant="invisible" size="icon-sm" aria-label="Add filter" onClick={onAdd}>
|
|
113
|
+
<Plus className="tw-size-4" />
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
<Button argo variant="invisible" size="small" onClick={onClear}>
|
|
117
|
+
Clear
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const FilteredList = ({
|
|
123
|
+
rows,
|
|
124
|
+
showSort,
|
|
125
|
+
showViewToggle,
|
|
126
|
+
showRefresh,
|
|
127
|
+
showReorder,
|
|
128
|
+
}: {
|
|
129
|
+
rows: Row[];
|
|
130
|
+
showSort: boolean;
|
|
131
|
+
showViewToggle: boolean;
|
|
132
|
+
showRefresh: boolean;
|
|
133
|
+
showReorder: boolean;
|
|
134
|
+
}) => {
|
|
9
135
|
const [query, setQuery] = useState('');
|
|
10
|
-
const [
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
136
|
+
const [sort, setSort] = useState<SortKey>('name-desc');
|
|
137
|
+
const [view, setView] = useState<ViewMode>('list');
|
|
138
|
+
const [active, setActive] = useState<FilterKey[]>(['scheduled', 'failed']);
|
|
139
|
+
|
|
140
|
+
const filtered = useMemo(() => {
|
|
141
|
+
const byQuery = rows.filter((row) => row.name.toLowerCase().includes(query.toLowerCase()));
|
|
142
|
+
const byStatus = active.length ? byQuery.filter((row) => active.includes(row.status)) : byQuery;
|
|
143
|
+
return [...byStatus].sort((a, b) => {
|
|
144
|
+
if (sort === 'name-asc') return a.name.localeCompare(b.name);
|
|
145
|
+
if (sort === 'name-desc') return b.name.localeCompare(a.name);
|
|
146
|
+
return b.id.localeCompare(a.id);
|
|
147
|
+
});
|
|
148
|
+
}, [rows, query, active, sort]);
|
|
149
|
+
|
|
150
|
+
const addFilter = () => {
|
|
151
|
+
setActive((prev) => {
|
|
152
|
+
const next = ALL_FILTERS.find((key) => !prev.includes(key));
|
|
153
|
+
return next ? [...prev, next] : prev;
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const showRightCluster = showSort || showViewToggle || showRefresh || showReorder;
|
|
158
|
+
const showUtilities = showRefresh || showReorder;
|
|
159
|
+
const showDivider = showSort && (showUtilities || showViewToggle);
|
|
21
160
|
|
|
22
161
|
return (
|
|
23
162
|
<PageLayout>
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
163
|
+
<PageToolbar className="tw-flex-col tw-flex-nowrap tw-items-stretch tw-gap-2">
|
|
164
|
+
<div className="tw-flex tw-w-full tw-items-center tw-gap-6">
|
|
165
|
+
<div className="tw-flex tw-min-w-0 tw-flex-1">
|
|
166
|
+
<SearchField value={query} onValueChange={setQuery} />
|
|
167
|
+
</div>
|
|
168
|
+
{showRightCluster && (
|
|
169
|
+
<div className="tw-flex tw-items-center tw-gap-2">
|
|
170
|
+
<Button argo variant="outline" size="icon" aria-label="Open filters">
|
|
171
|
+
<ListFilter className="tw-size-4" />
|
|
172
|
+
</Button>
|
|
173
|
+
{showSort && (
|
|
174
|
+
<Select
|
|
175
|
+
variant="argo"
|
|
176
|
+
className="tw-w-[200px]"
|
|
177
|
+
value={sort}
|
|
178
|
+
onValueChange={(next: SortKey) => setSort(next)}
|
|
179
|
+
items={(Object.keys(SORT_LABEL) as SortKey[]).map((key) => ({
|
|
180
|
+
value: key,
|
|
181
|
+
label: SORT_LABEL[key],
|
|
182
|
+
}))}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
{showDivider && <Separator orientation="vertical" className="tw-h-9" />}
|
|
186
|
+
{showReorder && (
|
|
187
|
+
<Button argo variant="outline" size="icon" aria-label="Reorder">
|
|
188
|
+
<ChevronsUpDown className="tw-size-4" />
|
|
189
|
+
</Button>
|
|
190
|
+
)}
|
|
191
|
+
{showRefresh && (
|
|
192
|
+
<Button argo variant="outline" size="icon" aria-label="Refresh">
|
|
193
|
+
<RefreshCw className="tw-size-4" />
|
|
194
|
+
</Button>
|
|
195
|
+
)}
|
|
196
|
+
{showViewToggle && (
|
|
197
|
+
<ToggleGroup
|
|
198
|
+
type="single"
|
|
199
|
+
value={view}
|
|
200
|
+
onValueChange={(next: ViewMode | '') => next && setView(next)}
|
|
201
|
+
aria-label="View mode"
|
|
202
|
+
>
|
|
203
|
+
<ToggleGroup.Item value="list" aria-label="List view">
|
|
204
|
+
<List className="tw-size-4" />
|
|
205
|
+
</ToggleGroup.Item>
|
|
206
|
+
<ToggleGroup.Item value="grid" aria-label="Grid view">
|
|
207
|
+
<LayoutGrid className="tw-size-4" />
|
|
208
|
+
</ToggleGroup.Item>
|
|
209
|
+
</ToggleGroup>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
{active.length > 0 && (
|
|
215
|
+
<ActiveFiltersRow
|
|
216
|
+
active={active}
|
|
217
|
+
onRemove={(key) => setActive((prev) => prev.filter((k) => k !== key))}
|
|
218
|
+
onAdd={addFilter}
|
|
219
|
+
onClear={() => setActive([])}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
222
|
+
</PageToolbar>
|
|
42
223
|
<PageLayout.Content>
|
|
43
|
-
<ul className="tw-flex tw-flex-col tw-gap-1">
|
|
224
|
+
<ul className="tw-flex tw-flex-col tw-gap-1 tw-text-sm">
|
|
44
225
|
{filtered.map((row) => (
|
|
45
|
-
<li key={row.id}>
|
|
226
|
+
<li key={row.id} className="tw-flex tw-items-center tw-gap-2">
|
|
227
|
+
<span className="tw-text-muted-foreground">[{row.status}]</span>
|
|
228
|
+
{row.name}
|
|
229
|
+
</li>
|
|
46
230
|
))}
|
|
47
231
|
</ul>
|
|
48
232
|
</PageLayout.Content>
|
|
@@ -53,19 +237,39 @@ const FilteredList = ({ rows }: { rows: Row[] }) => {
|
|
|
53
237
|
const meta = {
|
|
54
238
|
title: 'Patterns/FilterToolbar',
|
|
55
239
|
component: FilteredList,
|
|
240
|
+
parameters: { layout: 'padded' },
|
|
241
|
+
args: {
|
|
242
|
+
showSort: true,
|
|
243
|
+
showViewToggle: true,
|
|
244
|
+
showRefresh: false,
|
|
245
|
+
showReorder: false,
|
|
246
|
+
rows: [
|
|
247
|
+
{ id: '1', name: 'marketing.users', type: 'table', status: 'succeeded' },
|
|
248
|
+
{ id: '2', name: 'sales.leads', type: 'table', status: 'scheduled' },
|
|
249
|
+
{ id: '3', name: 'export.csv', type: 'file', status: 'failed' },
|
|
250
|
+
{ id: '4', name: 'snapshot.json', type: 'file', status: 'scheduled' },
|
|
251
|
+
{ id: '5', name: 'events.parquet', type: 'file', status: 'failed' },
|
|
252
|
+
],
|
|
253
|
+
},
|
|
56
254
|
} satisfies Meta<typeof FilteredList>;
|
|
57
255
|
|
|
58
256
|
export default meta;
|
|
59
|
-
|
|
60
257
|
type Story = StoryObj<typeof meta>;
|
|
61
258
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
259
|
+
/** Full toolbar — search, filter, sort, view-mode toggle, and the active-filters row. */
|
|
260
|
+
export const Default: Story = {};
|
|
261
|
+
|
|
262
|
+
/** Minimal variant: only the search field and a filter button. */
|
|
263
|
+
export const SearchAndFilterOnly: Story = {
|
|
264
|
+
args: { showSort: false, showViewToggle: false },
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/** Adds the refresh utility next to the sort, separated by a vertical divider. */
|
|
268
|
+
export const WithRefresh: Story = {
|
|
269
|
+
args: { showRefresh: true },
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/** Reorder + refresh utilities. View toggle hidden, mirrors a sortable list page. */
|
|
273
|
+
export const WithReorderUtilities: Story = {
|
|
274
|
+
args: { showViewToggle: false, showRefresh: true, showReorder: true },
|
|
71
275
|
};
|