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