@dust-tt/sparkle 0.2.617-rc-1 → 0.2.618-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.
Files changed (104) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/esm/components/Avatar.d.ts +1 -1
  3. package/dist/esm/components/Avatar.js +4 -4
  4. package/dist/esm/components/Avatar.js.map +1 -1
  5. package/dist/esm/components/Button.d.ts +3 -0
  6. package/dist/esm/components/Button.d.ts.map +1 -1
  7. package/dist/esm/components/Button.js +21 -10
  8. package/dist/esm/components/Button.js.map +1 -1
  9. package/dist/esm/components/Card.d.ts +1 -0
  10. package/dist/esm/components/Card.d.ts.map +1 -1
  11. package/dist/esm/components/ConversationMessage.d.ts +4 -0
  12. package/dist/esm/components/ConversationMessage.d.ts.map +1 -1
  13. package/dist/esm/components/ConversationMessage.js +9 -4
  14. package/dist/esm/components/ConversationMessage.js.map +1 -1
  15. package/dist/esm/components/DataTable.d.ts.map +1 -1
  16. package/dist/esm/components/DataTable.js +12 -50
  17. package/dist/esm/components/DataTable.js.map +1 -1
  18. package/dist/esm/components/Input.d.ts.map +1 -1
  19. package/dist/esm/components/Input.js +6 -2
  20. package/dist/esm/components/Input.js.map +1 -1
  21. package/dist/esm/components/NavigationList.d.ts +4 -1
  22. package/dist/esm/components/NavigationList.d.ts.map +1 -1
  23. package/dist/esm/components/NavigationList.js +2 -2
  24. package/dist/esm/components/NavigationList.js.map +1 -1
  25. package/dist/esm/components/ScrollArea.d.ts +1 -0
  26. package/dist/esm/components/ScrollArea.d.ts.map +1 -1
  27. package/dist/esm/components/ScrollArea.js +7 -4
  28. package/dist/esm/components/ScrollArea.js.map +1 -1
  29. package/dist/esm/components/Spinner.d.ts +1 -1
  30. package/dist/esm/components/Spinner.js +7 -7
  31. package/dist/esm/components/Spinner.js.map +1 -1
  32. package/dist/esm/components/TextArea.d.ts.map +1 -1
  33. package/dist/esm/components/TextArea.js +7 -3
  34. package/dist/esm/components/TextArea.js.map +1 -1
  35. package/dist/esm/components/ToolCard.d.ts +6 -0
  36. package/dist/esm/components/ToolCard.d.ts.map +1 -1
  37. package/dist/esm/components/ToolCard.js +18 -11
  38. package/dist/esm/components/ToolCard.js.map +1 -1
  39. package/dist/esm/components/Tooltip.d.ts.map +1 -1
  40. package/dist/esm/components/Tooltip.js.map +1 -1
  41. package/dist/esm/components/WindowUtility.d.ts +3 -2
  42. package/dist/esm/components/WindowUtility.d.ts.map +1 -1
  43. package/dist/esm/components/WindowUtility.js +25 -17
  44. package/dist/esm/components/WindowUtility.js.map +1 -1
  45. package/dist/esm/components/markdown/CodeBlock.d.ts +2 -1
  46. package/dist/esm/components/markdown/CodeBlock.d.ts.map +1 -1
  47. package/dist/esm/components/markdown/CodeBlock.js +9 -2
  48. package/dist/esm/components/markdown/CodeBlock.js.map +1 -1
  49. package/dist/esm/icons/app/Mic.d.ts +5 -0
  50. package/dist/esm/icons/app/Mic.d.ts.map +1 -0
  51. package/dist/esm/icons/app/Mic.js +6 -0
  52. package/dist/esm/icons/app/Mic.js.map +1 -0
  53. package/dist/esm/icons/app/index.d.ts +1 -0
  54. package/dist/esm/icons/app/index.d.ts.map +1 -1
  55. package/dist/esm/icons/app/index.js +1 -0
  56. package/dist/esm/icons/app/index.js.map +1 -1
  57. package/dist/esm/icons/src/app/mic.svg +3 -0
  58. package/dist/esm/index.d.ts +1 -0
  59. package/dist/esm/index.d.ts.map +1 -1
  60. package/dist/esm/index.js +1 -0
  61. package/dist/esm/index.js.map +1 -1
  62. package/dist/esm/stories/Avatar.stories.js +4 -4
  63. package/dist/esm/stories/CodeBlock.stories.d.ts +17 -0
  64. package/dist/esm/stories/CodeBlock.stories.d.ts.map +1 -0
  65. package/dist/esm/stories/CodeBlock.stories.js +125 -0
  66. package/dist/esm/stories/CodeBlock.stories.js.map +1 -0
  67. package/dist/esm/stories/ConversationMessage.stories.d.ts.map +1 -1
  68. package/dist/esm/stories/ConversationMessage.stories.js +3 -3
  69. package/dist/esm/stories/ConversationMessage.stories.js.map +1 -1
  70. package/dist/esm/stories/MultiPageDialog.stories.js +1 -1
  71. package/dist/esm/stories/MultiPageDialog.stories.js.map +1 -1
  72. package/dist/esm/stories/Playground.stories.d.ts.map +1 -1
  73. package/dist/esm/stories/Playground.stories.js +166 -29
  74. package/dist/esm/stories/Playground.stories.js.map +1 -1
  75. package/dist/esm/stories/Spinner.stories.js +3 -3
  76. package/dist/esm/stories/ToolCard.stories.d.ts.map +1 -1
  77. package/dist/esm/stories/ToolCard.stories.js +14 -6
  78. package/dist/esm/stories/ToolCard.stories.js.map +1 -1
  79. package/dist/sparkle.css +100 -0
  80. package/package.json +1 -1
  81. package/src/components/Avatar.tsx +4 -4
  82. package/src/components/Button.tsx +35 -7
  83. package/src/components/ConversationMessage.tsx +26 -7
  84. package/src/components/DataTable.tsx +7 -46
  85. package/src/components/Input.tsx +5 -2
  86. package/src/components/NavigationList.tsx +7 -2
  87. package/src/components/ScrollArea.tsx +9 -3
  88. package/src/components/Spinner.tsx +7 -7
  89. package/src/components/TextArea.tsx +7 -4
  90. package/src/components/ToolCard.tsx +60 -35
  91. package/src/components/Tooltip.tsx +49 -38
  92. package/src/components/WindowUtility.tsx +11 -18
  93. package/src/components/markdown/CodeBlock.tsx +10 -0
  94. package/src/icons/app/Mic.tsx +18 -0
  95. package/src/icons/app/index.ts +1 -0
  96. package/src/icons/src/app/mic.svg +3 -0
  97. package/src/index.ts +1 -0
  98. package/src/stories/Avatar.stories.tsx +4 -4
  99. package/src/stories/CodeBlock.stories.tsx +361 -0
  100. package/src/stories/ConversationMessage.stories.tsx +6 -0
  101. package/src/stories/MultiPageDialog.stories.tsx +1 -1
  102. package/src/stories/Playground.stories.tsx +311 -56
  103. package/src/stories/Spinner.stories.tsx +3 -3
  104. package/src/stories/ToolCard.stories.tsx +49 -35
@@ -35,8 +35,10 @@ interface ConversationMessageProps
35
35
  citations?: React.ReactElement[];
36
36
  isDisabled?: boolean;
37
37
  name?: string;
38
+ timestamp?: string;
38
39
  pictureUrl?: string | React.ReactNode | null;
39
40
  renderName?: (name: string | null) => React.ReactNode;
41
+ infoChip?: React.ReactNode;
40
42
  }
41
43
 
42
44
  const messageVariants = cva("s-flex s-w-full s-flex-col s-rounded-2xl", {
@@ -81,8 +83,10 @@ export const ConversationMessage = React.forwardRef<
81
83
  citations,
82
84
  isDisabled = false,
83
85
  name,
86
+ timestamp,
84
87
  pictureUrl,
85
88
  renderName = (name) => <span>{name}</span>,
89
+ infoChip,
86
90
  type,
87
91
  className,
88
92
  ...props
@@ -95,9 +99,11 @@ export const ConversationMessage = React.forwardRef<
95
99
  <ConversationMessageHeader
96
100
  avatarUrl={pictureUrl}
97
101
  name={name}
102
+ timestamp={timestamp}
98
103
  isBusy={avatarBusy}
99
104
  isDisabled={isDisabled}
100
105
  renderName={renderName}
106
+ infoChip={infoChip}
101
107
  />
102
108
 
103
109
  <ConversationMessageContent citations={citations}>
@@ -158,6 +164,8 @@ interface ConversationMessageHeaderProps
158
164
  isBusy?: boolean;
159
165
  isDisabled?: boolean;
160
166
  name?: string;
167
+ timestamp?: string;
168
+ infoChip?: React.ReactNode;
161
169
  renderName: (name: string | null) => React.ReactNode;
162
170
  }
163
171
 
@@ -171,6 +179,8 @@ export const ConversationMessageHeader = React.forwardRef<
171
179
  isBusy,
172
180
  isDisabled,
173
181
  name = "",
182
+ timestamp,
183
+ infoChip,
174
184
  renderName,
175
185
  className,
176
186
  ...props
@@ -202,13 +212,22 @@ export const ConversationMessageHeader = React.forwardRef<
202
212
  disabled={isDisabled}
203
213
  size="sm"
204
214
  />
205
- <div
206
- className={cn(
207
- "s-text-sm s-font-semibold @sm:s-text-base",
208
- "s-text-foreground dark:s-text-foreground-night"
209
- )}
210
- >
211
- {renderName(name)}
215
+ <div className="s-flex s-w-full s-flex-row s-justify-between s-gap-0.5">
216
+ <div
217
+ className={cn(
218
+ "s-text-sm s-font-semibold @sm:s-text-base",
219
+ "s-text-foreground dark:s-text-foreground-night",
220
+ "s-flex s-flex-row s-items-center s-gap-2"
221
+ )}
222
+ >
223
+ {renderName(name)}
224
+ {infoChip}
225
+ </div>
226
+ <div>
227
+ <span className="s-text-xs s-font-normal s-text-muted-foreground dark:s-text-muted-foreground-night">
228
+ {timestamp}
229
+ </span>
230
+ </div>
212
231
  </div>
213
232
  </div>
214
233
  );
@@ -15,13 +15,7 @@ import {
15
15
  useReactTable,
16
16
  } from "@tanstack/react-table";
17
17
  import { useVirtualizer } from "@tanstack/react-virtual";
18
- import React, {
19
- ReactNode,
20
- useEffect,
21
- useLayoutEffect,
22
- useRef,
23
- useState,
24
- } from "react";
18
+ import React, { ReactNode, useEffect, useRef, useState } from "react";
25
19
 
26
20
  import {
27
21
  Avatar,
@@ -345,24 +339,6 @@ export interface ScrollableDataTableProps<TData extends TBaseData>
345
339
  const COLUMN_HEIGHT = 48;
346
340
  const MIN_COLUMN_WIDTH = 40;
347
341
 
348
- // Helper function to compare column sizing objects
349
- function shallowEqualSizing(
350
- a: Record<string, number>,
351
- b: Record<string, number>
352
- ) {
353
- const aKeys = Object.keys(a);
354
- const bKeys = Object.keys(b);
355
- if (aKeys.length !== bKeys.length) {
356
- return false;
357
- }
358
- for (const k of aKeys) {
359
- if (a[k] !== b[k]) {
360
- return false;
361
- }
362
- }
363
- return true;
364
- }
365
-
366
342
  export function ScrollableDataTable<TData extends TBaseData>({
367
343
  data,
368
344
  totalRowCount,
@@ -384,7 +360,7 @@ export function ScrollableDataTable<TData extends TBaseData>({
384
360
  const loadMoreRef = useRef<HTMLDivElement>(null);
385
361
  const [tableWidth, setTableWidth] = useState(0);
386
362
 
387
- // Monitor table width changes with guard against tiny changes
363
+ // Monitor table width changes
388
364
  useEffect(() => {
389
365
  if (!tableContainerRef.current) {
390
366
  return;
@@ -392,8 +368,7 @@ export function ScrollableDataTable<TData extends TBaseData>({
392
368
 
393
369
  const resizeObserver = new ResizeObserver((entries) => {
394
370
  for (const entry of entries) {
395
- const w = Math.round(entry.contentRect.width);
396
- setTableWidth((prev) => (prev !== w ? w : prev)); // update only on real change
371
+ setTableWidth(entry.contentRect.width);
397
372
  }
398
373
  });
399
374
 
@@ -430,20 +405,12 @@ export function ScrollableDataTable<TData extends TBaseData>({
430
405
  getRowId,
431
406
  });
432
407
 
433
- useLayoutEffect(() => {
408
+ useEffect(() => {
434
409
  if (!tableContainerRef.current || !table || !tableWidth) {
435
410
  return;
436
411
  }
437
412
  const columns = table.getAllColumns();
438
413
 
439
- // Skip sizing if no columns have ratios defined
440
- const hasRatios = columns.some(
441
- (c) => (c.columnDef.meta?.sizeRatio ?? 0) > 0
442
- );
443
- if (!hasRatios) {
444
- return;
445
- }
446
-
447
414
  // Calculate ideal widths and handle minimums
448
415
  const idealSizing = columns.reduce(
449
416
  (acc, column) => {
@@ -452,8 +419,7 @@ export function ScrollableDataTable<TData extends TBaseData>({
452
419
  Math.floor((ratio / 100) * tableWidth),
453
420
  MIN_COLUMN_WIDTH
454
421
  );
455
- acc[column.id] = calculated;
456
- return acc;
422
+ return { ...acc, [column.id]: calculated };
457
423
  },
458
424
  {} as Record<string, number>
459
425
  );
@@ -473,13 +439,8 @@ export function ScrollableDataTable<TData extends TBaseData>({
473
439
 
474
440
  idealSizing[adjustColumnId] += widthDifference;
475
441
  }
476
-
477
- // Only set when sizing actually changes
478
- const currentSizing = table.getState().columnSizing;
479
- if (!shallowEqualSizing(idealSizing, currentSizing)) {
480
- table.setColumnSizing(idealSizing);
481
- }
482
- }, [tableWidth]); // intentionally remove `table` from deps
442
+ table.setColumnSizing(idealSizing);
443
+ }, [table, tableWidth]);
483
444
 
484
445
  // Get the current column sizing from the table for rendering
485
446
  const columnSizing = table.getState().columnSizing;
@@ -42,7 +42,9 @@ const stateVariantStyles: Record<InputStateType, string> = {
42
42
  disabled: cn(
43
43
  "disabled:s-cursor-not-allowed",
44
44
  "disabled:s-text-muted-foreground dark:disabled:s-text-muted-foreground-night",
45
- "disabled:s-border-border dark:disabled:s-border-border-night"
45
+ "disabled:s-border-border dark:disabled:s-border-border-night",
46
+ // Use muted background only when disabled
47
+ "s-bg-muted-background dark:s-bg-muted-background-night"
46
48
  ),
47
49
  error: cn(
48
50
  "s-border-border-warning/40 dark:s-border-border-warning-night/60",
@@ -66,7 +68,8 @@ const inputStyleClasses = cva(
66
68
  cn(
67
69
  "dark:s-text-primary-50",
68
70
  "s-text-sm s-rounded-xl s-flex s-h-9 s-w-full s-px-3 s-py-1.5 ",
69
- "s-bg-muted-background dark:s-bg-muted-background-night",
71
+ // Default to plain background; disabled state will override to muted
72
+ "s-bg-background dark:s-bg-background-night",
70
73
  "s-border focus-visible:s-ring",
71
74
  "file:s-border-0 file:s-bg-transparent file:s-text-sm file:s-font-medium file:s-text-foreground",
72
75
  "placeholder:s-text-muted-foreground placeholder:s-italic dark:placeholder:s-text-muted-foreground-night"
@@ -41,13 +41,18 @@ const NavigationListItemStyles = cva(
41
41
  }
42
42
  );
43
43
 
44
+ interface NavigationListProps {
45
+ viewportRef?: React.RefObject<HTMLDivElement>;
46
+ }
44
47
  const NavigationList = React.forwardRef<
45
48
  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
46
- React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
47
- >(({ className, children, ...props }, ref) => {
49
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> &
50
+ NavigationListProps
51
+ >(({ className, children, viewportRef, ...props }, ref) => {
48
52
  return (
49
53
  <ScrollArea
50
54
  ref={ref}
55
+ viewportRef={viewportRef}
51
56
  className={cn(className, "s-transition-all s-duration-300")}
52
57
  {...props}
53
58
  >
@@ -14,6 +14,7 @@ interface ScrollAreaProps
14
14
  active?: string;
15
15
  inactive?: string;
16
16
  };
17
+ viewportRef?: React.RefObject<HTMLDivElement>;
17
18
  }
18
19
 
19
20
  const ScrollArea = React.forwardRef<
@@ -29,11 +30,12 @@ const ScrollArea = React.forwardRef<
29
30
  scrollBarClassName,
30
31
  viewportClassName,
31
32
  scrollStyles,
33
+ viewportRef,
32
34
  ...props
33
35
  },
34
36
  ref
35
37
  ) => {
36
- const viewportRef = React.useRef<HTMLDivElement>(null);
38
+ const localViewportRef = React.useRef<HTMLDivElement>(null);
37
39
  const [isScrolled, setIsScrolled] = React.useState(false);
38
40
 
39
41
  const hasCustomScrollBar = useMemo(
@@ -50,9 +52,13 @@ const ScrollArea = React.forwardRef<
50
52
  const shouldHideDefaultScrollBar = hideScrollBar || hasCustomScrollBar;
51
53
 
52
54
  const handleScroll = React.useCallback(() => {
53
- if (viewportRef.current) {
55
+ if (viewportRef && viewportRef.current) {
54
56
  setIsScrolled(viewportRef.current.scrollTop > 0);
55
57
  }
58
+
59
+ if (localViewportRef.current) {
60
+ setIsScrolled(localViewportRef.current.scrollTop > 0);
61
+ }
56
62
  }, []);
57
63
 
58
64
  return (
@@ -66,7 +72,7 @@ const ScrollArea = React.forwardRef<
66
72
  {...props}
67
73
  >
68
74
  <ScrollAreaPrimitive.Viewport
69
- ref={viewportRef}
75
+ ref={viewportRef || localViewportRef}
70
76
  onScroll={handleScroll}
71
77
  className={cn(
72
78
  "s-h-full s-w-full s-rounded-[inherit]",
@@ -13,7 +13,7 @@ import animLightLG from "@sparkle/lottie/spinnerLightLG";
13
13
  import animLightXS from "@sparkle/lottie/spinnerLightXS";
14
14
 
15
15
  type SpinnerSizeType = (typeof SPINNER_SIZES)[number];
16
- const SPINNER_SIZES = ["xs", "sm", "md", "lg", "xl", "xxl"] as const;
16
+ const SPINNER_SIZES = ["xs", "sm", "md", "lg", "xl", "2xl"] as const;
17
17
 
18
18
  type SpinnerVariant =
19
19
  | "mono"
@@ -43,7 +43,7 @@ const pxSizeClasses: Record<SpinnerSizeType, string> = {
43
43
  md: "24",
44
44
  lg: "32",
45
45
  xl: "128",
46
- xxl: "192",
46
+ "2xl": "192",
47
47
  };
48
48
 
49
49
  type LottieColorType = [number, number, number, number];
@@ -111,7 +111,7 @@ const Spinner: React.FC<SpinnerProps> = ({ size = "md", variant = "mono" }) => {
111
111
  anim = animLightXS;
112
112
  break;
113
113
  case "xl":
114
- case "xxl":
114
+ case "2xl":
115
115
  anim = animLightLG;
116
116
  break;
117
117
  default:
@@ -139,7 +139,7 @@ const Spinner: React.FC<SpinnerProps> = ({ size = "md", variant = "mono" }) => {
139
139
  anim = animColorXS;
140
140
  break;
141
141
  case "xl":
142
- case "xxl":
142
+ case "2xl":
143
143
  anim = animColorLG;
144
144
  break;
145
145
  default:
@@ -162,7 +162,7 @@ const Spinner: React.FC<SpinnerProps> = ({ size = "md", variant = "mono" }) => {
162
162
  anim = animLightXS;
163
163
  break;
164
164
  case "xl":
165
- case "xxl":
165
+ case "2xl":
166
166
  anim = animLightLG;
167
167
  break;
168
168
  default:
@@ -185,7 +185,7 @@ const Spinner: React.FC<SpinnerProps> = ({ size = "md", variant = "mono" }) => {
185
185
  anim = animDarkXS;
186
186
  break;
187
187
  case "xl":
188
- case "xxl":
188
+ case "2xl":
189
189
  anim = animDarkLG;
190
190
  break;
191
191
  default:
@@ -210,7 +210,7 @@ const Spinner: React.FC<SpinnerProps> = ({ size = "md", variant = "mono" }) => {
210
210
  darkAnim = animDarkXS;
211
211
  break;
212
212
  case "xl":
213
- case "xxl":
213
+ case "2xl":
214
214
  lightAnim = animLightLG;
215
215
  darkAnim = animDarkLG;
216
216
  break;
@@ -20,7 +20,8 @@ const textAreaVariants = cva(
20
20
  cn(
21
21
  "s-flex s-w-full s-px-3 s-py-2 s-text-sm",
22
22
  "s-text-foreground dark:s-text-foreground-night",
23
- "s-bg-muted-background dark:s-bg-muted-background-night",
23
+ // Default to plain background; disabled variant will set muted background
24
+ "s-bg-background dark:s-bg-background-night",
24
25
  "s-ring-offset-background",
25
26
  "s-border s-rounded-xl s-transition s-duration-100 focus-visible:s-outline-none",
26
27
  "focus-visible:s-border-border dark:focus-visible:s-border-border-night focus-visible:s-ring"
@@ -52,7 +53,9 @@ const textAreaVariants = cva(
52
53
  disabled: {
53
54
  true: cn(
54
55
  "disabled:s-cursor-not-allowed",
55
- "disabled:s-text-muted-foreground dark:disabled:s-text-muted-foreground-night"
56
+ "disabled:s-text-muted-foreground dark:disabled:s-text-muted-foreground-night",
57
+ // Use muted background only when disabled
58
+ "s-bg-muted-background dark:s-bg-muted-background-night"
56
59
  ),
57
60
  false: "",
58
61
  },
@@ -117,8 +120,8 @@ const ReadOnlyTextArea = ({ content }: { content: string | null }) => {
117
120
  isDisplay
118
121
  className={cn(
119
122
  "s-copy-sm s-h-full s-min-h-60 s-w-full s-min-w-0 s-rounded-xl",
120
- "s-resize-none s-border-border s-bg-muted-background",
121
- "dark:s-border-border-night dark:s-bg-muted-background-night"
123
+ "s-resize-none s-border-border",
124
+ "dark:s-border-border-night"
122
125
  )}
123
126
  defaultValue={content ?? ""}
124
127
  />
@@ -15,10 +15,13 @@ export interface ToolCardProps {
15
15
  isSelected: boolean;
16
16
  canAdd: boolean;
17
17
  cantAddReason?: string;
18
+ toolInfo?: { label: string; onClick: () => void };
18
19
  onClick?: () => void;
19
20
  className?: string;
21
+ cardContainerClassName?: string;
20
22
  mountPortal?: boolean;
21
23
  mountPortalContainer?: HTMLElement;
24
+ descriptionLineClamp?: number;
22
25
  }
23
26
 
24
27
  export const ToolCard = React.forwardRef<HTMLDivElement, ToolCardProps>(
@@ -30,10 +33,13 @@ export const ToolCard = React.forwardRef<HTMLDivElement, ToolCardProps>(
30
33
  isSelected,
31
34
  canAdd,
32
35
  cantAddReason,
36
+ toolInfo,
33
37
  onClick,
34
38
  className,
39
+ cardContainerClassName,
35
40
  mountPortal,
36
41
  mountPortalContainer,
42
+ descriptionLineClamp = 2,
37
43
  },
38
44
  ref
39
45
  ) => {
@@ -43,44 +49,63 @@ export const ToolCard = React.forwardRef<HTMLDivElement, ToolCardProps>(
43
49
  variant={isSelected ? "secondary" : "primary"}
44
50
  onClick={onClick}
45
51
  disabled={!canAdd}
46
- className={cn("s-h-24 s-p-3", className)}
52
+ containerClassName={cardContainerClassName}
53
+ className={cn("s-p-3", className)}
47
54
  >
48
- <div className="s-flex s-w-full s-flex-col">
49
- <div className="s-mb-2 s-flex s-items-start s-justify-between s-gap-2">
50
- <div className="s-flex s-items-center s-gap-2">
51
- <Avatar icon={icon} size="sm" />
52
- <span className="s-text-sm s-font-medium">{label}</span>
53
- {isSelected && (
54
- <Chip
55
- size="xs"
56
- color="green"
57
- label="ADDED"
58
- className={cn(FADE_TRANSITION_CLASSES, "s-opacity-100")}
59
- />
60
- )}
61
- </div>
62
- {canAdd && (
63
- <Button
64
- size="xs"
65
- variant="outline"
66
- icon={PlusIcon}
67
- label="Add"
68
- className={cn(FADE_TRANSITION_CLASSES, "s-flex-shrink-0")}
69
- />
70
- )}
71
- {cantAddReason && (
72
- <div className="s-flex-shrink-0 s-text-xs s-italic s-text-muted-foreground dark:s-text-muted-foreground-night">
73
- {cantAddReason}
55
+ <div className="s-flex s-h-full s-w-full s-flex-col s-justify-between">
56
+ <div className="s-flex s-flex-col">
57
+ <div className="s-mb-2 s-flex s-items-center s-justify-between s-gap-2">
58
+ <div className="s-flex s-items-center s-gap-2">
59
+ <Avatar icon={icon} size="sm" />
60
+ <span className="s-text-sm s-font-medium">{label}</span>
61
+ {isSelected && (
62
+ <Chip
63
+ size="xs"
64
+ color="green"
65
+ label="ADDED"
66
+ className={cn(FADE_TRANSITION_CLASSES, "s-opacity-100")}
67
+ />
68
+ )}
69
+ </div>
70
+ <div className="s-flex s-flex-shrink-0 s-items-center s-gap-2">
71
+ {canAdd && (
72
+ <Button
73
+ size="xs"
74
+ variant="outline"
75
+ icon={PlusIcon}
76
+ label="Add"
77
+ className={cn(FADE_TRANSITION_CLASSES, "s-flex-shrink-0")}
78
+ />
79
+ )}
80
+ {cantAddReason && (
81
+ <div className="s-flex-shrink-0 s-text-xs s-italic s-text-muted-foreground dark:s-text-muted-foreground-night">
82
+ {cantAddReason}
83
+ </div>
84
+ )}
74
85
  </div>
75
- )}
86
+ </div>
87
+ <TruncatedText
88
+ className="s-text-sm s-text-muted-foreground dark:s-text-muted-foreground-night"
89
+ mountPortal={mountPortal}
90
+ mountPortalContainer={mountPortalContainer}
91
+ lineClamp={descriptionLineClamp}
92
+ >
93
+ {description}
94
+ </TruncatedText>
76
95
  </div>
77
- <TruncatedText
78
- className="s-text-sm s-text-muted-foreground dark:s-text-muted-foreground-night"
79
- mountPortal={mountPortal}
80
- mountPortalContainer={mountPortalContainer}
81
- >
82
- {description}
83
- </TruncatedText>
96
+ {toolInfo && (
97
+ <div>
98
+ <a
99
+ onClick={(e) => {
100
+ e.stopPropagation();
101
+ toolInfo.onClick();
102
+ }}
103
+ className="hover:s-underline-offset-2 dark:s-text-muted-foreground-night s-cursor-pointer s-text-sm s-font-semibold s-text-muted-foreground hover:s-text-highlight-light hover:s-underline dark:hover:s-text-highlight-light-night"
104
+ >
105
+ {toolInfo.label}
106
+ </a>
107
+ </div>
108
+ )}
84
109
  </div>
85
110
  </Card>
86
111
  );
@@ -20,47 +20,58 @@ interface TooltipContentProps
20
20
  const TooltipContent = React.forwardRef<
21
21
  React.ElementRef<typeof TooltipPrimitive.Content>,
22
22
  TooltipContentProps
23
- >(({ className, sideOffset = 4, mountPortal = true, mountPortalContainer, ...props }, ref) => {
24
- const content = (
25
- <TooltipPrimitive.Content
26
- ref={ref}
27
- sideOffset={sideOffset}
28
- className={classNames(
29
- "s-z-50 s-max-w-sm s-overflow-hidden s-whitespace-pre-wrap s-break-words s-rounded-md s-border",
30
- "s-bg-background dark:s-bg-background-night",
31
- "s-text-foreground dark:s-text-foreground-night",
32
- "s-border-border dark:s-border-border-night",
33
- "s-px-3 s-py-1.5 s-text-sm s-shadow-md",
34
- "s-animate-in s-fade-in-0 s-zoom-in-95",
35
- "data-[state=closed]:s-animate-out data-[state=closed]:s-fade-out-0 data-[state=closed]:s-zoom-out-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",
36
- className || ""
37
- )}
38
- {...props}
39
- />
40
- );
23
+ >(
24
+ (
25
+ {
26
+ className,
27
+ sideOffset = 4,
28
+ mountPortal = true,
29
+ mountPortalContainer,
30
+ ...props
31
+ },
32
+ ref
33
+ ) => {
34
+ const content = (
35
+ <TooltipPrimitive.Content
36
+ ref={ref}
37
+ sideOffset={sideOffset}
38
+ className={classNames(
39
+ "s-z-50 s-max-w-sm s-overflow-hidden s-whitespace-pre-wrap s-break-words s-rounded-md s-border",
40
+ "s-bg-background dark:s-bg-background-night",
41
+ "s-text-foreground dark:s-text-foreground-night",
42
+ "s-border-border dark:s-border-border-night",
43
+ "s-px-3 s-py-1.5 s-text-sm s-shadow-md",
44
+ "s-animate-in s-fade-in-0 s-zoom-in-95",
45
+ "data-[state=closed]:s-animate-out data-[state=closed]:s-fade-out-0 data-[state=closed]:s-zoom-out-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",
46
+ className || ""
47
+ )}
48
+ {...props}
49
+ />
50
+ );
41
51
 
42
- const [container, setContainer] = useState<Element | undefined>(
43
- mountPortalContainer
44
- );
52
+ const [container, setContainer] = useState<Element | undefined>(
53
+ mountPortalContainer
54
+ );
45
55
 
46
- useEffect(() => {
47
- if (mountPortal && !container) {
48
- const dialogElements = document.querySelectorAll(
49
- ".s-sheet[role=dialog][data-state=open]"
50
- );
51
- const defaultContainer = dialogElements[dialogElements.length - 1];
52
- setContainer(defaultContainer);
53
- }
54
- }, [mountPortal, container]);
56
+ useEffect(() => {
57
+ if (mountPortal && !container) {
58
+ const dialogElements = document.querySelectorAll(
59
+ ".s-sheet[role=dialog][data-state=open]"
60
+ );
61
+ const defaultContainer = dialogElements[dialogElements.length - 1];
62
+ setContainer(defaultContainer);
63
+ }
64
+ }, [mountPortal, container]);
55
65
 
56
- return mountPortal ? (
57
- <TooltipPrimitive.Portal container={container}>
58
- {content}
59
- </TooltipPrimitive.Portal>
60
- ) : (
61
- content
62
- );
63
- });
66
+ return mountPortal ? (
67
+ <TooltipPrimitive.Portal container={container}>
68
+ {content}
69
+ </TooltipPrimitive.Portal>
70
+ ) : (
71
+ content
72
+ );
73
+ }
74
+ );
64
75
 
65
76
  interface TooltipProps extends TooltipContentProps {
66
77
  trigger: React.ReactNode;
@@ -2,30 +2,25 @@ import { useEffect, useState } from "react";
2
2
 
3
3
  // Define breakpoints
4
4
  export const breakpoints = {
5
- xs: 0,
5
+ xxs: 0,
6
+ xs: 512,
6
7
  sm: 640,
7
8
  md: 768,
8
9
  lg: 1024,
9
10
  xl: 1280,
10
- xxl: 1536,
11
+ "2xl": 1536,
11
12
  };
12
13
 
13
14
  // Helper function to determine active breakpoint
14
15
  function getActiveBreakpoint(width: number): keyof typeof breakpoints {
15
- if (width >= breakpoints.xxl) {
16
- return "xxl";
17
- }
18
- if (width >= breakpoints.xl) {
19
- return "xl";
20
- }
21
- if (width >= breakpoints.lg) {
22
- return "lg";
23
- }
24
- if (width >= breakpoints.md) {
25
- return "md";
26
- }
27
- if (width >= breakpoints.sm) {
28
- return "sm";
16
+ const breakpointEntries = Object.entries(breakpoints) as [keyof typeof breakpoints, number][];
17
+ // Sort breakpoints from largest to smallest
18
+ const sortedBreakpoints = breakpointEntries.sort(([, a], [, b]) => b - a);
19
+
20
+ for (const [breakpoint, minWidth] of sortedBreakpoints) {
21
+ if (width >= minWidth) {
22
+ return breakpoint;
23
+ }
29
24
  }
30
25
  return "xs";
31
26
  }
@@ -62,5 +57,3 @@ export function useWindowSize() {
62
57
 
63
58
  return windowSize;
64
59
  }
65
-
66
- export default useWindowSize;