@aircall/blocks 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,388 @@
1
1
  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";
2
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ 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, useSidebar } from "@aircall/ds";
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/components/empty-state.tsx
13
+ /** Generic root. Anchor of the family + escape hatch for ad-hoc empty states. */
14
+ const EmptyState = React.forwardRef((componentProps, forwardRef) => {
15
+ return /* @__PURE__ */ jsx(Empty, {
16
+ ref: forwardRef,
17
+ ...componentProps
18
+ });
19
+ });
20
+ EmptyState.displayName = "EmptyState";
21
+ /** Groups the media, title and description with the design's spacing. */
22
+ const EmptyStateHeader = React.forwardRef((componentProps, forwardRef) => {
23
+ return /* @__PURE__ */ jsx(EmptyHeader, {
24
+ ref: forwardRef,
25
+ ...componentProps
26
+ });
27
+ });
28
+ EmptyStateHeader.displayName = "EmptyStateHeader";
29
+ /** Decorative media slot (icon/avatar). Hidden from assistive tech. */
30
+ const EmptyStateMedia = React.forwardRef((componentProps, forwardRef) => {
31
+ const { children, ...props } = componentProps;
32
+ return /* @__PURE__ */ jsx(EmptyMedia, {
33
+ ref: forwardRef,
34
+ variant: "icon",
35
+ "aria-hidden": true,
36
+ ...props,
37
+ children
38
+ });
39
+ });
40
+ EmptyStateMedia.displayName = "EmptyStateMedia";
41
+ const EmptyStateTitle = React.forwardRef((componentProps, forwardRef) => {
42
+ return /* @__PURE__ */ jsx(EmptyTitle, {
43
+ ref: forwardRef,
44
+ ...componentProps
45
+ });
46
+ });
47
+ EmptyStateTitle.displayName = "EmptyStateTitle";
48
+ const EmptyStateDescription = React.forwardRef((componentProps, forwardRef) => {
49
+ return /* @__PURE__ */ jsx(EmptyDescription, {
50
+ ref: forwardRef,
51
+ ...componentProps
52
+ });
53
+ });
54
+ EmptyStateDescription.displayName = "EmptyStateDescription";
55
+ /**
56
+ * Row of action buttons below the header. Lays buttons out horizontally
57
+ * (wrapping when needed), matching the design. Place an `EmptyStateExternalLink`
58
+ * link as a sibling after it — not inside — to sit on its own line below.
59
+ */
60
+ const EmptyStateActions = React.forwardRef((componentProps, forwardRef) => {
61
+ const { className, ...props } = componentProps;
62
+ return /* @__PURE__ */ jsx(EmptyContent, {
63
+ ref: forwardRef,
64
+ className: cn("flex-row flex-wrap justify-center gap-3", className),
65
+ ...props
66
+ });
67
+ });
68
+ EmptyStateActions.displayName = "EmptyStateActions";
69
+ const EmptyStateButton = React.forwardRef((componentProps, forwardRef) => {
70
+ return /* @__PURE__ */ jsx(Button, {
71
+ ref: forwardRef,
72
+ size: "default",
73
+ ...componentProps
74
+ });
75
+ });
76
+ EmptyStateButton.displayName = "EmptyStateButton";
77
+ const EmptyStateExternalLink = React.forwardRef((componentProps, forwardRef) => {
78
+ const { href, children = "Learn more", ...props } = componentProps;
79
+ return /* @__PURE__ */ jsx(EmptyStateButton, {
80
+ ref: forwardRef,
81
+ variant: "link",
82
+ render: /* @__PURE__ */ jsx("a", {
83
+ href,
84
+ target: "_blank",
85
+ rel: "noopener noreferrer"
86
+ }),
87
+ ...props,
88
+ children
89
+ });
90
+ });
91
+ EmptyStateExternalLink.displayName = "EmptyStateExternalLink";
92
+ function createEmptyStateVariant(config) {
93
+ const Root = React.forwardRef((componentProps, forwardRef) => /* @__PURE__ */ jsx(EmptyState, {
94
+ ref: forwardRef,
95
+ role: config.role,
96
+ ...componentProps
97
+ }));
98
+ Root.displayName = `${config.name}EmptyState`;
99
+ const Media = React.forwardRef((componentProps, forwardRef) => /* @__PURE__ */ jsx(EmptyStateMedia, {
100
+ ref: forwardRef,
101
+ ...componentProps,
102
+ children: config.icon
103
+ }));
104
+ Media.displayName = `${config.name}Media`;
105
+ const Title = React.forwardRef(({ children, ...componentProps }, forwardRef) => /* @__PURE__ */ jsx(EmptyStateTitle, {
106
+ ref: forwardRef,
107
+ ...componentProps,
108
+ children: children ?? config.title
109
+ }));
110
+ Title.displayName = `${config.name}Title`;
111
+ const Description = React.forwardRef(({ children, ...componentProps }, forwardRef) => /* @__PURE__ */ jsx(EmptyStateDescription, {
112
+ ref: forwardRef,
113
+ ...componentProps,
114
+ children: children ?? config.description
115
+ }));
116
+ Description.displayName = `${config.name}Description`;
117
+ return {
118
+ Root,
119
+ Media,
120
+ Title,
121
+ Description
122
+ };
123
+ }
124
+
125
+ //#endregion
126
+ //#region src/components/coming-soon.tsx
127
+ /**
128
+ * ComingSoon — empty state shown when a feature exists in the UI but is not yet
129
+ * available. Compose it from its parts:
130
+ *
131
+ * ```tsx
132
+ * <ComingSoonEmptyState>
133
+ * <EmptyStateHeader>
134
+ * <ComingSoonMedia />
135
+ * <ComingSoonTitle />
136
+ * <ComingSoonDescription />
137
+ * </EmptyStateHeader>
138
+ * <EmptyStateActions>
139
+ * <EmptyStateButton variant="outline" onClick={goBack}>Go back</EmptyStateButton>
140
+ * <EmptyStateExternalLink href={docsUrl} />
141
+ * </EmptyStateActions>
142
+ * </ComingSoonEmptyState>
143
+ * ```
144
+ *
145
+ * `ComingSoonTitle` / `ComingSoonDescription` render the design copy by default;
146
+ * pass children to override (e.g. for i18n).
147
+ */
148
+ const ComingSoon = createEmptyStateVariant({
149
+ name: "ComingSoon",
150
+ icon: /* @__PURE__ */ jsx(Sprout, {}),
151
+ title: "Coming soon!",
152
+ description: "You’ll be able to access this feature in the near future."
153
+ });
154
+ const ComingSoonEmptyState = ComingSoon.Root;
155
+ const ComingSoonMedia = ComingSoon.Media;
156
+ const ComingSoonTitle = ComingSoon.Title;
157
+ const ComingSoonDescription = ComingSoon.Description;
158
+
159
+ //#endregion
160
+ //#region src/components/restricted-access.tsx
161
+ /**
162
+ * RestrictedAccess — shown when the user lacks permission to view a page.
163
+ * Pair it with an `EmptyStateExternalLink` for the "Learn more" action.
164
+ */
165
+ const RestrictedAccess = createEmptyStateVariant({
166
+ name: "RestrictedAccess",
167
+ icon: /* @__PURE__ */ jsx(LockKeyhole, {}),
168
+ title: "You don’t have access to this page.",
169
+ description: "Contact an admin on your team to get access."
170
+ });
171
+ const RestrictedAccessEmptyState = RestrictedAccess.Root;
172
+ const RestrictedAccessMedia = RestrictedAccess.Media;
173
+ const RestrictedAccessTitle = RestrictedAccess.Title;
174
+ const RestrictedAccessDescription = RestrictedAccess.Description;
175
+
176
+ //#endregion
177
+ //#region src/components/not-found.tsx
178
+ /**
179
+ * NotFound — shown when a page/route cannot be found. Pair it with an
180
+ * `EmptyStateButton` for the navigation action (e.g. "Go to inbox").
181
+ */
182
+ const NotFound = createEmptyStateVariant({
183
+ name: "NotFound",
184
+ icon: /* @__PURE__ */ jsx(Search, {}),
185
+ title: "We couldn’t find that page",
186
+ description: "It may have been moved, deleted, or the link may be incorrect."
187
+ });
188
+ const NotFoundEmptyState = NotFound.Root;
189
+ const NotFoundMedia = NotFound.Media;
190
+ const NotFoundTitle = NotFound.Title;
191
+ const NotFoundDescription = NotFound.Description;
192
+
193
+ //#endregion
194
+ //#region src/components/no-data.tsx
195
+ /**
196
+ * NoData — shown when a search or filter returns no results. Pair it with an
197
+ * `EmptyStateButton` for the "Reset" action. Appears in response to a user
198
+ * action, so it announces politely via `role="status"`.
199
+ */
200
+ const NoData = createEmptyStateVariant({
201
+ name: "NoData",
202
+ icon: /* @__PURE__ */ jsx(Search, {}),
203
+ title: "No results matching",
204
+ description: "Try resetting your search or removing a filter.",
205
+ role: "status"
206
+ });
207
+ const NoDataEmptyState = NoData.Root;
208
+ const NoDataMedia = NoData.Media;
209
+ const NoDataTitle = NoData.Title;
210
+ const NoDataDescription = NoData.Description;
211
+
212
+ //#endregion
213
+ //#region src/components/no-support.tsx
214
+ /**
215
+ * NoSupport — shown when the current browser can't render a page. Pair it with
216
+ * an `EmptyStateExternalLink` for the "Learn more" action.
217
+ */
218
+ const NoSupport = createEmptyStateVariant({
219
+ name: "NoSupport",
220
+ icon: /* @__PURE__ */ jsx(Settings, {}),
221
+ title: "We can’t support this page",
222
+ description: "Try updating your browser to the latest version."
223
+ });
224
+ const NoSupportEmptyState = NoSupport.Root;
225
+ const NoSupportMedia = NoSupport.Media;
226
+ const NoSupportTitle = NoSupport.Title;
227
+ const NoSupportDescription = NoSupport.Description;
228
+
229
+ //#endregion
230
+ //#region src/components/unknown-error.tsx
231
+ /**
232
+ * UnknownError — shown when content fails to load unexpectedly. Pair it with an
233
+ * `EmptyStateButton` (primary) for the "Try again" action. As an error state it
234
+ * announces assertively via `role="alert"`.
235
+ */
236
+ const UnknownError = createEmptyStateVariant({
237
+ name: "UnknownError",
238
+ icon: /* @__PURE__ */ jsx(TriangleAlert, {}),
239
+ title: "We couldn’t load this page",
240
+ description: "Something went wrong while loading. Try again to load the data.",
241
+ role: "alert"
242
+ });
243
+ const UnknownErrorEmptyState = UnknownError.Root;
244
+ const UnknownErrorMedia = UnknownError.Media;
245
+ const UnknownErrorTitle = UnknownError.Title;
246
+ const UnknownErrorDescription = UnknownError.Description;
247
+
248
+ //#endregion
249
+ //#region src/components/chatbot-page.tsx
250
+ const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
251
+ const { children, className } = componentProps;
252
+ return /* @__PURE__ */ jsx(SidebarProvider, {
253
+ ref: forwardRef,
254
+ className: cn("!min-h-0 h-full", className),
255
+ style: { "--sidebar-width": "14rem" },
256
+ children
257
+ });
258
+ });
259
+ ChatbotPage.displayName = "ChatbotPage";
260
+
261
+ //#endregion
262
+ //#region src/components/chatbot-sidebar.tsx
263
+ const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
264
+ const { conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, recentLabel = "Recent", className } = componentProps;
265
+ return /* @__PURE__ */ jsx(Sidebar, {
266
+ ref: forwardRef,
267
+ collapsible: "none",
268
+ className,
269
+ children: /* @__PURE__ */ jsxs(SidebarContent, { children: [/* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
270
+ isActive: !activeConversationId || !conversations.some((c) => c.id === activeConversationId),
271
+ onClick: onNewChat,
272
+ children: [/* @__PURE__ */ jsx(Plus, {}), /* @__PURE__ */ jsx("span", { children: "New chat" })]
273
+ }) }) }) }), conversations.length > 0 && /* @__PURE__ */ jsxs(SidebarGroup, { children: [/* @__PURE__ */ jsx(SidebarGroupLabel, { children: recentLabel }), /* @__PURE__ */ jsx(SidebarMenu, { children: conversations.map((conversation) => {
274
+ const isActive = conversation.id === activeConversationId;
275
+ return /* @__PURE__ */ jsx(SidebarMenuItem, { children: renderConversationMenuContent ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
276
+ /* @__PURE__ */ jsx(SidebarMenuButton, {
277
+ isActive,
278
+ onClick: () => onSelectConversation?.(conversation.id),
279
+ children: /* @__PURE__ */ jsx("span", {
280
+ className: "min-w-0 truncate",
281
+ children: conversation.title
282
+ })
283
+ }),
284
+ /* @__PURE__ */ jsx(SidebarMenuAction, {
285
+ showOnHover: true,
286
+ render: /* @__PURE__ */ jsx(DropdownMenuTrigger, {}),
287
+ "aria-label": `Options for ${conversation.title}`,
288
+ children: /* @__PURE__ */ jsx(Ellipsis, {})
289
+ }),
290
+ /* @__PURE__ */ jsx(DropdownMenuContent, {
291
+ side: "right",
292
+ children: renderConversationMenuContent(conversation)
293
+ })
294
+ ] }) : /* @__PURE__ */ jsx(SidebarMenuButton, {
295
+ isActive,
296
+ onClick: () => onSelectConversation?.(conversation.id),
297
+ children: /* @__PURE__ */ jsx("span", {
298
+ className: "min-w-0 truncate",
299
+ children: conversation.title
300
+ })
301
+ }) }, conversation.id);
302
+ }) })] })] })
303
+ });
304
+ });
305
+ ChatbotSidebar.displayName = "ChatbotSidebar";
306
+
307
+ //#endregion
308
+ //#region src/components/chatbot-input.tsx
309
+ const textareaVariants = cva([
310
+ "min-h-8 py-1.5 pr-2 pl-0 text-sm",
311
+ "placeholder:text-neutral-500",
312
+ "disabled:bg-transparent dark:disabled:bg-transparent disabled:opacity-100",
313
+ "[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"
314
+ ], {
315
+ variants: { size: {
316
+ default: "max-h-20",
317
+ large: "max-h-32 min-h-20"
318
+ } },
319
+ defaultVariants: { size: "default" }
320
+ });
321
+ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, placeholder = "Ask anything...", loading = false, size = "default", disabled, ...textareaProps }) {
322
+ const [currentValue = "", setCurrentValue] = useControllableState({
323
+ prop: value,
324
+ defaultProp: "",
325
+ onChange: onValueChange
326
+ });
327
+ const hasValue = currentValue.trim().length > 0;
328
+ const handleChange = (event) => {
329
+ setCurrentValue(event.target.value);
330
+ };
331
+ const submit = () => {
332
+ if (loading || disabled || !hasValue) return;
333
+ onSubmit?.(currentValue);
334
+ setCurrentValue("");
335
+ };
336
+ const handleKeyDown = (event) => {
337
+ if (event.key === "Enter" && !event.shiftKey) {
338
+ event.preventDefault();
339
+ submit();
340
+ }
341
+ textareaProps.onKeyDown?.(event);
342
+ };
343
+ return /* @__PURE__ */ jsxs(InputGroup, {
344
+ "data-slot": "chatbot-input",
345
+ "data-loading": loading || void 0,
346
+ 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),
347
+ "aria-disabled": disabled || void 0,
348
+ children: [/* @__PURE__ */ jsx(InputGroupTextarea, {
349
+ rows: 1,
350
+ placeholder,
351
+ "aria-label": "Message",
352
+ ...textareaProps,
353
+ value: currentValue,
354
+ disabled,
355
+ onChange: handleChange,
356
+ onKeyDown: handleKeyDown,
357
+ className: cn(textareaVariants({ size }))
358
+ }), /* @__PURE__ */ jsx("div", {
359
+ className: "flex shrink-0 items-center gap-2",
360
+ children: loading ? /* @__PURE__ */ jsx(InputGroupButton, {
361
+ variant: "secondary",
362
+ size: "icon",
363
+ className: "rounded-full",
364
+ "aria-label": "Stop generating",
365
+ disabled,
366
+ onClick: onStop,
367
+ children: /* @__PURE__ */ jsx(Square, {})
368
+ }) : /* @__PURE__ */ jsx(InputGroupButton, {
369
+ variant: hasValue ? "default" : "ghost",
370
+ size: "icon",
371
+ className: "rounded-full",
372
+ "aria-label": "Send message",
373
+ disabled: disabled || !hasValue,
374
+ onClick: submit,
375
+ children: /* @__PURE__ */ jsx(ArrowUp, {})
376
+ })
377
+ })]
378
+ });
379
+ }
380
+
381
+ //#endregion
11
382
  //#region src/hooks/use-copy-to-clipboard.ts
12
383
  function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
13
384
  const [copied, setCopied] = useState(false);
14
- const timerRef = useRef();
385
+ const timerRef = useRef(void 0);
15
386
  const getIsMounted = useIsMounted();
16
387
  const onCopiedRef = useCallbackRef(onCopied);
17
388
  useUnmount(() => clearTimeout(timerRef.current));
@@ -49,7 +420,8 @@ function useCopyButtonContext() {
49
420
  if (ctx === null) throw new Error("CopyButtonIcon and CopyButtonLabel must be rendered inside a CopyButton.");
50
421
  return ctx;
51
422
  }
52
- function CopyButton({ value, timeout, onCopied, variant = "outline", children, ...buttonProps }) {
423
+ const CopyButton = React.forwardRef((componentProps, forwardRef) => {
424
+ const { value, timeout, onCopied, variant = "outline", children, ...buttonProps } = componentProps;
53
425
  const { copy, copied } = useCopyToClipboard(value, {
54
426
  timeout,
55
427
  onCopied
@@ -57,6 +429,7 @@ function CopyButton({ value, timeout, onCopied, variant = "outline", children, .
57
429
  return /* @__PURE__ */ jsx(CopyButtonContext.Provider, {
58
430
  value: { copied },
59
431
  children: /* @__PURE__ */ jsx(Button, {
432
+ ref: forwardRef,
60
433
  onClick: () => {
61
434
  copy();
62
435
  },
@@ -65,61 +438,191 @@ function CopyButton({ value, timeout, onCopied, variant = "outline", children, .
65
438
  children
66
439
  })
67
440
  });
68
- }
69
- function CopyButtonIcon({ className }) {
441
+ });
442
+ CopyButton.displayName = "CopyButton";
443
+ const CopyButtonIcon = React.forwardRef((componentProps, forwardRef) => {
444
+ const { className } = componentProps;
70
445
  const { copied } = useCopyButtonContext();
71
- return /* @__PURE__ */ jsx(copied ? Check : Copy, { className });
72
- }
73
- function CopyButtonLabel({ label = "Copy", copiedLabel = "Copied", className }) {
446
+ return /* @__PURE__ */ jsx(copied ? Check : Copy, {
447
+ ref: forwardRef,
448
+ className
449
+ });
450
+ });
451
+ CopyButtonIcon.displayName = "CopyButtonIcon";
452
+ const CopyButtonLabel = React.forwardRef((componentProps, forwardRef) => {
453
+ const { label = "Copy", copiedLabel = "Copied", className } = componentProps;
74
454
  const { copied } = useCopyButtonContext();
75
455
  return /* @__PURE__ */ jsx("span", {
456
+ ref: forwardRef,
76
457
  className: cn(className),
77
458
  children: copied ? copiedLabel : label
78
459
  });
460
+ });
461
+ CopyButtonLabel.displayName = "CopyButtonLabel";
462
+
463
+ //#endregion
464
+ //#region src/components/chatbot-panel-suggestion.tsx
465
+ function ChatbotPanelSuggestion({ className, ...props }) {
466
+ return /* @__PURE__ */ jsx(Button, {
467
+ "data-slot": "chatbot-panel-suggestion",
468
+ variant: "ghost",
469
+ 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),
470
+ ...props
471
+ });
472
+ }
473
+
474
+ //#endregion
475
+ //#region src/components/chatbot-user-message.tsx
476
+ function ChatbotUserMessage({ className, children, ...props }) {
477
+ const [expanded, setExpanded] = useState(false);
478
+ const [showToggle, setShowToggle] = useState(false);
479
+ const contentRef = useRef(null);
480
+ useLayoutEffect(() => {
481
+ if (expanded || !contentRef.current) return;
482
+ const el = contentRef.current;
483
+ setShowToggle(el.scrollHeight > el.clientHeight);
484
+ }, [children, expanded]);
485
+ return /* @__PURE__ */ jsxs("div", {
486
+ "data-slot": "chatbot-user-message",
487
+ 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),
488
+ ...props,
489
+ children: [/* @__PURE__ */ jsx("div", {
490
+ ref: contentRef,
491
+ className: cn(!expanded && "line-clamp-6"),
492
+ children
493
+ }), showToggle && /* @__PURE__ */ jsx("button", {
494
+ type: "button",
495
+ "aria-expanded": expanded,
496
+ onClick: () => setExpanded((prev) => !prev),
497
+ className: "mt-1 block w-full cursor-pointer text-right text-xs font-semibold text-foreground/70 hover:text-foreground",
498
+ children: expanded ? "Show less" : "Show more"
499
+ })]
500
+ });
79
501
  }
80
502
 
503
+ //#endregion
504
+ //#region src/components/dashboard-standalone-page.tsx
505
+ const DashboardStandalonePage = React.forwardRef((componentProps, forwardRef) => {
506
+ const { className, ...props } = componentProps;
507
+ return /* @__PURE__ */ jsx("div", {
508
+ ref: forwardRef,
509
+ "data-slot": "dashboard-standalone-page",
510
+ className: cn("flex h-screen flex-col bg-neutral-200 dark:bg-neutral-950", className),
511
+ ...props
512
+ });
513
+ });
514
+ DashboardStandalonePage.displayName = "DashboardStandalonePage";
515
+ const DashboardStandalonePageHeader = React.forwardRef((componentProps, forwardRef) => {
516
+ const { className, ...props } = componentProps;
517
+ return /* @__PURE__ */ jsx("header", {
518
+ ref: forwardRef,
519
+ "data-slot": "dashboard-standalone-page-header",
520
+ className: cn("grid h-16 shrink-0 grid-cols-[1fr_auto_1fr] items-center gap-4 border-b border-border bg-background px-4 md:px-6", className),
521
+ ...props
522
+ });
523
+ });
524
+ DashboardStandalonePageHeader.displayName = "DashboardStandalonePageHeader";
525
+ const DashboardStandalonePageTitle = React.forwardRef((componentProps, forwardRef) => {
526
+ const { className, ...props } = componentProps;
527
+ return /* @__PURE__ */ jsx("h1", {
528
+ ref: forwardRef,
529
+ "data-slot": "dashboard-standalone-page-title",
530
+ className: cn("col-start-2 row-start-1 truncate text-center text-base font-bold text-foreground", className),
531
+ ...props
532
+ });
533
+ });
534
+ DashboardStandalonePageTitle.displayName = "DashboardStandalonePageTitle";
535
+ const DashboardStandalonePageActions = React.forwardRef((componentProps, forwardRef) => {
536
+ const { side = "end", className, ...props } = componentProps;
537
+ return /* @__PURE__ */ jsx("div", {
538
+ ref: forwardRef,
539
+ "data-slot": "dashboard-standalone-page-actions",
540
+ "data-side": side,
541
+ className: cn("row-start-1 flex min-w-0 items-center gap-2", side === "start" ? "col-start-1 justify-start" : "col-start-3 justify-end", className),
542
+ ...props
543
+ });
544
+ });
545
+ DashboardStandalonePageActions.displayName = "DashboardStandalonePageActions";
546
+ const DashboardStandalonePageAction = React.forwardRef((componentProps, forwardRef) => {
547
+ const { variant = "ghost", ...props } = componentProps;
548
+ return /* @__PURE__ */ jsx(Button, {
549
+ ref: forwardRef,
550
+ size: "lg",
551
+ variant,
552
+ ...props
553
+ });
554
+ });
555
+ DashboardStandalonePageAction.displayName = "DashboardStandalonePageAction";
556
+ const DashboardStandalonePageContent = React.forwardRef((componentProps, forwardRef) => {
557
+ const { className, ...props } = componentProps;
558
+ return /* @__PURE__ */ jsx("main", {
559
+ ref: forwardRef,
560
+ "data-slot": "dashboard-standalone-page-content",
561
+ className: cn("min-h-0 flex-1 overflow-auto p-4 md:p-6", className),
562
+ ...props
563
+ });
564
+ });
565
+ DashboardStandalonePageContent.displayName = "DashboardStandalonePageContent";
566
+
81
567
  //#endregion
82
568
  //#region src/components/dashboard-page.tsx
83
- function DashboardPage({ className, ...props }) {
569
+ const DashboardPage = React.forwardRef((componentProps, forwardRef) => {
570
+ const { className, ...props } = componentProps;
84
571
  return /* @__PURE__ */ jsx("div", {
572
+ ref: forwardRef,
85
573
  "data-slot": "dashboard-page",
86
574
  className: cn("flex flex-col h-screen bg-page-background p-2 gap-2", className),
87
575
  ...props
88
576
  });
89
- }
90
- function DashboardPageBanner({ className, ...props }) {
577
+ });
578
+ DashboardPage.displayName = "DashboardPage";
579
+ const DashboardPageBanner = React.forwardRef((componentProps, forwardRef) => {
580
+ const { className, ...props } = componentProps;
91
581
  return /* @__PURE__ */ jsx("div", {
582
+ ref: forwardRef,
92
583
  "data-slot": "dashboard-page-banner",
93
584
  className: cn("-mt-2 -mx-2", className),
94
585
  ...props
95
586
  });
96
- }
97
- function DashboardPageContent({ className, ...props }) {
587
+ });
588
+ DashboardPageBanner.displayName = "DashboardPageBanner";
589
+ const DashboardPageContent = React.forwardRef((componentProps, forwardRef) => {
590
+ const { className, ...props } = componentProps;
98
591
  return /* @__PURE__ */ jsx("div", {
592
+ ref: forwardRef,
99
593
  "data-slot": "dashboard-page-content",
100
594
  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),
101
595
  ...props
102
596
  });
103
- }
104
- function DashboardPageMain({ className, ...props }) {
597
+ });
598
+ DashboardPageContent.displayName = "DashboardPageContent";
599
+ const DashboardPageMain = React.forwardRef((componentProps, forwardRef) => {
600
+ const { className, ...props } = componentProps;
105
601
  return /* @__PURE__ */ jsx("main", {
602
+ ref: forwardRef,
106
603
  "data-slot": "dashboard-page-main",
107
604
  className: cn("flex-1 min-h-0 overflow-auto py-4 px-6", className),
108
605
  ...props
109
606
  });
110
- }
111
- function DashboardPageTabs({ className, ...props }) {
607
+ });
608
+ DashboardPageMain.displayName = "DashboardPageMain";
609
+ const DashboardPageTabs = React.forwardRef((componentProps, forwardRef) => {
610
+ const { className, ...props } = componentProps;
112
611
  return /* @__PURE__ */ jsx(TabsList, {
612
+ ref: forwardRef,
113
613
  ...props,
114
614
  "data-slot": "dph-tabs",
115
615
  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)
116
616
  });
117
- }
617
+ });
618
+ DashboardPageTabs.displayName = "DashboardPageTabs";
118
619
 
119
620
  //#endregion
120
621
  //#region src/components/dashboard-page-header.tsx
121
- function DashboardPageHeader({ className, children, ...props }) {
622
+ const DashboardPageHeader = React.forwardRef((componentProps, forwardRef) => {
623
+ const { className, children, ...props } = componentProps;
122
624
  return /* @__PURE__ */ jsx("div", {
625
+ ref: forwardRef,
123
626
  "data-slot": "dph",
124
627
  className: cn("@container bg-background border-b border-border w-full", className),
125
628
  ...props,
@@ -129,77 +632,105 @@ function DashboardPageHeader({ className, children, ...props }) {
129
632
  children
130
633
  })
131
634
  });
132
- }
133
- function DashboardPageHeaderNav({ className, children, ...props }) {
635
+ });
636
+ DashboardPageHeader.displayName = "DashboardPageHeader";
637
+ const DashboardPageHeaderNav = React.forwardRef((componentProps, forwardRef) => {
638
+ const { className, children, ...props } = componentProps;
134
639
  return /* @__PURE__ */ jsx("div", {
640
+ ref: forwardRef,
135
641
  "data-slot": "dph-nav",
136
642
  className: cn("[grid-area:nav] flex items-center gap-1", className),
137
643
  ...props,
138
644
  children
139
645
  });
140
- }
141
- function DashboardPageHeaderNavBack({ children, ...props }) {
646
+ });
647
+ DashboardPageHeaderNav.displayName = "DashboardPageHeaderNav";
648
+ const DashboardPageHeaderNavBack = React.forwardRef((componentProps, forwardRef) => {
649
+ const { children, ...props } = componentProps;
142
650
  return /* @__PURE__ */ jsxs(Button, {
651
+ ref: forwardRef,
143
652
  variant: "ghost",
144
653
  size: "sm",
145
654
  nativeButton: false,
146
655
  ...props,
147
656
  children: [/* @__PURE__ */ jsx(ChevronLeft, {}), children]
148
657
  });
149
- }
150
- function DashboardPageHeaderPrefix({ className, children, ...props }) {
658
+ });
659
+ DashboardPageHeaderNavBack.displayName = "DashboardPageHeaderNavBack";
660
+ const DashboardPageHeaderPrefix = React.forwardRef((componentProps, forwardRef) => {
661
+ const { className, children, ...props } = componentProps;
151
662
  return /* @__PURE__ */ jsx("div", {
663
+ ref: forwardRef,
152
664
  "data-slot": "dph-prefix",
153
665
  className: cn("[grid-area:prefix] flex shrink-0 px-2 pt-2 items-center justify-center self-start", className),
154
666
  ...props,
155
667
  children
156
668
  });
157
- }
158
- function DashboardPageHeaderTitleGroup({ className, children, ...props }) {
669
+ });
670
+ DashboardPageHeaderPrefix.displayName = "DashboardPageHeaderPrefix";
671
+ const DashboardPageHeaderTitleGroup = React.forwardRef((componentProps, forwardRef) => {
672
+ const { className, children, ...props } = componentProps;
159
673
  return /* @__PURE__ */ jsx("div", {
674
+ ref: forwardRef,
160
675
  "data-slot": "dph-title-group",
161
676
  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),
162
677
  ...props,
163
678
  children
164
679
  });
165
- }
166
- function DashboardPageHeaderTitle({ size = "lg", className, children, ...props }) {
680
+ });
681
+ DashboardPageHeaderTitleGroup.displayName = "DashboardPageHeaderTitleGroup";
682
+ const DashboardPageHeaderTitle = React.forwardRef((componentProps, forwardRef) => {
683
+ const { size = "lg", className, children, ...props } = componentProps;
167
684
  return /* @__PURE__ */ jsx("h1", {
685
+ ref: forwardRef,
168
686
  "data-slot": "dph-title",
169
687
  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),
170
688
  ...props,
171
689
  children
172
690
  });
173
- }
174
- function DashboardPageHeaderSubtitle({ className, children, ...props }) {
691
+ });
692
+ DashboardPageHeaderTitle.displayName = "DashboardPageHeaderTitle";
693
+ const DashboardPageHeaderSubtitle = React.forwardRef((componentProps, forwardRef) => {
694
+ const { className, children, ...props } = componentProps;
175
695
  return /* @__PURE__ */ jsx("div", {
696
+ ref: forwardRef,
176
697
  "data-slot": "dph-subtitle",
177
698
  className: cn("flex flex-col items-start gap-0.5 max-w-xl", className),
178
699
  ...props,
179
700
  children
180
701
  });
181
- }
182
- function DashboardPageHeaderDescription({ className, children, ...props }) {
702
+ });
703
+ DashboardPageHeaderSubtitle.displayName = "DashboardPageHeaderSubtitle";
704
+ const DashboardPageHeaderDescription = React.forwardRef((componentProps, forwardRef) => {
705
+ const { className, children, ...props } = componentProps;
183
706
  return /* @__PURE__ */ jsx("div", {
707
+ ref: forwardRef,
184
708
  "data-slot": "dph-description",
185
709
  className: cn("text-sm text-muted-foreground line-clamp-3 -mt-1", className),
186
710
  ...props,
187
711
  children
188
712
  });
189
- }
190
- function DashboardPageHeaderActions({ className, children, ...props }) {
713
+ });
714
+ DashboardPageHeaderDescription.displayName = "DashboardPageHeaderDescription";
715
+ const DashboardPageHeaderActions = React.forwardRef((componentProps, forwardRef) => {
716
+ const { className, children, ...props } = componentProps;
191
717
  return /* @__PURE__ */ jsx("div", {
718
+ ref: forwardRef,
192
719
  "data-slot": "dph-actions",
193
720
  className: cn("[grid-area:actions] flex items-center gap-2 self-start pl-2", className),
194
721
  ...props,
195
722
  children
196
723
  });
197
- }
198
- const DashboardPageHeaderAction = React.forwardRef(({ size = "lg", ...props }, ref) => /* @__PURE__ */ jsx(Button, {
199
- ref,
200
- size,
201
- ...props
202
- }));
724
+ });
725
+ DashboardPageHeaderActions.displayName = "DashboardPageHeaderActions";
726
+ const DashboardPageHeaderAction = React.forwardRef((componentProps, forwardRef) => {
727
+ const { size = "lg", ...props } = componentProps;
728
+ return /* @__PURE__ */ jsx(Button, {
729
+ ref: forwardRef,
730
+ size,
731
+ ...props
732
+ });
733
+ });
203
734
  DashboardPageHeaderAction.displayName = "DashboardPageHeaderAction";
204
735
 
205
736
  //#endregion
@@ -219,14 +750,16 @@ const PRODUCT_BADGE_LABEL = {
219
750
  };
220
751
  function createProductBadge(type) {
221
752
  const label = PRODUCT_BADGE_LABEL[type];
222
- function ProductBadge({ className, ...props }) {
753
+ const ProductBadge = React.forwardRef((componentProps, forwardRef) => {
754
+ const { className, ...props } = componentProps;
223
755
  return /* @__PURE__ */ jsx(Badge, {
756
+ ref: forwardRef,
224
757
  "data-slot": `badge-${type}`,
225
758
  className: cn(productBadgeVariants({ type }), className),
226
759
  ...props,
227
760
  children: label
228
761
  });
229
- }
762
+ });
230
763
  ProductBadge.displayName = label.replace(/\s+/g, "") + "Badge";
231
764
  return ProductBadge;
232
765
  }
@@ -239,15 +772,18 @@ const DeprecatedBadge = createProductBadge("deprecated");
239
772
  /** Blue pill — user is in a trial period (same blue family as Beta) */
240
773
  const TrialBadge = createProductBadge("trial");
241
774
  /** Charcoal pill — Professional tier (no gradient; solid secondary Badge) */
242
- function ProfessionalBadge({ className, ...props }) {
775
+ const ProfessionalBadge = React.forwardRef((componentProps, forwardRef) => {
776
+ const { className, ...props } = componentProps;
243
777
  return /* @__PURE__ */ jsx(Badge, {
778
+ ref: forwardRef,
244
779
  "data-slot": "badge-professional",
245
780
  tone: "dark",
246
781
  color: "charcoal",
247
782
  ...props,
248
783
  children: "Professional"
249
784
  });
250
- }
785
+ });
786
+ ProfessionalBadge.displayName = "ProfessionalBadge";
251
787
  const roleBadgeVariants = cva(GRADIENT_BASE, { variants: { role: {
252
788
  agent: "[--badge-accent:var(--color-yellow-400)]",
253
789
  supervisor: "[--badge-accent:var(--color-blue-500)]",
@@ -260,19 +796,560 @@ const ROLE_BADGE_LABEL = {
260
796
  admin: "Admin",
261
797
  owner: "Owner"
262
798
  };
263
- function RoleBadge({ role, className, ...props }) {
799
+ const RoleBadge = React.forwardRef((componentProps, forwardRef) => {
800
+ const { role, className, ...props } = componentProps;
264
801
  return /* @__PURE__ */ jsx(Badge, {
802
+ ref: forwardRef,
265
803
  "data-slot": `badge-${role}`,
266
804
  className: cn(roleBadgeVariants({ role }), className),
267
805
  ...props,
268
806
  children: ROLE_BADGE_LABEL[role]
269
807
  });
808
+ });
809
+ RoleBadge.displayName = "RoleBadge";
810
+
811
+ //#endregion
812
+ //#region src/components/chatbot-panel-trigger.tsx
813
+ /**
814
+ * Floating trigger that shows / hides the chatbot panel.
815
+ *
816
+ * Rests as a 40px icon-only circle and expands into a pill that reveals its
817
+ * label on hover and keyboard focus. The four Figma states map to real CSS
818
+ * pseudo-states: default (rest), hover, focus (`:focus-visible` ring) and
819
+ * pressed (`:active`, dimmed to 60%).
820
+ */
821
+ function ChatbotPanelTrigger({ children = "Ask anything", icon = /* @__PURE__ */ jsx(AircallLogo, {}), className, ...buttonProps }) {
822
+ return /* @__PURE__ */ jsxs(Button, {
823
+ "data-slot": "chatbot-panel-trigger",
824
+ 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),
825
+ ...buttonProps,
826
+ children: [/* @__PURE__ */ jsx("span", {
827
+ className: "grid size-5 shrink-0 place-items-center [&_svg]:size-5",
828
+ children: icon
829
+ }), /* @__PURE__ */ jsx("span", {
830
+ 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"),
831
+ children: /* @__PURE__ */ jsx("span", {
832
+ className: "block pl-1 leading-6 whitespace-nowrap",
833
+ children
834
+ })
835
+ })]
836
+ });
837
+ }
838
+
839
+ //#endregion
840
+ //#region src/form/form-context.ts
841
+ /**
842
+ * TanStack Form contexts for `@aircall/blocks`.
843
+ *
844
+ * - `fieldContext` / `formContext` — pass to `createFormHook` when extending `useForm` with custom field or form components.
845
+ * - `useFieldContext<T>()` — read the bound field's value, meta, and handlers inside a custom field component.
846
+ * - `useFormContext()` — read form-level state (isDirty, isSubmitting, …) inside a custom form component.
847
+ *
848
+ * @example
849
+ * // Extending useForm with a custom SwitchField
850
+ * const { useAppForm } = createFormHook({ fieldContext, formContext, fieldComponents: { SwitchField }, formComponents: {} })
851
+ */
852
+ const { fieldContext, formContext, useFieldContext, useFormContext } = createFormHookContexts();
853
+
854
+ //#endregion
855
+ //#region src/components/card-save-bar.tsx
856
+ function CardSaveBar({ submitLabel = "Save changes", savingLabel = "Saving…", resetLabel = "Discard" }) {
857
+ const form = useFormContext();
858
+ return /* @__PURE__ */ jsx(form.Subscribe, {
859
+ selector: (s) => ({
860
+ isDirty: s.isDirty,
861
+ isSubmitting: s.isSubmitting,
862
+ canSubmit: s.canSubmit
863
+ }),
864
+ children: ({ isDirty, isSubmitting, canSubmit }) => /* @__PURE__ */ jsx("div", {
865
+ className: cn("grid transition-[grid-template-rows] duration-300 ease-in-out", isDirty ? "grid-rows-[1fr]" : "grid-rows-[0fr]"),
866
+ children: /* @__PURE__ */ jsx("div", {
867
+ className: "overflow-hidden",
868
+ children: /* @__PURE__ */ jsxs("div", {
869
+ className: "flex items-center justify-end gap-2 border-t px-6 py-3",
870
+ children: [/* @__PURE__ */ jsx(Button, {
871
+ type: "button",
872
+ variant: "ghost",
873
+ onClick: () => form.reset(),
874
+ disabled: !isDirty || isSubmitting,
875
+ children: resetLabel
876
+ }), /* @__PURE__ */ jsx(Button, {
877
+ type: "submit",
878
+ disabled: !isDirty || !canSubmit || isSubmitting,
879
+ children: isSubmitting ? savingLabel : submitLabel
880
+ })]
881
+ })
882
+ })
883
+ })
884
+ });
885
+ }
886
+
887
+ //#endregion
888
+ //#region src/components/submit-button.tsx
889
+ function SubmitButton({ children = "Submit", loadingLabel, className, ...buttonProps }) {
890
+ return /* @__PURE__ */ jsx(useFormContext().Subscribe, {
891
+ selector: (s) => ({
892
+ isSubmitting: s.isSubmitting,
893
+ canSubmit: s.canSubmit
894
+ }),
895
+ children: ({ isSubmitting, canSubmit }) => /* @__PURE__ */ jsx(Button, {
896
+ type: "submit",
897
+ disabled: !canSubmit || isSubmitting,
898
+ className: cn(className),
899
+ ...buttonProps,
900
+ children: isSubmitting ? loadingLabel ?? "Saving…" : children
901
+ })
902
+ });
903
+ }
904
+
905
+ //#endregion
906
+ //#region src/form/use-form.ts
907
+ /**
908
+ * Pre-configured TanStack Form hook for `@aircall/blocks`.
909
+ *
910
+ * Fields are built with the composable, typed `Form*Field` wrappers (e.g. `FormSelectField`),
911
+ * which take the `form` as a prop — there are no registered `fieldComponents`. Only the
912
+ * form-level UI (`SubmitButton`, `CardSaveBar`) is registered, so it's available as
913
+ * `form.SubmitButton` / `form.CardSaveBar` inside `form.AppForm`.
914
+ *
915
+ * `CommonForm` is the full form object — use it directly or extend it:
916
+ * - `CommonForm.useAppForm` — primary hook; types inferred from `defaultValues`
917
+ * - `CommonForm.withForm` — HOC equivalent for higher-order function patterns
918
+ *
919
+ * @example
920
+ * const form = useForm({ defaultValues: { name: '' }, onSubmit: async ({ value }) => save(value) })
921
+ */
922
+ const CommonForm = createFormHook({
923
+ fieldContext,
924
+ formContext,
925
+ fieldComponents: {},
926
+ formComponents: {
927
+ SubmitButton,
928
+ CardSaveBar
929
+ }
930
+ });
931
+ const useForm = CommonForm.useAppForm;
932
+ const { withForm } = CommonForm;
933
+
934
+ //#endregion
935
+ //#region src/form/field-errors.ts
936
+ /**
937
+ * Normalize a TanStack Form field's `meta.errors` into the `{ message }[]` shape
938
+ * that `@aircall/ds`'s `FieldError` expects.
939
+ *
940
+ * Errors come from two validator sources with different shapes:
941
+ * - per-field validators return plain strings (e.g. `'Email is required'`)
942
+ * - standard-schema validators (e.g. Zod on `useForm`) return issue **objects** with a `message`
943
+ *
944
+ * Both must render their human-readable text — never `"[object Object]"`.
945
+ */
946
+ function toFieldErrors(errors) {
947
+ return errors.map((error) => ({ message: typeof error === "string" ? error : error?.message ?? String(error) }));
948
+ }
949
+
950
+ //#endregion
951
+ //#region src/components/form-field.tsx
952
+ function FormFieldShell({ label, description, children }) {
953
+ const errors = useFieldContext().state.meta.errors;
954
+ return /* @__PURE__ */ jsxs(Field, {
955
+ "data-invalid": errors.length > 0 ? true : void 0,
956
+ children: [
957
+ /* @__PURE__ */ jsx(FieldLabel, { children: label }),
958
+ description && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
959
+ children,
960
+ /* @__PURE__ */ jsx(FieldError, { errors: toFieldErrors(errors) })
961
+ ]
962
+ });
963
+ }
964
+ /**
965
+ * Shared runtime binder for the per-primitive wrappers (FormSelectField, FormInputField, …).
966
+ * Field-name/value/validator typing is enforced at each wrapper's boundary; this just renders
967
+ * the AppField + shared shell. `TData`/`TControl` carry the precise field + bundle shapes through.
968
+ */
969
+ function FormFieldBase(props) {
970
+ const { form, name, validators, label, description, buildControl, children } = props;
971
+ const AppField = form.AppField;
972
+ return /* @__PURE__ */ jsx(AppField, {
973
+ name,
974
+ validators,
975
+ children: (field) => /* @__PURE__ */ jsx(FormFieldShell, {
976
+ label,
977
+ description,
978
+ children: children(field, buildControl(field))
979
+ })
980
+ });
981
+ }
982
+
983
+ //#endregion
984
+ //#region src/components/form-select-field.tsx
985
+ function selectControl(field) {
986
+ return {
987
+ selectProps: {
988
+ value: field.state.value || null,
989
+ onValueChange: (value) => field.handleChange(value ?? "")
990
+ },
991
+ selectTriggerProps: {
992
+ onBlur: field.handleBlur,
993
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
994
+ }
995
+ };
996
+ }
997
+ /**
998
+ * Select field wrapper. Owns the binding + shared `Field/Label/Error` shell, and hands the
999
+ * consumer pre-wired `selectProps` / `selectTriggerProps` to spread onto `@aircall/ds` Select
1000
+ * primitives — trigger, value, and content stay fully composable (Combobox-style triggers too).
1001
+ *
1002
+ * Typing is derived from the threaded `form`: `name` is restricted to the form's **string**
1003
+ * fields, and `validators` infer the field value — identical to using `form.AppField` directly.
1004
+ *
1005
+ * @example
1006
+ * <FormSelectField form={form} name="language" label="Language" validators={{ onChange }}>
1007
+ * {(field, { selectProps, selectTriggerProps }) => (
1008
+ * <Select {...selectProps}>
1009
+ * <SelectTrigger {...selectTriggerProps}><SelectValue placeholder="Pick one" /></SelectTrigger>
1010
+ * <SelectContent>{options}</SelectContent>
1011
+ * </Select>
1012
+ * )}
1013
+ * </FormSelectField>
1014
+ */
1015
+ function FormSelectField(props) {
1016
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1017
+ ...props,
1018
+ buildControl: selectControl
1019
+ });
1020
+ }
1021
+
1022
+ //#endregion
1023
+ //#region src/components/form-input-field.tsx
1024
+ function inputControl(field) {
1025
+ return { inputProps: {
1026
+ value: field.state.value ?? "",
1027
+ onChange: (event) => field.handleChange(event.target.value),
1028
+ onBlur: field.handleBlur,
1029
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1030
+ } };
1031
+ }
1032
+ /**
1033
+ * Text-input field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1034
+ * with an `inputProps` bundle. Compose the input yourself (add adornments, counters, prefixes).
1035
+ * `name` is restricted to the form's **string** fields.
1036
+ *
1037
+ * @example
1038
+ * <FormInputField form={form} name="email" label="Email" validators={{ onChange }}>
1039
+ * {(field, { inputProps }) => <Input {...inputProps} type="email" placeholder="you@co.com" />}
1040
+ * </FormInputField>
1041
+ */
1042
+ function FormInputField(props) {
1043
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1044
+ ...props,
1045
+ buildControl: inputControl
1046
+ });
1047
+ }
1048
+
1049
+ //#endregion
1050
+ //#region src/components/form-combobox-field.tsx
1051
+ function comboboxControl(field) {
1052
+ return {
1053
+ comboboxProps: {
1054
+ value: field.state.value || null,
1055
+ onValueChange: (value) => field.handleChange(value ?? "")
1056
+ },
1057
+ comboboxInputProps: {
1058
+ onBlur: field.handleBlur,
1059
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1060
+ }
1061
+ };
1062
+ }
1063
+ /**
1064
+ * Combobox (searchable single-select) field wrapper for **string-valued** fields. Same threaded-
1065
+ * `form` typing and shared shell as the other wrappers; the render-prop hands you `comboboxProps`
1066
+ * and `comboboxInputProps` to spread, leaving the input, trigger, grouping, and item rendering
1067
+ * entirely up to you — the case that motivated the render-prop API.
1068
+ *
1069
+ * @example
1070
+ * <FormComboboxField form={form} name="framework" label="Framework" validators={{ onChange }}>
1071
+ * {(field, { comboboxProps, comboboxInputProps }) => (
1072
+ * <Combobox items={FRAMEWORKS} {...comboboxProps}>
1073
+ * <ComboboxInput {...comboboxInputProps} placeholder="Search…" />
1074
+ * <ComboboxContent>
1075
+ * <ComboboxEmpty>No match.</ComboboxEmpty>
1076
+ * <ComboboxList>{item => <ComboboxItem key={item} value={item}>{item}</ComboboxItem>}</ComboboxList>
1077
+ * </ComboboxContent>
1078
+ * </Combobox>
1079
+ * )}
1080
+ * </FormComboboxField>
1081
+ */
1082
+ function FormComboboxField(props) {
1083
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1084
+ ...props,
1085
+ buildControl: comboboxControl
1086
+ });
1087
+ }
1088
+
1089
+ //#endregion
1090
+ //#region src/components/form-multi-combobox-field.tsx
1091
+ function multiComboboxControl(field) {
1092
+ return {
1093
+ comboboxProps: {
1094
+ value: field.state.value ?? [],
1095
+ onValueChange: (values) => field.handleChange(values)
1096
+ },
1097
+ comboboxInputProps: {
1098
+ onBlur: field.handleBlur,
1099
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1100
+ }
1101
+ };
1102
+ }
1103
+ /**
1104
+ * Multi-select combobox field wrapper — same threaded-`form` typing and shared shell as
1105
+ * `FormSelectField`, with `comboboxProps` / `comboboxInputProps` bundles. `name` is restricted
1106
+ * to the form's **string[]** fields — a string/number `name` is a type error.
1107
+ *
1108
+ * @example
1109
+ * <FormMultiComboboxField form={form} name="tags" label="Tags" validators={{ onChange }}>
1110
+ * {(field, { comboboxProps, comboboxInputProps }) => (
1111
+ * <Combobox multiple items={items} {...comboboxProps}>
1112
+ * <ComboboxChips><ComboboxInput {...comboboxInputProps} /></ComboboxChips>
1113
+ * </Combobox>
1114
+ * )}
1115
+ * </FormMultiComboboxField>
1116
+ */
1117
+ function FormMultiComboboxField(props) {
1118
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1119
+ ...props,
1120
+ buildControl: multiComboboxControl
1121
+ });
1122
+ }
1123
+
1124
+ //#endregion
1125
+ //#region src/components/form-textarea-field.tsx
1126
+ function textareaControl(field) {
1127
+ return { textareaProps: {
1128
+ value: field.state.value ?? "",
1129
+ onChange: (event) => field.handleChange(event.target.value),
1130
+ onBlur: field.handleBlur,
1131
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1132
+ } };
1133
+ }
1134
+ /**
1135
+ * Multiline text-input field wrapper — same threaded-`form` typing and shared shell as
1136
+ * `FormSelectField`, with a `textareaProps` bundle. Compose the textarea yourself (rows,
1137
+ * counters, auto-grow). `name` is restricted to the form's **string** fields.
1138
+ *
1139
+ * @example
1140
+ * <FormTextareaField form={form} name="bio" label="Bio" validators={{ onChange }}>
1141
+ * {(field, { textareaProps }) => <Textarea {...textareaProps} rows={4} />}
1142
+ * </FormTextareaField>
1143
+ */
1144
+ function FormTextareaField(props) {
1145
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1146
+ ...props,
1147
+ buildControl: textareaControl
1148
+ });
1149
+ }
1150
+
1151
+ //#endregion
1152
+ //#region src/components/form-radio-group-field.tsx
1153
+ function radioGroupControl(field) {
1154
+ return { radioGroupProps: {
1155
+ value: field.state.value || null,
1156
+ onValueChange: (value) => field.handleChange(value ?? ""),
1157
+ onBlur: field.handleBlur,
1158
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1159
+ } };
1160
+ }
1161
+ /**
1162
+ * Radio-group field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1163
+ * with a `radioGroupProps` bundle. Compose the items yourself. `name` is restricted to the
1164
+ * form's **string** fields.
1165
+ *
1166
+ * @example
1167
+ * <FormRadioGroupField form={form} name="plan" label="Plan" validators={{ onChange }}>
1168
+ * {(field, { radioGroupProps }) => (
1169
+ * <RadioGroup {...radioGroupProps}>
1170
+ * <RadioGroupItem value="a" />
1171
+ * </RadioGroup>
1172
+ * )}
1173
+ * </FormRadioGroupField>
1174
+ */
1175
+ function FormRadioGroupField(props) {
1176
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1177
+ ...props,
1178
+ buildControl: radioGroupControl
1179
+ });
1180
+ }
1181
+
1182
+ //#endregion
1183
+ //#region src/components/form-otp-field.tsx
1184
+ function otpControl(field) {
1185
+ return { otpProps: {
1186
+ value: field.state.value ?? "",
1187
+ onChange: (value) => field.handleChange(value),
1188
+ onBlur: field.handleBlur,
1189
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1190
+ } };
1191
+ }
1192
+ /**
1193
+ * One-time-code field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1194
+ * with an `otpProps` bundle. Compose the slots yourself. `name` is restricted to the form's
1195
+ * **string** fields.
1196
+ *
1197
+ * @example
1198
+ * <FormOTPField form={form} name="code" label="Code" validators={{ onChange }}>
1199
+ * {(field, { otpProps }) => <InputOTP maxLength={6} {...otpProps}>{slots}</InputOTP>}
1200
+ * </FormOTPField>
1201
+ */
1202
+ function FormOTPField(props) {
1203
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1204
+ ...props,
1205
+ buildControl: otpControl
1206
+ });
1207
+ }
1208
+
1209
+ //#endregion
1210
+ //#region src/components/form-switch-field.tsx
1211
+ function switchControl(field) {
1212
+ return { switchProps: {
1213
+ checked: field.state.value,
1214
+ onCheckedChange: (checked) => field.handleChange(checked),
1215
+ onBlur: field.handleBlur,
1216
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1217
+ } };
1218
+ }
1219
+ /**
1220
+ * Toggle field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1221
+ * with a `switchProps` bundle. `name` is restricted to the form's **boolean** fields — a
1222
+ * string/number `name` is a type error.
1223
+ *
1224
+ * @example
1225
+ * <FormSwitchField form={form} name="enabled" label="Enabled" validators={{ onChange }}>
1226
+ * {(field, { switchProps }) => <Switch {...switchProps} />}
1227
+ * </FormSwitchField>
1228
+ */
1229
+ function FormSwitchField(props) {
1230
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1231
+ ...props,
1232
+ buildControl: switchControl
1233
+ });
1234
+ }
1235
+
1236
+ //#endregion
1237
+ //#region src/components/form-number-field.tsx
1238
+ function numberControl(field) {
1239
+ return { numberProps: {
1240
+ value: field.state.value,
1241
+ onChange: (event) => field.handleChange(event.target.value === "" ? 0 : event.target.valueAsNumber),
1242
+ onBlur: field.handleBlur,
1243
+ "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
1244
+ } };
1245
+ }
1246
+ /**
1247
+ * Numeric-input field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1248
+ * with a `numberProps` bundle. `name` is restricted to the form's **number** fields — a
1249
+ * string/boolean `name` is a type error.
1250
+ *
1251
+ * @example
1252
+ * <FormNumberField form={form} name="age" label="Age" validators={{ onChange }}>
1253
+ * {(field, { numberProps }) => <Input type="number" {...numberProps} />}
1254
+ * </FormNumberField>
1255
+ */
1256
+ function FormNumberField(props) {
1257
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1258
+ ...props,
1259
+ buildControl: numberControl
1260
+ });
1261
+ }
1262
+
1263
+ //#endregion
1264
+ //#region src/components/form-slider-field.tsx
1265
+ function sliderControl(field) {
1266
+ return { sliderProps: {
1267
+ value: [field.state.value],
1268
+ onValueChange: (values) => field.handleChange(values[0] ?? 0),
1269
+ onBlur: field.handleBlur
1270
+ } };
1271
+ }
1272
+ /**
1273
+ * Slider field wrapper — same threaded-`form` typing and shared shell as `FormSelectField`,
1274
+ * with a `sliderProps` bundle. `name` is restricted to the form's **number** fields; the bundle
1275
+ * converts to/from the `number[]` the range primitive uses internally.
1276
+ *
1277
+ * @example
1278
+ * <FormSliderField form={form} name="volume" label="Volume" validators={{ onChange }}>
1279
+ * {(field, { sliderProps }) => <Slider min={0} max={100} step={1} {...sliderProps} />}
1280
+ * </FormSliderField>
1281
+ */
1282
+ function FormSliderField(props) {
1283
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1284
+ ...props,
1285
+ buildControl: sliderControl
1286
+ });
1287
+ }
1288
+
1289
+ //#endregion
1290
+ //#region src/components/form-toggle-group-field.tsx
1291
+ function toggleGroupControl(field) {
1292
+ return { toggleGroupProps: {
1293
+ value: field.state.value ? [field.state.value] : [],
1294
+ onValueChange: (values) => field.handleChange(values[values.length - 1] ?? null),
1295
+ onBlur: field.handleBlur
1296
+ } };
1297
+ }
1298
+ /**
1299
+ * Single-select toggle-group field wrapper — same threaded-`form` typing and shared shell as
1300
+ * `FormSelectField`, with a `toggleGroupProps` bundle. `name` is restricted to the form's
1301
+ * **string | null** fields; the bundle converts to/from the `string[]` the multi-select
1302
+ * primitive uses internally.
1303
+ *
1304
+ * @example
1305
+ * <FormToggleGroupField form={form} name="align" label="Align" validators={{ onChange }}>
1306
+ * {(field, { toggleGroupProps }) => (
1307
+ * <ToggleGroup {...toggleGroupProps}>
1308
+ * <ToggleGroupItem value="a">A</ToggleGroupItem>
1309
+ * </ToggleGroup>
1310
+ * )}
1311
+ * </FormToggleGroupField>
1312
+ */
1313
+ function FormToggleGroupField(props) {
1314
+ return /* @__PURE__ */ jsx(FormFieldBase, {
1315
+ ...props,
1316
+ buildControl: toggleGroupControl
1317
+ });
1318
+ }
1319
+
1320
+ //#endregion
1321
+ //#region src/components/save-bar.tsx
1322
+ function SaveBar({ isDirty, isSubmitting, canSubmit, onReset, submitLabel = "Save changes", savingLabel = "Saving…", resetLabel = "Discard", className }) {
1323
+ return /* @__PURE__ */ jsx("div", {
1324
+ className: cn("grid transition-[grid-template-rows] duration-300 ease-in-out", isDirty ? "grid-rows-[1fr]" : "grid-rows-[0fr]", className),
1325
+ children: /* @__PURE__ */ jsx("div", {
1326
+ className: "overflow-hidden",
1327
+ children: /* @__PURE__ */ jsxs("div", {
1328
+ className: "flex items-center justify-end gap-2 border-t px-4 py-2",
1329
+ children: [/* @__PURE__ */ jsx(Button, {
1330
+ type: "button",
1331
+ variant: "ghost",
1332
+ size: "sm",
1333
+ onClick: onReset,
1334
+ disabled: !isDirty || isSubmitting,
1335
+ children: resetLabel
1336
+ }), /* @__PURE__ */ jsx(Button, {
1337
+ type: "submit",
1338
+ size: "sm",
1339
+ disabled: !isDirty || !canSubmit || isSubmitting,
1340
+ children: isSubmitting ? savingLabel : submitLabel
1341
+ })]
1342
+ })
1343
+ })
1344
+ });
270
1345
  }
271
1346
 
272
1347
  //#endregion
273
1348
  //#region src/components/dashboard-sidebar.tsx
274
- function DashboardSidebarProvider({ className, style, ...props }) {
1349
+ const DashboardSidebarProvider = React.forwardRef((componentProps, forwardRef) => {
1350
+ const { className, style, ...props } = componentProps;
275
1351
  return /* @__PURE__ */ jsx(SidebarProvider, {
1352
+ ref: forwardRef,
276
1353
  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),
277
1354
  style: {
278
1355
  "--sidebar-width": "14rem",
@@ -281,10 +1358,13 @@ function DashboardSidebarProvider({ className, style, ...props }) {
281
1358
  },
282
1359
  ...props
283
1360
  });
284
- }
285
- function DashboardSidebar({ className, children, ...props }) {
1361
+ });
1362
+ DashboardSidebarProvider.displayName = "DashboardSidebarProvider";
1363
+ const DashboardSidebar = React.forwardRef((componentProps, forwardRef) => {
1364
+ const { className, children, ...props } = componentProps;
286
1365
  const { state } = useSidebar();
287
1366
  return /* @__PURE__ */ jsx("div", {
1367
+ ref: forwardRef,
288
1368
  "data-slot": "dashboard-sidebar",
289
1369
  "data-state": state,
290
1370
  "data-collapsible": state === "collapsed" ? "icon" : "",
@@ -292,18 +1372,24 @@ function DashboardSidebar({ className, children, ...props }) {
292
1372
  ...props,
293
1373
  children
294
1374
  });
295
- }
296
- function DashboardSidebarHeader({ className, children, ...props }) {
1375
+ });
1376
+ DashboardSidebar.displayName = "DashboardSidebar";
1377
+ const DashboardSidebarHeader = React.forwardRef((componentProps, forwardRef) => {
1378
+ const { className, children, ...props } = componentProps;
297
1379
  return /* @__PURE__ */ jsx("div", {
1380
+ ref: forwardRef,
298
1381
  "data-slot": "dashboard-sidebar-header",
299
1382
  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),
300
1383
  ...props,
301
1384
  children
302
1385
  });
303
- }
304
- function DashboardSidebarTrigger({ className, onClick, ...props }) {
1386
+ });
1387
+ DashboardSidebarHeader.displayName = "DashboardSidebarHeader";
1388
+ const DashboardSidebarTrigger = React.forwardRef((componentProps, forwardRef) => {
1389
+ const { className, onClick, ...props } = componentProps;
305
1390
  const { toggleSidebar, state } = useSidebar();
306
1391
  return /* @__PURE__ */ jsx(Button, {
1392
+ ref: forwardRef,
307
1393
  "data-slot": "dashboard-sidebar-trigger",
308
1394
  variant: "ghost",
309
1395
  size: "icon",
@@ -316,51 +1402,73 @@ function DashboardSidebarTrigger({ className, onClick, ...props }) {
316
1402
  ...props,
317
1403
  children: state === "expanded" ? /* @__PURE__ */ jsx(PanelLeftClose, { className: "size-4" }) : /* @__PURE__ */ jsx(PanelLeftOpen, { className: "size-4" })
318
1404
  });
319
- }
320
- function DashboardSidebarContent({ className, ...props }) {
1405
+ });
1406
+ DashboardSidebarTrigger.displayName = "DashboardSidebarTrigger";
1407
+ const DashboardSidebarContent = React.forwardRef((componentProps, forwardRef) => {
1408
+ const { className, ...props } = componentProps;
321
1409
  return /* @__PURE__ */ jsx("div", {
1410
+ ref: forwardRef,
322
1411
  "data-slot": "dashboard-sidebar-content",
323
1412
  className: cn("flex flex-1 flex-col overflow-y-auto overflow-x-hidden min-h-0", "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className),
324
1413
  ...props
325
1414
  });
326
- }
327
- function DashboardSidebarFooter({ className, ...props }) {
1415
+ });
1416
+ DashboardSidebarContent.displayName = "DashboardSidebarContent";
1417
+ const DashboardSidebarFooter = React.forwardRef((componentProps, forwardRef) => {
1418
+ const { className, ...props } = componentProps;
328
1419
  return /* @__PURE__ */ jsx("div", {
1420
+ ref: forwardRef,
329
1421
  "data-slot": "dashboard-sidebar-footer",
330
1422
  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),
331
1423
  ...props
332
1424
  });
333
- }
334
- function DashboardSidebarGroup({ className, ...props }) {
1425
+ });
1426
+ DashboardSidebarFooter.displayName = "DashboardSidebarFooter";
1427
+ const DashboardSidebarGroup = React.forwardRef((componentProps, forwardRef) => {
1428
+ const { className, ...props } = componentProps;
335
1429
  return /* @__PURE__ */ jsx("div", {
1430
+ ref: forwardRef,
336
1431
  "data-slot": "dashboard-sidebar-group",
337
1432
  className: cn("flex flex-col gap-1 items-start p-1 w-full", className),
338
1433
  ...props
339
1434
  });
340
- }
341
- function DashboardSidebarGroupLabel({ className, render, ...props }) {
1435
+ });
1436
+ DashboardSidebarGroup.displayName = "DashboardSidebarGroup";
1437
+ const DashboardSidebarGroupLabel = React.forwardRef((componentProps, forwardRef) => {
1438
+ const { className, render, ...props } = componentProps;
342
1439
  return useRender({
343
1440
  defaultTagName: "div",
344
- 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),
1441
+ props: mergeProps({
1442
+ ref: forwardRef,
1443
+ 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)
1444
+ }, props),
345
1445
  render,
346
1446
  state: { slot: "dashboard-sidebar-group-label" }
347
1447
  });
348
- }
349
- function DashboardSidebarMenu({ className, ...props }) {
1448
+ });
1449
+ DashboardSidebarGroupLabel.displayName = "DashboardSidebarGroupLabel";
1450
+ const DashboardSidebarMenu = React.forwardRef((componentProps, forwardRef) => {
1451
+ const { className, ...props } = componentProps;
350
1452
  return /* @__PURE__ */ jsx("ul", {
1453
+ ref: forwardRef,
351
1454
  "data-slot": "dashboard-sidebar-menu",
352
1455
  className: cn("flex w-full min-w-0 flex-col gap-1", className),
353
1456
  ...props
354
1457
  });
355
- }
356
- function DashboardSidebarMenuItem({ className, ...props }) {
1458
+ });
1459
+ DashboardSidebarMenu.displayName = "DashboardSidebarMenu";
1460
+ const DashboardSidebarMenuItem = React.forwardRef((componentProps, forwardRef) => {
1461
+ const { className, ...props } = componentProps;
357
1462
  return /* @__PURE__ */ jsx("li", {
1463
+ ref: forwardRef,
358
1464
  "data-slot": "dashboard-sidebar-menu-item",
359
1465
  className: cn("relative list-none", className),
360
1466
  ...props
361
1467
  });
362
- }
363
- const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMenuButton({ className, render, isActive = false, disabled = false, icon, action, children, ...props }, ref) {
1468
+ });
1469
+ DashboardSidebarMenuItem.displayName = "DashboardSidebarMenuItem";
1470
+ const DashboardSidebarMenuButton = React.forwardRef((componentProps, forwardRef) => {
1471
+ const { className, render, isActive = false, disabled = false, icon, action, children, ...props } = componentProps;
364
1472
  const buttonContent = /* @__PURE__ */ jsxs(Fragment, { children: [
365
1473
  icon && /* @__PURE__ */ jsx("span", {
366
1474
  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"),
@@ -378,7 +1486,7 @@ const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMen
378
1486
  return useRender({
379
1487
  defaultTagName: "button",
380
1488
  props: mergeProps({
381
- ref,
1489
+ ref: forwardRef,
382
1490
  type: "button",
383
1491
  "data-active": isActive,
384
1492
  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),
@@ -392,44 +1500,61 @@ const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMen
392
1500
  });
393
1501
  });
394
1502
  DashboardSidebarMenuButton.displayName = "DashboardSidebarMenuButton";
395
- function DashboardSidebarMenuAddon({ className, ...props }) {
1503
+ const DashboardSidebarMenuAddon = React.forwardRef((componentProps, forwardRef) => {
1504
+ const { className, ...props } = componentProps;
396
1505
  return /* @__PURE__ */ jsx("div", {
1506
+ ref: forwardRef,
397
1507
  "data-slot": "dashboard-sidebar-menu-addon",
398
1508
  className: cn("ml-auto shrink-0 inline-flex items-center", className),
399
1509
  ...props
400
1510
  });
401
- }
402
- function DashboardSidebarMenuBadge({ className, ...props }) {
1511
+ });
1512
+ DashboardSidebarMenuAddon.displayName = "DashboardSidebarMenuAddon";
1513
+ const DashboardSidebarMenuBadge = React.forwardRef((componentProps, forwardRef) => {
1514
+ const { className, ...props } = componentProps;
403
1515
  return /* @__PURE__ */ jsx(CounterBadge, {
1516
+ ref: forwardRef,
404
1517
  "data-slot": "dashboard-sidebar-menu-badge",
405
1518
  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),
406
1519
  ...props
407
1520
  });
408
- }
409
- function DashboardSidebarSubmenu({ className, ...props }) {
1521
+ });
1522
+ DashboardSidebarMenuBadge.displayName = "DashboardSidebarMenuBadge";
1523
+ const DashboardSidebarSubmenu = React.forwardRef((componentProps, forwardRef) => {
1524
+ const { className, ...props } = componentProps;
410
1525
  return /* @__PURE__ */ jsx("div", {
1526
+ ref: forwardRef,
411
1527
  "data-slot": "dashboard-sidebar-submenu",
412
1528
  className: cn("bg-popover border border-border shadow-md rounded-md p-1", "flex flex-col w-[238px]", className),
413
1529
  ...props
414
1530
  });
415
- }
416
- function DashboardSidebarSubmenuItem({ className, render, isActive = false, ...props }) {
1531
+ });
1532
+ DashboardSidebarSubmenu.displayName = "DashboardSidebarSubmenu";
1533
+ const DashboardSidebarSubmenuItem = React.forwardRef((componentProps, forwardRef) => {
1534
+ const { className, render, isActive = false, ...props } = componentProps;
417
1535
  return useRender({
418
1536
  defaultTagName: "div",
419
- 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),
1537
+ props: mergeProps({
1538
+ ref: forwardRef,
1539
+ 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)
1540
+ }, props),
420
1541
  render,
421
1542
  state: {
422
1543
  slot: "dashboard-sidebar-submenu-item",
423
1544
  active: isActive
424
1545
  }
425
1546
  });
426
- }
427
- function DashboardSidebarSubmenuSeparator({ className }) {
1547
+ });
1548
+ DashboardSidebarSubmenuItem.displayName = "DashboardSidebarSubmenuItem";
1549
+ const DashboardSidebarSubmenuSeparator = React.forwardRef((componentProps, forwardRef) => {
1550
+ const { className } = componentProps;
428
1551
  return /* @__PURE__ */ jsx("div", {
1552
+ ref: forwardRef,
429
1553
  "data-slot": "dashboard-sidebar-submenu-separator",
430
1554
  className: cn("h-px bg-border -mx-1 my-1", className)
431
1555
  });
432
- }
1556
+ });
1557
+ DashboardSidebarSubmenuSeparator.displayName = "DashboardSidebarSubmenuSeparator";
433
1558
 
434
1559
  //#endregion
435
1560
  //#region src/components/dashboard-sidebar-nav.tsx
@@ -525,4 +1650,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
525
1650
  }
526
1651
 
527
1652
  //#endregion
528
- 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, DeprecatedBadge, NewBadge, ProfessionalBadge, RoleBadge, TrialBadge, useCopyToClipboard, useSidebar as useDashboardSidebar };
1653
+ 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 };