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