@dust-tt/sparkle 0.7.6-rc-3 → 0.7.6

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 (79) hide show
  1. package/dist/cjs/index.js +14 -14
  2. package/dist/cjs/index.js.map +4 -4
  3. package/dist/esm/components/AssistantCard.js +1 -1
  4. package/dist/esm/components/AssistantCard.js.map +1 -1
  5. package/dist/esm/components/Button.d.ts +10 -25
  6. package/dist/esm/components/Button.d.ts.map +1 -1
  7. package/dist/esm/components/Button.js +7 -21
  8. package/dist/esm/components/Button.js.map +1 -1
  9. package/dist/esm/components/ButtonGroup.d.ts +2 -2
  10. package/dist/esm/components/ButtonGroup.d.ts.map +1 -1
  11. package/dist/esm/components/ButtonGroup.js +2 -4
  12. package/dist/esm/components/ButtonGroup.js.map +1 -1
  13. package/dist/esm/components/Card.d.ts +13 -6
  14. package/dist/esm/components/Card.d.ts.map +1 -1
  15. package/dist/esm/components/Card.js +2 -2
  16. package/dist/esm/components/Card.js.map +1 -1
  17. package/dist/esm/components/InteractiveImageGrid.d.ts +3 -3
  18. package/dist/esm/components/InteractiveImageGrid.js +4 -4
  19. package/dist/esm/components/InteractiveImageGrid.js.map +1 -1
  20. package/dist/esm/components/NavigationList.js +3 -3
  21. package/dist/esm/components/Tabs.d.ts +1 -14
  22. package/dist/esm/components/Tabs.d.ts.map +1 -1
  23. package/dist/esm/components/VoicePicker.d.ts +17 -0
  24. package/dist/esm/components/VoicePicker.d.ts.map +1 -0
  25. package/dist/esm/components/VoicePicker.js +137 -0
  26. package/dist/esm/components/VoicePicker.js.map +1 -0
  27. package/dist/esm/components/index.d.ts +2 -0
  28. package/dist/esm/components/index.d.ts.map +1 -1
  29. package/dist/esm/components/index.js +1 -0
  30. package/dist/esm/components/index.js.map +1 -1
  31. package/dist/esm/logo/platforms/Ukg.d.ts +5 -0
  32. package/dist/esm/logo/platforms/Ukg.d.ts.map +1 -0
  33. package/dist/esm/logo/platforms/Ukg.js +6 -0
  34. package/dist/esm/logo/platforms/Ukg.js.map +1 -0
  35. package/dist/esm/logo/platforms/index.d.ts +1 -0
  36. package/dist/esm/logo/platforms/index.d.ts.map +1 -1
  37. package/dist/esm/logo/platforms/index.js +1 -0
  38. package/dist/esm/logo/platforms/index.js.map +1 -1
  39. package/dist/esm/logo/src/platforms/UKG.svg +5 -0
  40. package/dist/esm/lottie/collapseBar.d.ts +21 -21
  41. package/dist/esm/lottie/dragArea.d.ts +30 -30
  42. package/dist/esm/lottie/spinnerColor.d.ts +73 -73
  43. package/dist/esm/lottie/spinnerColorLG.d.ts +125 -125
  44. package/dist/esm/lottie/spinnerColorXS.d.ts +11 -11
  45. package/dist/esm/lottie/spinnerDark.d.ts +73 -73
  46. package/dist/esm/lottie/spinnerDarkLG.d.ts +142 -142
  47. package/dist/esm/lottie/spinnerDarkXS.d.ts +38 -38
  48. package/dist/esm/lottie/spinnerLight.d.ts +66 -66
  49. package/dist/esm/lottie/spinnerLightLG.d.ts +142 -142
  50. package/dist/esm/lottie/spinnerLightXS.d.ts +38 -38
  51. package/dist/esm/stories/Button.stories.d.ts +12 -30
  52. package/dist/esm/stories/Button.stories.d.ts.map +1 -1
  53. package/dist/esm/stories/Button.stories.js +7 -15
  54. package/dist/esm/stories/Button.stories.js.map +1 -1
  55. package/dist/esm/stories/ButtonGroup.stories.d.ts +1 -1
  56. package/dist/esm/stories/ButtonGroup.stories.js +1 -1
  57. package/dist/esm/stories/ButtonGroup.stories.js.map +1 -1
  58. package/dist/esm/stories/Card.stories.js +2 -2
  59. package/dist/esm/stories/Card.stories.js.map +1 -1
  60. package/dist/esm/stories/InteractiveImageGrid.stories.d.ts.map +1 -1
  61. package/dist/esm/stories/InteractiveImageGrid.stories.js.map +1 -1
  62. package/dist/esm/stories/VoicePicker.stories.d.ts +49 -0
  63. package/dist/esm/stories/VoicePicker.stories.d.ts.map +1 -0
  64. package/dist/esm/stories/VoicePicker.stories.js +111 -0
  65. package/dist/esm/stories/VoicePicker.stories.js.map +1 -0
  66. package/dist/esm/styles/allotment.css +36 -14
  67. package/package.json +1 -1
  68. package/src/components/AssistantCard.tsx +1 -1
  69. package/src/components/Button.tsx +15 -30
  70. package/src/components/ButtonGroup.tsx +6 -9
  71. package/src/components/Card.tsx +2 -7
  72. package/src/components/InteractiveImageGrid.tsx +4 -4
  73. package/src/components/NavigationList.tsx +3 -3
  74. package/src/components/VoicePicker.tsx +267 -0
  75. package/src/components/index.ts +2 -0
  76. package/src/logo/platforms/Ukg.tsx +22 -0
  77. package/src/logo/platforms/index.ts +1 -0
  78. package/src/logo/src/platforms/UKG.svg +5 -0
  79. package/src/styles/allotment.css +36 -14
@@ -17,28 +17,37 @@
17
17
  width: 100%;
18
18
  }
19
19
 
20
- .allotment-module_splitView__L-yRc > .allotment-module_sashContainer__fzwJF > .allotment-module_sash__QA-2t {
20
+ .allotment-module_splitView__L-yRc
21
+ > .allotment-module_sashContainer__fzwJF
22
+ > .allotment-module_sash__QA-2t {
21
23
  pointer-events: auto;
22
24
  }
23
25
 
24
- .allotment-module_splitView__L-yRc > .allotment-module_splitViewContainer__rQnVa {
26
+ .allotment-module_splitView__L-yRc
27
+ > .allotment-module_splitViewContainer__rQnVa {
25
28
  height: 100%;
26
29
  position: relative;
27
30
  white-space: nowrap;
28
31
  width: 100%;
29
32
  }
30
33
 
31
- .allotment-module_splitView__L-yRc > .allotment-module_splitViewContainer__rQnVa > .allotment-module_splitViewView__MGZ6O {
34
+ .allotment-module_splitView__L-yRc
35
+ > .allotment-module_splitViewContainer__rQnVa
36
+ > .allotment-module_splitViewView__MGZ6O {
32
37
  overflow: hidden;
33
38
  position: absolute;
34
39
  white-space: initial;
35
40
  }
36
41
 
37
- .allotment-module_splitView__L-yRc.allotment-module_vertical__WSwwa > .allotment-module_splitViewContainer__rQnVa > .allotment-module_splitViewView__MGZ6O {
42
+ .allotment-module_splitView__L-yRc.allotment-module_vertical__WSwwa
43
+ > .allotment-module_splitViewContainer__rQnVa
44
+ > .allotment-module_splitViewView__MGZ6O {
38
45
  width: 100%;
39
46
  }
40
47
 
41
- .allotment-module_splitView__L-yRc.allotment-module_horizontal__7doS8 > .allotment-module_splitViewContainer__rQnVa > .allotment-module_splitViewView__MGZ6O {
48
+ .allotment-module_splitView__L-yRc.allotment-module_horizontal__7doS8
49
+ > .allotment-module_splitViewContainer__rQnVa
50
+ > .allotment-module_splitViewView__MGZ6O {
42
51
  height: 100%;
43
52
  }
44
53
 
@@ -129,7 +138,8 @@
129
138
  height: var(--sash-size);
130
139
  }
131
140
 
132
- .sash-module_sash__K-9lB:not(.sash-module_disabled__Hm-wx) > .sash-module_orthogonal-drag-handle__Yii2- {
141
+ .sash-module_sash__K-9lB:not(.sash-module_disabled__Hm-wx)
142
+ > .sash-module_orthogonal-drag-handle__Yii2- {
133
143
  content: " ";
134
144
  height: calc(var(--sash-size) * 2);
135
145
  width: calc(var(--sash-size) * 2);
@@ -139,36 +149,48 @@
139
149
  position: absolute;
140
150
  }
141
151
 
142
- .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-north__f7Noe:not(.sash-module_disabled__Hm-wx)
152
+ .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-north__f7Noe:not(
153
+ .sash-module_disabled__Hm-wx
154
+ )
143
155
  > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_start__uZEDk,
144
- .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-south__6ZrFC:not(.sash-module_disabled__Hm-wx)
156
+ .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-south__6ZrFC:not(
157
+ .sash-module_disabled__Hm-wx
158
+ )
145
159
  > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_end__0TP-R {
146
160
  cursor: nwse-resize;
147
161
  }
148
162
 
149
- .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-north__f7Noe:not(.sash-module_disabled__Hm-wx)
163
+ .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-north__f7Noe:not(
164
+ .sash-module_disabled__Hm-wx
165
+ )
150
166
  > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_end__0TP-R,
151
- .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-south__6ZrFC:not(.sash-module_disabled__Hm-wx)
167
+ .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw.sash-module_orthogonal-edge-south__6ZrFC:not(
168
+ .sash-module_disabled__Hm-wx
169
+ )
152
170
  > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_start__uZEDk {
153
171
  cursor: nesw-resize;
154
172
  }
155
173
 
156
- .sash-module_sash__K-9lB.sash-module_vertical__pB-rs > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_start__uZEDk {
174
+ .sash-module_sash__K-9lB.sash-module_vertical__pB-rs
175
+ > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_start__uZEDk {
157
176
  left: calc(var(--sash-size) * -0.5);
158
177
  top: calc(var(--sash-size) * -1);
159
178
  }
160
179
 
161
- .sash-module_sash__K-9lB.sash-module_vertical__pB-rs > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_end__0TP-R {
180
+ .sash-module_sash__K-9lB.sash-module_vertical__pB-rs
181
+ > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_end__0TP-R {
162
182
  left: calc(var(--sash-size) * -0.5);
163
183
  bottom: calc(var(--sash-size) * -1);
164
184
  }
165
185
 
166
- .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_start__uZEDk {
186
+ .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw
187
+ > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_start__uZEDk {
167
188
  top: calc(var(--sash-size) * -0.5);
168
189
  left: calc(var(--sash-size) * -1);
169
190
  }
170
191
 
171
- .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_end__0TP-R {
192
+ .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw
193
+ > .sash-module_orthogonal-drag-handle__Yii2-.sash-module_end__0TP-R {
172
194
  top: calc(var(--sash-size) * -0.5);
173
195
  right: calc(var(--sash-size) * -1);
174
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dust-tt/sparkle",
3
- "version": "0.7.6-rc-3",
3
+ "version": "0.7.6",
4
4
  "scripts": {
5
5
  "build": "rm -rf dist && npm run tailwind && npm run build:esm && npm run build:cjs",
6
6
  "tailwind": "npx tailwindcss -i ./src/styles/tailwind.css -o dist/sparkle.css",
@@ -27,7 +27,7 @@ export const AssistantCardMore = React.forwardRef<
27
27
  HTMLButtonElement,
28
28
  AssistantCardMore
29
29
  >(({ ...props }, ref) => {
30
- return <CardActionButton ref={ref} icon={MoreIcon} {...props} />;
30
+ return <CardActionButton size="mini" ref={ref} icon={MoreIcon} {...props} />;
31
31
  });
32
32
  AssistantCardMore.displayName = "AssistantCardMore";
33
33
 
@@ -34,15 +34,9 @@ export const BUTTON_VARIANTS = [
34
34
 
35
35
  export type ButtonVariantType = (typeof BUTTON_VARIANTS)[number];
36
36
 
37
- export const BUTTON_SIZES = ["icon-xs", "icon", "xmini", "mini", "xs", "sm", "md"] as const;
37
+ export const BUTTON_SIZES = ["xmini", "mini", "xs", "sm", "md"] as const;
38
38
  export type ButtonSizeType = (typeof BUTTON_SIZES)[number];
39
39
 
40
- // Icon-only button sizes (fixed width, labels hidden)
41
- export const ICON_ONLY_SIZES = ["icon-xs", "icon"] as const;
42
-
43
- // Small button sizes that use xs spinner size
44
- export const SMALL_BUTTON_SIZES = ["icon-xs", "icon", "xmini", "mini"] as const;
45
-
46
40
  // Define button styling with cva
47
41
  const buttonVariants = cva(
48
42
  cn(
@@ -147,17 +141,13 @@ const buttonVariants = cva(
147
141
  ),
148
142
  },
149
143
  size: {
150
- "icon-xs": "s-h-6 s-w-6 s-label-xs s-gap-1 s-shrink-0",
151
- icon: "s-h-7 s-w-7 s-label-xs s-gap-1.5 s-shrink-0",
152
- xmini: "s-h-6 s-px-1.5 s-label-xs s-gap-1 s-shrink-0",
153
- mini: "s-h-7 s-px-2 s-label-xs s-gap-1.5 s-shrink-0",
144
+ xmini: "s-h-6 s-w-6 s-label-xs s-gap-1 s-shrink-0",
145
+ mini: "s-h-7 s-w-7 s-label-xs s-gap-1.5 s-shrink-0",
154
146
  xs: "s-h-7 s-px-2.5 s-label-xs s-gap-1.5 s-shrink-0",
155
147
  sm: "s-h-9 s-px-3 s-label-sm s-gap-2 s-shrink-0",
156
148
  md: "s-h-12 s-px-4 s-py-2 s-label-base s-gap-2.5 s-shrink-0",
157
149
  },
158
150
  rounded: {
159
- "icon-xs": "s-rounded-lg",
160
- icon: "s-rounded-lg",
161
151
  xmini: "s-rounded-lg",
162
152
  mini: "s-rounded-lg",
163
153
  xs: "s-rounded-lg",
@@ -177,10 +167,8 @@ const buttonVariants = cva(
177
167
  const labelVariants = cva("", {
178
168
  variants: {
179
169
  size: {
180
- "icon-xs": "s-label-xs s-hidden",
181
- icon: "s-label-xs s-hidden",
182
- xmini: "s-label-xs",
183
- mini: "s-label-xs",
170
+ xmini: "s-label-xs s-hidden",
171
+ mini: "s-label-xs s-hidden",
184
172
  xs: "s-label-xs",
185
173
  sm: "s-label-sm",
186
174
  md: "s-label-base",
@@ -258,8 +246,6 @@ type IconSizeType = "xs" | "sm" | "md";
258
246
  type CounterSizeType = "xs" | "sm" | "md";
259
247
 
260
248
  export const ICON_SIZE_MAP: Record<ButtonSizeType, IconSizeType> = {
261
- "icon-xs": "xs",
262
- icon: "sm",
263
249
  xmini: "xs",
264
250
  mini: "sm",
265
251
  xs: "xs",
@@ -268,8 +254,6 @@ export const ICON_SIZE_MAP: Record<ButtonSizeType, IconSizeType> = {
268
254
  };
269
255
 
270
256
  const COUNTER_SIZE_MAP: Record<ButtonSizeType, CounterSizeType> = {
271
- "icon-xs": "xs",
272
- icon: "xs",
273
257
  xmini: "xs",
274
258
  mini: "xs",
275
259
  xs: "xs",
@@ -289,19 +273,19 @@ type CommonButtonProps = Omit<MetaButtonProps, "children"> &
289
273
  isRounded?: boolean;
290
274
  };
291
275
 
292
- export type ButtonProps = CommonButtonProps & {
293
- size?: ButtonSizeType;
294
- icon?: React.ComponentType;
295
- label?: string;
296
- };
297
-
298
276
  export type MiniButtonProps = CommonButtonProps & {
299
277
  size: "mini";
300
278
  icon: React.ComponentType;
301
279
  label?: never;
302
280
  };
303
281
 
304
- export type RegularButtonProps = ButtonProps;
282
+ export type RegularButtonProps = CommonButtonProps & {
283
+ size?: Exclude<ButtonSizeType, "mini">;
284
+ icon?: React.ComponentType;
285
+ label?: string;
286
+ };
287
+
288
+ export type ButtonProps = MiniButtonProps | RegularButtonProps;
305
289
 
306
290
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
307
291
  (
@@ -368,11 +352,12 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
368
352
  <div
369
353
  className={cn(
370
354
  "-s-mx-0.5",
371
- SMALL_BUTTON_SIZES.includes(size as typeof SMALL_BUTTON_SIZES[number]) && "s-w-5 s-px-0.5"
355
+ size === "mini" && "s-w-5 s-px-0.5",
356
+ size === "xmini" && "s-w-5 s-px-0.5"
372
357
  )}
373
358
  >
374
359
  <Spinner
375
- size={SMALL_BUTTON_SIZES.includes(size as typeof SMALL_BUTTON_SIZES[number]) ? "xs" : iconSize}
360
+ size={size === "mini" || size === "xmini" ? "xs" : iconSize}
376
361
  variant={(variant && spinnerVariantsMap[variant]) || "gray400"}
377
362
  />
378
363
  </div>
@@ -4,10 +4,7 @@ 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, ICON_ONLY_SIZES } from "./Button";
8
-
9
- // Sizes that must be set per-item in ButtonGroup (not at group level)
10
- const PER_ITEM_SIZES = ["mini", ...ICON_ONLY_SIZES] as const;
7
+ import { Button } from "./Button";
11
8
  import type { DropdownMenuItemProps } from "./Dropdown";
12
9
  import {
13
10
  DropdownMenu,
@@ -95,9 +92,9 @@ export interface ButtonGroupProps
95
92
  */
96
93
  variant?: ButtonGroupVariantType;
97
94
  /**
98
- * Size to apply to all buttons in the group. Icon-only buttons and mini must opt-in per item.
95
+ * Size to apply to all buttons in the group. Mini buttons must opt-in per item.
99
96
  */
100
- size?: Exclude<ButtonSizeType, "mini" | "icon" | "icon-xs">;
97
+ size?: Exclude<ButtonSizeType, "mini">;
101
98
  /**
102
99
  * Whether every button should be disabled.
103
100
  */
@@ -171,10 +168,10 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
171
168
  if (item.type === "button") {
172
169
  const nextVariant = sanitizeVariant(variant ?? item.props.variant);
173
170
  const rawSize = size ?? item.props.size;
174
- const nextSize: Exclude<ButtonSizeType, "mini" | "icon" | "icon-xs"> | undefined =
175
- PER_ITEM_SIZES.includes(rawSize as typeof PER_ITEM_SIZES[number])
171
+ const nextSize: Exclude<ButtonSizeType, "mini"> | undefined =
172
+ rawSize === "mini"
176
173
  ? undefined
177
- : (rawSize as Exclude<ButtonSizeType, "mini" | "icon" | "icon-xs"> | undefined);
174
+ : (rawSize as Exclude<ButtonSizeType, "mini"> | undefined);
178
175
 
179
176
  return (
180
177
  <Button
@@ -225,20 +225,15 @@ const CardActions = React.forwardRef<
225
225
 
226
226
  CardActions.displayName = "CardActions";
227
227
 
228
- export type CardActionButtonProps = Omit<MiniButtonProps, "size"> & {
229
- size?: "icon" | "icon-xs";
230
- };
231
-
232
228
  export const CardActionButton = React.forwardRef<
233
229
  HTMLButtonElement,
234
- CardActionButtonProps
235
- >(({ className, variant = "outline", icon = XMarkIcon, size = "icon", ...props }, ref) => {
230
+ MiniButtonProps
231
+ >(({ className, variant = "outline", icon = XMarkIcon, ...props }, ref) => {
236
232
  return (
237
233
  <Button
238
234
  ref={ref}
239
235
  variant={variant}
240
236
  icon={icon}
241
- size={size}
242
237
  className={className}
243
238
  {...props}
244
239
  />
@@ -5,9 +5,9 @@ import { ImageZoomDialog } from "@sparkle/components/ImageZoomDialog";
5
5
  import { cn } from "@sparkle/lib/utils";
6
6
 
7
7
  const SIZE_CLASSES = {
8
- sm: "s-h-24 s-w-24",
9
- md: "s-h-48 s-w-48",
10
- lg: "s-h-80 s-w-80",
8
+ sm: "s-relative s-h-24 s-w-24",
9
+ md: "s-relative s-h-48 s-w-48",
10
+ lg: "s-relative s-h-80 s-w-80",
11
11
  } as const;
12
12
 
13
13
  type InteractiveImageGridSize = keyof typeof SIZE_CLASSES;
@@ -101,7 +101,7 @@ function InteractiveImageGrid({
101
101
  downloadUrl={image.downloadUrl}
102
102
  isLoading={image.isLoading}
103
103
  onClick={() => setCurrentImageIndex(idx)}
104
- variant="embedded"
104
+ variant="standalone"
105
105
  titlePosition="center"
106
106
  manageZoomDialog={false}
107
107
  />
@@ -167,7 +167,7 @@ const NavigationListItem = React.forwardRef<
167
167
  {icon && <Icon visual={icon} size="xs" className="s-m-0.5" />}
168
168
  {avatar}
169
169
  {label && (
170
- <span className="s-grow s-overflow-hidden s-text-ellipsis s-whitespace-nowrap group-hover/menu-item:s-pr-8 group-focus-within/menu-item:s-pr-8 group-data-[selected=true]/menu-item:s-pr-8">
170
+ <span className="s-grow s-overflow-hidden s-text-ellipsis s-whitespace-nowrap group-focus-within/menu-item:s-pr-8 group-hover/menu-item:s-pr-8 group-data-[selected=true]/menu-item:s-pr-8">
171
171
  {label}
172
172
  </span>
173
173
  )}
@@ -179,7 +179,7 @@ const NavigationListItem = React.forwardRef<
179
179
  className={cn(
180
180
  "s-flex-shrink-0 s-translate-x-0.5",
181
181
  moreMenu &&
182
- "group-hover/menu-item:s-hidden group-focus-within/menu-item:s-hidden"
182
+ "group-focus-within/menu-item:s-hidden group-hover/menu-item:s-hidden"
183
183
  )}
184
184
  />
185
185
  )}
@@ -188,7 +188,7 @@ const NavigationListItem = React.forwardRef<
188
188
  className={cn(
189
189
  "s-heading-xs s-flex s-flex-shrink-0 s-items-center s-justify-center s-rounded-full",
190
190
  moreMenu &&
191
- "group-hover/menu-item:s-hidden group-focus-within/menu-item:s-hidden",
191
+ "group-focus-within/menu-item:s-hidden group-hover/menu-item:s-hidden",
192
192
  getStatusDotColor()
193
193
  )}
194
194
  />
@@ -0,0 +1,267 @@
1
+ import * as React from "react";
2
+
3
+ import type { ButtonProps, ButtonSizeType } from "@sparkle/components/Button";
4
+ import { Button } from "@sparkle/components/Button";
5
+ import { MicIcon, SquareIcon } from "@sparkle/icons/app";
6
+ import { cn } from "@sparkle/lib/utils";
7
+
8
+ const DEFAULT_PRESS_DELAY_MS = 150;
9
+ const VOICE_LEVEL_BASE_HEIGHTS = [
10
+ 22, 33, 18, 64, 98, 56, 6, 34, 76, 46, 12, 22,
11
+ ];
12
+
13
+ export type VoicePickerStatus =
14
+ | "idle"
15
+ | "authorizing_microphone"
16
+ | "recording"
17
+ | "transcribing";
18
+
19
+ type VoicePickerInteractionMode = "hold" | "click";
20
+
21
+ export interface VoicePickerProps {
22
+ status: VoicePickerStatus;
23
+ level: number;
24
+ elapsedSeconds: number;
25
+ onRecordStart: () => void | Promise<void>;
26
+ onRecordStop: () => void | Promise<void>;
27
+ size?: Exclude<ButtonSizeType, "xmini" | "mini">;
28
+ disabled?: boolean;
29
+ showStopLabel?: boolean;
30
+ pressDelayMs?: number;
31
+ buttonProps?: Omit<
32
+ ButtonProps,
33
+ "icon" | "label" | "variant" | "isLoading" | "disabled" | "size"
34
+ >;
35
+ }
36
+
37
+ export function VoicePicker({
38
+ status,
39
+ level,
40
+ elapsedSeconds,
41
+ onRecordStart,
42
+ onRecordStop,
43
+ size = "xs",
44
+ disabled = false,
45
+ showStopLabel = false,
46
+ pressDelayMs = DEFAULT_PRESS_DELAY_MS,
47
+ buttonProps,
48
+ }: VoicePickerProps): React.ReactElement {
49
+ const [interactionMode, setInteractionMode] =
50
+ React.useState<VoicePickerInteractionMode>("hold");
51
+ const pressStartRef = React.useRef<number | null>(null);
52
+ const pressTimeoutRef = React.useRef<number | null>(null);
53
+
54
+ const isRecording = status === "recording";
55
+ const isTranscribing = status === "transcribing";
56
+ const isLoading =
57
+ status === "transcribing" || status === "authorizing_microphone";
58
+ const shouldShowStop = isRecording && interactionMode === "click";
59
+
60
+ function clearPressTimeout(): void {
61
+ if (pressTimeoutRef.current !== null) {
62
+ window.clearTimeout(pressTimeoutRef.current);
63
+ pressTimeoutRef.current = null;
64
+ }
65
+ }
66
+
67
+ function stopEvent(event: React.SyntheticEvent): void {
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ }
71
+
72
+ async function handlePointerDown(
73
+ event: React.PointerEvent<HTMLButtonElement>
74
+ ): Promise<void> {
75
+ buttonProps?.onPointerDown?.(event);
76
+ if (event.defaultPrevented) {
77
+ return;
78
+ }
79
+
80
+ stopEvent(event);
81
+
82
+ if (disabled) {
83
+ return;
84
+ }
85
+
86
+ pressStartRef.current = Date.now();
87
+
88
+ clearPressTimeout();
89
+ pressTimeoutRef.current = window.setTimeout(async () => {
90
+ if (pressStartRef.current !== null) {
91
+ setInteractionMode("hold");
92
+ if (status === "idle") {
93
+ await onRecordStart();
94
+ }
95
+ }
96
+ }, pressDelayMs);
97
+ }
98
+
99
+ async function handlePointerUp(
100
+ event: React.PointerEvent<HTMLButtonElement>
101
+ ): Promise<void> {
102
+ buttonProps?.onPointerUp?.(event);
103
+ if (event.defaultPrevented) {
104
+ return;
105
+ }
106
+
107
+ stopEvent(event);
108
+
109
+ if (disabled) {
110
+ return;
111
+ }
112
+
113
+ const start = pressStartRef.current;
114
+ clearPressTimeout();
115
+ pressStartRef.current = null;
116
+
117
+ const duration =
118
+ start === null ? Number.POSITIVE_INFINITY : Date.now() - start;
119
+
120
+ if (duration < pressDelayMs) {
121
+ setInteractionMode("click");
122
+ if (status === "idle") {
123
+ await onRecordStart();
124
+ return;
125
+ }
126
+ await onRecordStop();
127
+ return;
128
+ }
129
+
130
+ if (status === "recording") {
131
+ await onRecordStop();
132
+ }
133
+ }
134
+
135
+ async function handlePointerLeave(
136
+ event: React.PointerEvent<HTMLButtonElement>
137
+ ): Promise<void> {
138
+ buttonProps?.onPointerLeave?.(event);
139
+ if (event.defaultPrevented) {
140
+ return;
141
+ }
142
+
143
+ stopEvent(event);
144
+
145
+ if (disabled || interactionMode !== "hold") {
146
+ return;
147
+ }
148
+ if (status === "recording") {
149
+ await onRecordStop();
150
+ }
151
+ }
152
+
153
+ async function handleClick(
154
+ event: React.MouseEvent<HTMLButtonElement>
155
+ ): Promise<void> {
156
+ buttonProps?.onClick?.(event);
157
+ if (event.defaultPrevented) {
158
+ return;
159
+ }
160
+
161
+ stopEvent(event);
162
+
163
+ if (disabled || interactionMode !== "click") {
164
+ return;
165
+ }
166
+ if (status === "recording") {
167
+ await onRecordStop();
168
+ }
169
+ }
170
+
171
+ const icon = shouldShowStop ? SquareIcon : MicIcon;
172
+ const variant = shouldShowStop ? "highlight" : "ghost-secondary";
173
+ const label = shouldShowStop && showStopLabel ? "Stop" : undefined;
174
+ const tooltip = computeTooltip(interactionMode, isRecording, isTranscribing);
175
+
176
+ return (
177
+ <>
178
+ <div
179
+ className={cn(
180
+ "s-duration-600 s-flex s-items-center s-justify-end s-gap-2 s-overflow-hidden s-px-2 s-transition-all s-ease-in-out",
181
+ isRecording ? "s-opacity-100" : "s-hidden"
182
+ )}
183
+ >
184
+ <div className="s-heading-xs s-font-mono">
185
+ {formatTime(elapsedSeconds)}
186
+ </div>
187
+ <VoiceLevelDisplay level={level} />
188
+ </div>
189
+ <Button
190
+ {...buttonProps}
191
+ size={size}
192
+ icon={icon}
193
+ isLoading={isLoading}
194
+ variant={variant}
195
+ tooltip={tooltip}
196
+ label={label}
197
+ disabled={disabled}
198
+ onPointerDown={handlePointerDown}
199
+ onPointerUp={handlePointerUp}
200
+ onPointerLeave={handlePointerLeave}
201
+ onClick={handleClick}
202
+ />
203
+ </>
204
+ );
205
+ }
206
+
207
+ interface VoiceLevelDisplayProps {
208
+ level: number;
209
+ }
210
+
211
+ function VoiceLevelDisplay({
212
+ level,
213
+ }: VoiceLevelDisplayProps): React.ReactElement {
214
+ const clampedLevel = Math.max(0, Math.min(1, level * 1.2));
215
+ const easedLevel = Math.pow(clampedLevel, 0.8);
216
+
217
+ const minHeights = VOICE_LEVEL_BASE_HEIGHTS.map((height) =>
218
+ Math.max(6, Math.round(height * 0.3))
219
+ );
220
+
221
+ const heights = VOICE_LEVEL_BASE_HEIGHTS.map((height, index) =>
222
+ Math.max(
223
+ 1,
224
+ Math.min(
225
+ 100,
226
+ Math.round(
227
+ minHeights[index] + (height - minHeights[index]) * easedLevel
228
+ )
229
+ )
230
+ )
231
+ );
232
+
233
+ return (
234
+ <div className="s-flex s-h-5 s-items-center s-gap-0.5">
235
+ {heights.map((height, index) => (
236
+ <div
237
+ key={index}
238
+ className="s-min-h-1 s-w-0.5 s-rounded-full s-bg-muted-foreground s-transition-all s-duration-150 s-ease-out"
239
+ style={{ height: `${height}%` }}
240
+ />
241
+ ))}
242
+ </div>
243
+ );
244
+ }
245
+
246
+ function computeTooltip(
247
+ mode: VoicePickerInteractionMode,
248
+ isRecording: boolean,
249
+ isTranscribing: boolean
250
+ ): string {
251
+ if (isTranscribing) {
252
+ return "Transcribing...";
253
+ }
254
+ if (mode === "hold" && isRecording) {
255
+ return "Release to stop";
256
+ }
257
+ if (isRecording) {
258
+ return "Stop recording";
259
+ }
260
+ return "Click, or Press & Hold to record";
261
+ }
262
+
263
+ function formatTime(seconds: number): string {
264
+ const minutes = Math.floor(seconds / 60);
265
+ const remainingSeconds = seconds % 60;
266
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
267
+ }
@@ -221,3 +221,5 @@ export {
221
221
  export { Tree } from "./Tree";
222
222
  export { TypingAnimation } from "./TypingAnimation";
223
223
  export { ValueCard } from "./ValueCard";
224
+ export type { VoicePickerProps, VoicePickerStatus } from "./VoicePicker";
225
+ export { VoicePicker } from "./VoicePicker";
@@ -0,0 +1,22 @@
1
+ import type { SVGProps } from "react";
2
+ import * as React from "react";
3
+ const SvgUkg = (props: SVGProps<SVGSVGElement>) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="1em"
7
+ height="1em"
8
+ fill="none"
9
+ viewBox="0 0 24 24"
10
+ {...props}
11
+ >
12
+ <path
13
+ fill="#2CD5C4"
14
+ d="M6.937 4.467A2.469 2.469 0 1 1 2 4.47a2.469 2.469 0 0 1 4.937-.003ZM22 4.467A2.46 2.46 0 1 1 19.55 2 2.47 2.47 0 0 1 22 4.467Z"
15
+ />
16
+ <path
17
+ fill="#005857"
18
+ d="M12.02 22c-2.657-.002-5.205-.976-7.084-2.709-1.878-1.733-2.934-4.083-2.934-6.533v-1.466a2.02 2.02 0 0 1 .152-.857c.113-.273.285-.522.504-.733.22-.21.484-.378.776-.492a2.529 2.529 0 0 1 1.845 0c.291.114.555.282.775.492.22.21.391.46.504.733.113.274.165.565.152.857v1.523c0 2.685 1.727 4.874 5.29 4.874 3.314 0 5.288-2.19 5.288-4.874v-1.523c-.018-.295.03-.59.14-.867.111-.278.282-.531.502-.746a2.37 2.37 0 0 1 .78-.502 2.53 2.53 0 0 1 1.864 0c.294.117.56.288.78.502.22.215.391.468.502.745.11.278.158.573.14.868v1.466a8.57 8.57 0 0 1-.746 3.534 9.175 9.175 0 0 1-2.162 3c-.928.858-2.03 1.538-3.245 2.002a10.71 10.71 0 0 1-3.83.701"
19
+ />
20
+ </svg>
21
+ );
22
+ export default SvgUkg;
@@ -59,6 +59,7 @@ export { default as SnowflakeLogo } from "./Snowflake";
59
59
  export { default as StripeLogo } from "./Stripe";
60
60
  export { default as SupabaseLogo } from "./Supabase";
61
61
  export { default as TogetheraiLogo } from "./Togetherai";
62
+ export { default as UkgLogo } from "./Ukg";
62
63
  export { default as ValTownLogo } from "./ValTown";
63
64
  export { default as VantaLogo } from "./Vanta";
64
65
  export { default as ZapierLogo } from "./Zapier";
@@ -0,0 +1,5 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M6.9373 4.46699C6.93764 4.95539 6.7931 5.43291 6.52197 5.83914C6.25084 6.24538 5.86531 6.56205 5.41415 6.74911C4.963 6.93617 4.46649 6.9852 3.98746 6.89001C3.50843 6.79481 3.0684 6.55966 2.72305 6.21431C2.3777 5.86896 2.14255 5.42893 2.04735 4.9499C1.95215 4.47087 2.00119 3.97436 2.18824 3.52321C2.3753 3.07205 2.69198 2.68652 3.09821 2.41539C3.50444 2.14426 3.98197 1.99972 4.47037 2.00006C5.1245 2.00052 5.75171 2.26057 6.21425 2.72311C6.67679 3.18565 6.93684 3.81286 6.9373 4.46699Z" fill="#2CD5C4"/>
3
+ <path d="M22 4.46695C21.9986 4.95365 21.8529 5.429 21.5813 5.83287C21.3097 6.23674 20.9245 6.55097 20.4742 6.73581C20.024 6.92064 19.529 6.96777 19.052 6.87124C18.575 6.7747 18.1373 6.53884 17.7944 6.19349C17.4514 5.84815 17.2186 5.40883 17.1254 4.93114C17.0322 4.45345 17.0828 3.95885 17.2708 3.50991C17.4588 3.06098 17.7757 2.6779 18.1814 2.40913C18.5872 2.14036 19.0636 1.99798 19.5503 2.00002C20.2013 2.00545 20.8238 2.26765 21.2825 2.7296C21.7413 3.19156 21.9991 3.81592 22 4.46695Z" fill="#2CD5C4"/>
4
+ <path d="M12.0209 22C9.36334 21.9983 6.8152 21.0239 4.93649 19.291C3.05778 17.5581 2.00221 15.2084 2.00174 12.7583V11.292C1.98955 10.9999 2.04141 10.7086 2.15424 10.4354C2.26706 10.1622 2.43852 9.9129 2.65835 9.70228C2.87818 9.49167 3.14185 9.32412 3.43358 9.20968C3.7253 9.09523 4.03908 9.03624 4.35612 9.03624C4.67316 9.03624 4.98694 9.09523 5.27867 9.20968C5.57039 9.32412 5.83407 9.49167 6.05389 9.70228C6.27372 9.9129 6.44518 10.1622 6.558 10.4354C6.67083 10.7086 6.72269 10.9999 6.7105 11.292V12.8146C6.7105 15.4996 8.43704 17.6895 12 17.6895C15.3136 17.6895 17.2878 15.4996 17.2878 12.8146V11.292C17.2703 10.997 17.3183 10.7018 17.4288 10.4245C17.5392 10.1472 17.7098 9.89355 17.9301 9.67905C18.1505 9.46456 18.4159 9.29372 18.7103 9.17697C19.0046 9.06021 19.3217 9 19.6421 9C19.9626 9 20.2796 9.06021 20.574 9.17697C20.8683 9.29372 21.1338 9.46456 21.3541 9.67905C21.5745 9.89355 21.7451 10.1472 21.8555 10.4245C21.966 10.7018 22.0139 10.997 21.9965 11.292V12.7583C22.0027 13.9702 21.749 15.1713 21.2498 16.2924C20.7507 17.4136 20.0161 18.4327 19.0881 19.2911C18.1602 20.1496 17.0574 20.8304 15.843 21.2945C14.6287 21.7585 13.3268 21.9967 12.0122 21.9952" fill="#005857"/>
5
+ </svg>