@dust-tt/sparkle 0.4.11 → 0.4.12-rc-2
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/cjs/index.js +1 -1
- package/dist/esm/components/ButtonGroup.d.ts +19 -2
- package/dist/esm/components/ButtonGroup.d.ts.map +1 -1
- package/dist/esm/components/ButtonGroup.js +27 -20
- package/dist/esm/components/ButtonGroup.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -1
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/stories/ButtonGroup.stories.d.ts +4 -3
- package/dist/esm/stories/ButtonGroup.stories.d.ts.map +1 -1
- package/dist/esm/stories/ButtonGroup.stories.js +130 -41
- package/dist/esm/stories/ButtonGroup.stories.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ButtonGroup.tsx +91 -32
- package/src/components/index.ts +1 -1
- package/src/stories/ButtonGroup.stories.tsx +162 -63
|
@@ -4,6 +4,30 @@ import * as React from "react";
|
|
|
4
4
|
import { cn } from "@sparkle/lib/utils";
|
|
5
5
|
|
|
6
6
|
import type { ButtonProps, ButtonSizeType, ButtonVariantType } from "./Button";
|
|
7
|
+
import { Button } from "./Button";
|
|
8
|
+
import type { DropdownMenuItemProps } from "./Dropdown";
|
|
9
|
+
import {
|
|
10
|
+
DropdownMenu,
|
|
11
|
+
DropdownMenuContent,
|
|
12
|
+
DropdownMenuItem,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from "./Dropdown";
|
|
15
|
+
|
|
16
|
+
type ButtonGroupButtonItem = {
|
|
17
|
+
type: "button";
|
|
18
|
+
props: ButtonProps;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ButtonGroupDropdownItem = {
|
|
22
|
+
type: "dropdown";
|
|
23
|
+
triggerProps: Omit<ButtonProps, "onClick">;
|
|
24
|
+
dropdownProps: {
|
|
25
|
+
items: DropdownMenuItemProps[];
|
|
26
|
+
align?: "start" | "center" | "end";
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ButtonGroupItem = ButtonGroupButtonItem | ButtonGroupDropdownItem;
|
|
7
31
|
|
|
8
32
|
type DisallowedButtonGroupVariant =
|
|
9
33
|
| "ghost"
|
|
@@ -58,13 +82,16 @@ const buttonGroupVariants = cva("s-inline-flex", {
|
|
|
58
82
|
export interface ButtonGroupProps
|
|
59
83
|
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">,
|
|
60
84
|
VariantProps<typeof buttonGroupVariants> {
|
|
61
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Array of button or dropdown items to render in the group.
|
|
87
|
+
*/
|
|
88
|
+
items: ButtonGroupItem[];
|
|
62
89
|
/**
|
|
63
90
|
* Variant to apply to all buttons in the group.
|
|
64
91
|
*/
|
|
65
92
|
variant?: ButtonGroupVariantType;
|
|
66
93
|
/**
|
|
67
|
-
* Size to apply to all buttons in the group. Mini buttons must opt-in per
|
|
94
|
+
* Size to apply to all buttons in the group. Mini buttons must opt-in per item.
|
|
68
95
|
*/
|
|
69
96
|
size?: Exclude<ButtonSizeType, "mini">;
|
|
70
97
|
/**
|
|
@@ -86,26 +113,19 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
|
86
113
|
size,
|
|
87
114
|
disabled,
|
|
88
115
|
removeGaps = true,
|
|
89
|
-
|
|
116
|
+
items,
|
|
90
117
|
...props
|
|
91
118
|
},
|
|
92
119
|
ref
|
|
93
120
|
) => {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
const clonedChildren = childrenArray.map((child, index) => {
|
|
99
|
-
if (!React.isValidElement<ButtonProps>(child)) {
|
|
100
|
-
return child;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const totalChildren = childrenArray.length;
|
|
121
|
+
const totalItems = items?.length ?? 0;
|
|
122
|
+
|
|
123
|
+
const renderedItems = items.map((item, index) => {
|
|
104
124
|
const isFirst = index === 0;
|
|
105
|
-
const isLast = index ===
|
|
125
|
+
const isLast = index === totalItems - 1;
|
|
106
126
|
|
|
107
127
|
const borderRadiusClasses = (() => {
|
|
108
|
-
if (!removeGaps ||
|
|
128
|
+
if (!removeGaps || totalItems === 1) {
|
|
109
129
|
return "";
|
|
110
130
|
}
|
|
111
131
|
|
|
@@ -140,22 +160,61 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
|
140
160
|
return isLast ? "" : "s-border-b-0";
|
|
141
161
|
})();
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
if (item.type === "button") {
|
|
164
|
+
const nextVariant = sanitizeVariant(variant ?? item.props.variant);
|
|
165
|
+
const rawSize = size ?? item.props.size;
|
|
166
|
+
const nextSize: Exclude<ButtonSizeType, "mini"> | undefined =
|
|
167
|
+
rawSize === "mini"
|
|
168
|
+
? undefined
|
|
169
|
+
: (rawSize as Exclude<ButtonSizeType, "mini"> | undefined);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<Button
|
|
173
|
+
key={index}
|
|
174
|
+
{...item.props}
|
|
175
|
+
variant={nextVariant}
|
|
176
|
+
size={nextSize}
|
|
177
|
+
disabled={disabled ?? item.props.disabled}
|
|
178
|
+
isRounded={false}
|
|
179
|
+
className={cn(
|
|
180
|
+
item.props.className,
|
|
181
|
+
borderRadiusClasses,
|
|
182
|
+
borderClasses
|
|
183
|
+
)}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const nextVariant = sanitizeVariant(variant ?? item.triggerProps.variant);
|
|
189
|
+
const rawSize = size ?? item.triggerProps.size;
|
|
190
|
+
const nextSize: Exclude<ButtonSizeType, "mini"> | undefined =
|
|
191
|
+
rawSize === "mini"
|
|
192
|
+
? undefined
|
|
193
|
+
: (rawSize as Exclude<ButtonSizeType, "mini"> | undefined);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<DropdownMenu key={index}>
|
|
197
|
+
<DropdownMenuTrigger asChild>
|
|
198
|
+
<Button
|
|
199
|
+
{...item.triggerProps}
|
|
200
|
+
variant={nextVariant}
|
|
201
|
+
size={nextSize}
|
|
202
|
+
disabled={disabled ?? item.triggerProps.disabled}
|
|
203
|
+
isRounded={false}
|
|
204
|
+
className={cn(
|
|
205
|
+
item.triggerProps.className,
|
|
206
|
+
borderRadiusClasses,
|
|
207
|
+
borderClasses
|
|
208
|
+
)}
|
|
209
|
+
/>
|
|
210
|
+
</DropdownMenuTrigger>
|
|
211
|
+
<DropdownMenuContent align={item.dropdownProps.align ?? "center"}>
|
|
212
|
+
{item.dropdownProps.items.map((dropdownItem, dropdownIndex) => (
|
|
213
|
+
<DropdownMenuItem key={dropdownIndex} {...dropdownItem} />
|
|
214
|
+
))}
|
|
215
|
+
</DropdownMenuContent>
|
|
216
|
+
</DropdownMenu>
|
|
217
|
+
);
|
|
159
218
|
});
|
|
160
219
|
|
|
161
220
|
return (
|
|
@@ -169,7 +228,7 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
|
169
228
|
role="group"
|
|
170
229
|
{...props}
|
|
171
230
|
>
|
|
172
|
-
{
|
|
231
|
+
{renderedItems}
|
|
173
232
|
</div>
|
|
174
233
|
);
|
|
175
234
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type {
|
|
|
17
17
|
RegularButtonProps,
|
|
18
18
|
} from "./Button";
|
|
19
19
|
export { Button } from "./Button";
|
|
20
|
-
export type { ButtonGroupProps } from "./ButtonGroup";
|
|
20
|
+
export type { ButtonGroupItem, ButtonGroupProps } from "./ButtonGroup";
|
|
21
21
|
export { ButtonGroup } from "./ButtonGroup";
|
|
22
22
|
export { ButtonsSwitch, ButtonsSwitchList } from "./ButtonsSwitch";
|
|
23
23
|
export type { CardProps } from "./Card";
|
|
@@ -6,23 +6,27 @@ import {
|
|
|
6
6
|
BUTTON_VARIANTS,
|
|
7
7
|
type ButtonVariantType,
|
|
8
8
|
} from "@sparkle/components/Button";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ButtonGroupItem,
|
|
11
|
+
ButtonGroupVariantType,
|
|
12
|
+
} from "@sparkle/components/ButtonGroup";
|
|
10
13
|
|
|
11
14
|
import {
|
|
12
|
-
|
|
15
|
+
ArrowPathIcon,
|
|
13
16
|
ButtonGroup,
|
|
17
|
+
ChevronDownIcon,
|
|
18
|
+
ClipboardIcon,
|
|
14
19
|
PlusIcon,
|
|
15
20
|
RobotIcon,
|
|
16
21
|
Separator,
|
|
22
|
+
TrashIcon,
|
|
17
23
|
} from "../index_with_tw_base";
|
|
18
24
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</>
|
|
25
|
-
);
|
|
25
|
+
const DEFAULT_ITEMS: ButtonGroupItem[] = [
|
|
26
|
+
{ type: "button", props: { label: "First" } },
|
|
27
|
+
{ type: "button", props: { label: "Second" } },
|
|
28
|
+
{ type: "button", props: { label: "Third" } },
|
|
29
|
+
];
|
|
26
30
|
|
|
27
31
|
const DISALLOWED_GROUP_VARIANTS: ButtonVariantType[] = [
|
|
28
32
|
"ghost",
|
|
@@ -41,12 +45,12 @@ const meta = {
|
|
|
41
45
|
tags: ["autodocs"],
|
|
42
46
|
argTypes: {
|
|
43
47
|
variant: {
|
|
44
|
-
description: "Variant applied to every
|
|
48
|
+
description: "Variant applied to every button",
|
|
45
49
|
control: { type: "select" },
|
|
46
50
|
options: BUTTON_GROUP_VARIANTS,
|
|
47
51
|
},
|
|
48
52
|
size: {
|
|
49
|
-
description: "Size applied to every
|
|
53
|
+
description: "Size applied to every button",
|
|
50
54
|
control: { type: "select" },
|
|
51
55
|
options: BUTTON_SIZES.filter((size) => size !== "mini"),
|
|
52
56
|
},
|
|
@@ -63,12 +67,12 @@ const meta = {
|
|
|
63
67
|
description: "Remove gaps and merge button borders",
|
|
64
68
|
control: "boolean",
|
|
65
69
|
},
|
|
66
|
-
|
|
70
|
+
items: {
|
|
67
71
|
table: { disable: true },
|
|
68
72
|
},
|
|
69
73
|
},
|
|
70
74
|
args: {
|
|
71
|
-
|
|
75
|
+
items: DEFAULT_ITEMS,
|
|
72
76
|
variant: "outline",
|
|
73
77
|
size: "sm",
|
|
74
78
|
orientation: "horizontal",
|
|
@@ -85,55 +89,58 @@ type Story = StoryObj<typeof meta>;
|
|
|
85
89
|
export const Playground: Story = {};
|
|
86
90
|
|
|
87
91
|
export const WithIcons: Story = {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
args: {
|
|
93
|
+
items: [
|
|
94
|
+
{ type: "button", props: { icon: PlusIcon, label: "Add" } },
|
|
95
|
+
{ type: "button", props: { icon: RobotIcon, label: "Agent" } },
|
|
96
|
+
{ type: "button", props: { label: "More" } },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
95
99
|
};
|
|
96
100
|
|
|
97
101
|
export const WithCounters: Story = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
args: {
|
|
103
|
+
items: [
|
|
104
|
+
{
|
|
105
|
+
type: "button",
|
|
106
|
+
props: { label: "Inbox", isCounter: true, counterValue: "5" },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: "button",
|
|
110
|
+
props: { label: "Sent", isCounter: true, counterValue: "12" },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: "button",
|
|
114
|
+
props: { label: "Drafts", isCounter: true, counterValue: "3" },
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
105
118
|
};
|
|
106
119
|
|
|
107
120
|
export const Vertical: Story = {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<Button label="Second" />
|
|
112
|
-
<Button label="Third" />
|
|
113
|
-
</ButtonGroup>
|
|
114
|
-
),
|
|
121
|
+
args: {
|
|
122
|
+
orientation: "vertical",
|
|
123
|
+
},
|
|
115
124
|
};
|
|
116
125
|
|
|
117
126
|
export const Disabled: Story = {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<Button label="Second" />
|
|
122
|
-
<Button label="Third" />
|
|
123
|
-
</ButtonGroup>
|
|
124
|
-
),
|
|
127
|
+
args: {
|
|
128
|
+
disabled: true,
|
|
129
|
+
},
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
export const WithGaps: Story = {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<Button label="Second" />
|
|
132
|
-
<Button label="Third" />
|
|
133
|
-
</ButtonGroup>
|
|
134
|
-
),
|
|
133
|
+
args: {
|
|
134
|
+
removeGaps: false,
|
|
135
|
+
},
|
|
135
136
|
};
|
|
136
137
|
|
|
138
|
+
const VARIANT_ITEMS: ButtonGroupItem[] = [
|
|
139
|
+
{ type: "button", props: { label: "One" } },
|
|
140
|
+
{ type: "button", props: { label: "Two" } },
|
|
141
|
+
{ type: "button", props: { label: "Three" } },
|
|
142
|
+
];
|
|
143
|
+
|
|
137
144
|
const ButtonGroupByVariant = ({
|
|
138
145
|
variant,
|
|
139
146
|
}: {
|
|
@@ -143,21 +150,9 @@ const ButtonGroupByVariant = ({
|
|
|
143
150
|
<Separator />
|
|
144
151
|
<h3 className="s-text-primary dark:s-text-primary-50">{variant}</h3>
|
|
145
152
|
<div className="s-flex s-items-center s-gap-4">
|
|
146
|
-
<ButtonGroup variant={variant} size="xs"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<Button label="Three" />
|
|
150
|
-
</ButtonGroup>
|
|
151
|
-
<ButtonGroup variant={variant} size="sm">
|
|
152
|
-
<Button label="One" />
|
|
153
|
-
<Button label="Two" />
|
|
154
|
-
<Button label="Three" />
|
|
155
|
-
</ButtonGroup>
|
|
156
|
-
<ButtonGroup variant={variant} size="md">
|
|
157
|
-
<Button label="One" />
|
|
158
|
-
<Button label="Two" />
|
|
159
|
-
<Button label="Three" />
|
|
160
|
-
</ButtonGroup>
|
|
153
|
+
<ButtonGroup variant={variant} size="xs" items={VARIANT_ITEMS} />
|
|
154
|
+
<ButtonGroup variant={variant} size="sm" items={VARIANT_ITEMS} />
|
|
155
|
+
<ButtonGroup variant={variant} size="md" items={VARIANT_ITEMS} />
|
|
161
156
|
</div>
|
|
162
157
|
</>
|
|
163
158
|
);
|
|
@@ -172,3 +167,107 @@ export const Gallery: Story = {
|
|
|
172
167
|
</div>
|
|
173
168
|
),
|
|
174
169
|
};
|
|
170
|
+
|
|
171
|
+
export const WithDropdownMenu: Story = {
|
|
172
|
+
render: () => (
|
|
173
|
+
<div className="s-flex s-flex-col s-gap-4">
|
|
174
|
+
<div>
|
|
175
|
+
<h3 className="s-mb-2 s-text-sm s-font-medium">
|
|
176
|
+
Split button with dropdown
|
|
177
|
+
</h3>
|
|
178
|
+
<ButtonGroup
|
|
179
|
+
variant="outline"
|
|
180
|
+
items={[
|
|
181
|
+
{
|
|
182
|
+
type: "button",
|
|
183
|
+
props: {
|
|
184
|
+
icon: ClipboardIcon,
|
|
185
|
+
tooltip: "Copy to clipboard",
|
|
186
|
+
variant: "ghost-secondary",
|
|
187
|
+
size: "xs",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: "dropdown",
|
|
192
|
+
triggerProps: {
|
|
193
|
+
variant: "ghost-secondary",
|
|
194
|
+
size: "xs",
|
|
195
|
+
icon: ChevronDownIcon,
|
|
196
|
+
},
|
|
197
|
+
dropdownProps: {
|
|
198
|
+
items: [
|
|
199
|
+
{ label: "Retry", icon: ArrowPathIcon },
|
|
200
|
+
{ label: "Delete", icon: TrashIcon, variant: "warning" },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
]}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div>
|
|
209
|
+
<h3 className="s-mb-2 s-text-sm s-font-medium">Multiple variations</h3>
|
|
210
|
+
<div className="s-flex s-flex-wrap s-gap-4">
|
|
211
|
+
<ButtonGroup
|
|
212
|
+
variant="outline"
|
|
213
|
+
items={[
|
|
214
|
+
{ type: "button", props: { label: "Copy", size: "sm" } },
|
|
215
|
+
{
|
|
216
|
+
type: "dropdown",
|
|
217
|
+
triggerProps: { size: "sm", icon: ChevronDownIcon },
|
|
218
|
+
dropdownProps: {
|
|
219
|
+
items: [
|
|
220
|
+
{ label: "Option 1" },
|
|
221
|
+
{ label: "Option 2" },
|
|
222
|
+
{ label: "Option 3" },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
]}
|
|
227
|
+
/>
|
|
228
|
+
|
|
229
|
+
<ButtonGroup
|
|
230
|
+
variant="primary"
|
|
231
|
+
items={[
|
|
232
|
+
{ type: "button", props: { label: "Save", size: "sm" } },
|
|
233
|
+
{
|
|
234
|
+
type: "dropdown",
|
|
235
|
+
triggerProps: { size: "sm", icon: ChevronDownIcon },
|
|
236
|
+
dropdownProps: {
|
|
237
|
+
items: [
|
|
238
|
+
{ label: "Save and close" },
|
|
239
|
+
{ label: "Save as draft" },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
]}
|
|
244
|
+
/>
|
|
245
|
+
|
|
246
|
+
<ButtonGroup
|
|
247
|
+
variant="outline"
|
|
248
|
+
items={[
|
|
249
|
+
{
|
|
250
|
+
type: "button",
|
|
251
|
+
props: { icon: PlusIcon, label: "Add", size: "sm" },
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: "button",
|
|
255
|
+
props: { icon: RobotIcon, label: "Agent", size: "sm" },
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
type: "dropdown",
|
|
259
|
+
triggerProps: { size: "sm", icon: ChevronDownIcon },
|
|
260
|
+
dropdownProps: {
|
|
261
|
+
items: [
|
|
262
|
+
{ label: "More options", icon: PlusIcon },
|
|
263
|
+
{ label: "Settings" },
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
]}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
),
|
|
273
|
+
};
|