@dust-tt/sparkle 0.7.6 → 0.7.7-rc-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/cjs/index.js +10 -10
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/components/Toolbar.d.ts +51 -0
- package/dist/esm/components/Toolbar.d.ts.map +1 -0
- package/dist/esm/components/Toolbar.js +124 -0
- package/dist/esm/components/Toolbar.js.map +1 -0
- package/dist/esm/components/index.d.ts +2 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/stories/Toolbar.stories.d.ts +25 -0
- package/dist/esm/stories/Toolbar.stories.d.ts.map +1 -0
- package/dist/esm/stories/Toolbar.stories.js +100 -0
- package/dist/esm/stories/Toolbar.stories.js.map +1 -0
- package/dist/sparkle.css +42 -0
- package/package.json +1 -1
- package/src/components/Toolbar.tsx +310 -0
- package/src/components/index.ts +9 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import { Button, type ButtonProps } from "@sparkle/components/Button";
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContainer,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from "@sparkle/components/Dialog";
|
|
14
|
+
import { Input } from "@sparkle/components/Input";
|
|
15
|
+
import { ScrollArea } from "@sparkle/components/ScrollArea";
|
|
16
|
+
import { Separator } from "@sparkle/components/Separator";
|
|
17
|
+
import { LinkMIcon, XMarkIcon } from "@sparkle/icons/app";
|
|
18
|
+
import { cn } from "@sparkle/lib/utils";
|
|
19
|
+
|
|
20
|
+
export type ToolbarVariant = "inline" | "overlay";
|
|
21
|
+
|
|
22
|
+
type ToolbarButtonSize = NonNullable<ButtonProps["size"]>;
|
|
23
|
+
|
|
24
|
+
const toolbarRootVariants = cva("s-inline-flex s-items-center", {
|
|
25
|
+
variants: {
|
|
26
|
+
variant: {
|
|
27
|
+
overlay:
|
|
28
|
+
"s-absolute s-left-0 s-top-0 s-z-10 s-justify-start s-gap-3 s-overflow-hidden s-rounded-xl s-bg-primary-50 s-py-1 s-pl-3 s-duration-700 s-ease-in-out dark:s-bg-muted-background-night",
|
|
29
|
+
inline:
|
|
30
|
+
"s-gap-1 s-border-b s-border-t s-border-border s-bg-background s-p-1 dark:s-border-border-night/50 dark:s-bg-background-night sm:s-rounded-2xl sm:s-border sm:s-border-border/50 sm:s-shadow-md",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "inline",
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const toolbarContentVariants = cva("", {
|
|
39
|
+
variants: {
|
|
40
|
+
variant: {
|
|
41
|
+
overlay:
|
|
42
|
+
"s-flex s-h-full s-w-max s-flex-row s-items-center s-gap-3 s-px-3",
|
|
43
|
+
inline: "s-inline-flex s-items-center s-gap-1",
|
|
44
|
+
},
|
|
45
|
+
scrollable: {
|
|
46
|
+
true: "s-overflow-x-scroll",
|
|
47
|
+
false: "",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
defaultVariants: {
|
|
51
|
+
variant: "inline",
|
|
52
|
+
scrollable: false,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const toolbarScrollAreaVariants = cva("s-h-full s-w-full", {
|
|
57
|
+
variants: {
|
|
58
|
+
variant: {
|
|
59
|
+
overlay: "s-border-l s-border-border dark:s-border-border-night/50",
|
|
60
|
+
inline: "",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
variant: "inline",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export interface ToolbarProps {
|
|
69
|
+
variant?: ToolbarVariant;
|
|
70
|
+
children: React.ReactNode;
|
|
71
|
+
className?: string;
|
|
72
|
+
contentClassName?: string;
|
|
73
|
+
scrollAreaClassName?: string;
|
|
74
|
+
scroll?: boolean;
|
|
75
|
+
onClose?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
76
|
+
closeButtonProps?: Omit<ButtonProps, "icon" | "onClick" | "label">;
|
|
77
|
+
startSlot?: React.ReactNode;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Toolbar({
|
|
81
|
+
variant = "inline",
|
|
82
|
+
children,
|
|
83
|
+
className,
|
|
84
|
+
contentClassName,
|
|
85
|
+
scrollAreaClassName,
|
|
86
|
+
scroll,
|
|
87
|
+
onClose,
|
|
88
|
+
closeButtonProps,
|
|
89
|
+
startSlot,
|
|
90
|
+
}: ToolbarProps) {
|
|
91
|
+
const isOverlay = variant === "overlay";
|
|
92
|
+
const isScrollable = scroll ?? isOverlay;
|
|
93
|
+
const {
|
|
94
|
+
size: closeButtonSizeProp,
|
|
95
|
+
variant: closeVariant = "outline",
|
|
96
|
+
...restCloseButtonProps
|
|
97
|
+
} = closeButtonProps ?? {};
|
|
98
|
+
const closeButtonSize: ToolbarButtonSize = closeButtonSizeProp ?? "mini";
|
|
99
|
+
|
|
100
|
+
const rootClassName = toolbarRootVariants({ variant, className });
|
|
101
|
+
const contentBaseClassName = toolbarContentVariants({
|
|
102
|
+
variant,
|
|
103
|
+
scrollable: isScrollable,
|
|
104
|
+
className: contentClassName,
|
|
105
|
+
});
|
|
106
|
+
const scrollAreaClassNames = toolbarScrollAreaVariants({
|
|
107
|
+
variant,
|
|
108
|
+
className: scrollAreaClassName,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
function renderCloseButton(): JSX.Element | null {
|
|
112
|
+
if (!onClose) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const buttonProps = {
|
|
117
|
+
variant: closeVariant,
|
|
118
|
+
icon: XMarkIcon,
|
|
119
|
+
onClick: onClose,
|
|
120
|
+
...restCloseButtonProps,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (closeButtonSize === "mini") {
|
|
124
|
+
return <Button size="mini" {...buttonProps} />;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return <Button size={closeButtonSize} {...buttonProps} />;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const closeButton = renderCloseButton();
|
|
131
|
+
|
|
132
|
+
const leadingContent = startSlot ?? closeButton;
|
|
133
|
+
const content = <div className={contentBaseClassName}>{children}</div>;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div className={rootClassName}>
|
|
137
|
+
{leadingContent}
|
|
138
|
+
{isScrollable ? (
|
|
139
|
+
<ScrollArea
|
|
140
|
+
orientation="horizontal"
|
|
141
|
+
className={scrollAreaClassNames}
|
|
142
|
+
hideScrollBar
|
|
143
|
+
>
|
|
144
|
+
{content}
|
|
145
|
+
</ScrollArea>
|
|
146
|
+
) : (
|
|
147
|
+
content
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ToolbarContentGroup {
|
|
154
|
+
id: string;
|
|
155
|
+
items: readonly React.ReactNode[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ToolbarContentProps {
|
|
159
|
+
groups: readonly ToolbarContentGroup[];
|
|
160
|
+
separatorClassName?: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function ToolbarContent({ groups, separatorClassName }: ToolbarContentProps) {
|
|
164
|
+
return (
|
|
165
|
+
<>
|
|
166
|
+
{groups.map((group, groupIndex) => (
|
|
167
|
+
<React.Fragment key={group.id}>
|
|
168
|
+
{group.items}
|
|
169
|
+
{groupIndex < groups.length - 1 && (
|
|
170
|
+
<Separator
|
|
171
|
+
orientation="vertical"
|
|
172
|
+
className={cn("s-my-1", separatorClassName)}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
</React.Fragment>
|
|
176
|
+
))}
|
|
177
|
+
</>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ToolbarIconProps {
|
|
182
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
183
|
+
onClick: () => void;
|
|
184
|
+
size: ToolbarButtonSize;
|
|
185
|
+
active?: boolean;
|
|
186
|
+
tooltip?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function ToolbarIcon({
|
|
190
|
+
icon,
|
|
191
|
+
onClick,
|
|
192
|
+
size,
|
|
193
|
+
active,
|
|
194
|
+
tooltip,
|
|
195
|
+
}: ToolbarIconProps) {
|
|
196
|
+
function handleClick(event: React.MouseEvent<HTMLButtonElement>): void {
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
event.stopPropagation();
|
|
199
|
+
onClick();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (size === "mini") {
|
|
203
|
+
return (
|
|
204
|
+
<Button
|
|
205
|
+
tooltip={tooltip}
|
|
206
|
+
icon={icon}
|
|
207
|
+
onClick={handleClick}
|
|
208
|
+
size="mini"
|
|
209
|
+
variant={active ? "ghost" : "ghost-secondary"}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<Button
|
|
216
|
+
tooltip={tooltip}
|
|
217
|
+
icon={icon}
|
|
218
|
+
onClick={handleClick}
|
|
219
|
+
size={size}
|
|
220
|
+
variant={active ? "ghost" : "ghost-secondary"}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface ToolbarLinkProps {
|
|
226
|
+
isOpen: boolean;
|
|
227
|
+
onOpenChange: (open: boolean) => void;
|
|
228
|
+
onOpenDialog: () => void;
|
|
229
|
+
onSubmit: () => void;
|
|
230
|
+
linkText: string;
|
|
231
|
+
linkUrl: string;
|
|
232
|
+
onLinkTextChange: (value: string) => void;
|
|
233
|
+
onLinkUrlChange: (value: string) => void;
|
|
234
|
+
size: ToolbarButtonSize;
|
|
235
|
+
active?: boolean;
|
|
236
|
+
tooltip?: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ToolbarLink({
|
|
240
|
+
isOpen,
|
|
241
|
+
onOpenChange,
|
|
242
|
+
onOpenDialog,
|
|
243
|
+
onSubmit,
|
|
244
|
+
linkText,
|
|
245
|
+
linkUrl,
|
|
246
|
+
onLinkTextChange,
|
|
247
|
+
onLinkUrlChange,
|
|
248
|
+
size,
|
|
249
|
+
active,
|
|
250
|
+
tooltip,
|
|
251
|
+
}: ToolbarLinkProps) {
|
|
252
|
+
function handleDialogClick(event: React.MouseEvent<HTMLDivElement>): void {
|
|
253
|
+
event.stopPropagation();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function handleCancelClick(): void {
|
|
257
|
+
onOpenChange(false);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
|
262
|
+
<ToolbarIcon
|
|
263
|
+
icon={LinkMIcon}
|
|
264
|
+
onClick={onOpenDialog}
|
|
265
|
+
active={active}
|
|
266
|
+
tooltip={tooltip}
|
|
267
|
+
size={size}
|
|
268
|
+
/>
|
|
269
|
+
<DialogContent onClick={handleDialogClick}>
|
|
270
|
+
<DialogHeader>
|
|
271
|
+
<DialogTitle>Insert Link</DialogTitle>
|
|
272
|
+
<DialogDescription>
|
|
273
|
+
Add a link to your message with custom text.
|
|
274
|
+
</DialogDescription>
|
|
275
|
+
</DialogHeader>
|
|
276
|
+
<DialogContainer>
|
|
277
|
+
<Input
|
|
278
|
+
id="link-text"
|
|
279
|
+
label="Text"
|
|
280
|
+
placeholder="Text"
|
|
281
|
+
value={linkText}
|
|
282
|
+
onChange={(event) => onLinkTextChange(event.target.value)}
|
|
283
|
+
/>
|
|
284
|
+
<Input
|
|
285
|
+
id="link-url"
|
|
286
|
+
label="Link"
|
|
287
|
+
placeholder="Link"
|
|
288
|
+
value={linkUrl}
|
|
289
|
+
autoFocus
|
|
290
|
+
onChange={(event) => onLinkUrlChange(event.target.value)}
|
|
291
|
+
/>
|
|
292
|
+
</DialogContainer>
|
|
293
|
+
<DialogFooter
|
|
294
|
+
leftButtonProps={{
|
|
295
|
+
label: "Cancel",
|
|
296
|
+
variant: "outline",
|
|
297
|
+
onClick: handleCancelClick,
|
|
298
|
+
}}
|
|
299
|
+
rightButtonProps={{
|
|
300
|
+
label: "Save",
|
|
301
|
+
variant: "highlight",
|
|
302
|
+
onClick: onSubmit,
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
</DialogContent>
|
|
306
|
+
</Dialog>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export { Toolbar, ToolbarContent, ToolbarIcon, ToolbarLink };
|
package/src/components/index.ts
CHANGED
|
@@ -210,6 +210,15 @@ export { FlexSplitButton } from "./SplitButton";
|
|
|
210
210
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs";
|
|
211
211
|
export { ReadOnlyTextArea, TextArea } from "./TextArea";
|
|
212
212
|
export { Timeline, TimelineItem } from "./Timeline";
|
|
213
|
+
export type {
|
|
214
|
+
ToolbarContentGroup,
|
|
215
|
+
ToolbarContentProps,
|
|
216
|
+
ToolbarIconProps,
|
|
217
|
+
ToolbarLinkProps,
|
|
218
|
+
ToolbarProps,
|
|
219
|
+
ToolbarVariant,
|
|
220
|
+
} from "./Toolbar";
|
|
221
|
+
export { Toolbar, ToolbarContent, ToolbarIcon, ToolbarLink } from "./Toolbar";
|
|
213
222
|
export {
|
|
214
223
|
Tooltip,
|
|
215
224
|
TooltipContent,
|