@dust-tt/sparkle 0.2.474-rc-4 → 0.2.474-rc

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.
@@ -19,6 +19,32 @@ const positionVariants = cva("s-flex s-absolute", {
19
19
  },
20
20
  });
21
21
 
22
+ const sizeVariants = cva("s-relative", {
23
+ variants: {
24
+ size: {
25
+ md: "s-h-6 s-w-6",
26
+ lg: "s-h-8 s-w-8 s-p-0.5",
27
+ xl: "s-h-10 s-w-10",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ size: "lg",
32
+ },
33
+ });
34
+
35
+ const iconSizeVariants = cva("s-absolute", {
36
+ variants: {
37
+ size: {
38
+ md: "s-bottom-0 s-right-0",
39
+ lg: "s-bottom-0 s-right-0",
40
+ xl: "s-bottom-0 s-right-0",
41
+ },
42
+ },
43
+ defaultVariants: {
44
+ size: "lg",
45
+ },
46
+ });
47
+
22
48
  export interface DoubleIconProps extends VariantProps<typeof positionVariants> {
23
49
  mainIconProps: IconProps;
24
50
  secondaryIconProps: IconProps;
@@ -41,3 +67,33 @@ export const DoubleIcon = ({
41
67
  </div>
42
68
  );
43
69
  };
70
+
71
+ export interface SimpleDoubleIconProps
72
+ extends VariantProps<typeof sizeVariants> {
73
+ mainIcon: React.ComponentType;
74
+ secondaryIcon: React.ComponentType;
75
+ size?: "md" | "lg" | "xl";
76
+ className?: string;
77
+ }
78
+
79
+ export const SimpleDoubleIcon = ({
80
+ mainIcon,
81
+ secondaryIcon,
82
+ className,
83
+ size = "lg",
84
+ }: SimpleDoubleIconProps) => {
85
+ return (
86
+ <div className={cn(sizeVariants({ size }), className)}>
87
+ <Icon
88
+ className="s-text-foreground dark:s-text-foreground-night"
89
+ size={size === "md" ? "sm" : size === "lg" ? "md" : "lg"}
90
+ visual={mainIcon}
91
+ />
92
+ <Icon
93
+ size={size === "md" ? "xs" : size === "lg" ? "xs" : "md"}
94
+ visual={secondaryIcon}
95
+ className={cn("s-absolute", iconSizeVariants({ size }))}
96
+ />
97
+ </div>
98
+ );
99
+ };
@@ -3,10 +3,8 @@ import { cva } from "class-variance-authority";
3
3
  import * as React from "react";
4
4
  import { useRef } from "react";
5
5
 
6
- import { DoubleIcon } from "@sparkle/components/DoubleIcon";
7
6
  import { Icon } from "@sparkle/components/Icon";
8
7
  import { LinkWrapper, LinkWrapperProps } from "@sparkle/components/LinkWrapper";
9
- import { ScrollArea } from "@sparkle/components/ScrollArea";
10
8
  import { SearchInput, SearchInputProps } from "@sparkle/components/SearchInput";
11
9
  import { CheckIcon, ChevronRightIcon, CircleIcon } from "@sparkle/icons/app";
12
10
  import { cn } from "@sparkle/lib/utils";
@@ -22,7 +20,7 @@ export const menuStyleClasses = {
22
20
  "s-border s-border-border dark:s-border-border-night",
23
21
  "s-bg-background dark:s-bg-muted-background-night",
24
22
  "s-text-foreground dark:s-text-foreground-night",
25
- "s-z-50 s-min-w-[8rem]",
23
+ "s-z-50 s-min-w-[8rem] s-overflow-hidden",
26
24
  "data-[state=open]:s-animate-in data-[state=closed]:s-animate-out data-[state=closed]:s-fade-out-0 data-[state=open]:s-fade-in-0 data-[state=closed]:s-zoom-out-95 data-[state=open]:s-zoom-in-95 data-[side=bottom]:s-slide-in-from-top-2 data-[side=left]:s-slide-in-from-right-2 data-[side=right]:s-slide-in-from-left-2 data-[side=top]:s-slide-in-from-bottom-2"
27
25
  ),
28
26
  item: cva(
@@ -85,7 +83,7 @@ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
85
83
 
86
84
  interface LabelAndIconProps {
87
85
  label: string;
88
- icon?: React.ComponentType;
86
+ icon?: React.ComponentType | React.ReactNode;
89
87
  }
90
88
 
91
89
  type Simplify<T> = { [K in keyof T]: T[K] };
@@ -100,19 +98,27 @@ type MutuallyExclusiveProps<BaseProps, ExtraProps> = Simplify<
100
98
 
101
99
  interface ItemWithLabelIconAndDescriptionProps {
102
100
  label?: string;
103
- icon?: React.ComponentType;
104
- extraIcon?: React.ComponentType;
101
+ icon?: React.ComponentType | React.ReactNode;
105
102
  description?: string;
106
103
  children?: React.ReactNode;
107
104
  truncate?: boolean;
108
105
  }
109
106
 
107
+ const renderIcon = (
108
+ icon: React.ComponentType | React.ReactNode,
109
+ size: "xs" | "sm" = "xs"
110
+ ) => {
111
+ if (!icon) {
112
+ return null;
113
+ }
114
+ return typeof icon === "function" ? <Icon size={size} visual={icon} /> : icon;
115
+ };
116
+
110
117
  const ItemWithLabelIconAndDescription = <
111
118
  T extends ItemWithLabelIconAndDescriptionProps,
112
119
  >({
113
120
  label,
114
121
  icon,
115
- extraIcon,
116
122
  description,
117
123
  truncate,
118
124
  children,
@@ -121,30 +127,7 @@ const ItemWithLabelIconAndDescription = <
121
127
  <>
122
128
  {label && (
123
129
  <div className="s-grid s-grid-cols-[auto,1fr,auto] s-items-center s-gap-x-2.5">
124
- {(icon || extraIcon) && (
125
- <div
126
- className={cn(
127
- "s-flex",
128
- description ? "s-items-start s-pt-0.5" : "s-items-center"
129
- )}
130
- >
131
- {icon && extraIcon ? (
132
- <DoubleIcon
133
- mainIconProps={{
134
- visual: icon,
135
- size: "sm",
136
- }}
137
- secondaryIconProps={{
138
- visual: extraIcon,
139
- size: "xs",
140
- }}
141
- position="bottom-right"
142
- />
143
- ) : icon ? (
144
- <Icon size="sm" visual={icon} />
145
- ) : null}
146
- </div>
147
- )}
130
+ {renderIcon(icon, "sm")}
148
131
  <div className="s-flex s-flex-col">
149
132
  <span className={truncate ? "s-line-clamp-1" : undefined}>
150
133
  {label}
@@ -187,7 +170,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
187
170
  >
188
171
  {label && (
189
172
  <>
190
- {icon && <Icon size="xs" visual={icon} />}
173
+ {renderIcon(icon)}
191
174
  {label}
192
175
  <span className={menuStyleClasses.subTrigger.default}>
193
176
  <Icon size="xs" visual={ChevronRightIcon} />
@@ -217,7 +200,6 @@ interface DropdownMenuContentProps
217
200
  extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {
218
201
  mountPortal?: boolean;
219
202
  mountPortalContainer?: HTMLElement;
220
- dropdownHeaders?: React.ReactNode;
221
203
  }
222
204
 
223
205
  const DropdownMenuContent = React.forwardRef<
@@ -230,8 +212,6 @@ const DropdownMenuContent = React.forwardRef<
230
212
  sideOffset = 4,
231
213
  mountPortal = true,
232
214
  mountPortalContainer,
233
- dropdownHeaders,
234
- children,
235
215
  ...props
236
216
  },
237
217
  ref
@@ -240,26 +220,9 @@ const DropdownMenuContent = React.forwardRef<
240
220
  <DropdownMenuPrimitive.Content
241
221
  ref={ref}
242
222
  sideOffset={sideOffset}
243
- className={cn(
244
- menuStyleClasses.container,
245
- "s-flex s-flex-col s-p-0 s-shadow-md",
246
- className
247
- )}
223
+ className={cn(menuStyleClasses.container, "s-shadow-md", className)}
248
224
  {...props}
249
- >
250
- <div className="s-sticky s-top-0 s-bg-background dark:s-bg-background-night">
251
- {dropdownHeaders && dropdownHeaders}
252
- </div>
253
- <ScrollArea
254
- className="s-w-full s-flex-1"
255
- viewportClassName={cn(
256
- "s-flex-1",
257
- "s-max-h-[var(--radix-dropdown-menu-content-available-height)]"
258
- )}
259
- >
260
- {children}
261
- </ScrollArea>
262
- </DropdownMenuPrimitive.Content>
225
+ />
263
226
  );
264
227
 
265
228
  const [container, setContainer] = React.useState<Element | undefined>(
@@ -294,7 +257,6 @@ export type DropdownMenuItemProps = MutuallyExclusiveProps<
294
257
  } & Omit<LinkWrapperProps, "children" | "className">,
295
258
  LabelAndIconProps & {
296
259
  description?: string;
297
- extraIcon?: React.ComponentType;
298
260
  truncateText?: boolean;
299
261
  }
300
262
  >;
@@ -311,7 +273,6 @@ const DropdownMenuItem = React.forwardRef<
311
273
  className,
312
274
  inset,
313
275
  icon,
314
- extraIcon,
315
276
  truncateText,
316
277
  label,
317
278
  href,
@@ -347,7 +308,6 @@ const DropdownMenuItem = React.forwardRef<
347
308
  <ItemWithLabelIconAndDescription
348
309
  label={label}
349
310
  icon={icon}
350
- extraIcon={extraIcon}
351
311
  description={description}
352
312
  truncate={truncateText}
353
313
  >
@@ -79,7 +79,7 @@ export function Tree({
79
79
  const treeItemStyleClasses = {
80
80
  base: "s-group/tree s-flex s-cursor-default s-flex-row s-items-center s-gap-2 s-h-9",
81
81
  isNavigatableBase:
82
- "s-rounded-xl s-pl-1 s-pr-3 s-transition-colors s-duration-300 s-ease-out s-cursor-pointer",
82
+ "s-rounded-xl s-pl-1.5 s-pr-3 s-transition-colors s-duration-300 s-ease-out s-cursor-pointer",
83
83
  isNavigatableUnselected: cn(
84
84
  "s-bg-primary-100/0 dark:s-bg-primary-100-night/0",
85
85
  "hover:s-bg-primary-100 dark:hover:s-bg-primary-100-night"
@@ -2,8 +2,10 @@ import type { Meta } from "@storybook/react";
2
2
  import React from "react";
3
3
 
4
4
  import { DoubleIcon } from "@sparkle/components";
5
+ import { SimpleDoubleIcon } from "@sparkle/components/DoubleIcon";
5
6
  import {
6
7
  ChatBubbleBottomCenterTextIcon,
8
+ ChatBubbleThoughtIcon,
7
9
  CheckCircleIcon,
8
10
  DocumentIcon,
9
11
  FolderIcon,
@@ -13,7 +15,7 @@ import {
13
15
  StarIcon,
14
16
  UserIcon,
15
17
  } from "@sparkle/icons/app";
16
- import { SlackLogo } from "@sparkle/logo";
18
+ import { DriveLogo, NotionLogo, SlackLogo } from "@sparkle/logo";
17
19
 
18
20
  const meta = {
19
21
  title: "Primitives/DoubleIcon",
@@ -24,6 +26,57 @@ export default meta;
24
26
 
25
27
  export const IconPositions = () => (
26
28
  <div className="s-flex s-flex-col s-gap-8">
29
+ <div className="s-flex s-items-center s-gap-8">
30
+ <SimpleDoubleIcon
31
+ size="xl"
32
+ mainIcon={FolderIcon}
33
+ secondaryIcon={DriveLogo}
34
+ />{" "}
35
+ <SimpleDoubleIcon
36
+ size="xl"
37
+ mainIcon={DocumentIcon}
38
+ secondaryIcon={NotionLogo}
39
+ />{" "}
40
+ <SimpleDoubleIcon
41
+ size="xl"
42
+ mainIcon={ChatBubbleThoughtIcon}
43
+ secondaryIcon={SlackLogo}
44
+ />
45
+ </div>
46
+ <div className="s-flex s-items-center s-gap-8">
47
+ <SimpleDoubleIcon
48
+ size="lg"
49
+ mainIcon={FolderIcon}
50
+ secondaryIcon={DriveLogo}
51
+ />{" "}
52
+ <SimpleDoubleIcon
53
+ size="lg"
54
+ mainIcon={DocumentIcon}
55
+ secondaryIcon={NotionLogo}
56
+ />{" "}
57
+ <SimpleDoubleIcon
58
+ size="lg"
59
+ mainIcon={ChatBubbleThoughtIcon}
60
+ secondaryIcon={SlackLogo}
61
+ />
62
+ </div>
63
+ <div className="s-flex s-items-center s-gap-8">
64
+ <SimpleDoubleIcon
65
+ size="md"
66
+ mainIcon={FolderIcon}
67
+ secondaryIcon={DriveLogo}
68
+ />{" "}
69
+ <SimpleDoubleIcon
70
+ size="md"
71
+ mainIcon={DocumentIcon}
72
+ secondaryIcon={NotionLogo}
73
+ />{" "}
74
+ <SimpleDoubleIcon
75
+ size="md"
76
+ mainIcon={ChatBubbleThoughtIcon}
77
+ secondaryIcon={SlackLogo}
78
+ />
79
+ </div>
27
80
  <div className="s-flex s-items-center s-gap-16">
28
81
  <div className="s-flex s-flex-col s-items-center s-gap-2">
29
82
  <DoubleIcon
@@ -93,7 +146,6 @@ export const IconPositions = () => (
93
146
  <span className="s-text-xs s-text-muted-foreground">top-left</span>
94
147
  </div>
95
148
  </div>
96
-
97
149
  <div className="s-flex s-items-center s-gap-16">
98
150
  <div className="s-flex s-flex-col s-items-center s-gap-2">
99
151
  <DoubleIcon
@@ -2,6 +2,7 @@ import { DropdownMenuCheckboxItemProps } from "@radix-ui/react-dropdown-menu";
2
2
  import type { Meta } from "@storybook/react";
3
3
  import React from "react";
4
4
 
5
+ import { SimpleDoubleIcon } from "@sparkle/components/DoubleIcon";
5
6
  import {
6
7
  DropdownMenu,
7
8
  DropdownMenuCheckboxItem,
@@ -12,7 +13,6 @@ import {
12
13
  DropdownMenuPortal,
13
14
  DropdownMenuRadioGroup,
14
15
  DropdownMenuRadioItem,
15
- DropdownMenuSearchbar,
16
16
  DropdownMenuSeparator,
17
17
  DropdownMenuStaticItem,
18
18
  DropdownMenuSub,
@@ -56,6 +56,7 @@ import {
56
56
  MagnifyingGlassIcon,
57
57
  PlusIcon,
58
58
  RobotIcon,
59
+ ScrollArea,
59
60
  SearchInput,
60
61
  SuitcaseIcon,
61
62
  UserGroupIcon,
@@ -518,77 +519,84 @@ function AttachFileDemo() {
518
519
  <Button icon={ArrowUpOnSquareIcon} label="Upload File" />
519
520
  </div>
520
521
  <DropdownMenuSeparator />
521
- {searchText ? (
522
- filteredItems.map((item) => {
523
- const randomMainIcon =
524
- mainIcons[Math.floor(Math.random() * mainIcons.length)];
525
- const randomExtraIcon =
526
- extraIcons[Math.floor(Math.random() * extraIcons.length)];
527
- return (
528
- <DropdownMenuItem
529
- key={item}
530
- label={item}
531
- description="Company Space/Notion"
532
- icon={randomMainIcon}
533
- extraIcon={randomExtraIcon}
534
- onClick={() => {
535
- setSelectedItem(item);
536
- setSearchText("");
537
- }}
538
- truncateText
539
- />
540
- );
541
- })
542
- ) : (
543
- <div className="s-flex s-h-full s-w-full s-items-center s-justify-center s-py-8">
544
- <div className="s-flex s-flex-col s-items-center s-justify-center s-gap-0 s-text-center s-text-base s-font-semibold s-text-primary-400">
545
- <Icon visual={MagnifyingGlassIcon} size="sm" />
546
- Search in Dust
522
+ <ScrollArea className="s-h-[380px]">
523
+ {searchText ? (
524
+ filteredItems.map((item) => {
525
+ const randomMainIcon =
526
+ mainIcons[Math.floor(Math.random() * mainIcons.length)];
527
+ const randomExtraIcon =
528
+ extraIcons[Math.floor(Math.random() * extraIcons.length)];
529
+ return (
530
+ <DropdownMenuItem
531
+ key={item}
532
+ label={item}
533
+ description="Company Space/Notion"
534
+ icon={
535
+ <SimpleDoubleIcon
536
+ size="lg"
537
+ mainIcon={randomMainIcon}
538
+ secondaryIcon={randomExtraIcon}
539
+ />
540
+ }
541
+ onClick={() => {
542
+ setSelectedItem(item);
543
+ setSearchText("");
544
+ }}
545
+ truncateText
546
+ />
547
+ );
548
+ })
549
+ ) : (
550
+ <div className="s-flex s-h-full s-w-full s-items-center s-justify-center s-py-8">
551
+ <div className="s-flex s-flex-col s-items-center s-justify-center s-gap-0 s-text-center s-text-base s-font-semibold s-text-primary-400">
552
+ <Icon visual={MagnifyingGlassIcon} size="sm" />
553
+ Search in Dust
554
+ </div>
547
555
  </div>
548
- </div>
549
- )}
556
+ )}
557
+ </ScrollArea>
550
558
  </DropdownMenuContent>
551
559
  </DropdownMenu>
552
560
  <DropdownMenu open={openAgents} onOpenChange={setOpenAgents}>
553
561
  <DropdownMenuTrigger asChild>
554
562
  <Button icon={RobotIcon} variant="outline" size="sm" isSelect />
555
563
  </DropdownMenuTrigger>
556
- <DropdownMenuContent
557
- className="s-h-96 s-w-[380px]"
558
- dropdownHeaders={
559
- <DropdownMenuSearchbar
564
+ <DropdownMenuContent className="s-w-[380px]">
565
+ <div className="s-flex s-gap-1.5 s-p-1.5">
566
+ <SearchInput
560
567
  ref={agentsSearchInputRef}
561
568
  name="search"
562
569
  onChange={() => {}}
563
570
  onKeyDown={() => {}}
564
571
  placeholder="Search Agents"
565
572
  value=""
566
- button={<Button icon={PlusIcon} label="Create" />}
567
573
  />
568
- }
569
- >
574
+ <Button icon={PlusIcon} label="Create" />
575
+ </div>
570
576
  <DropdownMenuSeparator />
571
- {filteredAgents.map((agent) => {
572
- return (
573
- <DropdownMenuItem
574
- key={agent.name}
575
- label={agent.name}
576
- description={agent.description}
577
- icon={() => (
578
- <Avatar
579
- size="sm"
580
- emoji={agent.emoji}
581
- backgroundColor={agent.backgroundColor}
582
- />
583
- )}
584
- onClick={() => {
585
- setSelectedItem(agent.name);
586
- setSearchText("");
587
- }}
588
- truncateText
589
- />
590
- );
591
- })}
577
+ <ScrollArea className="s-h-[380px]">
578
+ {filteredAgents.map((agent) => {
579
+ return (
580
+ <DropdownMenuItem
581
+ key={agent.name}
582
+ label={agent.name}
583
+ description={agent.description}
584
+ icon={() => (
585
+ <Avatar
586
+ size="sm"
587
+ emoji={agent.emoji}
588
+ backgroundColor={agent.backgroundColor}
589
+ />
590
+ )}
591
+ onClick={() => {
592
+ setSelectedItem(agent.name);
593
+ setSearchText("");
594
+ }}
595
+ truncateText
596
+ />
597
+ );
598
+ })}
599
+ </ScrollArea>
592
600
  </DropdownMenuContent>
593
601
  </DropdownMenu>
594
602
  <DropdownMenu open={openToolsets} onOpenChange={setOpenToolsets}>
@@ -600,36 +608,36 @@ function AttachFileDemo() {
600
608
  size="sm"
601
609
  />
602
610
  </DropdownMenuTrigger>
603
- <DropdownMenuContent
604
- className="s-h-96 s-w-[380px]"
605
- dropdownHeaders={
606
- <DropdownMenuSearchbar
611
+ <DropdownMenuContent className="s-w-[380px]">
612
+ <div className="s-flex s-gap-1.5 s-p-1.5">
613
+ <SearchInput
607
614
  ref={toolsetsSearchInputRef}
608
615
  name="search"
609
616
  onChange={() => {}}
610
617
  onKeyDown={() => {}}
611
618
  placeholder="Search Tools"
612
619
  value=""
613
- button={<Button icon={PlusIcon} label="Add MCP Server" />}
614
620
  />
615
- }
616
- >
621
+ <Button icon={PlusIcon} label="Add MCP Server" />
622
+ </div>
617
623
  <DropdownMenuSeparator />
618
- {filteredToolsetList.map((toolset) => {
619
- return (
620
- <DropdownMenuItem
621
- key={toolset.name}
622
- label={toolset.name}
623
- description={toolset.description}
624
- icon={() => <Avatar size="sm" icon={toolset.icon} />}
625
- onClick={() => {
626
- setSelectedItem(toolset.name);
627
- setSearchText("");
628
- }}
629
- truncateText
630
- />
631
- );
632
- })}
624
+ <ScrollArea className="s-h-[380px]">
625
+ {filteredToolsetList.map((toolset) => {
626
+ return (
627
+ <DropdownMenuItem
628
+ key={toolset.name}
629
+ label={toolset.name}
630
+ description={toolset.description}
631
+ icon={() => <Avatar size="sm" icon={toolset.icon} />}
632
+ onClick={() => {
633
+ setSelectedItem(toolset.name);
634
+ setSearchText("");
635
+ }}
636
+ truncateText
637
+ />
638
+ );
639
+ })}
640
+ </ScrollArea>
633
641
  </DropdownMenuContent>
634
642
  </DropdownMenu>
635
643
  </div>