@dust-tt/sparkle 0.2.470 → 0.2.472-rc1

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.
Files changed (35) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/esm/components/Button.js +1 -1
  3. package/dist/esm/components/Button.js.map +1 -1
  4. package/dist/esm/components/Dropdown.d.ts +3 -0
  5. package/dist/esm/components/Dropdown.d.ts.map +1 -1
  6. package/dist/esm/components/Dropdown.js +26 -6
  7. package/dist/esm/components/Dropdown.js.map +1 -1
  8. package/dist/esm/components/Input.d.ts.map +1 -1
  9. package/dist/esm/components/Input.js +3 -3
  10. package/dist/esm/components/Input.js.map +1 -1
  11. package/dist/esm/components/SearchDropdownMenu.d.ts +11 -0
  12. package/dist/esm/components/SearchDropdownMenu.d.ts.map +1 -0
  13. package/dist/esm/components/SearchDropdownMenu.js +55 -0
  14. package/dist/esm/components/SearchDropdownMenu.js.map +1 -0
  15. package/dist/esm/components/SearchInput.js +1 -1
  16. package/dist/esm/components/SearchInput.js.map +1 -1
  17. package/dist/esm/components/index.d.ts +1 -0
  18. package/dist/esm/components/index.d.ts.map +1 -1
  19. package/dist/esm/components/index.js +1 -0
  20. package/dist/esm/components/index.js.map +1 -1
  21. package/dist/esm/stories/Dropdown.stories.js +1 -1
  22. package/dist/esm/stories/Dropdown.stories.js.map +1 -1
  23. package/dist/esm/stories/Playground.stories.d.ts.map +1 -1
  24. package/dist/esm/stories/Playground.stories.js +9 -2
  25. package/dist/esm/stories/Playground.stories.js.map +1 -1
  26. package/dist/sparkle.css +53 -0
  27. package/package.json +1 -1
  28. package/src/components/Button.tsx +2 -2
  29. package/src/components/Dropdown.tsx +68 -18
  30. package/src/components/Input.tsx +7 -3
  31. package/src/components/SearchDropdownMenu.tsx +96 -0
  32. package/src/components/SearchInput.tsx +1 -1
  33. package/src/components/index.ts +1 -0
  34. package/src/stories/Dropdown.stories.tsx +1 -1
  35. package/src/stories/Playground.stories.tsx +27 -2
package/dist/sparkle.css CHANGED
@@ -1608,6 +1608,10 @@ select {
1608
1608
  width: var(--radix-popover-trigger-width);
1609
1609
  }
1610
1610
 
1611
+ .s-w-\[--radix-popper-anchor-width\] {
1612
+ width: var(--radix-popper-anchor-width);
1613
+ }
1614
+
1611
1615
  .s-w-\[100\%\] {
1612
1616
  width: 100%;
1613
1617
  }
@@ -1726,6 +1730,10 @@ select {
1726
1730
  max-width: 56rem;
1727
1731
  }
1728
1732
 
1733
+ .s-max-w-\[200px\] {
1734
+ max-width: 200px;
1735
+ }
1736
+
1729
1737
  .s-max-w-\[380px\] {
1730
1738
  max-width: 380px;
1731
1739
  }
@@ -2381,6 +2389,10 @@ select {
2381
2389
  border-color: rgb(255 195 223 / 0.3);
2382
2390
  }
2383
2391
 
2392
+ .s-border-border-warning\/40 {
2393
+ border-color: rgb(255 195 223 / 0.4);
2394
+ }
2395
+
2384
2396
  .s-border-border\/0 {
2385
2397
  border-color: rgb(238 238 239 / 0);
2386
2398
  }
@@ -4888,6 +4900,10 @@ select {
4888
4900
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
4889
4901
  }
4890
4902
 
4903
+ .s-ring-inset {
4904
+ --tw-ring-inset: inset;
4905
+ }
4906
+
4891
4907
  .s-ring-highlight-300 {
4892
4908
  --tw-ring-opacity: 1;
4893
4909
  --tw-ring-color: rgb(122 198 255 / var(--tw-ring-opacity));
@@ -5168,6 +5184,14 @@ select {
5168
5184
  color: rgb(17 20 24 / var(--tw-text-opacity));
5169
5185
  }
5170
5186
 
5187
+ .placeholder\:s-italic::-moz-placeholder {
5188
+ font-style: italic;
5189
+ }
5190
+
5191
+ .placeholder\:s-italic::placeholder {
5192
+ font-style: italic;
5193
+ }
5194
+
5171
5195
  .placeholder\:s-text-muted-foreground::-moz-placeholder {
5172
5196
  --tw-text-opacity: 1;
5173
5197
  color: rgb(84 93 108 / var(--tw-text-opacity));
@@ -5530,6 +5554,10 @@ select {
5530
5554
  --tw-ring-color: rgb(225 67 34 / 0.1);
5531
5555
  }
5532
5556
 
5557
+ .focus-visible\:s-ring-offset-0:focus-visible {
5558
+ --tw-ring-offset-width: 0px;
5559
+ }
5560
+
5533
5561
  .focus-visible\:s-ring-offset-1:focus-visible {
5534
5562
  --tw-ring-offset-width: 1px;
5535
5563
  }
@@ -5733,6 +5761,16 @@ select {
5733
5761
  color: rgb(28 145 255 / var(--tw-text-opacity));
5734
5762
  }
5735
5763
 
5764
+ .s-group:hover .group-hover\:s-text-primary-400 {
5765
+ --tw-text-opacity: 1;
5766
+ color: rgb(150 156 165 / var(--tw-text-opacity));
5767
+ }
5768
+
5769
+ .s-group:hover .group-hover\:s-text-primary-600 {
5770
+ --tw-text-opacity: 1;
5771
+ color: rgb(84 93 108 / var(--tw-text-opacity));
5772
+ }
5773
+
5736
5774
  .s-group\/card:hover .group-hover\/card\:s-opacity-100 {
5737
5775
  opacity: 1;
5738
5776
  }
@@ -5764,6 +5802,11 @@ select {
5764
5802
  background-color: rgb(238 238 239 / 0.6);
5765
5803
  }
5766
5804
 
5805
+ .s-group:active .group-active\:s-text-primary-950 {
5806
+ --tw-text-opacity: 1;
5807
+ color: rgb(17 20 24 / var(--tw-text-opacity));
5808
+ }
5809
+
5767
5810
  .s-group:active .group-active\:s-brightness-90 {
5768
5811
  --tw-brightness: brightness(.9);
5769
5812
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
@@ -8409,10 +8452,20 @@ select {
8409
8452
  color: rgb(28 145 255 / var(--tw-text-opacity));
8410
8453
  }
8411
8454
 
8455
+ :is(.s-dark .s-group:hover .dark\:group-hover\:s-text-muted-foreground-night) {
8456
+ --tw-text-opacity: 1;
8457
+ color: rgb(150 156 165 / var(--tw-text-opacity));
8458
+ }
8459
+
8412
8460
  :is(.s-dark .s-group:active .dark\:group-active\:s-bg-primary-100-night\/60) {
8413
8461
  background-color: rgb(28 34 45 / 0.6);
8414
8462
  }
8415
8463
 
8464
+ :is(.s-dark .s-group:active .dark\:group-active\:s-text-primary-950-night) {
8465
+ --tw-text-opacity: 1;
8466
+ color: rgb(238 238 239 / var(--tw-text-opacity));
8467
+ }
8468
+
8416
8469
  :is(.s-dark .dark\:data-\[state\=checked\]\:s-bg-primary-night[data-state=checked]) {
8417
8470
  --tw-bg-opacity: 1;
8418
8471
  background-color: rgb(211 213 217 / var(--tw-bg-opacity));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dust-tt/sparkle",
3
- "version": "0.2.470",
3
+ "version": "0.2.472-rc1",
4
4
  "scripts": {
5
5
  "build": "rm -rf dist && npm run tailwind && npm run build:esm && npm run build:cjs",
6
6
  "tailwind": "tailwindcss -i ./src/styles/tailwind.css -o dist/sparkle.css",
@@ -35,8 +35,8 @@ export type ButtonSizeType = (typeof BUTTON_SIZES)[number];
35
35
  // Define button styling with cva
36
36
  const buttonVariants = cva(
37
37
  cn(
38
- "s-inline-flex s-items-center s-justify-center s-whitespace-nowrap s-ring-offset-background s-transition-colors",
39
- "focus-visible:s-outline-none focus-visible:s-ring-2 focus-visible:s-ring-ring focus-visible:s-ring-offset-2",
38
+ "s-inline-flex s-items-center s-justify-center s-whitespace-nowrap s-ring-offset-background s-transition-colors s-ring-inset",
39
+ "focus-visible:s-outline-none focus-visible:s-ring-2 focus-visible:s-ring-ring focus-visible:s-ring-offset-0",
40
40
  "dark:focus-visible:s-ring-0 dark:focus-visible:s-ring-offset-1"
41
41
  ),
42
42
  {
@@ -1,6 +1,7 @@
1
1
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
2
  import { cva } from "class-variance-authority";
3
3
  import * as React from "react";
4
+ import { useRef } from "react";
4
5
 
5
6
  import { DoubleIcon } from "@sparkle/components/DoubleIcon";
6
7
  import { Icon } from "@sparkle/components/Icon";
@@ -214,26 +215,55 @@ DropdownMenuSubContent.displayName =
214
215
  interface DropdownMenuContentProps
215
216
  extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {
216
217
  mountPortal?: boolean;
218
+ mountPortalContainer?: HTMLElement;
217
219
  }
218
220
 
219
221
  const DropdownMenuContent = React.forwardRef<
220
222
  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
221
223
  DropdownMenuContentProps
222
- >(({ className, sideOffset = 4, mountPortal = true, ...props }, ref) => {
223
- const content = (
224
- <DropdownMenuPrimitive.Content
225
- ref={ref}
226
- sideOffset={sideOffset}
227
- className={cn(menuStyleClasses.container, "s-shadow-md", className)}
228
- {...props}
229
- />
230
- );
231
- return mountPortal ? (
232
- <DropdownMenuPrimitive.Portal>{content}</DropdownMenuPrimitive.Portal>
233
- ) : (
234
- content
235
- );
236
- });
224
+ >(
225
+ (
226
+ {
227
+ className,
228
+ sideOffset = 4,
229
+ mountPortal = true,
230
+ mountPortalContainer,
231
+ ...props
232
+ },
233
+ ref
234
+ ) => {
235
+ const content = (
236
+ <DropdownMenuPrimitive.Content
237
+ ref={ref}
238
+ sideOffset={sideOffset}
239
+ className={cn(menuStyleClasses.container, "s-shadow-md", className)}
240
+ {...props}
241
+ />
242
+ );
243
+
244
+ const [container, setContainer] = React.useState<Element | undefined>(
245
+ mountPortalContainer
246
+ );
247
+
248
+ React.useEffect(() => {
249
+ if (mountPortal && !container) {
250
+ const dialogElements = document.querySelectorAll(
251
+ ".s-sheet[role=dialog][data-state=open]"
252
+ );
253
+ const defaultContainer = dialogElements[dialogElements.length - 1];
254
+ setContainer(defaultContainer);
255
+ }
256
+ }, []);
257
+
258
+ return mountPortal ? (
259
+ <DropdownMenuPrimitive.Portal container={container}>
260
+ {content}
261
+ </DropdownMenuPrimitive.Portal>
262
+ ) : (
263
+ content
264
+ );
265
+ }
266
+ );
237
267
  DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
238
268
 
239
269
  export type DropdownMenuItemProps = MutuallyExclusiveProps<
@@ -412,7 +442,10 @@ const DropdownMenuShortcut = ({
412
442
  };
413
443
  DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
414
444
 
415
- interface DropdownMenuSearchbarProps extends SearchInputProps {}
445
+ interface DropdownMenuSearchbarProps extends SearchInputProps {
446
+ button?: React.ReactNode;
447
+ autoFocus?: boolean;
448
+ }
416
449
 
417
450
  const DropdownMenuSearchbar = React.forwardRef<
418
451
  HTMLInputElement,
@@ -427,18 +460,34 @@ const DropdownMenuSearchbar = React.forwardRef<
427
460
  name,
428
461
  className,
429
462
  disabled = false,
463
+ button,
464
+ autoFocus,
430
465
  },
431
466
  ref
432
467
  ) => {
468
+ const internalRef = useRef<HTMLInputElement>(null);
469
+ React.useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
470
+ ref,
471
+ () => internalRef.current
472
+ );
473
+
474
+ React.useEffect(() => {
475
+ if (autoFocus) {
476
+ setTimeout(() => {
477
+ internalRef.current?.focus();
478
+ }, 0);
479
+ }
480
+ }, [autoFocus]);
481
+
433
482
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
434
483
  e.stopPropagation();
435
484
  onKeyDown?.(e);
436
485
  };
437
486
 
438
487
  return (
439
- <div className={cn("s-px-1 s-py-1", className)}>
488
+ <div className={cn("s-flex s-gap-1.5 s-p-1.5", className)}>
440
489
  <SearchInput
441
- ref={ref}
490
+ ref={internalRef}
442
491
  placeholder={placeholder}
443
492
  name={name}
444
493
  value={value}
@@ -446,6 +495,7 @@ const DropdownMenuSearchbar = React.forwardRef<
446
495
  onKeyDown={handleKeyDown}
447
496
  disabled={disabled}
448
497
  />
498
+ {button}
449
499
  </div>
450
500
  );
451
501
  }
@@ -45,7 +45,7 @@ const stateVariantStyles: Record<InputStateType, string> = {
45
45
  "disabled:s-border-border dark:disabled:s-border-border-night"
46
46
  ),
47
47
  error: cn(
48
- "s-border-border-warning/30 dark:s-border-border-warning-night/60",
48
+ "s-border-border-warning/40 dark:s-border-border-warning-night/60",
49
49
  "s-ring-warning/0 dark:s-ring-warning-night/0",
50
50
  "focus-visible:s-border-border-warning dark:focus-visible:s-border-border-warning-night",
51
51
  "focus-visible:s-outline-none focus-visible:s-ring-2",
@@ -69,7 +69,7 @@ const inputStyleClasses = cva(
69
69
  "s-bg-muted-background dark:s-bg-muted-background-night",
70
70
  "s-border focus-visible:s-ring",
71
71
  "file:s-border-0 file:s-bg-transparent file:s-text-sm file:s-font-medium file:s-text-foreground",
72
- "placeholder:s-text-muted-foreground dark:placeholder:s-text-muted-foreground-night"
72
+ "placeholder:s-text-muted-foreground placeholder:s-italic dark:placeholder:s-text-muted-foreground-night"
73
73
  ),
74
74
  {
75
75
  variants: {
@@ -110,7 +110,11 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
110
110
  )}
111
111
  <input
112
112
  ref={ref}
113
- className={cn(inputStyleClasses({ state }), className)}
113
+ className={cn(
114
+ "s-ring-inset",
115
+ inputStyleClasses({ state }),
116
+ className
117
+ )}
114
118
  data-1p-ignore={props.type !== "password"}
115
119
  value={value ?? undefined}
116
120
  disabled={disabled}
@@ -0,0 +1,96 @@
1
+ import React from "react";
2
+ import { useRef, useState } from "react";
3
+
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuTrigger,
8
+ } from "@sparkle/components/Dropdown";
9
+ import { SearchInput } from "@sparkle/components/SearchInput";
10
+
11
+ type SearchDropdownMenuProps = {
12
+ searchInputValue: string;
13
+ setSearchInputValue: (value: string) => void;
14
+ children: React.ReactNode;
15
+ disabled?: boolean;
16
+ minLengthToOpen?: number;
17
+ };
18
+
19
+ export function SearchDropdownMenu({
20
+ searchInputValue,
21
+ setSearchInputValue,
22
+ disabled,
23
+ minLengthToOpen = 1,
24
+ children,
25
+ }: SearchDropdownMenuProps) {
26
+ const [isOpen, setIsOpen] = useState(false);
27
+ const searchInputRef = useRef<HTMLInputElement>(null);
28
+
29
+ return (
30
+ <DropdownMenu
31
+ open={isOpen && searchInputValue.length >= minLengthToOpen}
32
+ modal={false}
33
+ onOpenChange={(open) => {
34
+ if (!open) {
35
+ setIsOpen(open);
36
+ }
37
+ }}
38
+ >
39
+ <DropdownMenuTrigger asChild>
40
+ <SearchInput
41
+ ref={searchInputRef}
42
+ name="search"
43
+ placeholder="Search"
44
+ value={searchInputValue}
45
+ disabled={disabled}
46
+ onFocus={() => {
47
+ if (!isOpen) {
48
+ setIsOpen(searchInputValue.length >= minLengthToOpen);
49
+ setTimeout(() => {
50
+ searchInputRef.current?.focus();
51
+ }, 0);
52
+ }
53
+ }}
54
+ onChange={(value) => {
55
+ setSearchInputValue(value);
56
+ setIsOpen(value.length >= minLengthToOpen);
57
+ setTimeout(() => {
58
+ searchInputRef.current?.focus();
59
+ }, 0);
60
+ }}
61
+ onKeyDown={(e) => {
62
+ if (e.key === "Enter") {
63
+ setIsOpen(false);
64
+ }
65
+ if (e.key === "Tab" || e.key === "ArrowDown") {
66
+ e.preventDefault();
67
+ const firstItem = document.querySelector('[role="menuitem"]');
68
+ if (firstItem instanceof HTMLElement) {
69
+ firstItem.focus();
70
+ }
71
+ }
72
+ }}
73
+ />
74
+ </DropdownMenuTrigger>
75
+ <DropdownMenuContent
76
+ side="bottom"
77
+ align="start"
78
+ className="s-w-[--radix-popper-anchor-width]"
79
+ onFocusOutside={(e) => {
80
+ // Prevent closing when search input is focused
81
+ if (e.target === searchInputRef.current) {
82
+ e.preventDefault();
83
+ }
84
+ }}
85
+ onInteractOutside={(e) => {
86
+ // Prevent closing when clicking on the search input
87
+ if (e.target === searchInputRef.current) {
88
+ e.preventDefault();
89
+ }
90
+ }}
91
+ >
92
+ {children}
93
+ </DropdownMenuContent>
94
+ </DropdownMenu>
95
+ );
96
+ }
@@ -222,7 +222,7 @@ function BaseSearchInputWithPopover<T>(
222
222
  mountPortalContainer={mountPortalContainer}
223
223
  >
224
224
  <div className="s-flex s-flex-col">
225
- {items.length > 0 && (
225
+ {items.length > 0 && (displayItemCount || onSelectAll) && (
226
226
  <div className="s-flex s-items-center s-justify-between s-p-2 s-text-sm s-text-gray-500">
227
227
  <div>
228
228
  {displayItemCount && (
@@ -118,6 +118,7 @@ export {
118
118
  ResizablePanelGroup,
119
119
  } from "./Resizable";
120
120
  export { ScrollArea, ScrollBar } from "./ScrollArea";
121
+ export { SearchDropdownMenu } from "./SearchDropdownMenu";
121
122
  export { SearchInput, SearchInputWithPopover } from "./SearchInput";
122
123
  export { Separator } from "./Separator";
123
124
  export {
@@ -609,7 +609,7 @@ function AttachFileDemo() {
609
609
  name="search"
610
610
  onChange={() => {}}
611
611
  onKeyDown={() => {}}
612
- placeholder="Search Toolsets"
612
+ placeholder="Search Tools"
613
613
  value=""
614
614
  />
615
615
  <Button icon={PlusIcon} label="Add MCP Server" />
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
2
 
3
- import { Button, FlexSplitButton } from "@sparkle/components";
3
+ import { Avatar, Button, FlexSplitButton, Icon } from "@sparkle/components";
4
4
  import { ArrowUpIcon, ChevronDownIcon } from "@sparkle/icons/app";
5
+ import { cn } from "@sparkle/lib";
5
6
 
6
7
  export default {
7
8
  title: "Playground/Demo",
@@ -9,7 +10,31 @@ export default {
9
10
 
10
11
  export const Demo = () => {
11
12
  return (
12
- <div className="s-flex s-h-full s-w-full s-flex-col s-gap-2">
13
+ <div className="s-flex s-h-full s-w-full s-cursor-pointer s-flex-col s-gap-2">
14
+ <div className="s-group s-flex s-max-w-[200px] s-items-center s-gap-2 s-bg-muted-background s-p-4">
15
+ <Avatar
16
+ size="sm"
17
+ visual="https://lh3.googleusercontent.com/a/ACg8ocItxZ3wFv94own6Sh86W9zOFy4RA_L9A0NtNz2sM0uftazvbhU=s96-c"
18
+ clickable
19
+ />
20
+ <div className="s-flex s-flex-col s-items-start">
21
+ <span
22
+ className={cn(
23
+ "s-heading-sm s-transition-colors s-duration-200",
24
+ "s-text-foreground group-hover:s-text-primary-600 group-active:s-text-primary-950 dark:s-text-foreground-night dark:group-hover:s-text-muted-foreground-night dark:group-active:s-text-primary-950-night"
25
+ )}
26
+ >
27
+ Edouard
28
+ </span>
29
+ <span className="-s-mt-1 s-text-sm s-text-muted-foreground dark:s-text-muted-foreground-night">
30
+ Dust
31
+ </span>
32
+ </div>
33
+ <Icon
34
+ visual={ChevronDownIcon}
35
+ className="s-text-muted-foreground group-hover:s-text-primary-400 group-active:s-text-primary-950 dark:s-text-foreground-night dark:group-hover:s-text-muted-foreground-night dark:group-active:s-text-primary-950-night"
36
+ />
37
+ </div>
13
38
  <div className="s-flex s-gap-3">
14
39
  <FlexSplitButton
15
40
  label="Send"