@rendra-ui/web 2.6.0 → 2.7.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.
Files changed (55) hide show
  1. package/README.md +2 -1
  2. package/dist/base.css +1 -1
  3. package/dist/{empty-state-DrQjqxvp.js → brand-feedback-icon-BYNcqBel.js} +4 -64
  4. package/dist/brand-feedback-icon-BYNcqBel.js.map +1 -0
  5. package/dist/{button-B6-644xP.js → button-DlzWwpZk.js} +6 -6
  6. package/dist/{button-B6-644xP.js.map → button-DlzWwpZk.js.map} +1 -1
  7. package/dist/chart.js +7 -7
  8. package/dist/cli/auditar.js +1 -1
  9. package/dist/cli/codigos.js +2 -2
  10. package/dist/cli/executar-auditar.js +1 -1
  11. package/dist/cli/help.js +1 -1
  12. package/dist/cli/trocar.js +2 -2
  13. package/dist/cli/typescript-loader.js +1 -1
  14. package/dist/{cn-TBlI6-gS.js → cn-B0N3Pek0.js} +2 -2
  15. package/dist/{cn-TBlI6-gS.js.map → cn-B0N3Pek0.js.map} +1 -1
  16. package/dist/{components-Btk1cUg2.js → components-CpQPMGfT.js} +18 -2
  17. package/dist/{components-Btk1cUg2.js.map → components-CpQPMGfT.js.map} +1 -1
  18. package/dist/components.css +90 -1
  19. package/dist/document-viewer.js +4 -4
  20. package/dist/{tooltip-kgsorOg6.js → dropdown-menu-M8vs0byD.js} +9 -75
  21. package/dist/dropdown-menu-M8vs0byD.js.map +1 -0
  22. package/dist/empty-state-a47Kd_Ci.js +67 -0
  23. package/dist/empty-state-a47Kd_Ci.js.map +1 -0
  24. package/dist/{features-max-CC6CorTg.js → features-max-ScxV8gb3.js} +1 -1
  25. package/dist/flow.js +1485 -0
  26. package/dist/flow.js.map +1 -0
  27. package/dist/index.js +72 -1854
  28. package/dist/index.js.map +1 -1
  29. package/dist/{motion-context-BL545cb6.js → motion-context-BUMPf0e9.js} +4 -4
  30. package/dist/{motion-context-BL545cb6.js.map → motion-context-BUMPf0e9.js.map} +1 -1
  31. package/dist/{motion-on-DMoTsWrG.js → motion-on-CNN6y8g-.js} +2 -2
  32. package/dist/{motion-on-DMoTsWrG.js.map → motion-on-CNN6y8g-.js.map} +1 -1
  33. package/dist/motion.js +2 -2
  34. package/dist/{rendra-provider-CeCkPafT.js → rendra-provider-DEURIIL3.js} +3 -3
  35. package/dist/{rendra-provider-CeCkPafT.js.map → rendra-provider-DEURIIL3.js.map} +1 -1
  36. package/dist/rich-text-editor.js +4 -3
  37. package/dist/rich-text-editor.js.map +1 -1
  38. package/dist/router-bridge.js +2 -2
  39. package/dist/timeline-BOh7gUdA.js +1803 -0
  40. package/dist/timeline-BOh7gUdA.js.map +1 -0
  41. package/dist/tokens.css +1 -1
  42. package/dist/tooltip-Ym9S4h9_.js +76 -0
  43. package/dist/tooltip-Ym9S4h9_.js.map +1 -0
  44. package/dist/types/components/ui/flow-editor.d.ts +11 -0
  45. package/dist/types/components/ui/flow-viewer.d.ts +58 -0
  46. package/dist/types/flow.d.ts +3 -0
  47. package/dist/types/lib/flow.d.ts +121 -0
  48. package/dist/{use-breakpoint-CNxDwEpc.js → use-breakpoint-En17oXKY.js} +2 -2
  49. package/dist/{use-breakpoint-CNxDwEpc.js.map → use-breakpoint-En17oXKY.js.map} +1 -1
  50. package/dist/{use-motion-presets-DCJPBfNJ.js → use-motion-presets-D3ww1oJX.js} +3 -3
  51. package/dist/{use-motion-presets-DCJPBfNJ.js.map → use-motion-presets-D3ww1oJX.js.map} +1 -1
  52. package/dist/widget-grid.js +2 -2
  53. package/package.json +7 -1
  54. package/dist/empty-state-DrQjqxvp.js.map +0 -1
  55. package/dist/tooltip-kgsorOg6.js.map +0 -1
@@ -0,0 +1,1803 @@
1
+ /*! Rendra Design System v2.7.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-B0N3Pek0.js";
3
+ import { i as useMotionLib, n as useLayoutReady, o as useMotionOn } from "./motion-context-BUMPf0e9.js";
4
+ import { t as useMotionPresets } from "./use-motion-presets-D3ww1oJX.js";
5
+ import { p as resolveCatalogCode } from "./components-CpQPMGfT.js";
6
+ import { r as Spinner, t as Button } from "./button-DlzWwpZk.js";
7
+ import { a as DropdownMenuItem, p as DropdownMenuTrigger, r as DropdownMenuContent, t as DropdownMenu$1 } from "./dropdown-menu-M8vs0byD.js";
8
+ import { r as useBrand, t as BrandFeedbackIcon } from "./brand-feedback-icon-BYNcqBel.js";
9
+ import { n as useBreakpoint } from "./use-breakpoint-En17oXKY.js";
10
+ import { Check, CheckCircle2, ChevronDown, Eye, EyeOff, Info, MoreHorizontal, Plus, Search, SkipForward, X, XCircle } from "lucide-react";
11
+ import { Dialog, Label, Popover } from "radix-ui";
12
+ import { cloneElement, createContext, forwardRef, isValidElement, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
13
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
+ import { createPortal } from "react-dom";
15
+ import { cva } from "class-variance-authority";
16
+ import IMask from "imask";
17
+ import { Command } from "cmdk";
18
+ //#region src/components/app-shell/shell-context.ts
19
+ var ShellContext = createContext(null);
20
+ function useShell() {
21
+ const ctx = useContext(ShellContext);
22
+ if (!ctx) throw new Error("useShell precisa estar dentro de <AppShell>.");
23
+ return ctx;
24
+ }
25
+ //#endregion
26
+ //#region src/components/ui/action-bar.tsx
27
+ /**
28
+ * Barra de ações única, com a regra de botões do sistema:
29
+ * 1 botão = 100%; 2 botões = cancelar 30% + principal 70%; 3 ou mais = extras no menu.
30
+ * Vale para drawer, modal e formulário em página, em qualquer tamanho de tela.
31
+ */
32
+ function ActionBar({ primary, cancel, secondary, sticky, className }) {
33
+ const slot = useContext(ShellContext)?.footerSlot;
34
+ const hasMenu = Boolean(secondary?.length);
35
+ const layout = cancel ? hasMenu ? "grid-actions-3" : "grid-actions-2" : hasMenu ? "grid-actions-3" : "";
36
+ const bar = /* @__PURE__ */ jsx("div", {
37
+ "data-rendra": "ACB-001",
38
+ className: cn(sticky && "border-t bg-card px-4 pt-4 pb-safe md:px-6", sticky && !slot && "sticky bottom-0 z-20", className),
39
+ children: /* @__PURE__ */ jsxs("div", {
40
+ className: cn("grid gap-3", layout || "grid-cols-1", sticky && "w-full"),
41
+ children: [
42
+ hasMenu && /* @__PURE__ */ jsxs(DropdownMenu$1, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
43
+ asChild: true,
44
+ children: /* @__PURE__ */ jsx(Button, {
45
+ variant: "outline",
46
+ iconOnly: true,
47
+ "aria-label": "Mais ações",
48
+ children: /* @__PURE__ */ jsx(MoreHorizontal, {})
49
+ })
50
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
51
+ align: "start",
52
+ side: "top",
53
+ children: secondary.map((a) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
54
+ onSelect: a.onClick,
55
+ disabled: a.disabled,
56
+ destructive: a.destructive,
57
+ children: [a.icon, a.label]
58
+ }, a.label))
59
+ })] }),
60
+ cancel ? /* @__PURE__ */ jsx(Button, {
61
+ variant: "outline",
62
+ icon: cancel.icon,
63
+ onClick: cancel.onClick,
64
+ disabled: cancel.disabled || primary.loading,
65
+ fullWidth: true,
66
+ children: cancel.label
67
+ }) : hasMenu ? /* @__PURE__ */ jsx("span", { "aria-hidden": true }) : null,
68
+ /* @__PURE__ */ jsx(Button, {
69
+ type: primary.type ?? "button",
70
+ form: primary.form,
71
+ variant: primary.destructive ? "destructive" : "primary",
72
+ icon: primary.icon,
73
+ onClick: primary.onClick,
74
+ disabled: primary.disabled,
75
+ loading: primary.loading,
76
+ fullWidth: true,
77
+ children: primary.loading ? primary.loadingLabel ?? "Salvando..." : primary.label
78
+ })
79
+ ]
80
+ })
81
+ });
82
+ return sticky && slot ? createPortal(bar, slot) : bar;
83
+ }
84
+ //#endregion
85
+ //#region src/components/ui/alert.tsx
86
+ /**
87
+ * Alert único do sistema: ícone da marca à esquerda, título em destaque e descrição.
88
+ * O tipo é prop; o formato vem do template ativo (quadrado, meio-termo ou arredondado).
89
+ */
90
+ var alertVariants = cva("flex w-full min-w-0 items-start gap-3 rounded-control border", {
91
+ variants: { type: {
92
+ success: "border-success/25 bg-success-soft text-success-soft-foreground",
93
+ error: "border-destructive/25 bg-destructive-soft text-destructive-soft-foreground",
94
+ warning: "border-warning/25 bg-warning-soft text-warning-soft-foreground",
95
+ info: "border-info/25 bg-info-soft text-info-soft-foreground"
96
+ } },
97
+ defaultVariants: { type: "info" }
98
+ });
99
+ function Alert({ type, title, description, action, onDismiss, animated = false, className }) {
100
+ const { shape } = useBrand();
101
+ const pill = shape === "pill";
102
+ return /* @__PURE__ */ jsxs("div", {
103
+ role: type === "error" || type === "warning" ? "alert" : "status",
104
+ "data-rendra": "ALRT-001",
105
+ className: cn(alertVariants({ type }), pill ? "items-center py-3 pr-4 pl-4 md:pr-6" : "p-4", className),
106
+ children: [
107
+ /* @__PURE__ */ jsx(BrandFeedbackIcon, {
108
+ type,
109
+ size: "lg",
110
+ animated,
111
+ className: cn(!pill && "mt-px")
112
+ }),
113
+ /* @__PURE__ */ jsxs("div", {
114
+ className: cn("flex min-w-0 flex-1 flex-col", !pill && "gap-1"),
115
+ children: [/* @__PURE__ */ jsx("p", {
116
+ className: "text-sm font-semibold",
117
+ children: title
118
+ }), description && /* @__PURE__ */ jsx("p", {
119
+ className: "text-sm opacity-90",
120
+ children: description
121
+ })]
122
+ }),
123
+ action && /* @__PURE__ */ jsx("div", {
124
+ className: "shrink-0 self-center",
125
+ children: action
126
+ }),
127
+ onDismiss && /* @__PURE__ */ jsx("button", {
128
+ type: "button",
129
+ onClick: onDismiss,
130
+ "aria-label": "Fechar aviso",
131
+ className: cn("flex size-touch shrink-0 items-center justify-center rounded-full transition-colors hover:bg-card/60 md:size-8", pill ? "-my-2" : "-mt-2 -mr-2"),
132
+ children: /* @__PURE__ */ jsx(X, {
133
+ className: "size-icon-sm",
134
+ "aria-hidden": true
135
+ })
136
+ })
137
+ ]
138
+ });
139
+ }
140
+ //#endregion
141
+ //#region src/components/ui/overlay-shell.tsx
142
+ function OverlayShell({ title, description, icon, children, footer, hideHeader, onRequestClose, bodyClassName }) {
143
+ const closeButton = /* @__PURE__ */ jsx("button", {
144
+ type: "button",
145
+ onClick: onRequestClose,
146
+ "aria-label": "Fechar",
147
+ className: cn("-mt-1 -mr-2 flex size-touch shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors hover:bg-accent hover:text-foreground md:size-control-md", hideHeader && "pointer-events-auto m-0 cursor-default opacity-0 focus-visible:cursor-pointer focus-visible:opacity-100"),
148
+ children: /* @__PURE__ */ jsx(X, {
149
+ className: "size-icon-md",
150
+ "aria-hidden": true
151
+ })
152
+ });
153
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
154
+ hideHeader && /* @__PURE__ */ jsx("div", {
155
+ className: "pointer-events-none absolute inset-x-0 top-0 z-10 flex justify-end px-2 pt-safe md:px-4 md:pt-4",
156
+ children: closeButton
157
+ }),
158
+ /* @__PURE__ */ jsxs("header", {
159
+ className: cn("flex shrink-0 items-start gap-3 border-b px-4 pt-safe pb-4 md:px-6 md:pt-6", hideHeader && "sr-only"),
160
+ children: [
161
+ icon && /* @__PURE__ */ jsx("span", {
162
+ "aria-hidden": true,
163
+ className: "flex size-control-md shrink-0 items-center justify-center rounded-control bg-primary-soft text-primary-soft-foreground [&_svg]:size-icon-md",
164
+ children: icon
165
+ }),
166
+ /* @__PURE__ */ jsxs("div", {
167
+ className: "flex min-w-0 flex-1 flex-col gap-1 pt-1",
168
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
169
+ className: "text-lg font-semibold wrap-break-word",
170
+ children: title
171
+ }), description ? /* @__PURE__ */ jsx(Dialog.Description, {
172
+ className: "text-sm wrap-break-word text-muted-foreground",
173
+ children: description
174
+ }) : /* @__PURE__ */ jsx(Dialog.Description, {
175
+ className: "sr-only",
176
+ children: title
177
+ })]
178
+ }),
179
+ !hideHeader && closeButton
180
+ ]
181
+ }),
182
+ /* @__PURE__ */ jsx("div", {
183
+ className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-6 md:px-6", bodyClassName),
184
+ children
185
+ }),
186
+ footer && /* @__PURE__ */ jsx("footer", {
187
+ className: "shrink-0 border-t bg-card px-4 pt-4 pb-safe md:px-6 md:pb-6",
188
+ children: footer
189
+ })
190
+ ] });
191
+ }
192
+ //#endregion
193
+ //#region src/components/ui/modal.tsx
194
+ var sizes$1 = {
195
+ sm: "md:max-w-sm",
196
+ md: "md:max-w-lg",
197
+ lg: "md:max-w-2xl"
198
+ };
199
+ var overlayCls$1 = "fixed inset-0 z-50 bg-overlay";
200
+ var overlayOffCls = cn(overlayCls$1, "data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0");
201
+ var contentCls = cn("fixed inset-0 z-50 flex h-dvh flex-col bg-card text-card-foreground outline-none", "md:inset-auto md:top-1/2 md:left-1/2 md:h-auto md:max-h-sheet md:w-full md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-surface md:border md:shadow-lg");
202
+ var feedback = {
203
+ confirm: "info",
204
+ destructive: "error",
205
+ info: "info"
206
+ };
207
+ function Modal({ open, onOpenChange, title, description, type = "confirm", size = "sm", children, confirmLabel = type === "destructive" ? "Excluir" : type === "info" ? "Entendi" : "Confirmar", cancelLabel, onConfirm, formId, loading }) {
208
+ const [busy, setBusy] = useState(false);
209
+ const close = () => onOpenChange(false);
210
+ const confirm = async () => {
211
+ if (!onConfirm) return close();
212
+ try {
213
+ setBusy(true);
214
+ await onConfirm();
215
+ close();
216
+ } finally {
217
+ setBusy(false);
218
+ }
219
+ };
220
+ const isForm = type === "form";
221
+ const lib = useMotionLib();
222
+ const presets = useMotionPresets();
223
+ const { isMobile } = useBreakpoint();
224
+ const code = resolveCatalogCode("Modal", { type: type === "destructive" ? "confirm" : type });
225
+ const sizeCls = sizes$1[size];
226
+ const shell = /* @__PURE__ */ jsxs(OverlayShell, {
227
+ title,
228
+ description: isForm ? description : void 0,
229
+ hideHeader: !isForm,
230
+ onRequestClose: close,
231
+ bodyClassName: cn(!isForm && "flex flex-col items-center justify-center gap-4 text-center"),
232
+ footer: /* @__PURE__ */ jsx(ActionBar, {
233
+ primary: {
234
+ label: confirmLabel,
235
+ destructive: type === "destructive",
236
+ loading: busy || loading,
237
+ loadingLabel: `${confirmLabel}...`,
238
+ type: isForm && formId ? "submit" : "button",
239
+ form: isForm ? formId : void 0,
240
+ onClick: isForm && formId ? void 0 : confirm
241
+ },
242
+ cancel: type === "info" && cancelLabel === void 0 ? void 0 : {
243
+ label: cancelLabel ?? "Cancelar",
244
+ onClick: close
245
+ }
246
+ }),
247
+ children: [!isForm && /* @__PURE__ */ jsxs(Fragment$1, { children: [
248
+ /* @__PURE__ */ jsx(BrandFeedbackIcon, {
249
+ type: feedback[type],
250
+ size: "2xl",
251
+ animated: true
252
+ }),
253
+ /* @__PURE__ */ jsxs("div", {
254
+ className: "flex flex-col gap-2",
255
+ children: [/* @__PURE__ */ jsx("p", {
256
+ className: "text-lg font-semibold wrap-anywhere",
257
+ children: title
258
+ }), description && /* @__PURE__ */ jsx("p", {
259
+ className: "text-sm wrap-anywhere text-muted-foreground",
260
+ children: description
261
+ })]
262
+ }),
263
+ children
264
+ ] }), isForm && /* @__PURE__ */ jsx("div", {
265
+ className: "flex flex-col gap-2",
266
+ children
267
+ })]
268
+ });
269
+ if (lib) return /* @__PURE__ */ jsx(Dialog.Root, {
270
+ open,
271
+ onOpenChange,
272
+ children: /* @__PURE__ */ jsx(lib.AnimatePresence, { children: open && /* @__PURE__ */ jsxs(Dialog.Portal, {
273
+ forceMount: true,
274
+ children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
275
+ forceMount: true,
276
+ asChild: true,
277
+ children: /* @__PURE__ */ jsx(lib.m.div, {
278
+ className: overlayCls$1,
279
+ ...presets.fade
280
+ })
281
+ }), /* @__PURE__ */ jsx(Dialog.Content, {
282
+ forceMount: true,
283
+ asChild: true,
284
+ "data-rendra": code,
285
+ className: cn(contentCls, sizeCls),
286
+ children: /* @__PURE__ */ jsx(lib.m.div, {
287
+ ...presets.open(isMobile ? { y: 8 } : void 0),
288
+ children: shell
289
+ })
290
+ })]
291
+ }, "modal") })
292
+ });
293
+ return /* @__PURE__ */ jsx(Dialog.Root, {
294
+ open,
295
+ onOpenChange,
296
+ children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, { className: overlayOffCls }), /* @__PURE__ */ jsx(Dialog.Content, {
297
+ "data-rendra": code,
298
+ className: cn(contentCls, "data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 md:data-[state=open]:zoom-in-95", sizeCls),
299
+ children: shell
300
+ })] })
301
+ });
302
+ }
303
+ //#endregion
304
+ //#region src/components/ui/info-hint.tsx
305
+ function InfoHint({ title, children, className }) {
306
+ const [open, setOpen] = useState(false);
307
+ return /* @__PURE__ */ jsxs("span", {
308
+ className: "contents",
309
+ "data-rendra": "INFO-001",
310
+ children: [/* @__PURE__ */ jsx(Button, {
311
+ variant: "ghost",
312
+ size: "sm",
313
+ iconOnly: true,
314
+ "aria-label": `Sobre: ${title}`,
315
+ onClick: () => setOpen(true),
316
+ className: cn("shrink-0 text-muted-foreground hover:text-primary-text max-md:size-touch", className),
317
+ children: /* @__PURE__ */ jsx(Info, { "aria-hidden": true })
318
+ }), /* @__PURE__ */ jsx(Modal, {
319
+ open,
320
+ onOpenChange: setOpen,
321
+ type: "info",
322
+ title,
323
+ size: "md",
324
+ children: /* @__PURE__ */ jsx("div", {
325
+ className: "flex flex-col gap-3 text-sm text-muted-foreground",
326
+ children
327
+ })
328
+ })]
329
+ });
330
+ }
331
+ //#endregion
332
+ //#region src/components/ui/card.tsx
333
+ function Card({ className, "data-rendra": dataRendra, ...props }) {
334
+ return /* @__PURE__ */ jsx("div", {
335
+ "data-slot": "card",
336
+ "data-rendra": dataRendra ?? "CARD-001",
337
+ className: cn("flex min-w-0 flex-col rounded-surface border bg-card text-card-foreground", className),
338
+ ...props
339
+ });
340
+ }
341
+ function CardHeader({ className, actions, children, ...props }) {
342
+ if (!actions) return /* @__PURE__ */ jsx("div", {
343
+ "data-slot": "card-header",
344
+ className: cn("flex flex-col gap-1 p-4 md:p-6", className),
345
+ ...props,
346
+ children
347
+ });
348
+ return /* @__PURE__ */ jsxs("div", {
349
+ "data-slot": "card-header",
350
+ className: cn("flex flex-wrap items-start justify-between gap-x-4 gap-y-3 p-4 md:p-6", className),
351
+ ...props,
352
+ children: [/* @__PURE__ */ jsx("div", {
353
+ className: "flex min-w-0 flex-1 flex-col gap-1",
354
+ children
355
+ }), /* @__PURE__ */ jsx("div", {
356
+ className: "flex shrink-0 items-center gap-2",
357
+ children: actions
358
+ })]
359
+ });
360
+ }
361
+ function CardTitle({ className, children, help, ...props }) {
362
+ const heading = /* @__PURE__ */ jsx("h3", {
363
+ "data-slot": "card-title",
364
+ className: cn("text-lg font-semibold", className),
365
+ ...props,
366
+ children
367
+ });
368
+ if (!help) return heading;
369
+ return /* @__PURE__ */ jsxs("div", {
370
+ className: "flex min-w-0 items-center gap-1",
371
+ children: [heading, /* @__PURE__ */ jsx(InfoHint, {
372
+ title: typeof children === "string" ? children : "Sobre esta seção",
373
+ children: help
374
+ })]
375
+ });
376
+ }
377
+ function CardDescription({ className, ...props }) {
378
+ return /* @__PURE__ */ jsx("p", {
379
+ "data-slot": "card-description",
380
+ className: cn("text-sm text-muted-foreground", className),
381
+ ...props
382
+ });
383
+ }
384
+ function CardContent({ className, ...props }) {
385
+ return /* @__PURE__ */ jsx("div", {
386
+ "data-slot": "card-content",
387
+ className: cn("p-4 md:p-6 [[data-slot=card-header]+&]:pt-0", className),
388
+ ...props
389
+ });
390
+ }
391
+ function CardFooter({ className, ...props }) {
392
+ return /* @__PURE__ */ jsx("div", {
393
+ "data-slot": "card-footer",
394
+ className: cn("flex items-center gap-3 border-t px-4 py-3 md:px-6", className),
395
+ ...props
396
+ });
397
+ }
398
+ //#endregion
399
+ //#region src/lib/lookup.ts
400
+ var digits = (v) => v.replace(/\D/g, "");
401
+ /** Endereço do CEP (8 dígitos). null se não existir; lança erro se a busca falhar. */
402
+ async function lookupCep(cep, signal) {
403
+ const d = digits(cep);
404
+ if (d.length !== 8) return null;
405
+ const res = await fetch(`https://viacep.com.br/ws/${d}/json/`, { signal });
406
+ if (!res.ok) throw new Error("Não foi possível buscar o CEP agora.");
407
+ const data = await res.json();
408
+ if (data.erro) return null;
409
+ return {
410
+ cep: String(data.cep ?? ""),
411
+ logradouro: String(data.logradouro ?? ""),
412
+ complemento: String(data.complemento ?? ""),
413
+ bairro: String(data.bairro ?? ""),
414
+ cidade: String(data.localidade ?? ""),
415
+ uf: String(data.uf ?? "")
416
+ };
417
+ }
418
+ /** Dados públicos do CNPJ (14 dígitos). null se não existir; lança erro se a busca falhar. */
419
+ async function lookupCnpj(cnpj, signal) {
420
+ const d = digits(cnpj);
421
+ if (d.length !== 14) return null;
422
+ const res = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${d}`, { signal });
423
+ if (res.status === 404) return null;
424
+ if (!res.ok) throw new Error("Não foi possível buscar o CNPJ agora.");
425
+ const data = await res.json();
426
+ const s = (k) => data[k] == null ? "" : String(data[k]);
427
+ const fone = digits(s("ddd_telefone_1"));
428
+ return {
429
+ razaoSocial: s("razao_social"),
430
+ nomeFantasia: s("nome_fantasia"),
431
+ email: s("email").toLowerCase(),
432
+ telefone: fone.length >= 10 ? `(${fone.slice(0, 2)}) ${fone.slice(2, -4)}-${fone.slice(-4)}` : "",
433
+ situacao: s("descricao_situacao_cadastral"),
434
+ endereco: {
435
+ cep: s("cep"),
436
+ logradouro: [s("descricao_tipo_de_logradouro"), s("logradouro")].filter(Boolean).join(" "),
437
+ numero: s("numero"),
438
+ complemento: s("complemento"),
439
+ bairro: s("bairro"),
440
+ cidade: s("municipio"),
441
+ uf: s("uf")
442
+ }
443
+ };
444
+ }
445
+ //#endregion
446
+ //#region src/hooks/use-lookup.ts
447
+ /**
448
+ * Busca CEP (8 dígitos) ou CNPJ (14 dígitos) assim que o valor fica completo. Cancela a
449
+ * busca anterior quando o valor muda. `kind` 'auto' decide pelo tamanho (CPF ou CNPJ: só
450
+ * o CNPJ busca). Sem `onResult`, não faz nada.
451
+ */
452
+ function useLookup(kind, value, onResult) {
453
+ const [loading, setLoading] = useState(false);
454
+ const cb = useRef(onResult);
455
+ cb.current = onResult;
456
+ const digits = value.replace(/\D/g, "");
457
+ const target = kind === "cep" && digits.length === 8 ? "cep" : (kind === "cnpj" || kind === "auto") && digits.length === 14 ? "cnpj" : null;
458
+ const enabled = Boolean(onResult) && target !== null;
459
+ useEffect(() => {
460
+ if (!enabled || !target) return;
461
+ const ctrl = new AbortController();
462
+ const send = (r) => {
463
+ if (!ctrl.signal.aborted) cb.current?.(r);
464
+ };
465
+ setLoading(true);
466
+ send({
467
+ status: "loading",
468
+ kind: target
469
+ });
470
+ (target === "cep" ? lookupCep(digits, ctrl.signal).then((data) => data ? send({
471
+ status: "found",
472
+ kind: "cep",
473
+ data,
474
+ message: "Endereço preenchido pelo CEP."
475
+ }) : send({
476
+ status: "not-found",
477
+ kind: "cep",
478
+ message: "CEP não encontrado. Preencha o endereço."
479
+ })) : lookupCnpj(digits, ctrl.signal).then((data) => data ? send({
480
+ status: "found",
481
+ kind: "cnpj",
482
+ data,
483
+ message: `${data.situacao ? `Situação: ${data.situacao}. ` : ""}Dados preenchidos pelo CNPJ.`
484
+ }) : send({
485
+ status: "not-found",
486
+ kind: "cnpj",
487
+ message: "CNPJ não encontrado. Preencha os dados abaixo."
488
+ }))).catch((e) => send({
489
+ status: "error",
490
+ kind: target,
491
+ message: e instanceof Error ? e.message : "Não foi possível buscar agora."
492
+ })).finally(() => {
493
+ if (!ctrl.signal.aborted) setLoading(false);
494
+ });
495
+ return () => {
496
+ ctrl.abort();
497
+ setLoading(false);
498
+ };
499
+ }, [
500
+ enabled,
501
+ target,
502
+ digits
503
+ ]);
504
+ return loading;
505
+ }
506
+ //#endregion
507
+ //#region src/lib/control.ts
508
+ /**
509
+ * Moldura única dos campos (Input, Select, DatePicker, Textarea). Mesma altura dos botões
510
+ * em cada tamanho; fonte de 16px no mobile para o iOS não dar zoom ao focar.
511
+ */
512
+ /** Marque o elemento com data-slot="control": o teste de toque mede a moldura, não o input. */
513
+ var controlFrame = cva("group/control flex w-full min-w-0 items-center gap-2 rounded-control border border-input bg-field text-base text-foreground transition-[border-color,box-shadow] duration-150 focus-within:border-ring focus-within:bg-card focus-within:ring-2 focus-within:ring-ring/25 hover:border-foreground/40 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-60 has-[:disabled]:hover:border-input md:text-sm", {
514
+ variants: {
515
+ size: {
516
+ sm: "h-control-sm px-3",
517
+ md: "h-control-md px-3",
518
+ lg: "h-control-lg px-4"
519
+ },
520
+ invalid: {
521
+ true: "border-destructive focus-within:border-destructive focus-within:ring-destructive/25 hover:border-destructive",
522
+ false: ""
523
+ }
524
+ },
525
+ defaultVariants: {
526
+ size: "md",
527
+ invalid: false
528
+ }
529
+ });
530
+ /** Classe do <input> nativo dentro da moldura. */
531
+ var controlInput = "h-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed";
532
+ /** Botão pequeno dentro da moldura (limpar, mostrar senha). */
533
+ var controlAdornmentButton = "-mr-2 flex size-control-sm shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring md:-mr-1 md:size-8 [&_svg]:size-icon-sm";
534
+ /**
535
+ * Reserva, dentro de um gatilho, o espaço do botão sobreposto (limpar do Select e do
536
+ * DatePicker), para o texto não passar por baixo dele.
537
+ */
538
+ var controlAdornmentSpace = "size-control-sm shrink-0 md:size-8";
539
+ //#endregion
540
+ //#region src/lib/masks.ts
541
+ var numberBlock = (scale, max) => ({
542
+ mask: Number,
543
+ scale,
544
+ thousandsSeparator: ".",
545
+ radix: ",",
546
+ mapToRadix: ["."],
547
+ padFractionalZeros: true,
548
+ normalizeZeros: true,
549
+ min: 0,
550
+ ...max !== void 0 ? { max } : {}
551
+ });
552
+ var masks = {
553
+ cpf: {
554
+ options: { mask: "000.000.000-00" },
555
+ inputMode: "numeric",
556
+ placeholder: "000.000.000-00"
557
+ },
558
+ cnpj: {
559
+ options: { mask: "00.000.000/0000-00" },
560
+ inputMode: "numeric",
561
+ placeholder: "00.000.000/0000-00"
562
+ },
563
+ cpfCnpj: {
564
+ options: { mask: [{ mask: "000.000.000-00" }, { mask: "00.000.000/0000-00" }] },
565
+ inputMode: "numeric",
566
+ placeholder: "CPF ou CNPJ"
567
+ },
568
+ phone: {
569
+ options: { mask: [{ mask: "(00) 0000-0000" }, { mask: "(00) 00000-0000" }] },
570
+ inputMode: "tel",
571
+ placeholder: "(00) 00000-0000"
572
+ },
573
+ cep: {
574
+ options: { mask: "00000-000" },
575
+ inputMode: "numeric",
576
+ placeholder: "00000-000"
577
+ },
578
+ date: {
579
+ options: { mask: "00/00/0000" },
580
+ inputMode: "numeric",
581
+ placeholder: "DD/MM/AAAA"
582
+ },
583
+ time: {
584
+ options: { mask: "00:00" },
585
+ inputMode: "numeric",
586
+ placeholder: "00:00"
587
+ },
588
+ currency: {
589
+ options: {
590
+ mask: "R$ num",
591
+ lazy: false,
592
+ blocks: { num: numberBlock(2) }
593
+ },
594
+ inputMode: "decimal",
595
+ placeholder: "R$ 0,00"
596
+ },
597
+ percent: {
598
+ options: {
599
+ mask: "num %",
600
+ lazy: false,
601
+ blocks: { num: numberBlock(2, 100) }
602
+ },
603
+ inputMode: "decimal",
604
+ placeholder: "0,00 %"
605
+ }
606
+ };
607
+ /** Máscara de percentual com teto configurável (padrão 100), para o Input com unidades (A9). */
608
+ function percentMask(max = 100) {
609
+ return {
610
+ options: {
611
+ mask: "num %",
612
+ lazy: false,
613
+ blocks: { num: numberBlock(2, max) }
614
+ },
615
+ inputMode: "decimal",
616
+ placeholder: "0,00 %"
617
+ };
618
+ }
619
+ /** DDI padrão do campo de telefone. */
620
+ var DEFAULT_DDI = "55";
621
+ /** Lista padrão do seletor de DDI (Brasil primeiro). Troque com a prop ddiOptions do Input. */
622
+ var phoneCountries = [
623
+ {
624
+ ddi: "55",
625
+ name: "Brasil"
626
+ },
627
+ {
628
+ ddi: "54",
629
+ name: "Argentina"
630
+ },
631
+ {
632
+ ddi: "56",
633
+ name: "Chile"
634
+ },
635
+ {
636
+ ddi: "57",
637
+ name: "Colômbia"
638
+ },
639
+ {
640
+ ddi: "595",
641
+ name: "Paraguai"
642
+ },
643
+ {
644
+ ddi: "598",
645
+ name: "Uruguai"
646
+ },
647
+ {
648
+ ddi: "52",
649
+ name: "México"
650
+ },
651
+ {
652
+ ddi: "1",
653
+ name: "Estados Unidos e Canadá"
654
+ },
655
+ {
656
+ ddi: "351",
657
+ name: "Portugal"
658
+ },
659
+ {
660
+ ddi: "34",
661
+ name: "Espanha"
662
+ },
663
+ {
664
+ ddi: "33",
665
+ name: "França"
666
+ },
667
+ {
668
+ ddi: "39",
669
+ name: "Itália"
670
+ },
671
+ {
672
+ ddi: "49",
673
+ name: "Alemanha"
674
+ },
675
+ {
676
+ ddi: "44",
677
+ name: "Reino Unido"
678
+ }
679
+ ];
680
+ /** Máscara do número fora do Brasil: só dígitos, em grupos, até 15 (limite do padrão E.164). */
681
+ var internationalPhoneMask = {
682
+ options: { mask: "000 000 000 000 000" },
683
+ inputMode: "tel",
684
+ placeholder: "Número com DDD"
685
+ };
686
+ /** Número completo no padrão internacional: toE164("55", "(11) 91234-5678") = "+5511912345678". */
687
+ function toE164(ddi, national) {
688
+ const digits = national.replace(/\D/g, "");
689
+ return digits ? `+${ddi}${digits}` : "";
690
+ }
691
+ /** Converte "R$ 1.250,00" ou "12,5 %" em número. */
692
+ function parseLocaleNumber(masked) {
693
+ const clean = masked.replace(/[^\d,-]/g, "").replace(",", ".");
694
+ if (!clean) return null;
695
+ const n = Number(clean);
696
+ return Number.isFinite(n) ? n : null;
697
+ }
698
+ /**
699
+ * Moeda em centavos inteiros, sem erro de arredondamento de ponto flutuante:
700
+ * "R$ 1.250,50" -> 125050. Vazio -> null. Use centavos para guardar e somar dinheiro.
701
+ */
702
+ function toCents(masked) {
703
+ const d = masked.replace(/\D/g, "");
704
+ if (!d) return null;
705
+ if (!/,\d{1,2}\s*$/.test(masked.replace(/\s*%$/, ""))) return Number(d) * 100;
706
+ const [int = "", dec = ""] = masked.replace(/[^\d,]/g, "").split(",");
707
+ return Number(int || "0") * 100 + Number(dec.padEnd(2, "0").slice(0, 2));
708
+ }
709
+ /** Centavos para o texto do campo de moeda: 125050 -> "R$ 1.250,50". */
710
+ function formatCents(cents) {
711
+ return formatCurrency(cents / 100);
712
+ }
713
+ /** Formata número como moeda brasileira: 1250 -> "R$ 1.250,00". */
714
+ function formatCurrency(value) {
715
+ return value.toLocaleString("pt-BR", {
716
+ style: "currency",
717
+ currency: "BRL"
718
+ });
719
+ }
720
+ //#endregion
721
+ //#region src/components/ui/input.tsx
722
+ /**
723
+ * Input único. Tipos, máscaras, ícone, limpar, senha, unidades e valor guardado são props:
724
+ * nunca crie InputCPF, InputTelefone, InputSenha ou InputSegredo.
725
+ */
726
+ var Input = forwardRef(function Input({ size, mask, icon, suffix, clearable, invalid, type = "text", value, defaultValue, onChange, onValueChange, disabled, className, inputClassName, inputMode, placeholder, ddi: ddiProp, onDdiChange, ddiOptions = phoneCountries, hideDdi, onCentsChange, onLookup, units, unit: unitProp, onUnitChange, percentMax, variant, hasValue, maskedHint, isEditing, onStartEdit, onCancelEdit, onRemove, removing, ...props }, ref) {
727
+ const inputRef = useRef(null);
728
+ useImperativeHandle(ref, () => inputRef.current);
729
+ const maskRef = useRef(null);
730
+ const [showPassword, setShowPassword] = useState(false);
731
+ const [inner, setInner] = useState(String(value ?? defaultValue ?? ""));
732
+ const current = value ?? inner;
733
+ const [secretDraft, setSecretDraft] = useState("");
734
+ const [lastIsEditing, setLastIsEditing] = useState(isEditing);
735
+ if (isEditing !== lastIsEditing) {
736
+ setLastIsEditing(isEditing);
737
+ if (isEditing) setSecretDraft("");
738
+ }
739
+ const cb = useRef({
740
+ onChange,
741
+ onValueChange,
742
+ onCentsChange
743
+ });
744
+ cb.current = {
745
+ onChange,
746
+ onValueChange,
747
+ onCentsChange
748
+ };
749
+ const searching = useLookup(mask === "cep" ? "cep" : mask === "cnpj" ? "cnpj" : mask === "cpfCnpj" ? "auto" : void 0, current, onLookup);
750
+ const isPhone = mask === "phone";
751
+ const [innerDdi, setInnerDdi] = useState(ddiProp ?? "55");
752
+ const ddi = ddiProp ?? innerDdi;
753
+ const showDdi = isPhone && !hideDdi;
754
+ const intl = isPhone && ddi !== "55";
755
+ const hasUnits = Boolean(units && units.length > 0);
756
+ const [innerUnit, setInnerUnit] = useState(unitProp ?? units?.[0]?.id ?? "");
757
+ const unit = hasUnits ? unitProp ?? innerUnit : void 0;
758
+ const unitMaskName = unit === "percent" ? "percent" : unit === "currency" ? "currency" : void 0;
759
+ const def = intl ? internationalPhoneMask : hasUnits ? unitMaskName === "percent" ? percentMask(percentMax) : unitMaskName === "currency" ? masks.currency : void 0 : mask ? masks[mask] : void 0;
760
+ const maskKey = !def ? void 0 : intl ? "phone-intl" : hasUnits ? `unit-${unitMaskName}-${percentMax ?? ""}` : mask;
761
+ useEffect(() => {
762
+ if (!maskKey || !def || !inputRef.current) return;
763
+ const m = IMask(inputRef.current, def.options);
764
+ maskRef.current = m;
765
+ m.on("accept", () => {
766
+ setInner(m.value);
767
+ cb.current.onChange?.(m.value);
768
+ cb.current.onValueChange?.(m.unmaskedValue, m.value);
769
+ if (mask === "currency" || unitMaskName === "currency") cb.current.onCentsChange?.(toCents(m.value));
770
+ });
771
+ return () => {
772
+ m.destroy();
773
+ maskRef.current = null;
774
+ };
775
+ }, [maskKey]);
776
+ useEffect(() => {
777
+ const m = maskRef.current;
778
+ if (m && value !== void 0 && value !== m.value) m.value = value;
779
+ }, [value]);
780
+ const isPassword = type === "password";
781
+ const country = ddiOptions.find((c) => c.ddi === ddi);
782
+ const isSecret = variant === "secret";
783
+ const { id: fieldId, "aria-describedby": fieldDescribedBy, "aria-required": fieldRequired } = props;
784
+ const resetValue = () => {
785
+ if (maskRef.current) maskRef.current.value = "";
786
+ setInner("");
787
+ setSecretDraft("");
788
+ onChange?.("");
789
+ onValueChange?.("", "");
790
+ onCentsChange?.(null);
791
+ };
792
+ const clear = () => {
793
+ resetValue();
794
+ inputRef.current?.focus();
795
+ };
796
+ const changeUnit = (id) => {
797
+ setInnerUnit(id);
798
+ onUnitChange?.(id);
799
+ resetValue();
800
+ };
801
+ if (isSecret && !isEditing) return /* @__PURE__ */ jsxs("div", {
802
+ "data-slot": "control",
803
+ "data-rendra": "CAMP-001",
804
+ className: cn(controlFrame({
805
+ size,
806
+ invalid
807
+ }), className),
808
+ children: [
809
+ /* @__PURE__ */ jsx("span", {
810
+ className: "min-w-0 flex-1 truncate font-mono text-sm text-muted-foreground",
811
+ children: hasValue ? maskedHint ?? "••••••••" : "Nenhum valor salvo"
812
+ }),
813
+ /* @__PURE__ */ jsx(Button, {
814
+ type: "button",
815
+ variant: "ghost",
816
+ size: "sm",
817
+ onClick: onStartEdit,
818
+ disabled,
819
+ id: fieldId,
820
+ "aria-describedby": fieldDescribedBy,
821
+ "aria-required": fieldRequired,
822
+ children: "Trocar"
823
+ }),
824
+ hasValue && onRemove && /* @__PURE__ */ jsx(Button, {
825
+ type: "button",
826
+ variant: "ghost",
827
+ size: "sm",
828
+ onClick: onRemove,
829
+ disabled: disabled || removing,
830
+ loading: removing,
831
+ children: "Remover"
832
+ })
833
+ ]
834
+ });
835
+ return /* @__PURE__ */ jsxs("div", {
836
+ "data-slot": "control",
837
+ "data-rendra": "CAMP-001",
838
+ className: cn(controlFrame({
839
+ size,
840
+ invalid
841
+ }), className),
842
+ children: [
843
+ showDdi && /* @__PURE__ */ jsxs("span", {
844
+ className: "relative -ml-1 flex h-full min-w-touch shrink-0 items-center gap-1 border-r pr-2 text-sm font-medium",
845
+ children: [
846
+ /* @__PURE__ */ jsxs("span", {
847
+ "aria-hidden": true,
848
+ children: ["+", ddi]
849
+ }),
850
+ /* @__PURE__ */ jsx(ChevronDown, {
851
+ className: "size-icon-sm text-muted-foreground",
852
+ "aria-hidden": true
853
+ }),
854
+ /* @__PURE__ */ jsx("select", {
855
+ "aria-label": `Código do país (DDI): ${country?.name ?? ""} +${ddi}`,
856
+ value: ddi,
857
+ disabled,
858
+ onChange: (e) => {
859
+ setInnerDdi(e.target.value);
860
+ onDdiChange?.(e.target.value);
861
+ },
862
+ className: "absolute inset-0 cursor-pointer bg-popover text-popover-foreground opacity-0 disabled:cursor-not-allowed",
863
+ children: ddiOptions.map((c) => /* @__PURE__ */ jsxs("option", {
864
+ value: c.ddi,
865
+ className: "bg-popover text-popover-foreground",
866
+ children: [
867
+ c.name,
868
+ " (+",
869
+ c.ddi,
870
+ ")"
871
+ ]
872
+ }, c.ddi))
873
+ })
874
+ ]
875
+ }),
876
+ icon && /* @__PURE__ */ jsx("span", {
877
+ className: "flex shrink-0 text-muted-foreground [&_svg]:size-icon-sm",
878
+ "aria-hidden": true,
879
+ children: icon
880
+ }),
881
+ /* @__PURE__ */ jsx("input", {
882
+ ref: inputRef,
883
+ type: isSecret ? showPassword ? "text" : "password" : isPassword && showPassword ? "text" : type,
884
+ inputMode: inputMode ?? def?.inputMode,
885
+ placeholder: placeholder ?? def?.placeholder,
886
+ disabled,
887
+ "aria-invalid": invalid || void 0,
888
+ "aria-busy": searching || void 0,
889
+ className: cn(controlInput, inputClassName),
890
+ ...isSecret ? {
891
+ value: secretDraft,
892
+ onChange: (e) => {
893
+ setSecretDraft(e.target.value);
894
+ onChange?.(e.target.value);
895
+ onValueChange?.(e.target.value, e.target.value);
896
+ }
897
+ } : maskKey ? { defaultValue: current } : {
898
+ value: current,
899
+ onChange: (e) => {
900
+ setInner(e.target.value);
901
+ onChange?.(e.target.value);
902
+ onValueChange?.(e.target.value, e.target.value);
903
+ }
904
+ },
905
+ ...props
906
+ }, hasUnits ? `unit-${unit}` : void 0),
907
+ searching && /* @__PURE__ */ jsx(Spinner, {
908
+ size: "sm",
909
+ label: "Buscando…",
910
+ className: "shrink-0 text-muted-foreground"
911
+ }),
912
+ hasUnits && /* @__PURE__ */ jsxs("span", {
913
+ className: "relative -mr-1 flex h-full min-w-touch shrink-0 items-center gap-1 border-l pl-2 text-sm font-medium",
914
+ children: [
915
+ /* @__PURE__ */ jsx("span", {
916
+ "aria-hidden": true,
917
+ children: units?.find((u) => u.id === unit)?.label ?? unit
918
+ }),
919
+ /* @__PURE__ */ jsx(ChevronDown, {
920
+ className: "size-icon-sm text-muted-foreground",
921
+ "aria-hidden": true
922
+ }),
923
+ /* @__PURE__ */ jsx("select", {
924
+ "aria-label": `Unidade: ${units?.find((u) => u.id === unit)?.label ?? unit}`,
925
+ value: unit,
926
+ disabled,
927
+ onChange: (e) => changeUnit(e.target.value),
928
+ className: "absolute inset-0 cursor-pointer bg-popover text-popover-foreground opacity-0 disabled:cursor-not-allowed",
929
+ children: units?.map((u) => /* @__PURE__ */ jsx("option", {
930
+ value: u.id,
931
+ className: "bg-popover text-popover-foreground",
932
+ children: u.label
933
+ }, u.id))
934
+ })
935
+ ]
936
+ }),
937
+ !hasUnits && suffix && /* @__PURE__ */ jsx("span", {
938
+ className: "shrink-0 text-sm text-muted-foreground",
939
+ children: suffix
940
+ }),
941
+ clearable && (isSecret ? secretDraft : current) && !disabled && /* @__PURE__ */ jsx("button", {
942
+ type: "button",
943
+ onClick: clear,
944
+ "aria-label": "Limpar campo",
945
+ className: "-mr-2 flex size-control-sm shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring md:-mr-1 md:size-8 [&_svg]:size-icon-sm",
946
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
947
+ }),
948
+ (isPassword || isSecret) && /* @__PURE__ */ jsx("button", {
949
+ type: "button",
950
+ onClick: () => setShowPassword((s) => !s),
951
+ "aria-label": showPassword ? "Ocultar senha" : "Mostrar senha",
952
+ "aria-pressed": showPassword,
953
+ className: "-mr-2 flex size-control-sm shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring md:-mr-1 md:size-8 [&_svg]:size-icon-sm",
954
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": true }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": true })
955
+ }),
956
+ isSecret && /* @__PURE__ */ jsx(Button, {
957
+ type: "button",
958
+ variant: "ghost",
959
+ size: "sm",
960
+ onClick: onCancelEdit,
961
+ disabled,
962
+ children: "Cancelar"
963
+ })
964
+ ]
965
+ });
966
+ });
967
+ //#endregion
968
+ //#region src/components/ui/picker-panel.tsx
969
+ /** A folha fecha se o arraste passou de 40% da altura ou se foi um arremesso (> 0,11 px/ms). */
970
+ var DISMISS_FRACTION = .4;
971
+ var DISMISS_VELOCITY = 110;
972
+ /**
973
+ * A folha do celular em `on`: entra de baixo, acompanha o dedo (arraste vertical pela alça, com
974
+ * fricção ao puxar para cima), fecha por distância ou por arremesso e, se o dedo solta antes
975
+ * disso, volta à origem com a mola da folha (a velocidade do dedo entra na mola, sem costura). O
976
+ * fundo escurecido acompanha a distância. Só a alça arrasta: o corpo rola sem disputar o gesto.
977
+ * Nenhum valor passa por `style`: o Motion guarda a posição do arraste, e o escurecimento do
978
+ * fundo é gravado direto no elemento (sem render por quadro).
979
+ */
980
+ function MobileSheet({ open, onOpenChange, trigger, adornment, head, rest }) {
981
+ const { AnimatePresence, animate, m, useDragControls } = useMotionOn();
982
+ const presets = useMotionPresets();
983
+ const ready = useLayoutReady();
984
+ const controls = useDragControls();
985
+ const sheetRef = useRef(null);
986
+ const dimRef = useRef(null);
987
+ const height = useRef(1);
988
+ return /* @__PURE__ */ jsxs(Dialog.Root, {
989
+ open,
990
+ onOpenChange,
991
+ children: [/* @__PURE__ */ jsx(Anchor, {
992
+ adornment,
993
+ children: /* @__PURE__ */ jsx(Dialog.Trigger, {
994
+ asChild: true,
995
+ children: trigger
996
+ })
997
+ }), /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsxs(Dialog.Portal, {
998
+ forceMount: true,
999
+ children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
1000
+ forceMount: true,
1001
+ asChild: true,
1002
+ children: /* @__PURE__ */ jsx(m.div, {
1003
+ className: "fixed inset-0 z-40",
1004
+ ...presets.fade,
1005
+ children: /* @__PURE__ */ jsx("div", {
1006
+ ref: dimRef,
1007
+ className: "absolute inset-0 bg-overlay"
1008
+ })
1009
+ })
1010
+ }), /* @__PURE__ */ jsx(Dialog.Content, {
1011
+ forceMount: true,
1012
+ asChild: true,
1013
+ "aria-describedby": void 0,
1014
+ className: "fixed inset-x-0 bottom-0 z-50 flex max-h-sheet flex-col rounded-t-surface border-t bg-popover text-popover-foreground shadow-lg outline-none",
1015
+ children: /* @__PURE__ */ jsxs(m.div, {
1016
+ ref: sheetRef,
1017
+ ...presets.sheet("bottom"),
1018
+ drag: ready ? "y" : false,
1019
+ dragControls: controls,
1020
+ dragListener: false,
1021
+ dragConstraints: { top: 0 },
1022
+ dragElastic: {
1023
+ top: .2,
1024
+ bottom: 0
1025
+ },
1026
+ dragSnapToOrigin: true,
1027
+ dragTransition: presets.sheetBounce,
1028
+ onDragStart: () => {
1029
+ height.current = sheetRef.current?.offsetHeight || 1;
1030
+ },
1031
+ onDrag: (_, info) => {
1032
+ const dim = dimRef.current;
1033
+ if (dim) dim.style.opacity = String(1 - Math.min(1, Math.max(0, info.offset.y / height.current)));
1034
+ },
1035
+ onDragEnd: (_, info) => {
1036
+ if (info.offset.y > height.current * DISMISS_FRACTION || info.velocity.y > DISMISS_VELOCITY) {
1037
+ onOpenChange(false);
1038
+ return;
1039
+ }
1040
+ if (dimRef.current) animate(dimRef.current, { opacity: 1 }, presets.springSheet);
1041
+ },
1042
+ children: [/* @__PURE__ */ jsx("div", {
1043
+ "data-sheet-handle": true,
1044
+ onPointerDown: (e) => controls.start(e),
1045
+ className: "touch-none",
1046
+ children: head
1047
+ }), rest]
1048
+ })
1049
+ })]
1050
+ }, "sheet") })]
1051
+ });
1052
+ }
1053
+ function Anchor({ adornment, children }) {
1054
+ if (!adornment) return children;
1055
+ return /* @__PURE__ */ jsxs("div", {
1056
+ className: "relative w-full min-w-0",
1057
+ children: [children, adornment]
1058
+ });
1059
+ }
1060
+ function PickerPanel({ open, onOpenChange, trigger, title, header, children, footer, width = "trigger", adornment, align = "start" }) {
1061
+ const { isMobile } = useBreakpoint();
1062
+ const lib = useMotionLib();
1063
+ const presets = useMotionPresets();
1064
+ const on = lib !== null;
1065
+ const sheetHead = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
1066
+ className: "flex shrink-0 flex-col items-center pt-2",
1067
+ children: /* @__PURE__ */ jsx("span", {
1068
+ "aria-hidden": true,
1069
+ className: "h-1 w-12 rounded-full bg-border"
1070
+ })
1071
+ }), /* @__PURE__ */ jsxs("div", {
1072
+ className: "flex shrink-0 items-center justify-between gap-2 pr-2 pl-4",
1073
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
1074
+ className: "truncate text-base font-semibold",
1075
+ children: title
1076
+ }), /* @__PURE__ */ jsx(Dialog.Close, {
1077
+ "aria-label": "Fechar",
1078
+ className: "flex size-touch items-center justify-center rounded-item text-muted-foreground hover:bg-accent",
1079
+ children: /* @__PURE__ */ jsx(X, {
1080
+ className: "size-icon-md",
1081
+ "aria-hidden": true
1082
+ })
1083
+ })]
1084
+ })] });
1085
+ const sheetRest = /* @__PURE__ */ jsxs(Fragment$1, { children: [
1086
+ header && /* @__PURE__ */ jsx("div", {
1087
+ className: "shrink-0 border-b px-4 pb-3",
1088
+ children: header
1089
+ }),
1090
+ /* @__PURE__ */ jsx("div", {
1091
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-2",
1092
+ children
1093
+ }),
1094
+ footer && /* @__PURE__ */ jsx("div", {
1095
+ className: "shrink-0 border-t px-4 pt-3 pb-safe",
1096
+ children: footer
1097
+ })
1098
+ ] });
1099
+ const sheetBody = /* @__PURE__ */ jsxs(Fragment$1, { children: [sheetHead, sheetRest] });
1100
+ const panelCls = cn("z-50 flex max-h-command origin-(--radix-popover-content-transform-origin) flex-col overflow-hidden rounded-surface border bg-popover text-popover-foreground shadow-md outline-none", width === "trigger" ? "w-trigger max-w-popover" : "w-auto max-w-popover md:max-w-none");
1101
+ const panelBody = /* @__PURE__ */ jsxs(Fragment$1, { children: [
1102
+ header && /* @__PURE__ */ jsx("div", {
1103
+ className: "shrink-0 border-b p-2",
1104
+ children: header
1105
+ }),
1106
+ /* @__PURE__ */ jsx("div", {
1107
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-1",
1108
+ children
1109
+ }),
1110
+ footer && /* @__PURE__ */ jsx("div", {
1111
+ className: "shrink-0 border-t p-2",
1112
+ children: footer
1113
+ })
1114
+ ] });
1115
+ if (on && isMobile) return /* @__PURE__ */ jsx(MobileSheet, {
1116
+ open,
1117
+ onOpenChange,
1118
+ trigger,
1119
+ adornment,
1120
+ head: sheetHead,
1121
+ rest: sheetRest
1122
+ });
1123
+ if (on) return /* @__PURE__ */ jsxs(Popover.Root, {
1124
+ open,
1125
+ onOpenChange,
1126
+ children: [/* @__PURE__ */ jsx(Anchor, {
1127
+ adornment,
1128
+ children: /* @__PURE__ */ jsx(Popover.Trigger, {
1129
+ asChild: true,
1130
+ children: trigger
1131
+ })
1132
+ }), /* @__PURE__ */ jsx(lib.AnimatePresence, { children: open && /* @__PURE__ */ jsx(Popover.Portal, {
1133
+ forceMount: true,
1134
+ children: /* @__PURE__ */ jsx(Popover.Content, {
1135
+ forceMount: true,
1136
+ asChild: true,
1137
+ align,
1138
+ sideOffset: 6,
1139
+ collisionPadding: 16,
1140
+ className: panelCls,
1141
+ children: /* @__PURE__ */ jsx(lib.m.div, {
1142
+ ...presets.open(),
1143
+ children: panelBody
1144
+ })
1145
+ })
1146
+ }, "panel") })]
1147
+ });
1148
+ if (isMobile) return /* @__PURE__ */ jsxs(Dialog.Root, {
1149
+ open,
1150
+ onOpenChange,
1151
+ children: [/* @__PURE__ */ jsx(Anchor, {
1152
+ adornment,
1153
+ children: /* @__PURE__ */ jsx(Dialog.Trigger, {
1154
+ asChild: true,
1155
+ children: trigger
1156
+ })
1157
+ }), /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, { className: "fixed inset-0 z-40 bg-overlay data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0" }), /* @__PURE__ */ jsx(Dialog.Content, {
1158
+ "aria-describedby": void 0,
1159
+ className: "fixed inset-x-0 bottom-0 z-50 flex max-h-sheet flex-col rounded-t-surface border-t bg-popover text-popover-foreground shadow-lg outline-none data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:slide-out-to-bottom data-[state=open]:animate-in data-[state=open]:slide-in-from-bottom",
1160
+ children: sheetBody
1161
+ })] })]
1162
+ });
1163
+ return /* @__PURE__ */ jsxs(Popover.Root, {
1164
+ open,
1165
+ onOpenChange,
1166
+ children: [/* @__PURE__ */ jsx(Anchor, {
1167
+ adornment,
1168
+ children: /* @__PURE__ */ jsx(Popover.Trigger, {
1169
+ asChild: true,
1170
+ children: trigger
1171
+ })
1172
+ }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Content, {
1173
+ align,
1174
+ sideOffset: 6,
1175
+ collisionPadding: 16,
1176
+ className: cn("z-50 flex max-h-command origin-(--radix-popover-content-transform-origin) flex-col overflow-hidden rounded-surface border bg-popover text-popover-foreground shadow-md outline-none", "data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", width === "trigger" ? "w-trigger max-w-popover" : "w-auto max-w-popover md:max-w-none"),
1177
+ children: panelBody
1178
+ }) })]
1179
+ });
1180
+ }
1181
+ //#endregion
1182
+ //#region src/components/ui/drawer.tsx
1183
+ var sizes = {
1184
+ "30": "md:w-sheet-30",
1185
+ "40": "md:w-sheet-40",
1186
+ "50": "md:w-sheet-50",
1187
+ "75": "md:w-sheet-75",
1188
+ full: "md:w-full",
1189
+ sm: "md:w-sheet-30",
1190
+ md: "md:w-sheet-30",
1191
+ lg: "md:w-sheet-50",
1192
+ xl: "md:w-sheet-75"
1193
+ };
1194
+ var overlayCls = "fixed inset-0 z-50 bg-overlay";
1195
+ var panelCls = "fixed inset-y-0 right-0 z-50 flex h-dvh w-full flex-col bg-card text-card-foreground shadow-lg outline-none md:border-l";
1196
+ function Drawer({ open, onOpenChange, title, description, icon, size = "30", children, footer, dirty, discardTitle = "Descartar alterações?", discardDescription = "As informações preenchidas neste painel serão perdidas." }) {
1197
+ const [confirming, setConfirming] = useState(false);
1198
+ const requestClose = () => dirty ? setConfirming(true) : onOpenChange(false);
1199
+ const lib = useMotionLib();
1200
+ const presets = useMotionPresets();
1201
+ const keepOpenIfDirty = (e) => {
1202
+ if (dirty) {
1203
+ e.preventDefault();
1204
+ setConfirming(true);
1205
+ }
1206
+ };
1207
+ const shell = /* @__PURE__ */ jsx(OverlayShell, {
1208
+ title,
1209
+ description,
1210
+ icon,
1211
+ footer,
1212
+ onRequestClose: requestClose,
1213
+ children
1214
+ });
1215
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Dialog.Root, {
1216
+ open,
1217
+ onOpenChange: (o) => o ? onOpenChange(true) : requestClose(),
1218
+ children: lib ? /* @__PURE__ */ jsx(lib.AnimatePresence, { children: open && /* @__PURE__ */ jsxs(Dialog.Portal, {
1219
+ forceMount: true,
1220
+ children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
1221
+ forceMount: true,
1222
+ asChild: true,
1223
+ children: /* @__PURE__ */ jsx(lib.m.div, {
1224
+ className: overlayCls,
1225
+ ...presets.fade
1226
+ })
1227
+ }), /* @__PURE__ */ jsx(Dialog.Content, {
1228
+ forceMount: true,
1229
+ asChild: true,
1230
+ onEscapeKeyDown: keepOpenIfDirty,
1231
+ onPointerDownOutside: keepOpenIfDirty,
1232
+ "data-rendra": "GAV-001",
1233
+ className: cn(panelCls, sizes[size]),
1234
+ children: /* @__PURE__ */ jsx(lib.m.div, {
1235
+ ...presets.sheet("right"),
1236
+ children: shell
1237
+ })
1238
+ })]
1239
+ }, "drawer") }) : /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, { className: cn(overlayCls, "data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0") }), /* @__PURE__ */ jsx(Dialog.Content, {
1240
+ onEscapeKeyDown: keepOpenIfDirty,
1241
+ onPointerDownOutside: keepOpenIfDirty,
1242
+ "data-rendra": "GAV-001",
1243
+ className: cn(panelCls, "data-[state=closed]:animate-out data-[state=closed]:duration-(--rendra-duration-fast) data-[state=closed]:slide-out-to-right data-[state=open]:animate-in data-[state=open]:slide-in-from-right", sizes[size]),
1244
+ children: shell
1245
+ })] })
1246
+ }), /* @__PURE__ */ jsx(Modal, {
1247
+ open: confirming,
1248
+ onOpenChange: setConfirming,
1249
+ type: "destructive",
1250
+ title: discardTitle,
1251
+ description: discardDescription,
1252
+ confirmLabel: "Descartar",
1253
+ cancelLabel: "Continuar editando",
1254
+ onConfirm: () => onOpenChange(false)
1255
+ })] });
1256
+ }
1257
+ //#endregion
1258
+ //#region src/components/layout/tokens.ts
1259
+ var gapClass = {
1260
+ "0": "gap-0",
1261
+ "1": "gap-1",
1262
+ "2": "gap-2",
1263
+ "3": "gap-3",
1264
+ "4": "gap-4",
1265
+ "6": "gap-6",
1266
+ "8": "gap-8",
1267
+ "12": "gap-12",
1268
+ "16": "gap-16",
1269
+ "24": "gap-24",
1270
+ section: "gap-8 md:gap-12",
1271
+ fields: "gap-x-4 gap-y-4 md:gap-x-6"
1272
+ };
1273
+ /**
1274
+ * Grade de formulário, pela largura do próprio bloco (container query), não da tela:
1275
+ * 1 coluna estreito, 6 colunas a partir de 28rem, 12 colunas a partir de 48rem.
1276
+ * No largo, o padrão é 3 campos por linha (md = 4/12). Nunca 2 por linha por padrão.
1277
+ */
1278
+ var fieldGridClass = "grid grid-cols-1 @md:grid-cols-6 @3xl:grid-cols-12";
1279
+ /** Largura do Field na grade de formulário (no largo, em 12 colunas). */
1280
+ var fieldSpanClass = {
1281
+ /** 2/12, cerca de 17%: UF, número, DDD. */
1282
+ xs: "@md:col-span-2 @3xl:col-span-2",
1283
+ /** 3/12 = 25%: CEP, CNPJ, CPF, datas curtas. */
1284
+ sm: "@md:col-span-2 @3xl:col-span-3",
1285
+ /** 4/12 = 33%: o padrão, 3 campos por linha. */
1286
+ md: "@md:col-span-3 @3xl:col-span-4",
1287
+ /** 6/12 = 50%. */
1288
+ lg: "@md:col-span-3 @3xl:col-span-6",
1289
+ /** 8/12 = 67%: nome, logradouro longo. */
1290
+ xl: "@md:col-span-6 @3xl:col-span-8",
1291
+ /** Linha inteira. */
1292
+ full: "@md:col-span-6 @3xl:col-span-12"
1293
+ };
1294
+ var alignClass = {
1295
+ start: "items-start",
1296
+ center: "items-center",
1297
+ end: "items-end",
1298
+ stretch: "items-stretch",
1299
+ baseline: "items-baseline"
1300
+ };
1301
+ var justifyClass = {
1302
+ start: "justify-start",
1303
+ center: "justify-center",
1304
+ end: "justify-end",
1305
+ between: "justify-between"
1306
+ };
1307
+ //#endregion
1308
+ //#region src/components/ui/field.tsx
1309
+ function Label$1({ className, required, children, ...props }) {
1310
+ return /* @__PURE__ */ jsxs(Label.Root, {
1311
+ "data-rendra": "FLD-002",
1312
+ className: cn("text-label text-label-foreground label-case", className),
1313
+ ...props,
1314
+ children: [
1315
+ children,
1316
+ required && /* @__PURE__ */ jsxs("span", {
1317
+ className: "text-destructive",
1318
+ "aria-hidden": true,
1319
+ children: [" ", "*"]
1320
+ }),
1321
+ required && /* @__PURE__ */ jsx("span", {
1322
+ className: "sr-only",
1323
+ children: " (obrigatório)"
1324
+ })
1325
+ ]
1326
+ });
1327
+ }
1328
+ var FieldContext = createContext(null);
1329
+ /** Liga um controle ao Field mais próximo (id, aria-describedby, aria-invalid). */
1330
+ function useFieldControl() {
1331
+ return useContext(FieldContext);
1332
+ }
1333
+ function Field({ label, required, help, error, span = "md", newRow, reserveMessage = false, id, className, children }) {
1334
+ const auto = useId();
1335
+ const controlId = id ?? `campo${auto}`;
1336
+ const messageId = `${controlId}-mensagem`;
1337
+ const invalid = Boolean(error);
1338
+ const message = error ?? help;
1339
+ const child = isValidElement(children) && typeof children.type !== "string" ? cloneElement(children, {
1340
+ id: children.props.id ?? controlId,
1341
+ ...invalid ? { invalid: children.props.invalid ?? true } : {},
1342
+ "aria-describedby": message ? messageId : void 0,
1343
+ "aria-required": required || void 0
1344
+ }) : children;
1345
+ return /* @__PURE__ */ jsx(FieldContext.Provider, {
1346
+ value: {
1347
+ id: controlId,
1348
+ describedBy: message ? messageId : "",
1349
+ invalid
1350
+ },
1351
+ children: /* @__PURE__ */ jsxs("div", {
1352
+ "data-slot": "field",
1353
+ "data-rendra": "FLD-001",
1354
+ className: cn("flex min-w-0 flex-col gap-2", fieldSpanClass[span === "half" ? "md" : span], newRow && "@md:col-start-1 @3xl:col-start-1", className),
1355
+ children: [
1356
+ label && /* @__PURE__ */ jsx(Label$1, {
1357
+ htmlFor: controlId,
1358
+ required,
1359
+ children: label
1360
+ }),
1361
+ child,
1362
+ (reserveMessage || message) && /* @__PURE__ */ jsx("p", {
1363
+ id: messageId,
1364
+ role: invalid ? "alert" : void 0,
1365
+ className: cn("-mt-1 min-h-4 text-help", invalid ? "font-medium text-destructive" : "text-help-foreground"),
1366
+ children: message
1367
+ })
1368
+ ]
1369
+ })
1370
+ });
1371
+ }
1372
+ //#endregion
1373
+ //#region src/components/ui/select.tsx
1374
+ /** Acima deste número de opções, o "Selecionar todos" aparece sem a prop `selectAll`. */
1375
+ var SELECT_ALL_AUTO_MIN = 5;
1376
+ /**
1377
+ * Select único do sistema. Busca, múltiplo, selecionar todos, contador, criar opção,
1378
+ * busca remota e carregamento são props. No mobile abre como bottom sheet.
1379
+ */
1380
+ var Select = forwardRef(function Select(props, ref) {
1381
+ const { options: staticOptions = [], placeholder = "Selecione", label, size, invalid, disabled, searchable, creatable, onCreate, loadOptions, loading, clearable, emptyText = "Nenhuma opção encontrada.", id, className } = props;
1382
+ const multiple = props.multiple === true;
1383
+ const { isMobile } = useBreakpoint();
1384
+ const [open, setOpen] = useState(false);
1385
+ const listId = `${useId()}-lista`;
1386
+ const [query, setQuery] = useState("");
1387
+ const [remote, setRemote] = useState(null);
1388
+ const [remoteLoading, setRemoteLoading] = useState(false);
1389
+ const [created, setCreated] = useState([]);
1390
+ const [innerSingle, setInnerSingle] = useState(null);
1391
+ const [innerMulti, setInnerMulti] = useState([]);
1392
+ const [draft, setDraft] = useState([]);
1393
+ const selectedSingle = multiple ? null : props.value ?? innerSingle;
1394
+ const selectedMulti = multiple ? props.value ?? innerMulti : [];
1395
+ const seq = useRef(0);
1396
+ useEffect(() => {
1397
+ if (!loadOptions || !open) return;
1398
+ const n = ++seq.current;
1399
+ setRemoteLoading(true);
1400
+ const t = window.setTimeout(async () => {
1401
+ const res = await loadOptions(query);
1402
+ if (n === seq.current) {
1403
+ setRemote(res);
1404
+ setRemoteLoading(false);
1405
+ }
1406
+ }, 250);
1407
+ return () => window.clearTimeout(t);
1408
+ }, [
1409
+ loadOptions,
1410
+ query,
1411
+ open
1412
+ ]);
1413
+ const all = useMemo(() => {
1414
+ const base = remote ?? staticOptions;
1415
+ const map = /* @__PURE__ */ new Map();
1416
+ [...base, ...created].forEach((o) => map.set(o.value, o));
1417
+ return [...map.values()];
1418
+ }, [
1419
+ remote,
1420
+ staticOptions,
1421
+ created
1422
+ ]);
1423
+ const byValue = (v) => all.find((o) => o.value === v);
1424
+ const isSearchable = searchable || creatable || Boolean(loadOptions);
1425
+ const busy = loading || remoteLoading;
1426
+ const multiValue = isMobile && multiple ? draft : selectedMulti;
1427
+ const commitSingle = (v) => {
1428
+ setInnerSingle(v);
1429
+ props.onChange?.(v);
1430
+ };
1431
+ const commitMulti = (v) => {
1432
+ setInnerMulti(v);
1433
+ props.onChange?.(v);
1434
+ };
1435
+ const toggle = (v) => {
1436
+ if (!multiple) {
1437
+ commitSingle(v);
1438
+ setOpen(false);
1439
+ return;
1440
+ }
1441
+ const next = multiValue.includes(v) ? multiValue.filter((x) => x !== v) : [...multiValue, v];
1442
+ if (isMobile) setDraft(next);
1443
+ else commitMulti(next);
1444
+ };
1445
+ const enabled = all.filter((o) => !o.disabled);
1446
+ const allSelected = enabled.length > 0 && enabled.every((o) => multiValue.includes(o.value));
1447
+ const showSelectAll = multiple && (props.selectAll ?? all.length > SELECT_ALL_AUTO_MIN);
1448
+ const toggleAll = () => {
1449
+ const next = allSelected ? [] : enabled.map((o) => o.value);
1450
+ if (isMobile) setDraft(next);
1451
+ else commitMulti(next);
1452
+ };
1453
+ const create = async () => {
1454
+ const text = query.trim();
1455
+ if (!text) return;
1456
+ const opt = onCreate ? await onCreate(text) : {
1457
+ value: text,
1458
+ label: text
1459
+ };
1460
+ setCreated((c) => [...c, opt]);
1461
+ setQuery("");
1462
+ toggle(opt.value);
1463
+ };
1464
+ const onOpenChange = (o) => {
1465
+ setOpen(o);
1466
+ if (o) setDraft(selectedMulti);
1467
+ else setQuery("");
1468
+ };
1469
+ const hasValue = multiple ? selectedMulti.length > 0 : selectedSingle != null;
1470
+ const maxChips = props.maxChips ?? 2;
1471
+ let display = /* @__PURE__ */ jsx("span", {
1472
+ className: "truncate text-muted-foreground",
1473
+ children: placeholder
1474
+ });
1475
+ if (!multiple && selectedSingle != null) {
1476
+ const o = byValue(selectedSingle);
1477
+ display = /* @__PURE__ */ jsxs("span", {
1478
+ className: "flex min-w-0 items-center gap-2",
1479
+ children: [o?.icon, /* @__PURE__ */ jsx("span", {
1480
+ className: "truncate",
1481
+ children: o?.label ?? selectedSingle
1482
+ })]
1483
+ });
1484
+ } else if (multiple && selectedMulti.length > 0) display = props.showCount ? /* @__PURE__ */ jsxs("span", {
1485
+ className: "truncate",
1486
+ children: [
1487
+ selectedMulti.length,
1488
+ " ",
1489
+ selectedMulti.length === 1 ? "selecionado" : "selecionados"
1490
+ ]
1491
+ }) : /* @__PURE__ */ jsxs("span", {
1492
+ className: "flex min-w-0 items-center gap-1 overflow-hidden",
1493
+ children: [selectedMulti.slice(0, maxChips).map((v) => /* @__PURE__ */ jsx("span", {
1494
+ className: "max-w-3xs shrink-0 truncate rounded-item bg-primary-soft px-2 py-1 text-xs leading-none font-medium text-primary-soft-foreground",
1495
+ children: byValue(v)?.label ?? v
1496
+ }, v)), selectedMulti.length > maxChips && /* @__PURE__ */ jsxs("span", {
1497
+ className: "shrink-0 rounded-item bg-muted px-2 py-1 text-xs leading-none font-medium",
1498
+ children: ["+", selectedMulti.length - maxChips]
1499
+ })]
1500
+ });
1501
+ const showClear = clearable && hasValue && !disabled;
1502
+ const clear = showClear ? /* @__PURE__ */ jsx("button", {
1503
+ type: "button",
1504
+ "aria-label": "Limpar seleção",
1505
+ className: cn(controlAdornmentButton, "absolute top-1/2 right-8 mr-0 -translate-y-1/2 md:mr-0"),
1506
+ onClick: () => multiple ? commitMulti([]) : commitSingle(null),
1507
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
1508
+ }) : void 0;
1509
+ const trigger = /* @__PURE__ */ jsxs("button", {
1510
+ ref,
1511
+ id,
1512
+ type: "button",
1513
+ role: "combobox",
1514
+ "aria-expanded": open,
1515
+ "aria-controls": listId,
1516
+ "aria-haspopup": "listbox",
1517
+ "aria-invalid": invalid || void 0,
1518
+ "aria-describedby": props["aria-describedby"],
1519
+ "aria-label": id ? void 0 : label,
1520
+ disabled,
1521
+ "data-rendra": "SEL-001",
1522
+ onKeyDown: (e) => e.stopPropagation(),
1523
+ className: cn(controlFrame({
1524
+ size,
1525
+ invalid
1526
+ }), "cursor-pointer text-left disabled:cursor-not-allowed disabled:opacity-60", className),
1527
+ children: [
1528
+ /* @__PURE__ */ jsx("span", {
1529
+ className: "flex min-w-0 flex-1 items-center",
1530
+ children: display
1531
+ }),
1532
+ showClear && /* @__PURE__ */ jsx("span", {
1533
+ "aria-hidden": true,
1534
+ className: cn("size-control-sm shrink-0 md:size-8", "-mr-1")
1535
+ }),
1536
+ /* @__PURE__ */ jsx(ChevronDown, {
1537
+ "aria-hidden": true,
1538
+ className: cn("size-icon-sm shrink-0 text-muted-foreground transition-transform duration-(--rendra-duration-base) ease-move", open && "rotate-180")
1539
+ })
1540
+ ]
1541
+ });
1542
+ const groups = all.reduce((acc, o) => {
1543
+ (acc[o.group ?? ""] ??= []).push(o);
1544
+ return acc;
1545
+ }, {});
1546
+ const q = query.trim().toLowerCase();
1547
+ const showCreate = creatable && q && !all.some((o) => o.label.toLowerCase() === q);
1548
+ const itemClass = "relative flex min-h-touch cursor-pointer items-center gap-3 rounded-item px-3 text-sm outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent md:min-h-0 md:py-2";
1549
+ const checkbox = (checked) => /* @__PURE__ */ jsx("span", {
1550
+ "aria-hidden": true,
1551
+ className: cn("flex size-icon-sm shrink-0 items-center justify-center rounded-item border transition-colors", checked ? "border-primary bg-primary text-primary-foreground" : "border-input bg-card"),
1552
+ children: checked && /* @__PURE__ */ jsx(Check, {
1553
+ className: "size-3",
1554
+ strokeWidth: 3
1555
+ })
1556
+ });
1557
+ const list = /* @__PURE__ */ jsxs(Command.List, {
1558
+ id: listId,
1559
+ "aria-label": label ?? placeholder,
1560
+ children: [
1561
+ busy && /* @__PURE__ */ jsx(Command.Loading, { children: /* @__PURE__ */ jsxs("div", {
1562
+ className: "flex items-center gap-2 px-3 py-4 text-sm text-muted-foreground",
1563
+ children: [/* @__PURE__ */ jsx(Spinner, { size: "sm" }), "Carregando opções..."]
1564
+ }) }),
1565
+ !busy && /* @__PURE__ */ jsx(Command.Empty, {
1566
+ className: "px-3 py-6 text-center text-sm text-muted-foreground",
1567
+ children: emptyText
1568
+ }),
1569
+ showCreate && /* @__PURE__ */ jsxs(Command.Item, {
1570
+ value: `__criar__${query}`,
1571
+ onSelect: create,
1572
+ className: itemClass,
1573
+ children: [/* @__PURE__ */ jsx(Plus, {
1574
+ className: "size-icon-sm shrink-0 text-primary-text",
1575
+ "aria-hidden": true
1576
+ }), /* @__PURE__ */ jsxs("span", {
1577
+ className: "truncate",
1578
+ children: ["Criar ", /* @__PURE__ */ jsxs("strong", {
1579
+ className: "font-semibold",
1580
+ children: [
1581
+ "“",
1582
+ query.trim(),
1583
+ "”"
1584
+ ]
1585
+ })]
1586
+ })]
1587
+ }),
1588
+ showSelectAll && !q && enabled.length > 0 && /* @__PURE__ */ jsxs(Command.Item, {
1589
+ value: "__todos__",
1590
+ onSelect: toggleAll,
1591
+ className: cn(itemClass, "font-medium"),
1592
+ children: [checkbox(allSelected), allSelected ? "Desmarcar todos" : "Selecionar todos"]
1593
+ }),
1594
+ Object.entries(groups).map(([group, opts]) => /* @__PURE__ */ jsx(Command.Group, {
1595
+ heading: group || void 0,
1596
+ className: "[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
1597
+ children: opts.map((o) => {
1598
+ const checked = multiple ? multiValue.includes(o.value) : selectedSingle === o.value;
1599
+ return /* @__PURE__ */ jsxs(Command.Item, {
1600
+ value: `${o.label} ${o.description ?? ""} ${o.value}`,
1601
+ disabled: o.disabled,
1602
+ onSelect: () => toggle(o.value),
1603
+ "aria-selected": checked,
1604
+ className: itemClass,
1605
+ children: [
1606
+ multiple && checkbox(checked),
1607
+ o.icon && /* @__PURE__ */ jsx("span", {
1608
+ className: "flex shrink-0 [&_svg]:size-icon-sm",
1609
+ children: o.icon
1610
+ }),
1611
+ /* @__PURE__ */ jsxs("span", {
1612
+ className: "flex min-w-0 flex-1 flex-col",
1613
+ children: [/* @__PURE__ */ jsx("span", {
1614
+ className: cn("truncate", checked && !multiple && "font-medium"),
1615
+ children: o.label
1616
+ }), o.description && /* @__PURE__ */ jsx("span", {
1617
+ className: "truncate text-xs text-muted-foreground",
1618
+ children: o.description
1619
+ })]
1620
+ }),
1621
+ !multiple && checked && /* @__PURE__ */ jsx(Check, {
1622
+ className: "size-icon-sm shrink-0 text-primary-text",
1623
+ "aria-hidden": true
1624
+ })
1625
+ ]
1626
+ }, o.value);
1627
+ })
1628
+ }, group || "sem-grupo"))
1629
+ ]
1630
+ });
1631
+ const search = isSearchable ? /* @__PURE__ */ jsxs("div", {
1632
+ className: cn(controlFrame({ size: "md" }), "bg-background"),
1633
+ children: [/* @__PURE__ */ jsx(Search, {
1634
+ className: "size-icon-sm shrink-0 text-muted-foreground",
1635
+ "aria-hidden": true
1636
+ }), /* @__PURE__ */ jsx(Command.Input, {
1637
+ value: query,
1638
+ onValueChange: setQuery,
1639
+ placeholder: creatable ? "Buscar ou criar..." : "Buscar...",
1640
+ className: "h-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
1641
+ })]
1642
+ }) : null;
1643
+ const count = multiValue.length;
1644
+ const footer = multiple && (isMobile || count > 0) ? /* @__PURE__ */ jsxs("div", {
1645
+ className: "grid grid-actions-2 gap-3",
1646
+ children: [/* @__PURE__ */ jsx(Button, {
1647
+ variant: "outline",
1648
+ onClick: () => isMobile ? setDraft([]) : commitMulti([]),
1649
+ disabled: count === 0,
1650
+ children: "Limpar"
1651
+ }), isMobile ? /* @__PURE__ */ jsxs(Button, {
1652
+ onClick: () => {
1653
+ commitMulti(draft);
1654
+ setOpen(false);
1655
+ },
1656
+ children: ["Aplicar", count ? ` (${count})` : ""]
1657
+ }) : /* @__PURE__ */ jsxs(Button, {
1658
+ variant: "secondary",
1659
+ onClick: () => setOpen(false),
1660
+ children: [
1661
+ "Pronto (",
1662
+ count,
1663
+ ")"
1664
+ ]
1665
+ })]
1666
+ }) : void 0;
1667
+ return /* @__PURE__ */ jsx(Command, {
1668
+ shouldFilter: !loadOptions,
1669
+ loop: true,
1670
+ className: "contents",
1671
+ label: label ?? placeholder,
1672
+ children: /* @__PURE__ */ jsx(PickerPanel, {
1673
+ open,
1674
+ onOpenChange,
1675
+ trigger,
1676
+ adornment: clear,
1677
+ title: label ?? placeholder,
1678
+ header: search,
1679
+ footer,
1680
+ children: list
1681
+ })
1682
+ });
1683
+ });
1684
+ //#endregion
1685
+ //#region src/hooks/use-fill-height.ts
1686
+ /**
1687
+ * Mede o espaço que sobra na área de conteúdo (o <main>) abaixo do elemento e grava em
1688
+ * --board-h. Com a classe h-board, o elemento ocupa exatamente essa altura: a página não
1689
+ * rola e a rolagem acontece dentro dele (kanban, chat).
1690
+ */
1691
+ function useFillHeight() {
1692
+ const ref = useRef(null);
1693
+ useLayoutEffect(() => {
1694
+ const el = ref.current;
1695
+ if (!el) return;
1696
+ const main = el.closest("main");
1697
+ const measure = () => {
1698
+ const h = el.getBoundingClientRect().height;
1699
+ const page = main?.firstElementChild;
1700
+ const room = main && page ? main.clientHeight - (page.getBoundingClientRect().height - h) : window.innerHeight - el.getBoundingClientRect().top;
1701
+ el.style.setProperty("--board-h", `${Math.max(320, Math.floor(room))}px`);
1702
+ };
1703
+ measure();
1704
+ const ro = new ResizeObserver(measure);
1705
+ ro.observe(document.documentElement);
1706
+ if (main) ro.observe(main);
1707
+ return () => ro.disconnect();
1708
+ }, []);
1709
+ return ref;
1710
+ }
1711
+ //#endregion
1712
+ //#region src/components/ui/timeline.tsx
1713
+ var dot = {
1714
+ neutral: "bg-muted text-muted-foreground",
1715
+ info: "bg-info-soft text-info-soft-foreground",
1716
+ success: "bg-success-soft text-success-soft-foreground",
1717
+ warning: "bg-warning-soft text-warning-soft-foreground",
1718
+ error: "bg-destructive-soft text-destructive-soft-foreground"
1719
+ };
1720
+ var statusTone = {
1721
+ succeeded: "success",
1722
+ failed: "error",
1723
+ skipped: "neutral"
1724
+ };
1725
+ var statusIcon = {
1726
+ succeeded: CheckCircle2,
1727
+ failed: XCircle,
1728
+ skipped: SkipForward
1729
+ };
1730
+ var statusLabel = {
1731
+ succeeded: "Concluído",
1732
+ failed: "Falhou",
1733
+ skipped: "Ignorado"
1734
+ };
1735
+ var statusTextTone = {
1736
+ succeeded: "text-success-soft-foreground",
1737
+ failed: "text-destructive-soft-foreground",
1738
+ skipped: "text-muted-foreground"
1739
+ };
1740
+ /** Linha do tempo única, com eventos em ordem e tom semântico por evento. */
1741
+ function Timeline({ events, className }) {
1742
+ const lib = useMotionLib();
1743
+ const on = lib !== null;
1744
+ const presets = useMotionPresets();
1745
+ const Item = on ? lib.m.li : "li";
1746
+ const enter = presets.slide("bottom", "short");
1747
+ return /* @__PURE__ */ jsx("ol", {
1748
+ "data-rendra": "TLN-001",
1749
+ className: cn("@container flex flex-col", className),
1750
+ children: events.map((e, i) => {
1751
+ const StatusIcon = e.status ? statusIcon[e.status] : null;
1752
+ const tone = e.tone ?? (e.status ? statusTone[e.status] : "neutral");
1753
+ return /* @__PURE__ */ jsxs(Item, {
1754
+ className: "relative flex gap-3 pb-6 last:pb-0",
1755
+ ...on ? {
1756
+ ...enter,
1757
+ transition: {
1758
+ ...enter.transition,
1759
+ ...presets.stagger(i)
1760
+ }
1761
+ } : {},
1762
+ children: [
1763
+ i < events.length - 1 && /* @__PURE__ */ jsx("span", {
1764
+ "aria-hidden": true,
1765
+ className: "absolute top-8 bottom-0 left-4 w-px -translate-x-1/2 bg-border"
1766
+ }),
1767
+ /* @__PURE__ */ jsx("span", {
1768
+ "aria-hidden": true,
1769
+ className: cn("relative flex size-8 shrink-0 items-center justify-center rounded-full [&_svg]:size-icon-sm", dot[tone]),
1770
+ children: e.icon ?? (StatusIcon && /* @__PURE__ */ jsx(StatusIcon, {})) ?? /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-current" })
1771
+ }),
1772
+ /* @__PURE__ */ jsxs("div", {
1773
+ className: "flex min-w-0 flex-1 flex-col gap-1 pt-1",
1774
+ children: [
1775
+ /* @__PURE__ */ jsxs("div", {
1776
+ className: "flex min-w-0 flex-col gap-1 @sm:flex-row @sm:items-baseline @sm:justify-between @sm:gap-3",
1777
+ children: [/* @__PURE__ */ jsx("span", {
1778
+ className: "text-sm font-medium wrap-break-word",
1779
+ children: e.title
1780
+ }), /* @__PURE__ */ jsx("time", {
1781
+ className: "shrink-0 text-xs text-muted-foreground tabular-nums",
1782
+ children: e.date
1783
+ })]
1784
+ }),
1785
+ e.description && /* @__PURE__ */ jsx("p", {
1786
+ className: "text-sm wrap-break-word text-muted-foreground",
1787
+ children: e.description
1788
+ }),
1789
+ e.status && StatusIcon && /* @__PURE__ */ jsxs("span", {
1790
+ className: cn("inline-flex w-fit items-center gap-1 text-xs font-medium [&_svg]:size-icon-sm", statusTextTone[e.status]),
1791
+ children: [/* @__PURE__ */ jsx(StatusIcon, {}), statusLabel[e.status]]
1792
+ })
1793
+ ]
1794
+ })
1795
+ ]
1796
+ }, e.id);
1797
+ })
1798
+ });
1799
+ }
1800
+ //#endregion
1801
+ export { lookupCnpj as A, ActionBar as B, toE164 as C, controlInput as D, controlFrame as E, CardHeader as F, useShell as H, CardTitle as I, InfoHint as L, CardContent as M, CardDescription as N, useLookup as O, CardFooter as P, Modal as R, toCents as S, controlAdornmentSpace as T, ShellContext as V, internationalPhoneMask as _, Label$1 as a, percentMask as b, fieldGridClass as c, Drawer as d, PickerPanel as f, formatCurrency as g, formatCents as h, Field as i, Card as j, lookupCep as k, gapClass as l, DEFAULT_DDI as m, useFillHeight as n, useFieldControl as o, Input as p, Select as r, alignClass as s, Timeline as t, justifyClass as u, masks as v, controlAdornmentButton as w, phoneCountries as x, parseLocaleNumber as y, Alert as z };
1802
+
1803
+ //# sourceMappingURL=timeline-BOh7gUdA.js.map