@aircall/blocks 0.4.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,17 +438,66 @@ 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
 
81
503
  //#endregion
@@ -144,46 +566,63 @@ DashboardStandalonePageContent.displayName = "DashboardStandalonePageContent";
144
566
 
145
567
  //#endregion
146
568
  //#region src/components/dashboard-page.tsx
147
- function DashboardPage({ className, ...props }) {
569
+ const DashboardPage = React.forwardRef((componentProps, forwardRef) => {
570
+ const { className, ...props } = componentProps;
148
571
  return /* @__PURE__ */ jsx("div", {
572
+ ref: forwardRef,
149
573
  "data-slot": "dashboard-page",
150
574
  className: cn("flex flex-col h-screen bg-page-background p-2 gap-2", className),
151
575
  ...props
152
576
  });
153
- }
154
- function DashboardPageBanner({ className, ...props }) {
577
+ });
578
+ DashboardPage.displayName = "DashboardPage";
579
+ const DashboardPageBanner = React.forwardRef((componentProps, forwardRef) => {
580
+ const { className, ...props } = componentProps;
155
581
  return /* @__PURE__ */ jsx("div", {
582
+ ref: forwardRef,
156
583
  "data-slot": "dashboard-page-banner",
157
584
  className: cn("-mt-2 -mx-2", className),
158
585
  ...props
159
586
  });
160
- }
161
- function DashboardPageContent({ className, ...props }) {
587
+ });
588
+ DashboardPageBanner.displayName = "DashboardPageBanner";
589
+ const DashboardPageContent = React.forwardRef((componentProps, forwardRef) => {
590
+ const { className, ...props } = componentProps;
162
591
  return /* @__PURE__ */ jsx("div", {
592
+ ref: forwardRef,
163
593
  "data-slot": "dashboard-page-content",
164
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),
165
595
  ...props
166
596
  });
167
- }
168
- function DashboardPageMain({ className, ...props }) {
597
+ });
598
+ DashboardPageContent.displayName = "DashboardPageContent";
599
+ const DashboardPageMain = React.forwardRef((componentProps, forwardRef) => {
600
+ const { className, ...props } = componentProps;
169
601
  return /* @__PURE__ */ jsx("main", {
602
+ ref: forwardRef,
170
603
  "data-slot": "dashboard-page-main",
171
604
  className: cn("flex-1 min-h-0 overflow-auto py-4 px-6", className),
172
605
  ...props
173
606
  });
174
- }
175
- function DashboardPageTabs({ className, ...props }) {
607
+ });
608
+ DashboardPageMain.displayName = "DashboardPageMain";
609
+ const DashboardPageTabs = React.forwardRef((componentProps, forwardRef) => {
610
+ const { className, ...props } = componentProps;
176
611
  return /* @__PURE__ */ jsx(TabsList, {
612
+ ref: forwardRef,
177
613
  ...props,
178
614
  "data-slot": "dph-tabs",
179
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)
180
616
  });
181
- }
617
+ });
618
+ DashboardPageTabs.displayName = "DashboardPageTabs";
182
619
 
183
620
  //#endregion
184
621
  //#region src/components/dashboard-page-header.tsx
185
- function DashboardPageHeader({ className, children, ...props }) {
622
+ const DashboardPageHeader = React.forwardRef((componentProps, forwardRef) => {
623
+ const { className, children, ...props } = componentProps;
186
624
  return /* @__PURE__ */ jsx("div", {
625
+ ref: forwardRef,
187
626
  "data-slot": "dph",
188
627
  className: cn("@container bg-background border-b border-border w-full", className),
189
628
  ...props,
@@ -193,77 +632,105 @@ function DashboardPageHeader({ className, children, ...props }) {
193
632
  children
194
633
  })
195
634
  });
196
- }
197
- function DashboardPageHeaderNav({ className, children, ...props }) {
635
+ });
636
+ DashboardPageHeader.displayName = "DashboardPageHeader";
637
+ const DashboardPageHeaderNav = React.forwardRef((componentProps, forwardRef) => {
638
+ const { className, children, ...props } = componentProps;
198
639
  return /* @__PURE__ */ jsx("div", {
640
+ ref: forwardRef,
199
641
  "data-slot": "dph-nav",
200
642
  className: cn("[grid-area:nav] flex items-center gap-1", className),
201
643
  ...props,
202
644
  children
203
645
  });
204
- }
205
- function DashboardPageHeaderNavBack({ children, ...props }) {
646
+ });
647
+ DashboardPageHeaderNav.displayName = "DashboardPageHeaderNav";
648
+ const DashboardPageHeaderNavBack = React.forwardRef((componentProps, forwardRef) => {
649
+ const { children, ...props } = componentProps;
206
650
  return /* @__PURE__ */ jsxs(Button, {
651
+ ref: forwardRef,
207
652
  variant: "ghost",
208
653
  size: "sm",
209
654
  nativeButton: false,
210
655
  ...props,
211
656
  children: [/* @__PURE__ */ jsx(ChevronLeft, {}), children]
212
657
  });
213
- }
214
- function DashboardPageHeaderPrefix({ className, children, ...props }) {
658
+ });
659
+ DashboardPageHeaderNavBack.displayName = "DashboardPageHeaderNavBack";
660
+ const DashboardPageHeaderPrefix = React.forwardRef((componentProps, forwardRef) => {
661
+ const { className, children, ...props } = componentProps;
215
662
  return /* @__PURE__ */ jsx("div", {
663
+ ref: forwardRef,
216
664
  "data-slot": "dph-prefix",
217
665
  className: cn("[grid-area:prefix] flex shrink-0 px-2 pt-2 items-center justify-center self-start", className),
218
666
  ...props,
219
667
  children
220
668
  });
221
- }
222
- function DashboardPageHeaderTitleGroup({ className, children, ...props }) {
669
+ });
670
+ DashboardPageHeaderPrefix.displayName = "DashboardPageHeaderPrefix";
671
+ const DashboardPageHeaderTitleGroup = React.forwardRef((componentProps, forwardRef) => {
672
+ const { className, children, ...props } = componentProps;
223
673
  return /* @__PURE__ */ jsx("div", {
674
+ ref: forwardRef,
224
675
  "data-slot": "dph-title-group",
225
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),
226
677
  ...props,
227
678
  children
228
679
  });
229
- }
230
- 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;
231
684
  return /* @__PURE__ */ jsx("h1", {
685
+ ref: forwardRef,
232
686
  "data-slot": "dph-title",
233
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),
234
688
  ...props,
235
689
  children
236
690
  });
237
- }
238
- function DashboardPageHeaderSubtitle({ className, children, ...props }) {
691
+ });
692
+ DashboardPageHeaderTitle.displayName = "DashboardPageHeaderTitle";
693
+ const DashboardPageHeaderSubtitle = React.forwardRef((componentProps, forwardRef) => {
694
+ const { className, children, ...props } = componentProps;
239
695
  return /* @__PURE__ */ jsx("div", {
696
+ ref: forwardRef,
240
697
  "data-slot": "dph-subtitle",
241
698
  className: cn("flex flex-col items-start gap-0.5 max-w-xl", className),
242
699
  ...props,
243
700
  children
244
701
  });
245
- }
246
- function DashboardPageHeaderDescription({ className, children, ...props }) {
702
+ });
703
+ DashboardPageHeaderSubtitle.displayName = "DashboardPageHeaderSubtitle";
704
+ const DashboardPageHeaderDescription = React.forwardRef((componentProps, forwardRef) => {
705
+ const { className, children, ...props } = componentProps;
247
706
  return /* @__PURE__ */ jsx("div", {
707
+ ref: forwardRef,
248
708
  "data-slot": "dph-description",
249
709
  className: cn("text-sm text-muted-foreground line-clamp-3 -mt-1", className),
250
710
  ...props,
251
711
  children
252
712
  });
253
- }
254
- function DashboardPageHeaderActions({ className, children, ...props }) {
713
+ });
714
+ DashboardPageHeaderDescription.displayName = "DashboardPageHeaderDescription";
715
+ const DashboardPageHeaderActions = React.forwardRef((componentProps, forwardRef) => {
716
+ const { className, children, ...props } = componentProps;
255
717
  return /* @__PURE__ */ jsx("div", {
718
+ ref: forwardRef,
256
719
  "data-slot": "dph-actions",
257
720
  className: cn("[grid-area:actions] flex items-center gap-2 self-start pl-2", className),
258
721
  ...props,
259
722
  children
260
723
  });
261
- }
262
- const DashboardPageHeaderAction = React.forwardRef(({ size = "lg", ...props }, ref) => /* @__PURE__ */ jsx(Button, {
263
- ref,
264
- size,
265
- ...props
266
- }));
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
+ });
267
734
  DashboardPageHeaderAction.displayName = "DashboardPageHeaderAction";
268
735
 
269
736
  //#endregion
@@ -283,14 +750,16 @@ const PRODUCT_BADGE_LABEL = {
283
750
  };
284
751
  function createProductBadge(type) {
285
752
  const label = PRODUCT_BADGE_LABEL[type];
286
- function ProductBadge({ className, ...props }) {
753
+ const ProductBadge = React.forwardRef((componentProps, forwardRef) => {
754
+ const { className, ...props } = componentProps;
287
755
  return /* @__PURE__ */ jsx(Badge, {
756
+ ref: forwardRef,
288
757
  "data-slot": `badge-${type}`,
289
758
  className: cn(productBadgeVariants({ type }), className),
290
759
  ...props,
291
760
  children: label
292
761
  });
293
- }
762
+ });
294
763
  ProductBadge.displayName = label.replace(/\s+/g, "") + "Badge";
295
764
  return ProductBadge;
296
765
  }
@@ -303,15 +772,18 @@ const DeprecatedBadge = createProductBadge("deprecated");
303
772
  /** Blue pill — user is in a trial period (same blue family as Beta) */
304
773
  const TrialBadge = createProductBadge("trial");
305
774
  /** Charcoal pill — Professional tier (no gradient; solid secondary Badge) */
306
- function ProfessionalBadge({ className, ...props }) {
775
+ const ProfessionalBadge = React.forwardRef((componentProps, forwardRef) => {
776
+ const { className, ...props } = componentProps;
307
777
  return /* @__PURE__ */ jsx(Badge, {
778
+ ref: forwardRef,
308
779
  "data-slot": "badge-professional",
309
780
  tone: "dark",
310
781
  color: "charcoal",
311
782
  ...props,
312
783
  children: "Professional"
313
784
  });
314
- }
785
+ });
786
+ ProfessionalBadge.displayName = "ProfessionalBadge";
315
787
  const roleBadgeVariants = cva(GRADIENT_BASE, { variants: { role: {
316
788
  agent: "[--badge-accent:var(--color-yellow-400)]",
317
789
  supervisor: "[--badge-accent:var(--color-blue-500)]",
@@ -324,19 +796,560 @@ const ROLE_BADGE_LABEL = {
324
796
  admin: "Admin",
325
797
  owner: "Owner"
326
798
  };
327
- function RoleBadge({ role, className, ...props }) {
799
+ const RoleBadge = React.forwardRef((componentProps, forwardRef) => {
800
+ const { role, className, ...props } = componentProps;
328
801
  return /* @__PURE__ */ jsx(Badge, {
802
+ ref: forwardRef,
329
803
  "data-slot": `badge-${role}`,
330
804
  className: cn(roleBadgeVariants({ role }), className),
331
805
  ...props,
332
806
  children: ROLE_BADGE_LABEL[role]
333
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
+ });
334
1345
  }
335
1346
 
336
1347
  //#endregion
337
1348
  //#region src/components/dashboard-sidebar.tsx
338
- function DashboardSidebarProvider({ className, style, ...props }) {
1349
+ const DashboardSidebarProvider = React.forwardRef((componentProps, forwardRef) => {
1350
+ const { className, style, ...props } = componentProps;
339
1351
  return /* @__PURE__ */ jsx(SidebarProvider, {
1352
+ ref: forwardRef,
340
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),
341
1354
  style: {
342
1355
  "--sidebar-width": "14rem",
@@ -345,10 +1358,13 @@ function DashboardSidebarProvider({ className, style, ...props }) {
345
1358
  },
346
1359
  ...props
347
1360
  });
348
- }
349
- function DashboardSidebar({ className, children, ...props }) {
1361
+ });
1362
+ DashboardSidebarProvider.displayName = "DashboardSidebarProvider";
1363
+ const DashboardSidebar = React.forwardRef((componentProps, forwardRef) => {
1364
+ const { className, children, ...props } = componentProps;
350
1365
  const { state } = useSidebar();
351
1366
  return /* @__PURE__ */ jsx("div", {
1367
+ ref: forwardRef,
352
1368
  "data-slot": "dashboard-sidebar",
353
1369
  "data-state": state,
354
1370
  "data-collapsible": state === "collapsed" ? "icon" : "",
@@ -356,18 +1372,24 @@ function DashboardSidebar({ className, children, ...props }) {
356
1372
  ...props,
357
1373
  children
358
1374
  });
359
- }
360
- function DashboardSidebarHeader({ className, children, ...props }) {
1375
+ });
1376
+ DashboardSidebar.displayName = "DashboardSidebar";
1377
+ const DashboardSidebarHeader = React.forwardRef((componentProps, forwardRef) => {
1378
+ const { className, children, ...props } = componentProps;
361
1379
  return /* @__PURE__ */ jsx("div", {
1380
+ ref: forwardRef,
362
1381
  "data-slot": "dashboard-sidebar-header",
363
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),
364
1383
  ...props,
365
1384
  children
366
1385
  });
367
- }
368
- function DashboardSidebarTrigger({ className, onClick, ...props }) {
1386
+ });
1387
+ DashboardSidebarHeader.displayName = "DashboardSidebarHeader";
1388
+ const DashboardSidebarTrigger = React.forwardRef((componentProps, forwardRef) => {
1389
+ const { className, onClick, ...props } = componentProps;
369
1390
  const { toggleSidebar, state } = useSidebar();
370
1391
  return /* @__PURE__ */ jsx(Button, {
1392
+ ref: forwardRef,
371
1393
  "data-slot": "dashboard-sidebar-trigger",
372
1394
  variant: "ghost",
373
1395
  size: "icon",
@@ -380,51 +1402,73 @@ function DashboardSidebarTrigger({ className, onClick, ...props }) {
380
1402
  ...props,
381
1403
  children: state === "expanded" ? /* @__PURE__ */ jsx(PanelLeftClose, { className: "size-4" }) : /* @__PURE__ */ jsx(PanelLeftOpen, { className: "size-4" })
382
1404
  });
383
- }
384
- function DashboardSidebarContent({ className, ...props }) {
1405
+ });
1406
+ DashboardSidebarTrigger.displayName = "DashboardSidebarTrigger";
1407
+ const DashboardSidebarContent = React.forwardRef((componentProps, forwardRef) => {
1408
+ const { className, ...props } = componentProps;
385
1409
  return /* @__PURE__ */ jsx("div", {
1410
+ ref: forwardRef,
386
1411
  "data-slot": "dashboard-sidebar-content",
387
1412
  className: cn("flex flex-1 flex-col overflow-y-auto overflow-x-hidden min-h-0", "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className),
388
1413
  ...props
389
1414
  });
390
- }
391
- function DashboardSidebarFooter({ className, ...props }) {
1415
+ });
1416
+ DashboardSidebarContent.displayName = "DashboardSidebarContent";
1417
+ const DashboardSidebarFooter = React.forwardRef((componentProps, forwardRef) => {
1418
+ const { className, ...props } = componentProps;
392
1419
  return /* @__PURE__ */ jsx("div", {
1420
+ ref: forwardRef,
393
1421
  "data-slot": "dashboard-sidebar-footer",
394
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),
395
1423
  ...props
396
1424
  });
397
- }
398
- function DashboardSidebarGroup({ className, ...props }) {
1425
+ });
1426
+ DashboardSidebarFooter.displayName = "DashboardSidebarFooter";
1427
+ const DashboardSidebarGroup = React.forwardRef((componentProps, forwardRef) => {
1428
+ const { className, ...props } = componentProps;
399
1429
  return /* @__PURE__ */ jsx("div", {
1430
+ ref: forwardRef,
400
1431
  "data-slot": "dashboard-sidebar-group",
401
1432
  className: cn("flex flex-col gap-1 items-start p-1 w-full", className),
402
1433
  ...props
403
1434
  });
404
- }
405
- function DashboardSidebarGroupLabel({ className, render, ...props }) {
1435
+ });
1436
+ DashboardSidebarGroup.displayName = "DashboardSidebarGroup";
1437
+ const DashboardSidebarGroupLabel = React.forwardRef((componentProps, forwardRef) => {
1438
+ const { className, render, ...props } = componentProps;
406
1439
  return useRender({
407
1440
  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),
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),
409
1445
  render,
410
1446
  state: { slot: "dashboard-sidebar-group-label" }
411
1447
  });
412
- }
413
- function DashboardSidebarMenu({ className, ...props }) {
1448
+ });
1449
+ DashboardSidebarGroupLabel.displayName = "DashboardSidebarGroupLabel";
1450
+ const DashboardSidebarMenu = React.forwardRef((componentProps, forwardRef) => {
1451
+ const { className, ...props } = componentProps;
414
1452
  return /* @__PURE__ */ jsx("ul", {
1453
+ ref: forwardRef,
415
1454
  "data-slot": "dashboard-sidebar-menu",
416
1455
  className: cn("flex w-full min-w-0 flex-col gap-1", className),
417
1456
  ...props
418
1457
  });
419
- }
420
- function DashboardSidebarMenuItem({ className, ...props }) {
1458
+ });
1459
+ DashboardSidebarMenu.displayName = "DashboardSidebarMenu";
1460
+ const DashboardSidebarMenuItem = React.forwardRef((componentProps, forwardRef) => {
1461
+ const { className, ...props } = componentProps;
421
1462
  return /* @__PURE__ */ jsx("li", {
1463
+ ref: forwardRef,
422
1464
  "data-slot": "dashboard-sidebar-menu-item",
423
1465
  className: cn("relative list-none", className),
424
1466
  ...props
425
1467
  });
426
- }
427
- 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;
428
1472
  const buttonContent = /* @__PURE__ */ jsxs(Fragment, { children: [
429
1473
  icon && /* @__PURE__ */ jsx("span", {
430
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"),
@@ -442,7 +1486,7 @@ const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMen
442
1486
  return useRender({
443
1487
  defaultTagName: "button",
444
1488
  props: mergeProps({
445
- ref,
1489
+ ref: forwardRef,
446
1490
  type: "button",
447
1491
  "data-active": isActive,
448
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),
@@ -456,44 +1500,61 @@ const DashboardSidebarMenuButton = React.forwardRef(function DashboardSidebarMen
456
1500
  });
457
1501
  });
458
1502
  DashboardSidebarMenuButton.displayName = "DashboardSidebarMenuButton";
459
- function DashboardSidebarMenuAddon({ className, ...props }) {
1503
+ const DashboardSidebarMenuAddon = React.forwardRef((componentProps, forwardRef) => {
1504
+ const { className, ...props } = componentProps;
460
1505
  return /* @__PURE__ */ jsx("div", {
1506
+ ref: forwardRef,
461
1507
  "data-slot": "dashboard-sidebar-menu-addon",
462
1508
  className: cn("ml-auto shrink-0 inline-flex items-center", className),
463
1509
  ...props
464
1510
  });
465
- }
466
- function DashboardSidebarMenuBadge({ className, ...props }) {
1511
+ });
1512
+ DashboardSidebarMenuAddon.displayName = "DashboardSidebarMenuAddon";
1513
+ const DashboardSidebarMenuBadge = React.forwardRef((componentProps, forwardRef) => {
1514
+ const { className, ...props } = componentProps;
467
1515
  return /* @__PURE__ */ jsx(CounterBadge, {
1516
+ ref: forwardRef,
468
1517
  "data-slot": "dashboard-sidebar-menu-badge",
469
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),
470
1519
  ...props
471
1520
  });
472
- }
473
- function DashboardSidebarSubmenu({ className, ...props }) {
1521
+ });
1522
+ DashboardSidebarMenuBadge.displayName = "DashboardSidebarMenuBadge";
1523
+ const DashboardSidebarSubmenu = React.forwardRef((componentProps, forwardRef) => {
1524
+ const { className, ...props } = componentProps;
474
1525
  return /* @__PURE__ */ jsx("div", {
1526
+ ref: forwardRef,
475
1527
  "data-slot": "dashboard-sidebar-submenu",
476
1528
  className: cn("bg-popover border border-border shadow-md rounded-md p-1", "flex flex-col w-[238px]", className),
477
1529
  ...props
478
1530
  });
479
- }
480
- 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;
481
1535
  return useRender({
482
1536
  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),
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),
484
1541
  render,
485
1542
  state: {
486
1543
  slot: "dashboard-sidebar-submenu-item",
487
1544
  active: isActive
488
1545
  }
489
1546
  });
490
- }
491
- function DashboardSidebarSubmenuSeparator({ className }) {
1547
+ });
1548
+ DashboardSidebarSubmenuItem.displayName = "DashboardSidebarSubmenuItem";
1549
+ const DashboardSidebarSubmenuSeparator = React.forwardRef((componentProps, forwardRef) => {
1550
+ const { className } = componentProps;
492
1551
  return /* @__PURE__ */ jsx("div", {
1552
+ ref: forwardRef,
493
1553
  "data-slot": "dashboard-sidebar-submenu-separator",
494
1554
  className: cn("h-px bg-border -mx-1 my-1", className)
495
1555
  });
496
- }
1556
+ });
1557
+ DashboardSidebarSubmenuSeparator.displayName = "DashboardSidebarSubmenuSeparator";
497
1558
 
498
1559
  //#endregion
499
1560
  //#region src/components/dashboard-sidebar-nav.tsx
@@ -589,4 +1650,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
589
1650
  }
590
1651
 
591
1652
  //#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 };
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 };