@aircall/blocks 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,515 @@
1
+ import { Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, InputGroup, InputGroupButton, InputGroupTextarea, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, TabsList, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
1
2
  import * as React from "react";
2
- import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
3
- import { Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, SidebarProvider, TabsList, Tooltip, TooltipContent, TooltipTrigger, cn, useSidebar } from "@aircall/ds";
4
- import { Check, ChevronLeft, ChevronRight, Copy, PanelLeftClose, PanelLeftOpen } from "@aircall/react-icons";
5
- import { useCallbackRef, useIsMounted, useUnmount } from "@aircall/hooks";
3
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
6
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { AircallLogo, ArrowUp, Check, ChevronLeft, ChevronRight, Copy, Ellipsis, LockKeyhole, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, TriangleAlert } from "@aircall/react-icons";
7
6
  import { cva } from "class-variance-authority";
7
+ import { useCallbackRef, useControllableState, useIsMounted, useUnmount } from "@aircall/hooks";
8
+ import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
8
9
  import { mergeProps } from "@base-ui/react/merge-props";
9
10
  import { useRender } from "@base-ui/react/use-render";
10
11
 
12
+ //#region src/i18n/locales/de.ts
13
+ const de = {
14
+ "emptyState.comingSoon.title": "Demnächst verfügbar!",
15
+ "emptyState.comingSoon.description": "Sie werden in naher Zukunft auf diese Funktion zugreifen können.",
16
+ "emptyState.noData.title": "Keine passenden Ergebnisse",
17
+ "emptyState.noData.description": "Versuchen Sie, Ihre Suche zurückzusetzen oder einen Filter zu entfernen.",
18
+ "emptyState.restrictedAccess.title": "Sie haben keinen Zugriff auf diese Seite.",
19
+ "emptyState.restrictedAccess.description": "Wenden Sie sich an einen Administrator in Ihrem Team, um Zugriff zu erhalten.",
20
+ "emptyState.unknownError.title": "Diese Seite konnte nicht geladen werden",
21
+ "emptyState.unknownError.description": "Beim Laden ist ein Problem aufgetreten. Versuchen Sie es erneut, um die Daten zu laden.",
22
+ "emptyState.notFound.title": "Diese Seite konnte nicht gefunden werden",
23
+ "emptyState.notFound.description": "Sie wurde möglicherweise verschoben, gelöscht, oder der Link ist falsch.",
24
+ "emptyState.noSupport.title": "Diese Seite wird nicht unterstützt",
25
+ "emptyState.noSupport.description": "Versuchen Sie, Ihren Browser auf die neueste Version zu aktualisieren.",
26
+ "chatbotInput.placeholder": "Stellen Sie eine Frage...",
27
+ "chatbotInput.messageLabel": "Nachricht",
28
+ "chatbotInput.stopGenerating": "Generierung stoppen",
29
+ "chatbotInput.sendMessage": "Nachricht senden"
30
+ };
31
+ var de_default = de;
32
+
33
+ //#endregion
34
+ //#region src/i18n/locales/en.ts
35
+ const en = {
36
+ "emptyState.comingSoon.title": "Coming soon!",
37
+ "emptyState.comingSoon.description": "You'll be able to access this feature in the near future.",
38
+ "emptyState.noData.title": "No results matching",
39
+ "emptyState.noData.description": "Try resetting your search or removing a filter.",
40
+ "emptyState.restrictedAccess.title": "You don't have access to this page.",
41
+ "emptyState.restrictedAccess.description": "Contact an admin on your team to get access.",
42
+ "emptyState.unknownError.title": "We couldn't load this page",
43
+ "emptyState.unknownError.description": "Something went wrong while loading. Try again to load the data.",
44
+ "emptyState.notFound.title": "We couldn't find that page",
45
+ "emptyState.notFound.description": "It may have been moved, deleted, or the link may be incorrect.",
46
+ "emptyState.noSupport.title": "We can't support this page",
47
+ "emptyState.noSupport.description": "Try updating your browser to the latest version.",
48
+ "chatbotInput.placeholder": "Ask anything...",
49
+ "chatbotInput.messageLabel": "Message",
50
+ "chatbotInput.stopGenerating": "Stop generating",
51
+ "chatbotInput.sendMessage": "Send message"
52
+ };
53
+ var en_default = en;
54
+
55
+ //#endregion
56
+ //#region src/i18n/locales/es.ts
57
+ const es = {
58
+ "emptyState.comingSoon.title": "¡Próximamente!",
59
+ "emptyState.comingSoon.description": "Podrás acceder a esta función en un futuro próximo.",
60
+ "emptyState.noData.title": "No hay resultados que coincidan",
61
+ "emptyState.noData.description": "Intenta restablecer tu búsqueda o eliminar un filtro.",
62
+ "emptyState.restrictedAccess.title": "No tienes acceso a esta página.",
63
+ "emptyState.restrictedAccess.description": "Contacta a un administrador de tu equipo para obtener acceso.",
64
+ "emptyState.unknownError.title": "No pudimos cargar esta página",
65
+ "emptyState.unknownError.description": "Algo salió mal durante la carga. Inténtalo de nuevo para cargar los datos.",
66
+ "emptyState.notFound.title": "No pudimos encontrar esa página",
67
+ "emptyState.notFound.description": "Es posible que haya sido movida, eliminada, o el enlace sea incorrecto.",
68
+ "emptyState.noSupport.title": "No podemos mostrar esta página",
69
+ "emptyState.noSupport.description": "Intenta actualizar tu navegador a la última versión.",
70
+ "chatbotInput.placeholder": "Pregunta lo que quieras...",
71
+ "chatbotInput.messageLabel": "Mensaje",
72
+ "chatbotInput.stopGenerating": "Detener la generación",
73
+ "chatbotInput.sendMessage": "Enviar mensaje"
74
+ };
75
+ var es_default = es;
76
+
77
+ //#endregion
78
+ //#region src/i18n/locales/fr.ts
79
+ const fr = {
80
+ "emptyState.comingSoon.title": "Bientôt disponible !",
81
+ "emptyState.comingSoon.description": "Vous pourrez accéder à cette fonctionnalité dans un futur proche.",
82
+ "emptyState.noData.title": "Aucun résultat correspondant",
83
+ "emptyState.noData.description": "Réinitialisez votre recherche ou supprimez un filtre.",
84
+ "emptyState.restrictedAccess.title": "Vous n'avez pas accès à cette page.",
85
+ "emptyState.restrictedAccess.description": "Contactez un administrateur de votre équipe pour obtenir l'accès.",
86
+ "emptyState.unknownError.title": "Impossible de charger cette page",
87
+ "emptyState.unknownError.description": "Un problème est survenu lors du chargement. Réessayez pour charger les données.",
88
+ "emptyState.notFound.title": "Impossible de trouver cette page",
89
+ "emptyState.notFound.description": "Elle a peut-être été déplacée, supprimée, ou le lien est incorrect.",
90
+ "emptyState.noSupport.title": "Nous ne pouvons pas prendre en charge cette page",
91
+ "emptyState.noSupport.description": "Essayez de mettre à jour votre navigateur vers la dernière version.",
92
+ "chatbotInput.placeholder": "Posez votre question...",
93
+ "chatbotInput.messageLabel": "Message",
94
+ "chatbotInput.stopGenerating": "Arrêter la génération",
95
+ "chatbotInput.sendMessage": "Envoyer un message"
96
+ };
97
+ var fr_default = fr;
98
+
99
+ //#endregion
100
+ //#region src/i18n/locales/it.ts
101
+ const it = {
102
+ "emptyState.comingSoon.title": "Prossimamente!",
103
+ "emptyState.comingSoon.description": "Potrai accedere a questa funzionalità nel prossimo futuro.",
104
+ "emptyState.noData.title": "Nessun risultato corrispondente",
105
+ "emptyState.noData.description": "Prova a reimpostare la ricerca o a rimuovere un filtro.",
106
+ "emptyState.restrictedAccess.title": "Non hai accesso a questa pagina.",
107
+ "emptyState.restrictedAccess.description": "Contatta un amministratore del tuo team per ottenere l'accesso.",
108
+ "emptyState.unknownError.title": "Impossibile caricare questa pagina",
109
+ "emptyState.unknownError.description": "Si è verificato un problema durante il caricamento. Riprova a caricare i dati.",
110
+ "emptyState.notFound.title": "Impossibile trovare quella pagina",
111
+ "emptyState.notFound.description": "Potrebbe essere stata spostata, eliminata, o il link potrebbe essere errato.",
112
+ "emptyState.noSupport.title": "Non possiamo supportare questa pagina",
113
+ "emptyState.noSupport.description": "Prova ad aggiornare il browser all'ultima versione.",
114
+ "chatbotInput.placeholder": "Chiedi qualsiasi cosa...",
115
+ "chatbotInput.messageLabel": "Messaggio",
116
+ "chatbotInput.stopGenerating": "Interrompi la generazione",
117
+ "chatbotInput.sendMessage": "Invia messaggio"
118
+ };
119
+ var it_default = it;
120
+
121
+ //#endregion
122
+ //#region src/i18n/register.ts
123
+ registerI18nNamespace("blocks", {
124
+ en: en_default,
125
+ fr: fr_default,
126
+ es: es_default,
127
+ de: de_default,
128
+ it: it_default
129
+ });
130
+
131
+ //#endregion
132
+ //#region src/components/empty-state.tsx
133
+ /** Generic root. Anchor of the family + escape hatch for ad-hoc empty states. */
134
+ const EmptyState = React.forwardRef((componentProps, forwardRef) => {
135
+ return /* @__PURE__ */ jsx(Empty, {
136
+ ref: forwardRef,
137
+ ...componentProps
138
+ });
139
+ });
140
+ EmptyState.displayName = "EmptyState";
141
+ /** Groups the media, title and description with the design's spacing. */
142
+ const EmptyStateHeader = React.forwardRef((componentProps, forwardRef) => {
143
+ return /* @__PURE__ */ jsx(EmptyHeader, {
144
+ ref: forwardRef,
145
+ ...componentProps
146
+ });
147
+ });
148
+ EmptyStateHeader.displayName = "EmptyStateHeader";
149
+ /** Decorative media slot (icon/avatar). Hidden from assistive tech. */
150
+ const EmptyStateMedia = React.forwardRef((componentProps, forwardRef) => {
151
+ const { children, ...props } = componentProps;
152
+ return /* @__PURE__ */ jsx(EmptyMedia, {
153
+ ref: forwardRef,
154
+ variant: "icon",
155
+ "aria-hidden": true,
156
+ ...props,
157
+ children
158
+ });
159
+ });
160
+ EmptyStateMedia.displayName = "EmptyStateMedia";
161
+ const EmptyStateTitle = React.forwardRef((componentProps, forwardRef) => {
162
+ return /* @__PURE__ */ jsx(EmptyTitle, {
163
+ ref: forwardRef,
164
+ ...componentProps
165
+ });
166
+ });
167
+ EmptyStateTitle.displayName = "EmptyStateTitle";
168
+ const EmptyStateDescription = React.forwardRef((componentProps, forwardRef) => {
169
+ return /* @__PURE__ */ jsx(EmptyDescription, {
170
+ ref: forwardRef,
171
+ ...componentProps
172
+ });
173
+ });
174
+ EmptyStateDescription.displayName = "EmptyStateDescription";
175
+ /**
176
+ * Row of action buttons below the header. Lays buttons out horizontally
177
+ * (wrapping when needed), matching the design. Place an `EmptyStateExternalLink`
178
+ * link as a sibling after it — not inside — to sit on its own line below.
179
+ */
180
+ const EmptyStateActions = React.forwardRef((componentProps, forwardRef) => {
181
+ const { className, ...props } = componentProps;
182
+ return /* @__PURE__ */ jsx(EmptyContent, {
183
+ ref: forwardRef,
184
+ className: cn("flex-row flex-wrap justify-center gap-3", className),
185
+ ...props
186
+ });
187
+ });
188
+ EmptyStateActions.displayName = "EmptyStateActions";
189
+ const EmptyStateButton = React.forwardRef((componentProps, forwardRef) => {
190
+ return /* @__PURE__ */ jsx(Button, {
191
+ ref: forwardRef,
192
+ size: "default",
193
+ ...componentProps
194
+ });
195
+ });
196
+ EmptyStateButton.displayName = "EmptyStateButton";
197
+ const EmptyStateExternalLink = React.forwardRef((componentProps, forwardRef) => {
198
+ const { href, children = "Learn more", ...props } = componentProps;
199
+ return /* @__PURE__ */ jsx(EmptyStateButton, {
200
+ ref: forwardRef,
201
+ variant: "link",
202
+ render: /* @__PURE__ */ jsx("a", {
203
+ href,
204
+ target: "_blank",
205
+ rel: "noopener noreferrer"
206
+ }),
207
+ ...props,
208
+ children
209
+ });
210
+ });
211
+ EmptyStateExternalLink.displayName = "EmptyStateExternalLink";
212
+ function createEmptyStateVariant(config) {
213
+ const Root = React.forwardRef((componentProps, forwardRef) => /* @__PURE__ */ jsx(EmptyState, {
214
+ ref: forwardRef,
215
+ role: config.role,
216
+ ...componentProps
217
+ }));
218
+ Root.displayName = `${config.name}EmptyState`;
219
+ const Media = React.forwardRef((componentProps, forwardRef) => /* @__PURE__ */ jsx(EmptyStateMedia, {
220
+ ref: forwardRef,
221
+ ...componentProps,
222
+ children: config.icon
223
+ }));
224
+ Media.displayName = `${config.name}Media`;
225
+ const Title = React.forwardRef(({ children, ...componentProps }, forwardRef) => {
226
+ const t = useDsTranslation("blocks");
227
+ return /* @__PURE__ */ jsx(EmptyStateTitle, {
228
+ ref: forwardRef,
229
+ ...componentProps,
230
+ children: children ?? t(config.titleKey)
231
+ });
232
+ });
233
+ Title.displayName = `${config.name}Title`;
234
+ const Description = React.forwardRef(({ children, ...componentProps }, forwardRef) => {
235
+ const t = useDsTranslation("blocks");
236
+ return /* @__PURE__ */ jsx(EmptyStateDescription, {
237
+ ref: forwardRef,
238
+ ...componentProps,
239
+ children: children ?? (config.descriptionKey ? t(config.descriptionKey) : void 0)
240
+ });
241
+ });
242
+ Description.displayName = `${config.name}Description`;
243
+ return {
244
+ Root,
245
+ Media,
246
+ Title,
247
+ Description
248
+ };
249
+ }
250
+
251
+ //#endregion
252
+ //#region src/components/coming-soon.tsx
253
+ /**
254
+ * ComingSoon — empty state shown when a feature exists in the UI but is not yet
255
+ * available. Compose it from its parts:
256
+ *
257
+ * ```tsx
258
+ * <ComingSoonEmptyState>
259
+ * <EmptyStateHeader>
260
+ * <ComingSoonMedia />
261
+ * <ComingSoonTitle />
262
+ * <ComingSoonDescription />
263
+ * </EmptyStateHeader>
264
+ * <EmptyStateActions>
265
+ * <EmptyStateButton variant="outline" onClick={goBack}>Go back</EmptyStateButton>
266
+ * <EmptyStateExternalLink href={docsUrl} />
267
+ * </EmptyStateActions>
268
+ * </ComingSoonEmptyState>
269
+ * ```
270
+ *
271
+ * `ComingSoonTitle` / `ComingSoonDescription` render the design copy by default;
272
+ * pass children to override (e.g. for i18n).
273
+ */
274
+ const ComingSoon = createEmptyStateVariant({
275
+ name: "ComingSoon",
276
+ icon: /* @__PURE__ */ jsx(Sprout, {}),
277
+ titleKey: "emptyState.comingSoon.title",
278
+ descriptionKey: "emptyState.comingSoon.description"
279
+ });
280
+ const ComingSoonEmptyState = ComingSoon.Root;
281
+ const ComingSoonMedia = ComingSoon.Media;
282
+ const ComingSoonTitle = ComingSoon.Title;
283
+ const ComingSoonDescription = ComingSoon.Description;
284
+
285
+ //#endregion
286
+ //#region src/components/restricted-access.tsx
287
+ /**
288
+ * RestrictedAccess — shown when the user lacks permission to view a page.
289
+ * Pair it with an `EmptyStateExternalLink` for the "Learn more" action.
290
+ */
291
+ const RestrictedAccess = createEmptyStateVariant({
292
+ name: "RestrictedAccess",
293
+ icon: /* @__PURE__ */ jsx(LockKeyhole, {}),
294
+ titleKey: "emptyState.restrictedAccess.title",
295
+ descriptionKey: "emptyState.restrictedAccess.description"
296
+ });
297
+ const RestrictedAccessEmptyState = RestrictedAccess.Root;
298
+ const RestrictedAccessMedia = RestrictedAccess.Media;
299
+ const RestrictedAccessTitle = RestrictedAccess.Title;
300
+ const RestrictedAccessDescription = RestrictedAccess.Description;
301
+
302
+ //#endregion
303
+ //#region src/components/not-found.tsx
304
+ /**
305
+ * NotFound — shown when a page/route cannot be found. Pair it with an
306
+ * `EmptyStateButton` for the navigation action (e.g. "Go to inbox").
307
+ */
308
+ const NotFound = createEmptyStateVariant({
309
+ name: "NotFound",
310
+ icon: /* @__PURE__ */ jsx(Search, {}),
311
+ titleKey: "emptyState.notFound.title",
312
+ descriptionKey: "emptyState.notFound.description"
313
+ });
314
+ const NotFoundEmptyState = NotFound.Root;
315
+ const NotFoundMedia = NotFound.Media;
316
+ const NotFoundTitle = NotFound.Title;
317
+ const NotFoundDescription = NotFound.Description;
318
+
319
+ //#endregion
320
+ //#region src/components/no-data.tsx
321
+ /**
322
+ * NoData — shown when a search or filter returns no results. Pair it with an
323
+ * `EmptyStateButton` for the "Reset" action. Appears in response to a user
324
+ * action, so it announces politely via `role="status"`.
325
+ */
326
+ const NoData = createEmptyStateVariant({
327
+ name: "NoData",
328
+ icon: /* @__PURE__ */ jsx(Search, {}),
329
+ titleKey: "emptyState.noData.title",
330
+ descriptionKey: "emptyState.noData.description",
331
+ role: "status"
332
+ });
333
+ const NoDataEmptyState = NoData.Root;
334
+ const NoDataMedia = NoData.Media;
335
+ const NoDataTitle = NoData.Title;
336
+ const NoDataDescription = NoData.Description;
337
+
338
+ //#endregion
339
+ //#region src/components/no-support.tsx
340
+ /**
341
+ * NoSupport — shown when the current browser can't render a page. Pair it with
342
+ * an `EmptyStateExternalLink` for the "Learn more" action.
343
+ */
344
+ const NoSupport = createEmptyStateVariant({
345
+ name: "NoSupport",
346
+ icon: /* @__PURE__ */ jsx(Settings, {}),
347
+ titleKey: "emptyState.noSupport.title",
348
+ descriptionKey: "emptyState.noSupport.description"
349
+ });
350
+ const NoSupportEmptyState = NoSupport.Root;
351
+ const NoSupportMedia = NoSupport.Media;
352
+ const NoSupportTitle = NoSupport.Title;
353
+ const NoSupportDescription = NoSupport.Description;
354
+
355
+ //#endregion
356
+ //#region src/components/unknown-error.tsx
357
+ /**
358
+ * UnknownError — shown when content fails to load unexpectedly. Pair it with an
359
+ * `EmptyStateButton` (primary) for the "Try again" action. As an error state it
360
+ * announces assertively via `role="alert"`.
361
+ */
362
+ const UnknownError = createEmptyStateVariant({
363
+ name: "UnknownError",
364
+ icon: /* @__PURE__ */ jsx(TriangleAlert, {}),
365
+ titleKey: "emptyState.unknownError.title",
366
+ descriptionKey: "emptyState.unknownError.description",
367
+ role: "alert"
368
+ });
369
+ const UnknownErrorEmptyState = UnknownError.Root;
370
+ const UnknownErrorMedia = UnknownError.Media;
371
+ const UnknownErrorTitle = UnknownError.Title;
372
+ const UnknownErrorDescription = UnknownError.Description;
373
+
374
+ //#endregion
375
+ //#region src/components/chatbot-page.tsx
376
+ const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
377
+ const { children, className } = componentProps;
378
+ return /* @__PURE__ */ jsx(SidebarProvider, {
379
+ ref: forwardRef,
380
+ className: cn("!min-h-0 h-full", className),
381
+ style: { "--sidebar-width": "14rem" },
382
+ children
383
+ });
384
+ });
385
+ ChatbotPage.displayName = "ChatbotPage";
386
+
387
+ //#endregion
388
+ //#region src/components/chatbot-sidebar.tsx
389
+ const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
390
+ const { conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, recentLabel = "Recent", className } = componentProps;
391
+ return /* @__PURE__ */ jsx(Sidebar, {
392
+ ref: forwardRef,
393
+ collapsible: "none",
394
+ className,
395
+ children: /* @__PURE__ */ jsxs(SidebarContent, { children: [/* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
396
+ isActive: !activeConversationId || !conversations.some((c) => c.id === activeConversationId),
397
+ onClick: onNewChat,
398
+ children: [/* @__PURE__ */ jsx(Plus, {}), /* @__PURE__ */ jsx("span", { children: "New chat" })]
399
+ }) }) }) }), conversations.length > 0 && /* @__PURE__ */ jsxs(SidebarGroup, { children: [/* @__PURE__ */ jsx(SidebarGroupLabel, { children: recentLabel }), /* @__PURE__ */ jsx(SidebarMenu, { children: conversations.map((conversation) => {
400
+ const isActive = conversation.id === activeConversationId;
401
+ return /* @__PURE__ */ jsx(SidebarMenuItem, { children: renderConversationMenuContent ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
402
+ /* @__PURE__ */ jsx(SidebarMenuButton, {
403
+ isActive,
404
+ onClick: () => onSelectConversation?.(conversation.id),
405
+ children: /* @__PURE__ */ jsx("span", {
406
+ className: "min-w-0 truncate",
407
+ children: conversation.title
408
+ })
409
+ }),
410
+ /* @__PURE__ */ jsx(SidebarMenuAction, {
411
+ showOnHover: true,
412
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, {}),
413
+ "aria-label": `Options for ${conversation.title}`,
414
+ children: /* @__PURE__ */ jsx(Ellipsis, {})
415
+ }),
416
+ /* @__PURE__ */ jsx(DropdownMenuContent, {
417
+ side: "right",
418
+ children: renderConversationMenuContent(conversation)
419
+ })
420
+ ] }) : /* @__PURE__ */ jsx(SidebarMenuButton, {
421
+ isActive,
422
+ onClick: () => onSelectConversation?.(conversation.id),
423
+ children: /* @__PURE__ */ jsx("span", {
424
+ className: "min-w-0 truncate",
425
+ children: conversation.title
426
+ })
427
+ }) }, conversation.id);
428
+ }) })] })] })
429
+ });
430
+ });
431
+ ChatbotSidebar.displayName = "ChatbotSidebar";
432
+
433
+ //#endregion
434
+ //#region src/components/chatbot-input.tsx
435
+ const textareaVariants = cva([
436
+ "min-h-8 py-1.5 pr-2 pl-0 text-sm",
437
+ "placeholder:text-neutral-500",
438
+ "disabled:bg-transparent dark:disabled:bg-transparent disabled:opacity-100",
439
+ "[scrollbar-color:rgba(0,0,0,0.1)_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-black/10 [&::-webkit-scrollbar-track]:bg-transparent"
440
+ ], {
441
+ variants: { size: {
442
+ default: "max-h-20",
443
+ large: "max-h-32 min-h-20"
444
+ } },
445
+ defaultVariants: { size: "default" }
446
+ });
447
+ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, placeholder, loading = false, size = "default", disabled, ...textareaProps }) {
448
+ const t = useDsTranslation("blocks");
449
+ const [currentValue = "", setCurrentValue] = useControllableState({
450
+ prop: value,
451
+ defaultProp: "",
452
+ onChange: onValueChange
453
+ });
454
+ const hasValue = currentValue.trim().length > 0;
455
+ const handleChange = (event) => {
456
+ setCurrentValue(event.target.value);
457
+ };
458
+ const submit = () => {
459
+ if (loading || disabled || !hasValue) return;
460
+ onSubmit?.(currentValue);
461
+ setCurrentValue("");
462
+ };
463
+ const handleKeyDown = (event) => {
464
+ if (event.key === "Enter" && !event.shiftKey) {
465
+ event.preventDefault();
466
+ submit();
467
+ }
468
+ textareaProps.onKeyDown?.(event);
469
+ };
470
+ return /* @__PURE__ */ jsxs(InputGroup, {
471
+ "data-slot": "chatbot-input",
472
+ "data-loading": loading || void 0,
473
+ className: cn("h-auto items-end gap-2 overflow-clip rounded-3xl bg-background py-2 pr-2 pl-6", "shadow-[0px_0px_8px_0px_var(--input)]", "hover:shadow-[0px_0px_8px_0px_var(--ring)]", "has-[[data-slot=input-group-control]:focus-visible]:border-green-400", "has-[[data-slot=input-group-control]:focus-visible]:ring-0", "has-[[data-slot=input-group-control]:focus-visible]:shadow-[0px_0px_8px_0px_var(--ring)]", className),
474
+ "aria-disabled": disabled || void 0,
475
+ children: [/* @__PURE__ */ jsx(InputGroupTextarea, {
476
+ rows: 1,
477
+ placeholder: placeholder ?? t("chatbotInput.placeholder"),
478
+ "aria-label": t("chatbotInput.messageLabel"),
479
+ ...textareaProps,
480
+ value: currentValue,
481
+ disabled,
482
+ onChange: handleChange,
483
+ onKeyDown: handleKeyDown,
484
+ className: cn(textareaVariants({ size }))
485
+ }), /* @__PURE__ */ jsx("div", {
486
+ className: "flex shrink-0 items-center gap-2",
487
+ children: loading ? /* @__PURE__ */ jsx(InputGroupButton, {
488
+ variant: "secondary",
489
+ size: "icon",
490
+ className: "rounded-full",
491
+ "aria-label": t("chatbotInput.stopGenerating"),
492
+ disabled,
493
+ onClick: onStop,
494
+ children: /* @__PURE__ */ jsx(Square, {})
495
+ }) : /* @__PURE__ */ jsx(InputGroupButton, {
496
+ variant: hasValue ? "default" : "ghost",
497
+ size: "icon",
498
+ className: "rounded-full",
499
+ "aria-label": t("chatbotInput.sendMessage"),
500
+ disabled: disabled || !hasValue,
501
+ onClick: submit,
502
+ children: /* @__PURE__ */ jsx(ArrowUp, {})
503
+ })
504
+ })]
505
+ });
506
+ }
507
+
508
+ //#endregion
11
509
  //#region src/hooks/use-copy-to-clipboard.ts
12
510
  function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
13
511
  const [copied, setCopied] = useState(false);
14
- const timerRef = useRef();
512
+ const timerRef = useRef(void 0);
15
513
  const getIsMounted = useIsMounted();
16
514
  const onCopiedRef = useCallbackRef(onCopied);
17
515
  useUnmount(() => clearTimeout(timerRef.current));
@@ -49,7 +547,8 @@ function useCopyButtonContext() {
49
547
  if (ctx === null) throw new Error("CopyButtonIcon and CopyButtonLabel must be rendered inside a CopyButton.");
50
548
  return ctx;
51
549
  }
52
- function CopyButton({ value, timeout, onCopied, variant = "outline", children, ...buttonProps }) {
550
+ const CopyButton = React.forwardRef((componentProps, forwardRef) => {
551
+ const { value, timeout, onCopied, variant = "outline", children, ...buttonProps } = componentProps;
53
552
  const { copy, copied } = useCopyToClipboard(value, {
54
553
  timeout,
55
554
  onCopied
@@ -57,6 +556,7 @@ function CopyButton({ value, timeout, onCopied, variant = "outline", children, .
57
556
  return /* @__PURE__ */ jsx(CopyButtonContext.Provider, {
58
557
  value: { copied },
59
558
  children: /* @__PURE__ */ jsx(Button, {
559
+ ref: forwardRef,
60
560
  onClick: () => {
61
561
  copy();
62
562
  },
@@ -65,17 +565,66 @@ function CopyButton({ value, timeout, onCopied, variant = "outline", children, .
65
565
  children
66
566
  })
67
567
  });
68
- }
69
- function CopyButtonIcon({ className }) {
568
+ });
569
+ CopyButton.displayName = "CopyButton";
570
+ const CopyButtonIcon = React.forwardRef((componentProps, forwardRef) => {
571
+ const { className } = componentProps;
70
572
  const { copied } = useCopyButtonContext();
71
- return /* @__PURE__ */ jsx(copied ? Check : Copy, { className });
72
- }
73
- function CopyButtonLabel({ label = "Copy", copiedLabel = "Copied", className }) {
573
+ return /* @__PURE__ */ jsx(copied ? Check : Copy, {
574
+ ref: forwardRef,
575
+ className
576
+ });
577
+ });
578
+ CopyButtonIcon.displayName = "CopyButtonIcon";
579
+ const CopyButtonLabel = React.forwardRef((componentProps, forwardRef) => {
580
+ const { label = "Copy", copiedLabel = "Copied", className } = componentProps;
74
581
  const { copied } = useCopyButtonContext();
75
582
  return /* @__PURE__ */ jsx("span", {
583
+ ref: forwardRef,
76
584
  className: cn(className),
77
585
  children: copied ? copiedLabel : label
78
586
  });
587
+ });
588
+ CopyButtonLabel.displayName = "CopyButtonLabel";
589
+
590
+ //#endregion
591
+ //#region src/components/chatbot-panel-suggestion.tsx
592
+ function ChatbotPanelSuggestion({ className, ...props }) {
593
+ return /* @__PURE__ */ jsx(Button, {
594
+ "data-slot": "chatbot-panel-suggestion",
595
+ variant: "ghost",
596
+ className: cn("h-auto max-w-[280px] text-left rounded-[18px] border border-primary px-3 py-1.5", "bg-background text-primary whitespace-normal", "hover:bg-[color-mix(in_srgb,#5BF1B2_20%,var(--background))] hover:text-primary", className),
597
+ ...props
598
+ });
599
+ }
600
+
601
+ //#endregion
602
+ //#region src/components/chatbot-user-message.tsx
603
+ function ChatbotUserMessage({ className, children, ...props }) {
604
+ const [expanded, setExpanded] = useState(false);
605
+ const [showToggle, setShowToggle] = useState(false);
606
+ const contentRef = useRef(null);
607
+ useLayoutEffect(() => {
608
+ if (expanded || !contentRef.current) return;
609
+ const el = contentRef.current;
610
+ setShowToggle(el.scrollHeight > el.clientHeight);
611
+ }, [children, expanded]);
612
+ return /* @__PURE__ */ jsxs("div", {
613
+ "data-slot": "chatbot-user-message",
614
+ className: cn("ml-auto", "w-fit max-w-[80%]", "rounded-[18px]", "bg-green-200 text-foreground", "px-4 py-2", "text-sm", "break-words", className),
615
+ ...props,
616
+ children: [/* @__PURE__ */ jsx("div", {
617
+ ref: contentRef,
618
+ className: cn(!expanded && "line-clamp-6"),
619
+ children
620
+ }), showToggle && /* @__PURE__ */ jsx("button", {
621
+ type: "button",
622
+ "aria-expanded": expanded,
623
+ onClick: () => setExpanded((prev) => !prev),
624
+ className: "mt-1 block w-full cursor-pointer text-right text-xs font-semibold text-foreground/70 hover:text-foreground",
625
+ children: expanded ? "Show less" : "Show more"
626
+ })]
627
+ });
79
628
  }
80
629
 
81
630
  //#endregion
@@ -144,46 +693,63 @@ DashboardStandalonePageContent.displayName = "DashboardStandalonePageContent";
144
693
 
145
694
  //#endregion
146
695
  //#region src/components/dashboard-page.tsx
147
- function DashboardPage({ className, ...props }) {
696
+ const DashboardPage = React.forwardRef((componentProps, forwardRef) => {
697
+ const { className, ...props } = componentProps;
148
698
  return /* @__PURE__ */ jsx("div", {
699
+ ref: forwardRef,
149
700
  "data-slot": "dashboard-page",
150
701
  className: cn("flex flex-col h-screen bg-page-background p-2 gap-2", className),
151
702
  ...props
152
703
  });
153
- }
154
- function DashboardPageBanner({ className, ...props }) {
704
+ });
705
+ DashboardPage.displayName = "DashboardPage";
706
+ const DashboardPageBanner = React.forwardRef((componentProps, forwardRef) => {
707
+ const { className, ...props } = componentProps;
155
708
  return /* @__PURE__ */ jsx("div", {
709
+ ref: forwardRef,
156
710
  "data-slot": "dashboard-page-banner",
157
711
  className: cn("-mt-2 -mx-2", className),
158
712
  ...props
159
713
  });
160
- }
161
- function DashboardPageContent({ className, ...props }) {
714
+ });
715
+ DashboardPageBanner.displayName = "DashboardPageBanner";
716
+ const DashboardPageContent = React.forwardRef((componentProps, forwardRef) => {
717
+ const { className, ...props } = componentProps;
162
718
  return /* @__PURE__ */ jsx("div", {
719
+ ref: forwardRef,
163
720
  "data-slot": "dashboard-page-content",
164
721
  className: cn("flex-1 min-w-0 flex flex-col bg-background rounded-xl border border-border overflow-hidden", "[&:has([data-slot=dph-tabs])>[data-slot=dph]]:border-b-0", className),
165
722
  ...props
166
723
  });
167
- }
168
- function DashboardPageMain({ className, ...props }) {
724
+ });
725
+ DashboardPageContent.displayName = "DashboardPageContent";
726
+ const DashboardPageMain = React.forwardRef((componentProps, forwardRef) => {
727
+ const { className, ...props } = componentProps;
169
728
  return /* @__PURE__ */ jsx("main", {
729
+ ref: forwardRef,
170
730
  "data-slot": "dashboard-page-main",
171
731
  className: cn("flex-1 min-h-0 overflow-auto py-4 px-6", className),
172
732
  ...props
173
733
  });
174
- }
175
- function DashboardPageTabs({ className, ...props }) {
734
+ });
735
+ DashboardPageMain.displayName = "DashboardPageMain";
736
+ const DashboardPageTabs = React.forwardRef((componentProps, forwardRef) => {
737
+ const { className, ...props } = componentProps;
176
738
  return /* @__PURE__ */ jsx(TabsList, {
739
+ ref: forwardRef,
177
740
  ...props,
178
741
  "data-slot": "dph-tabs",
179
742
  className: cn("flex w-full justify-start gap-0 px-6 border-b border-border rounded-none bg-transparent", "[&>[data-slot=tabs-trigger]]:flex-none", "[&>[data-slot=tabs-trigger]]:rounded-none", "[&>[data-slot=tabs-trigger]]:after:hidden", "[&>[data-slot=tabs-trigger]]:border-b-2", "[&>[data-slot=tabs-trigger]]:border-b-transparent", "[&>[data-slot=tabs-trigger]]:-mb-px", "[&>[data-slot=tabs-trigger][data-active]]:border-b-foreground", className)
180
743
  });
181
- }
744
+ });
745
+ DashboardPageTabs.displayName = "DashboardPageTabs";
182
746
 
183
747
  //#endregion
184
748
  //#region src/components/dashboard-page-header.tsx
185
- function DashboardPageHeader({ className, children, ...props }) {
749
+ const DashboardPageHeader = React.forwardRef((componentProps, forwardRef) => {
750
+ const { className, children, ...props } = componentProps;
186
751
  return /* @__PURE__ */ jsx("div", {
752
+ ref: forwardRef,
187
753
  "data-slot": "dph",
188
754
  className: cn("@container bg-background border-b border-border w-full", className),
189
755
  ...props,
@@ -193,77 +759,105 @@ function DashboardPageHeader({ className, children, ...props }) {
193
759
  children
194
760
  })
195
761
  });
196
- }
197
- function DashboardPageHeaderNav({ className, children, ...props }) {
762
+ });
763
+ DashboardPageHeader.displayName = "DashboardPageHeader";
764
+ const DashboardPageHeaderNav = React.forwardRef((componentProps, forwardRef) => {
765
+ const { className, children, ...props } = componentProps;
198
766
  return /* @__PURE__ */ jsx("div", {
767
+ ref: forwardRef,
199
768
  "data-slot": "dph-nav",
200
769
  className: cn("[grid-area:nav] flex items-center gap-1", className),
201
770
  ...props,
202
771
  children
203
772
  });
204
- }
205
- function DashboardPageHeaderNavBack({ children, ...props }) {
773
+ });
774
+ DashboardPageHeaderNav.displayName = "DashboardPageHeaderNav";
775
+ const DashboardPageHeaderNavBack = React.forwardRef((componentProps, forwardRef) => {
776
+ const { children, ...props } = componentProps;
206
777
  return /* @__PURE__ */ jsxs(Button, {
778
+ ref: forwardRef,
207
779
  variant: "ghost",
208
780
  size: "sm",
209
781
  nativeButton: false,
210
782
  ...props,
211
783
  children: [/* @__PURE__ */ jsx(ChevronLeft, {}), children]
212
784
  });
213
- }
214
- function DashboardPageHeaderPrefix({ className, children, ...props }) {
785
+ });
786
+ DashboardPageHeaderNavBack.displayName = "DashboardPageHeaderNavBack";
787
+ const DashboardPageHeaderPrefix = React.forwardRef((componentProps, forwardRef) => {
788
+ const { className, children, ...props } = componentProps;
215
789
  return /* @__PURE__ */ jsx("div", {
790
+ ref: forwardRef,
216
791
  "data-slot": "dph-prefix",
217
792
  className: cn("[grid-area:prefix] flex shrink-0 px-2 pt-2 items-center justify-center self-start", className),
218
793
  ...props,
219
794
  children
220
795
  });
221
- }
222
- function DashboardPageHeaderTitleGroup({ className, children, ...props }) {
796
+ });
797
+ DashboardPageHeaderPrefix.displayName = "DashboardPageHeaderPrefix";
798
+ const DashboardPageHeaderTitleGroup = React.forwardRef((componentProps, forwardRef) => {
799
+ const { className, children, ...props } = componentProps;
223
800
  return /* @__PURE__ */ jsx("div", {
801
+ ref: forwardRef,
224
802
  "data-slot": "dph-title-group",
225
803
  className: cn("[grid-area:info] flex min-w-0 flex-col self-center pl-2", "group-has-[[data-slot=dph-prefix]]/dph:pl-0", className),
226
804
  ...props,
227
805
  children
228
806
  });
229
- }
230
- function DashboardPageHeaderTitle({ size = "lg", className, children, ...props }) {
807
+ });
808
+ DashboardPageHeaderTitleGroup.displayName = "DashboardPageHeaderTitleGroup";
809
+ const DashboardPageHeaderTitle = React.forwardRef((componentProps, forwardRef) => {
810
+ const { size = "lg", className, children, ...props } = componentProps;
231
811
  return /* @__PURE__ */ jsx("h1", {
812
+ ref: forwardRef,
232
813
  "data-slot": "dph-title",
233
814
  className: cn("[grid-area:info] self-center pl-2 group-has-[[data-slot=dph-prefix]]/dph:pl-0", "[[data-slot=dph-title-group]_&]:p-0", "[[data-slot=dph-title-group]_&]:self-auto", "truncate text-foreground", size === "lg" ? "text-3xl font-bold" : "text-xl font-bold", "leading-10", className),
234
815
  ...props,
235
816
  children
236
817
  });
237
- }
238
- function DashboardPageHeaderSubtitle({ className, children, ...props }) {
818
+ });
819
+ DashboardPageHeaderTitle.displayName = "DashboardPageHeaderTitle";
820
+ const DashboardPageHeaderSubtitle = React.forwardRef((componentProps, forwardRef) => {
821
+ const { className, children, ...props } = componentProps;
239
822
  return /* @__PURE__ */ jsx("div", {
823
+ ref: forwardRef,
240
824
  "data-slot": "dph-subtitle",
241
825
  className: cn("flex flex-col items-start gap-0.5 max-w-xl", className),
242
826
  ...props,
243
827
  children
244
828
  });
245
- }
246
- function DashboardPageHeaderDescription({ className, children, ...props }) {
829
+ });
830
+ DashboardPageHeaderSubtitle.displayName = "DashboardPageHeaderSubtitle";
831
+ const DashboardPageHeaderDescription = React.forwardRef((componentProps, forwardRef) => {
832
+ const { className, children, ...props } = componentProps;
247
833
  return /* @__PURE__ */ jsx("div", {
834
+ ref: forwardRef,
248
835
  "data-slot": "dph-description",
249
836
  className: cn("text-sm text-muted-foreground line-clamp-3 -mt-1", className),
250
837
  ...props,
251
838
  children
252
839
  });
253
- }
254
- function DashboardPageHeaderActions({ className, children, ...props }) {
840
+ });
841
+ DashboardPageHeaderDescription.displayName = "DashboardPageHeaderDescription";
842
+ const DashboardPageHeaderActions = React.forwardRef((componentProps, forwardRef) => {
843
+ const { className, children, ...props } = componentProps;
255
844
  return /* @__PURE__ */ jsx("div", {
845
+ ref: forwardRef,
256
846
  "data-slot": "dph-actions",
257
847
  className: cn("[grid-area:actions] flex items-center gap-2 self-start pl-2", className),
258
848
  ...props,
259
849
  children
260
850
  });
261
- }
262
- const DashboardPageHeaderAction = React.forwardRef(({ size = "lg", ...props }, ref) => /* @__PURE__ */ jsx(Button, {
263
- ref,
264
- size,
265
- ...props
266
- }));
851
+ });
852
+ DashboardPageHeaderActions.displayName = "DashboardPageHeaderActions";
853
+ const DashboardPageHeaderAction = React.forwardRef((componentProps, forwardRef) => {
854
+ const { size = "lg", ...props } = componentProps;
855
+ return /* @__PURE__ */ jsx(Button, {
856
+ ref: forwardRef,
857
+ size,
858
+ ...props
859
+ });
860
+ });
267
861
  DashboardPageHeaderAction.displayName = "DashboardPageHeaderAction";
268
862
 
269
863
  //#endregion
@@ -283,14 +877,16 @@ const PRODUCT_BADGE_LABEL = {
283
877
  };
284
878
  function createProductBadge(type) {
285
879
  const label = PRODUCT_BADGE_LABEL[type];
286
- function ProductBadge({ className, ...props }) {
880
+ const ProductBadge = React.forwardRef((componentProps, forwardRef) => {
881
+ const { className, ...props } = componentProps;
287
882
  return /* @__PURE__ */ jsx(Badge, {
883
+ ref: forwardRef,
288
884
  "data-slot": `badge-${type}`,
289
885
  className: cn(productBadgeVariants({ type }), className),
290
886
  ...props,
291
887
  children: label
292
888
  });
293
- }
889
+ });
294
890
  ProductBadge.displayName = label.replace(/\s+/g, "") + "Badge";
295
891
  return ProductBadge;
296
892
  }
@@ -303,15 +899,18 @@ const DeprecatedBadge = createProductBadge("deprecated");
303
899
  /** Blue pill — user is in a trial period (same blue family as Beta) */
304
900
  const TrialBadge = createProductBadge("trial");
305
901
  /** Charcoal pill — Professional tier (no gradient; solid secondary Badge) */
306
- function ProfessionalBadge({ className, ...props }) {
902
+ const ProfessionalBadge = React.forwardRef((componentProps, forwardRef) => {
903
+ const { className, ...props } = componentProps;
307
904
  return /* @__PURE__ */ jsx(Badge, {
905
+ ref: forwardRef,
308
906
  "data-slot": "badge-professional",
309
907
  tone: "dark",
310
908
  color: "charcoal",
311
909
  ...props,
312
910
  children: "Professional"
313
911
  });
314
- }
912
+ });
913
+ ProfessionalBadge.displayName = "ProfessionalBadge";
315
914
  const roleBadgeVariants = cva(GRADIENT_BASE, { variants: { role: {
316
915
  agent: "[--badge-accent:var(--color-yellow-400)]",
317
916
  supervisor: "[--badge-accent:var(--color-blue-500)]",
@@ -324,19 +923,560 @@ const ROLE_BADGE_LABEL = {
324
923
  admin: "Admin",
325
924
  owner: "Owner"
326
925
  };
327
- function RoleBadge({ role, className, ...props }) {
926
+ const RoleBadge = React.forwardRef((componentProps, forwardRef) => {
927
+ const { role, className, ...props } = componentProps;
328
928
  return /* @__PURE__ */ jsx(Badge, {
929
+ ref: forwardRef,
329
930
  "data-slot": `badge-${role}`,
330
931
  className: cn(roleBadgeVariants({ role }), className),
331
932
  ...props,
332
933
  children: ROLE_BADGE_LABEL[role]
333
934
  });
935
+ });
936
+ RoleBadge.displayName = "RoleBadge";
937
+
938
+ //#endregion
939
+ //#region src/components/chatbot-panel-trigger.tsx
940
+ /**
941
+ * Floating trigger that shows / hides the chatbot panel.
942
+ *
943
+ * Rests as a 40px icon-only circle and expands into a pill that reveals its
944
+ * label on hover and keyboard focus. The four Figma states map to real CSS
945
+ * pseudo-states: default (rest), hover, focus (`:focus-visible` ring) and
946
+ * pressed (`:active`, dimmed to 60%).
947
+ */
948
+ function ChatbotPanelTrigger({ children = "Ask anything", icon = /* @__PURE__ */ jsx(AircallLogo, {}), className, ...buttonProps }) {
949
+ return /* @__PURE__ */ jsxs(Button, {
950
+ "data-slot": "chatbot-panel-trigger",
951
+ className: cn("h-10 min-w-10 gap-0 rounded-full px-[9px] text-base", "border-white/20 bg-clip-border", "bg-green-600 shadow-[0px_0px_6px_0px_var(--color-green-500)]", "hover:bg-[color-mix(in_srgb,var(--color-green-600)_90%,white)]", "hover:shadow-[0px_0px_6px_0px_var(--color-green-500),0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]", "focus-visible:shadow-none focus-visible:ring-[3px] focus-visible:ring-[color-mix(in_srgb,var(--color-green-400)_40%,transparent)]", "active:bg-green-600 active:shadow-[0px_0px_4px_0px_var(--color-green-400)]", className),
952
+ ...buttonProps,
953
+ children: [/* @__PURE__ */ jsx("span", {
954
+ className: "grid size-5 shrink-0 place-items-center [&_svg]:size-5",
955
+ children: icon
956
+ }), /* @__PURE__ */ jsx("span", {
957
+ className: cn("max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-200", "group-hover/button:max-w-[12rem] group-hover/button:opacity-100", "group-focus-visible/button:max-w-[12rem] group-focus-visible/button:opacity-100", "group-active/button:max-w-[12rem] group-active/button:opacity-100"),
958
+ children: /* @__PURE__ */ jsx("span", {
959
+ className: "block pl-1 leading-6 whitespace-nowrap",
960
+ children
961
+ })
962
+ })]
963
+ });
964
+ }
965
+
966
+ //#endregion
967
+ //#region src/form/form-context.ts
968
+ /**
969
+ * TanStack Form contexts for `@aircall/blocks`.
970
+ *
971
+ * - `fieldContext` / `formContext` — pass to `createFormHook` when extending `useForm` with custom field or form components.
972
+ * - `useFieldContext<T>()` — read the bound field's value, meta, and handlers inside a custom field component.
973
+ * - `useFormContext()` — read form-level state (isDirty, isSubmitting, …) inside a custom form component.
974
+ *
975
+ * @example
976
+ * // Extending useForm with a custom SwitchField
977
+ * const { useAppForm } = createFormHook({ fieldContext, formContext, fieldComponents: { SwitchField }, formComponents: {} })
978
+ */
979
+ const { fieldContext, formContext, useFieldContext, useFormContext } = createFormHookContexts();
980
+
981
+ //#endregion
982
+ //#region src/components/card-save-bar.tsx
983
+ function CardSaveBar({ submitLabel = "Save changes", savingLabel = "Saving…", resetLabel = "Discard" }) {
984
+ const form = useFormContext();
985
+ return /* @__PURE__ */ jsx(form.Subscribe, {
986
+ selector: (s) => ({
987
+ isDirty: s.isDirty,
988
+ isSubmitting: s.isSubmitting,
989
+ canSubmit: s.canSubmit
990
+ }),
991
+ children: ({ isDirty, isSubmitting, canSubmit }) => /* @__PURE__ */ jsx("div", {
992
+ className: cn("grid transition-[grid-template-rows] duration-300 ease-in-out", isDirty ? "grid-rows-[1fr]" : "grid-rows-[0fr]"),
993
+ children: /* @__PURE__ */ jsx("div", {
994
+ className: "overflow-hidden",
995
+ children: /* @__PURE__ */ jsxs("div", {
996
+ className: "flex items-center justify-end gap-2 border-t px-6 py-3",
997
+ children: [/* @__PURE__ */ jsx(Button, {
998
+ type: "button",
999
+ variant: "ghost",
1000
+ onClick: () => form.reset(),
1001
+ disabled: !isDirty || isSubmitting,
1002
+ children: resetLabel
1003
+ }), /* @__PURE__ */ jsx(Button, {
1004
+ type: "submit",
1005
+ disabled: !isDirty || !canSubmit || isSubmitting,
1006
+ children: isSubmitting ? savingLabel : submitLabel
1007
+ })]
1008
+ })
1009
+ })
1010
+ })
1011
+ });
1012
+ }
1013
+
1014
+ //#endregion
1015
+ //#region src/components/submit-button.tsx
1016
+ function SubmitButton({ children = "Submit", loadingLabel, className, ...buttonProps }) {
1017
+ return /* @__PURE__ */ jsx(useFormContext().Subscribe, {
1018
+ selector: (s) => ({
1019
+ isSubmitting: s.isSubmitting,
1020
+ canSubmit: s.canSubmit
1021
+ }),
1022
+ children: ({ isSubmitting, canSubmit }) => /* @__PURE__ */ jsx(Button, {
1023
+ type: "submit",
1024
+ disabled: !canSubmit || isSubmitting,
1025
+ className: cn(className),
1026
+ ...buttonProps,
1027
+ children: isSubmitting ? loadingLabel ?? "Saving…" : children
1028
+ })
1029
+ });
1030
+ }
1031
+
1032
+ //#endregion
1033
+ //#region src/form/use-form.ts
1034
+ /**
1035
+ * Pre-configured TanStack Form hook for `@aircall/blocks`.
1036
+ *
1037
+ * Fields are built with the composable, typed `Form*Field` wrappers (e.g. `FormSelectField`),
1038
+ * which take the `form` as a prop — there are no registered `fieldComponents`. Only the
1039
+ * form-level UI (`SubmitButton`, `CardSaveBar`) is registered, so it's available as
1040
+ * `form.SubmitButton` / `form.CardSaveBar` inside `form.AppForm`.
1041
+ *
1042
+ * `CommonForm` is the full form object — use it directly or extend it:
1043
+ * - `CommonForm.useAppForm` — primary hook; types inferred from `defaultValues`
1044
+ * - `CommonForm.withForm` — HOC equivalent for higher-order function patterns
1045
+ *
1046
+ * @example
1047
+ * const form = useForm({ defaultValues: { name: '' }, onSubmit: async ({ value }) => save(value) })
1048
+ */
1049
+ const CommonForm = createFormHook({
1050
+ fieldContext,
1051
+ formContext,
1052
+ fieldComponents: {},
1053
+ formComponents: {
1054
+ SubmitButton,
1055
+ CardSaveBar
1056
+ }
1057
+ });
1058
+ const useForm = CommonForm.useAppForm;
1059
+ const { withForm } = CommonForm;
1060
+
1061
+ //#endregion
1062
+ //#region src/form/field-errors.ts
1063
+ /**
1064
+ * Normalize a TanStack Form field's `meta.errors` into the `{ message }[]` shape
1065
+ * that `@aircall/ds`'s `FieldError` expects.
1066
+ *
1067
+ * Errors come from two validator sources with different shapes:
1068
+ * - per-field validators return plain strings (e.g. `'Email is required'`)
1069
+ * - standard-schema validators (e.g. Zod on `useForm`) return issue **objects** with a `message`
1070
+ *
1071
+ * Both must render their human-readable text — never `"[object Object]"`.
1072
+ */
1073
+ function toFieldErrors(errors) {
1074
+ return errors.map((error) => ({ message: typeof error === "string" ? error : error?.message ?? String(error) }));
1075
+ }
1076
+
1077
+ //#endregion
1078
+ //#region src/components/form-field.tsx
1079
+ function FormFieldShell({ label, description, children }) {
1080
+ const errors = useFieldContext().state.meta.errors;
1081
+ return /* @__PURE__ */ jsxs(Field, {
1082
+ "data-invalid": errors.length > 0 ? true : void 0,
1083
+ children: [
1084
+ /* @__PURE__ */ jsx(FieldLabel, { children: label }),
1085
+ description && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
1086
+ children,
1087
+ /* @__PURE__ */ jsx(FieldError, { errors: toFieldErrors(errors) })
1088
+ ]
1089
+ });
1090
+ }
1091
+ /**
1092
+ * Shared runtime binder for the per-primitive wrappers (FormSelectField, FormInputField, …).
1093
+ * Field-name/value/validator typing is enforced at each wrapper's boundary; this just renders
1094
+ * the AppField + shared shell. `TData`/`TControl` carry the precise field + bundle shapes through.
1095
+ */
1096
+ function FormFieldBase(props) {
1097
+ const { form, name, validators, label, description, buildControl, children } = props;
1098
+ const AppField = form.AppField;
1099
+ return /* @__PURE__ */ jsx(AppField, {
1100
+ name,
1101
+ validators,
1102
+ children: (field) => /* @__PURE__ */ jsx(FormFieldShell, {
1103
+ label,
1104
+ description,
1105
+ children: children(field, buildControl(field))
1106
+ })
1107
+ });
1108
+ }
1109
+
1110
+ //#endregion
1111
+ //#region src/components/form-select-field.tsx
1112
+ function selectControl(field) {
1113
+ return {
1114
+ selectProps: {
1115
+ value: field.state.value || null,
1116
+ onValueChange: (value) => field.handleChange(value ?? "")
1117
+ },
1118
+ selectTriggerProps: {
1119
+ onBlur: field.handleBlur,
1120
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1121
+ }
1122
+ };
1123
+ }
1124
+ /**
1125
+ * Select field wrapper. Owns the binding + shared `Field/Label/Error` shell, and hands the
1126
+ * consumer pre-wired `selectProps` / `selectTriggerProps` to spread onto `@aircall/ds` Select
1127
+ * primitives — trigger, value, and content stay fully composable (Combobox-style triggers too).
1128
+ *
1129
+ * Typing is derived from the threaded `form`: `name` is restricted to the form's **string**
1130
+ * fields, and `validators` infer the field value — identical to using `form.AppField` directly.
1131
+ *
1132
+ * @example
1133
+ * <FormSelectField form={form} name="language" label="Language" validators={{ onChange }}>
1134
+ * {(field, { selectProps, selectTriggerProps }) => (
1135
+ * <Select {...selectProps}>
1136
+ * <SelectTrigger {...selectTriggerProps}><SelectValue placeholder="Pick one" /></SelectTrigger>
1137
+ * <SelectContent>{options}</SelectContent>
1138
+ * </Select>
1139
+ * )}
1140
+ * </FormSelectField>
1141
+ */
1142
+ function FormSelectField(props) {
1143
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1144
+ ...props,
1145
+ buildControl: selectControl
1146
+ });
1147
+ }
1148
+
1149
+ //#endregion
1150
+ //#region src/components/form-input-field.tsx
1151
+ function inputControl(field) {
1152
+ return { inputProps: {
1153
+ value: field.state.value ?? "",
1154
+ onChange: (event) => field.handleChange(event.target.value),
1155
+ onBlur: field.handleBlur,
1156
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1157
+ } };
1158
+ }
1159
+ /**
1160
+ * Text-input field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1161
+ * with an `inputProps` bundle. Compose the input yourself (add adornments, counters, prefixes).
1162
+ * `name` is restricted to the form's **string** fields.
1163
+ *
1164
+ * @example
1165
+ * <FormInputField form={form} name="email" label="Email" validators={{ onChange }}>
1166
+ * {(field, { inputProps }) => <Input {...inputProps} type="email" placeholder="you@co.com" />}
1167
+ * </FormInputField>
1168
+ */
1169
+ function FormInputField(props) {
1170
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1171
+ ...props,
1172
+ buildControl: inputControl
1173
+ });
1174
+ }
1175
+
1176
+ //#endregion
1177
+ //#region src/components/form-combobox-field.tsx
1178
+ function comboboxControl(field) {
1179
+ return {
1180
+ comboboxProps: {
1181
+ value: field.state.value || null,
1182
+ onValueChange: (value) => field.handleChange(value ?? "")
1183
+ },
1184
+ comboboxInputProps: {
1185
+ onBlur: field.handleBlur,
1186
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1187
+ }
1188
+ };
1189
+ }
1190
+ /**
1191
+ * Combobox (searchable single-select) field wrapper for **string-valued** fields. Same threaded-
1192
+ * `form` typing and shared shell as the other wrappers; the render-prop hands you `comboboxProps`
1193
+ * and `comboboxInputProps` to spread, leaving the input, trigger, grouping, and item rendering
1194
+ * entirely up to you — the case that motivated the render-prop API.
1195
+ *
1196
+ * @example
1197
+ * <FormComboboxField form={form} name="framework" label="Framework" validators={{ onChange }}>
1198
+ * {(field, { comboboxProps, comboboxInputProps }) => (
1199
+ * <Combobox items={FRAMEWORKS} {...comboboxProps}>
1200
+ * <ComboboxInput {...comboboxInputProps} placeholder="Search…" />
1201
+ * <ComboboxContent>
1202
+ * <ComboboxEmpty>No match.</ComboboxEmpty>
1203
+ * <ComboboxList>{item => <ComboboxItem key={item} value={item}>{item}</ComboboxItem>}</ComboboxList>
1204
+ * </ComboboxContent>
1205
+ * </Combobox>
1206
+ * )}
1207
+ * </FormComboboxField>
1208
+ */
1209
+ function FormComboboxField(props) {
1210
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1211
+ ...props,
1212
+ buildControl: comboboxControl
1213
+ });
1214
+ }
1215
+
1216
+ //#endregion
1217
+ //#region src/components/form-multi-combobox-field.tsx
1218
+ function multiComboboxControl(field) {
1219
+ return {
1220
+ comboboxProps: {
1221
+ value: field.state.value ?? [],
1222
+ onValueChange: (values) => field.handleChange(values)
1223
+ },
1224
+ comboboxInputProps: {
1225
+ onBlur: field.handleBlur,
1226
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1227
+ }
1228
+ };
1229
+ }
1230
+ /**
1231
+ * Multi-select combobox field wrapper — same threaded-`form` typing and shared shell as
1232
+ * `FormSelectField`, with `comboboxProps` / `comboboxInputProps` bundles. `name` is restricted
1233
+ * to the form's **string[]** fields — a string/number `name` is a type error.
1234
+ *
1235
+ * @example
1236
+ * <FormMultiComboboxField form={form} name="tags" label="Tags" validators={{ onChange }}>
1237
+ * {(field, { comboboxProps, comboboxInputProps }) => (
1238
+ * <Combobox multiple items={items} {...comboboxProps}>
1239
+ * <ComboboxChips><ComboboxInput {...comboboxInputProps} /></ComboboxChips>
1240
+ * </Combobox>
1241
+ * )}
1242
+ * </FormMultiComboboxField>
1243
+ */
1244
+ function FormMultiComboboxField(props) {
1245
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1246
+ ...props,
1247
+ buildControl: multiComboboxControl
1248
+ });
1249
+ }
1250
+
1251
+ //#endregion
1252
+ //#region src/components/form-textarea-field.tsx
1253
+ function textareaControl(field) {
1254
+ return { textareaProps: {
1255
+ value: field.state.value ?? "",
1256
+ onChange: (event) => field.handleChange(event.target.value),
1257
+ onBlur: field.handleBlur,
1258
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1259
+ } };
1260
+ }
1261
+ /**
1262
+ * Multiline text-input field wrapper — same threaded-`form` typing and shared shell as
1263
+ * `FormSelectField`, with a `textareaProps` bundle. Compose the textarea yourself (rows,
1264
+ * counters, auto-grow). `name` is restricted to the form's **string** fields.
1265
+ *
1266
+ * @example
1267
+ * <FormTextareaField form={form} name="bio" label="Bio" validators={{ onChange }}>
1268
+ * {(field, { textareaProps }) => <Textarea {...textareaProps} rows={4} />}
1269
+ * </FormTextareaField>
1270
+ */
1271
+ function FormTextareaField(props) {
1272
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1273
+ ...props,
1274
+ buildControl: textareaControl
1275
+ });
1276
+ }
1277
+
1278
+ //#endregion
1279
+ //#region src/components/form-radio-group-field.tsx
1280
+ function radioGroupControl(field) {
1281
+ return { radioGroupProps: {
1282
+ value: field.state.value || null,
1283
+ onValueChange: (value) => field.handleChange(value ?? ""),
1284
+ onBlur: field.handleBlur,
1285
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1286
+ } };
1287
+ }
1288
+ /**
1289
+ * Radio-group field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1290
+ * with a `radioGroupProps` bundle. Compose the items yourself. `name` is restricted to the
1291
+ * form's **string** fields.
1292
+ *
1293
+ * @example
1294
+ * <FormRadioGroupField form={form} name="plan" label="Plan" validators={{ onChange }}>
1295
+ * {(field, { radioGroupProps }) => (
1296
+ * <RadioGroup {...radioGroupProps}>
1297
+ * <RadioGroupItem value="a" />
1298
+ * </RadioGroup>
1299
+ * )}
1300
+ * </FormRadioGroupField>
1301
+ */
1302
+ function FormRadioGroupField(props) {
1303
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1304
+ ...props,
1305
+ buildControl: radioGroupControl
1306
+ });
1307
+ }
1308
+
1309
+ //#endregion
1310
+ //#region src/components/form-otp-field.tsx
1311
+ function otpControl(field) {
1312
+ return { otpProps: {
1313
+ value: field.state.value ?? "",
1314
+ onChange: (value) => field.handleChange(value),
1315
+ onBlur: field.handleBlur,
1316
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1317
+ } };
1318
+ }
1319
+ /**
1320
+ * One-time-code field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1321
+ * with an `otpProps` bundle. Compose the slots yourself. `name` is restricted to the form's
1322
+ * **string** fields.
1323
+ *
1324
+ * @example
1325
+ * <FormOTPField form={form} name="code" label="Code" validators={{ onChange }}>
1326
+ * {(field, { otpProps }) => <InputOTP maxLength={6} {...otpProps}>{slots}</InputOTP>}
1327
+ * </FormOTPField>
1328
+ */
1329
+ function FormOTPField(props) {
1330
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1331
+ ...props,
1332
+ buildControl: otpControl
1333
+ });
1334
+ }
1335
+
1336
+ //#endregion
1337
+ //#region src/components/form-switch-field.tsx
1338
+ function switchControl(field) {
1339
+ return { switchProps: {
1340
+ checked: field.state.value,
1341
+ onCheckedChange: (checked) => field.handleChange(checked),
1342
+ onBlur: field.handleBlur,
1343
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1344
+ } };
1345
+ }
1346
+ /**
1347
+ * Toggle field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1348
+ * with a `switchProps` bundle. `name` is restricted to the form's **boolean** fields — a
1349
+ * string/number `name` is a type error.
1350
+ *
1351
+ * @example
1352
+ * <FormSwitchField form={form} name="enabled" label="Enabled" validators={{ onChange }}>
1353
+ * {(field, { switchProps }) => <Switch {...switchProps} />}
1354
+ * </FormSwitchField>
1355
+ */
1356
+ function FormSwitchField(props) {
1357
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1358
+ ...props,
1359
+ buildControl: switchControl
1360
+ });
1361
+ }
1362
+
1363
+ //#endregion
1364
+ //#region src/components/form-number-field.tsx
1365
+ function numberControl(field) {
1366
+ return { numberProps: {
1367
+ value: field.state.value,
1368
+ onChange: (event) => field.handleChange(event.target.value === "" ? 0 : event.target.valueAsNumber),
1369
+ onBlur: field.handleBlur,
1370
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1371
+ } };
1372
+ }
1373
+ /**
1374
+ * Numeric-input field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1375
+ * with a `numberProps` bundle. `name` is restricted to the form's **number** fields — a
1376
+ * string/boolean `name` is a type error.
1377
+ *
1378
+ * @example
1379
+ * <FormNumberField form={form} name="age" label="Age" validators={{ onChange }}>
1380
+ * {(field, { numberProps }) => <Input type="number" {...numberProps} />}
1381
+ * </FormNumberField>
1382
+ */
1383
+ function FormNumberField(props) {
1384
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1385
+ ...props,
1386
+ buildControl: numberControl
1387
+ });
1388
+ }
1389
+
1390
+ //#endregion
1391
+ //#region src/components/form-slider-field.tsx
1392
+ function sliderControl(field) {
1393
+ return { sliderProps: {
1394
+ value: [field.state.value],
1395
+ onValueChange: (values) => field.handleChange(values[0] ?? 0),
1396
+ onBlur: field.handleBlur
1397
+ } };
1398
+ }
1399
+ /**
1400
+ * Slider field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1401
+ * with a `sliderProps` bundle. `name` is restricted to the form's **number** fields; the bundle
1402
+ * converts to/from the `number[]` the range primitive uses internally.
1403
+ *
1404
+ * @example
1405
+ * <FormSliderField form={form} name="volume" label="Volume" validators={{ onChange }}>
1406
+ * {(field, { sliderProps }) => <Slider min={0} max={100} step={1} {...sliderProps} />}
1407
+ * </FormSliderField>
1408
+ */
1409
+ function FormSliderField(props) {
1410
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1411
+ ...props,
1412
+ buildControl: sliderControl
1413
+ });
1414
+ }
1415
+
1416
+ //#endregion
1417
+ //#region src/components/form-toggle-group-field.tsx
1418
+ function toggleGroupControl(field) {
1419
+ return { toggleGroupProps: {
1420
+ value: field.state.value ? [field.state.value] : [],
1421
+ onValueChange: (values) => field.handleChange(values[values.length - 1] ?? null),
1422
+ onBlur: field.handleBlur
1423
+ } };
1424
+ }
1425
+ /**
1426
+ * Single-select toggle-group field wrapper — same threaded-`form` typing and shared shell as
1427
+ * `FormSelectField`, with a `toggleGroupProps` bundle. `name` is restricted to the form's
1428
+ * **string | null** fields; the bundle converts to/from the `string[]` the multi-select
1429
+ * primitive uses internally.
1430
+ *
1431
+ * @example
1432
+ * <FormToggleGroupField form={form} name="align" label="Align" validators={{ onChange }}>
1433
+ * {(field, { toggleGroupProps }) => (
1434
+ * <ToggleGroup {...toggleGroupProps}>
1435
+ * <ToggleGroupItem value="a">A</ToggleGroupItem>
1436
+ * </ToggleGroup>
1437
+ * )}
1438
+ * </FormToggleGroupField>
1439
+ */
1440
+ function FormToggleGroupField(props) {
1441
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1442
+ ...props,
1443
+ buildControl: toggleGroupControl
1444
+ });
1445
+ }
1446
+
1447
+ //#endregion
1448
+ //#region src/components/save-bar.tsx
1449
+ function SaveBar({ isDirty, isSubmitting, canSubmit, onReset, submitLabel = "Save changes", savingLabel = "Saving…", resetLabel = "Discard", className }) {
1450
+ return /* @__PURE__ */ jsx("div", {
1451
+ className: cn("grid transition-[grid-template-rows] duration-300 ease-in-out", isDirty ? "grid-rows-[1fr]" : "grid-rows-[0fr]", className),
1452
+ children: /* @__PURE__ */ jsx("div", {
1453
+ className: "overflow-hidden",
1454
+ children: /* @__PURE__ */ jsxs("div", {
1455
+ className: "flex items-center justify-end gap-2 border-t px-4 py-2",
1456
+ children: [/* @__PURE__ */ jsx(Button, {
1457
+ type: "button",
1458
+ variant: "ghost",
1459
+ size: "sm",
1460
+ onClick: onReset,
1461
+ disabled: !isDirty || isSubmitting,
1462
+ children: resetLabel
1463
+ }), /* @__PURE__ */ jsx(Button, {
1464
+ type: "submit",
1465
+ size: "sm",
1466
+ disabled: !isDirty || !canSubmit || isSubmitting,
1467
+ children: isSubmitting ? savingLabel : submitLabel
1468
+ })]
1469
+ })
1470
+ })
1471
+ });
334
1472
  }
335
1473
 
336
1474
  //#endregion
337
1475
  //#region src/components/dashboard-sidebar.tsx
338
- function DashboardSidebarProvider({ className, style, ...props }) {
1476
+ const DashboardSidebarProvider = React.forwardRef((componentProps, forwardRef) => {
1477
+ const { className, style, ...props } = componentProps;
339
1478
  return /* @__PURE__ */ jsx(SidebarProvider, {
1479
+ ref: forwardRef,
340
1480
  className: cn("!min-h-0 !w-auto flex flex-1 min-h-0 gap-2", "[&>[data-slot=tabs]]:flex-1 [&>[data-slot=tabs]]:min-w-0 [&>[data-slot=tabs]]:min-h-0 [&>[data-slot=tabs]]:flex [&>[data-slot=tabs]]:flex-col", className),
341
1481
  style: {
342
1482
  "--sidebar-width": "14rem",
@@ -345,10 +1485,13 @@ function DashboardSidebarProvider({ className, style, ...props }) {
345
1485
  },
346
1486
  ...props
347
1487
  });
348
- }
349
- function DashboardSidebar({ className, children, ...props }) {
1488
+ });
1489
+ DashboardSidebarProvider.displayName = "DashboardSidebarProvider";
1490
+ const DashboardSidebar = React.forwardRef((componentProps, forwardRef) => {
1491
+ const { className, children, ...props } = componentProps;
350
1492
  const { state } = useSidebar();
351
1493
  return /* @__PURE__ */ jsx("div", {
1494
+ ref: forwardRef,
352
1495
  "data-slot": "dashboard-sidebar",
353
1496
  "data-state": state,
354
1497
  "data-collapsible": state === "collapsed" ? "icon" : "",
@@ -356,18 +1499,24 @@ function DashboardSidebar({ className, children, ...props }) {
356
1499
  ...props,
357
1500
  children
358
1501
  });
359
- }
360
- function DashboardSidebarHeader({ className, children, ...props }) {
1502
+ });
1503
+ DashboardSidebar.displayName = "DashboardSidebar";
1504
+ const DashboardSidebarHeader = React.forwardRef((componentProps, forwardRef) => {
1505
+ const { className, children, ...props } = componentProps;
361
1506
  return /* @__PURE__ */ jsx("div", {
1507
+ ref: forwardRef,
362
1508
  "data-slot": "dashboard-sidebar-header",
363
1509
  className: cn("flex items-start gap-0 p-1 rounded-md shrink-0 justify-end w-full", "group-data-[collapsible=icon]/dbs:justify-center", className),
364
1510
  ...props,
365
1511
  children
366
1512
  });
367
- }
368
- function DashboardSidebarTrigger({ className, onClick, ...props }) {
1513
+ });
1514
+ DashboardSidebarHeader.displayName = "DashboardSidebarHeader";
1515
+ const DashboardSidebarTrigger = React.forwardRef((componentProps, forwardRef) => {
1516
+ const { className, onClick, ...props } = componentProps;
369
1517
  const { toggleSidebar, state } = useSidebar();
370
1518
  return /* @__PURE__ */ jsx(Button, {
1519
+ ref: forwardRef,
371
1520
  "data-slot": "dashboard-sidebar-trigger",
372
1521
  variant: "ghost",
373
1522
  size: "icon",
@@ -380,51 +1529,73 @@ function DashboardSidebarTrigger({ className, onClick, ...props }) {
380
1529
  ...props,
381
1530
  children: state === "expanded" ? /* @__PURE__ */ jsx(PanelLeftClose, { className: "size-4" }) : /* @__PURE__ */ jsx(PanelLeftOpen, { className: "size-4" })
382
1531
  });
383
- }
384
- function DashboardSidebarContent({ className, ...props }) {
1532
+ });
1533
+ DashboardSidebarTrigger.displayName = "DashboardSidebarTrigger";
1534
+ const DashboardSidebarContent = React.forwardRef((componentProps, forwardRef) => {
1535
+ const { className, ...props } = componentProps;
385
1536
  return /* @__PURE__ */ jsx("div", {
1537
+ ref: forwardRef,
386
1538
  "data-slot": "dashboard-sidebar-content",
387
1539
  className: cn("flex flex-1 flex-col overflow-y-auto overflow-x-hidden min-h-0", "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className),
388
1540
  ...props
389
1541
  });
390
- }
391
- function DashboardSidebarFooter({ className, ...props }) {
1542
+ });
1543
+ DashboardSidebarContent.displayName = "DashboardSidebarContent";
1544
+ const DashboardSidebarFooter = React.forwardRef((componentProps, forwardRef) => {
1545
+ const { className, ...props } = componentProps;
392
1546
  return /* @__PURE__ */ jsx("div", {
1547
+ ref: forwardRef,
393
1548
  "data-slot": "dashboard-sidebar-footer",
394
1549
  className: cn("bg-background/80 dark:bg-accent/50 rounded-xl p-1 flex flex-col shrink-0", "items-start w-full", "group-data-[collapsible=icon]/dbs:items-center group-data-[collapsible=icon]/dbs:w-auto", className),
395
1550
  ...props
396
1551
  });
397
- }
398
- function DashboardSidebarGroup({ className, ...props }) {
1552
+ });
1553
+ DashboardSidebarFooter.displayName = "DashboardSidebarFooter";
1554
+ const DashboardSidebarGroup = React.forwardRef((componentProps, forwardRef) => {
1555
+ const { className, ...props } = componentProps;
399
1556
  return /* @__PURE__ */ jsx("div", {
1557
+ ref: forwardRef,
400
1558
  "data-slot": "dashboard-sidebar-group",
401
1559
  className: cn("flex flex-col gap-1 items-start p-1 w-full", className),
402
1560
  ...props
403
1561
  });
404
- }
405
- function DashboardSidebarGroupLabel({ className, render, ...props }) {
1562
+ });
1563
+ DashboardSidebarGroup.displayName = "DashboardSidebarGroup";
1564
+ const DashboardSidebarGroupLabel = React.forwardRef((componentProps, forwardRef) => {
1565
+ const { className, render, ...props } = componentProps;
406
1566
  return useRender({
407
1567
  defaultTagName: "div",
408
- props: mergeProps({ className: cn("truncate text-sm font-medium text-navbar-foreground opacity-90 px-2 py-1.5 rounded-md w-full", "group-data-[collapsible=icon]/dbs:hidden", className) }, props),
1568
+ props: mergeProps({
1569
+ ref: forwardRef,
1570
+ className: cn("truncate text-sm font-medium text-navbar-foreground opacity-90 px-2 py-1.5 rounded-md w-full", "group-data-[collapsible=icon]/dbs:hidden", className)
1571
+ }, props),
409
1572
  render,
410
1573
  state: { slot: "dashboard-sidebar-group-label" }
411
1574
  });
412
- }
413
- function DashboardSidebarMenu({ className, ...props }) {
1575
+ });
1576
+ DashboardSidebarGroupLabel.displayName = "DashboardSidebarGroupLabel";
1577
+ const DashboardSidebarMenu = React.forwardRef((componentProps, forwardRef) => {
1578
+ const { className, ...props } = componentProps;
414
1579
  return /* @__PURE__ */ jsx("ul", {
1580
+ ref: forwardRef,
415
1581
  "data-slot": "dashboard-sidebar-menu",
416
1582
  className: cn("flex w-full min-w-0 flex-col gap-1", className),
417
1583
  ...props
418
1584
  });
419
- }
420
- function DashboardSidebarMenuItem({ className, ...props }) {
1585
+ });
1586
+ DashboardSidebarMenu.displayName = "DashboardSidebarMenu";
1587
+ const DashboardSidebarMenuItem = React.forwardRef((componentProps, forwardRef) => {
1588
+ const { className, ...props } = componentProps;
421
1589
  return /* @__PURE__ */ jsx("li", {
1590
+ ref: forwardRef,
422
1591
  "data-slot": "dashboard-sidebar-menu-item",
423
1592
  className: cn("relative list-none", className),
424
1593
  ...props
425
1594
  });
426
- }
427
- const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMenuButton({ className, render, isActive = false, disabled = false, icon, action, children, ...props }, ref) {
1595
+ });
1596
+ DashboardSidebarMenuItem.displayName = "DashboardSidebarMenuItem";
1597
+ const DashboardSidebarMenuButton = React.forwardRef((componentProps, forwardRef) => {
1598
+ const { className, render, isActive = false, disabled = false, icon, action, children, ...props } = componentProps;
428
1599
  const buttonContent = /* @__PURE__ */ jsxs(Fragment, { children: [
429
1600
  icon && /* @__PURE__ */ jsx("span", {
430
1601
  className: cn("size-6 rounded-full bg-background text-foreground flex items-center justify-center shrink-0 overflow-hidden", "[&>svg]:size-4 [&>svg]:shrink-0"),
@@ -442,7 +1613,7 @@ const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMen
442
1613
  return useRender({
443
1614
  defaultTagName: "button",
444
1615
  props: mergeProps({
445
- ref,
1616
+ ref: forwardRef,
446
1617
  type: "button",
447
1618
  "data-active": isActive,
448
1619
  className: cn("peer/dbs-button relative flex w-full items-center gap-2 h-10 p-2 rounded-full overflow-hidden", "text-sm font-medium transition-[width,padding,color,background-color] duration-200 ease-linear", disabled ? "cursor-default" : "cursor-pointer hover:bg-sidebar-accent", "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50", disabled ? "text-muted-foreground" : "text-navbar-foreground", disabled && "opacity-60", isActive && "bg-navbar-accent text-navbar-accent-foreground hover:bg-navbar-accent", "aria-expanded:bg-navbar-accent aria-expanded:text-navbar-accent-foreground aria-expanded:hover:bg-navbar-accent", className),
@@ -456,44 +1627,61 @@ const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMen
456
1627
  });
457
1628
  });
458
1629
  DashboardSidebarMenuButton.displayName = "DashboardSidebarMenuButton";
459
- function DashboardSidebarMenuAddon({ className, ...props }) {
1630
+ const DashboardSidebarMenuAddon = React.forwardRef((componentProps, forwardRef) => {
1631
+ const { className, ...props } = componentProps;
460
1632
  return /* @__PURE__ */ jsx("div", {
1633
+ ref: forwardRef,
461
1634
  "data-slot": "dashboard-sidebar-menu-addon",
462
1635
  className: cn("ml-auto shrink-0 inline-flex items-center", className),
463
1636
  ...props
464
1637
  });
465
- }
466
- function DashboardSidebarMenuBadge({ className, ...props }) {
1638
+ });
1639
+ DashboardSidebarMenuAddon.displayName = "DashboardSidebarMenuAddon";
1640
+ const DashboardSidebarMenuBadge = React.forwardRef((componentProps, forwardRef) => {
1641
+ const { className, ...props } = componentProps;
467
1642
  return /* @__PURE__ */ jsx(CounterBadge, {
1643
+ ref: forwardRef,
468
1644
  "data-slot": "dashboard-sidebar-menu-badge",
469
1645
  className: cn("pointer-events-none absolute select-none", "right-2 top-1/2 -translate-y-1/2", "group-data-[collapsible=icon]/dbs:right-[-2px] group-data-[collapsible=icon]/dbs:top-[-3.5px] group-data-[collapsible=icon]/dbs:translate-y-0", "peer-data-[active=true]/dbs-button:bg-navbar-icon-bg peer-data-[active=true]/dbs-button:text-navbar-foreground", className),
470
1646
  ...props
471
1647
  });
472
- }
473
- function DashboardSidebarSubmenu({ className, ...props }) {
1648
+ });
1649
+ DashboardSidebarMenuBadge.displayName = "DashboardSidebarMenuBadge";
1650
+ const DashboardSidebarSubmenu = React.forwardRef((componentProps, forwardRef) => {
1651
+ const { className, ...props } = componentProps;
474
1652
  return /* @__PURE__ */ jsx("div", {
1653
+ ref: forwardRef,
475
1654
  "data-slot": "dashboard-sidebar-submenu",
476
1655
  className: cn("bg-popover border border-border shadow-md rounded-md p-1", "flex flex-col w-[238px]", className),
477
1656
  ...props
478
1657
  });
479
- }
480
- function DashboardSidebarSubmenuItem({ className, render, isActive = false, ...props }) {
1658
+ });
1659
+ DashboardSidebarSubmenu.displayName = "DashboardSidebarSubmenu";
1660
+ const DashboardSidebarSubmenuItem = React.forwardRef((componentProps, forwardRef) => {
1661
+ const { className, render, isActive = false, ...props } = componentProps;
481
1662
  return useRender({
482
1663
  defaultTagName: "div",
483
- props: mergeProps({ className: cn("flex items-center gap-2 p-2 rounded-md cursor-pointer", "text-sm text-foreground leading-snug", "hover:bg-accent transition-colors", isActive && "bg-accent font-medium", className) }, props),
1664
+ props: mergeProps({
1665
+ ref: forwardRef,
1666
+ className: cn("flex items-center gap-2 p-2 rounded-md cursor-pointer", "text-sm text-foreground leading-snug", "hover:bg-accent transition-colors", isActive && "bg-accent font-medium", className)
1667
+ }, props),
484
1668
  render,
485
1669
  state: {
486
1670
  slot: "dashboard-sidebar-submenu-item",
487
1671
  active: isActive
488
1672
  }
489
1673
  });
490
- }
491
- function DashboardSidebarSubmenuSeparator({ className }) {
1674
+ });
1675
+ DashboardSidebarSubmenuItem.displayName = "DashboardSidebarSubmenuItem";
1676
+ const DashboardSidebarSubmenuSeparator = React.forwardRef((componentProps, forwardRef) => {
1677
+ const { className } = componentProps;
492
1678
  return /* @__PURE__ */ jsx("div", {
1679
+ ref: forwardRef,
493
1680
  "data-slot": "dashboard-sidebar-submenu-separator",
494
1681
  className: cn("h-px bg-border -mx-1 my-1", className)
495
1682
  });
496
- }
1683
+ });
1684
+ DashboardSidebarSubmenuSeparator.displayName = "DashboardSidebarSubmenuSeparator";
497
1685
 
498
1686
  //#endregion
499
1687
  //#region src/components/dashboard-sidebar-nav.tsx
@@ -589,4 +1777,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
589
1777
  }
590
1778
 
591
1779
  //#endregion
592
- export { BetaBadge, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, NewBadge, ProfessionalBadge, RoleBadge, TrialBadge, useCopyToClipboard, useSidebar as useDashboardSidebar };
1780
+ export { BetaBadge, CardSaveBar, ChatbotInput, ChatbotPage, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormComboboxField, FormFieldBase, FormInputField, FormMultiComboboxField, FormNumberField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withForm };