@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/globals.css +1 -1
- package/dist/index.d.ts +1540 -220
- package/dist/index.js +1145 -84
- package/package.json +12 -5
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
|
-
|
|
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
|
-
|
|
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, {
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
size,
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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 };
|