@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.
- package/README.md +2 -1
- package/dist/base.css +1 -1
- package/dist/{empty-state-DrQjqxvp.js → brand-feedback-icon-BYNcqBel.js} +4 -64
- package/dist/brand-feedback-icon-BYNcqBel.js.map +1 -0
- package/dist/{button-B6-644xP.js → button-DlzWwpZk.js} +6 -6
- package/dist/{button-B6-644xP.js.map → button-DlzWwpZk.js.map} +1 -1
- package/dist/chart.js +7 -7
- package/dist/cli/auditar.js +1 -1
- package/dist/cli/codigos.js +2 -2
- package/dist/cli/executar-auditar.js +1 -1
- package/dist/cli/help.js +1 -1
- package/dist/cli/trocar.js +2 -2
- package/dist/cli/typescript-loader.js +1 -1
- package/dist/{cn-TBlI6-gS.js → cn-B0N3Pek0.js} +2 -2
- package/dist/{cn-TBlI6-gS.js.map → cn-B0N3Pek0.js.map} +1 -1
- package/dist/{components-Btk1cUg2.js → components-CpQPMGfT.js} +18 -2
- package/dist/{components-Btk1cUg2.js.map → components-CpQPMGfT.js.map} +1 -1
- package/dist/components.css +90 -1
- package/dist/document-viewer.js +4 -4
- package/dist/{tooltip-kgsorOg6.js → dropdown-menu-M8vs0byD.js} +9 -75
- package/dist/dropdown-menu-M8vs0byD.js.map +1 -0
- package/dist/empty-state-a47Kd_Ci.js +67 -0
- package/dist/empty-state-a47Kd_Ci.js.map +1 -0
- package/dist/{features-max-CC6CorTg.js → features-max-ScxV8gb3.js} +1 -1
- package/dist/flow.js +1485 -0
- package/dist/flow.js.map +1 -0
- package/dist/index.js +72 -1854
- package/dist/index.js.map +1 -1
- package/dist/{motion-context-BL545cb6.js → motion-context-BUMPf0e9.js} +4 -4
- package/dist/{motion-context-BL545cb6.js.map → motion-context-BUMPf0e9.js.map} +1 -1
- package/dist/{motion-on-DMoTsWrG.js → motion-on-CNN6y8g-.js} +2 -2
- package/dist/{motion-on-DMoTsWrG.js.map → motion-on-CNN6y8g-.js.map} +1 -1
- package/dist/motion.js +2 -2
- package/dist/{rendra-provider-CeCkPafT.js → rendra-provider-DEURIIL3.js} +3 -3
- package/dist/{rendra-provider-CeCkPafT.js.map → rendra-provider-DEURIIL3.js.map} +1 -1
- package/dist/rich-text-editor.js +4 -3
- package/dist/rich-text-editor.js.map +1 -1
- package/dist/router-bridge.js +2 -2
- package/dist/timeline-BOh7gUdA.js +1803 -0
- package/dist/timeline-BOh7gUdA.js.map +1 -0
- package/dist/tokens.css +1 -1
- package/dist/tooltip-Ym9S4h9_.js +76 -0
- package/dist/tooltip-Ym9S4h9_.js.map +1 -0
- package/dist/types/components/ui/flow-editor.d.ts +11 -0
- package/dist/types/components/ui/flow-viewer.d.ts +58 -0
- package/dist/types/flow.d.ts +3 -0
- package/dist/types/lib/flow.d.ts +121 -0
- package/dist/{use-breakpoint-CNxDwEpc.js → use-breakpoint-En17oXKY.js} +2 -2
- package/dist/{use-breakpoint-CNxDwEpc.js.map → use-breakpoint-En17oXKY.js.map} +1 -1
- package/dist/{use-motion-presets-DCJPBfNJ.js → use-motion-presets-D3ww1oJX.js} +3 -3
- package/dist/{use-motion-presets-DCJPBfNJ.js.map → use-motion-presets-D3ww1oJX.js.map} +1 -1
- package/dist/widget-grid.js +2 -2
- package/package.json +7 -1
- package/dist/empty-state-DrQjqxvp.js.map +0 -1
- 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
|