@assembleo/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1594 @@
1
+ import { cva, cx } from 'class-variance-authority';
2
+ import { Dialog as Dialog$1, Popover as Popover$1, Tabs as Tabs$1, Avatar as Avatar$1, Checkbox as Checkbox$1, Progress as Progress$1, RadioGroup as RadioGroup$1, ToggleGroup, Select as Select$1, Separator as Separator$1, Switch as Switch$1, Toast, Tooltip as Tooltip$1 } from 'radix-ui';
3
+ import { createContext, Children, useContext, useId, useRef, useState, useCallback, useMemo, useEffect } from 'react';
4
+ import { extendTailwindMerge } from 'tailwind-merge';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+ import { CircleAlert, TriangleAlert, CircleCheck, Info, X, Check, Minus, ChevronRight, Search, ChevronsUpDown } from 'lucide-react';
7
+
8
+ // src/components/avatar/avatar.tsx
9
+ var fontSizes = [
10
+ "display",
11
+ "title-1",
12
+ "title-2",
13
+ "title-3",
14
+ "headline",
15
+ "body",
16
+ "callout",
17
+ "subhead",
18
+ "footnote",
19
+ "caption"
20
+ ];
21
+ var twMergeAssembleo = extendTailwindMerge({
22
+ extend: {
23
+ classGroups: {
24
+ "font-size": fontSizes.map((size) => `text-${size}`)
25
+ }
26
+ }
27
+ });
28
+ function cn(...classes) {
29
+ return twMergeAssembleo(cx(classes));
30
+ }
31
+ var avatar = cva(
32
+ "relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full",
33
+ {
34
+ variants: {
35
+ size: {
36
+ sm: "size-7",
37
+ md: "size-9",
38
+ lg: "size-touch",
39
+ xl: "size-16"
40
+ }
41
+ },
42
+ defaultVariants: {
43
+ size: "md"
44
+ }
45
+ }
46
+ );
47
+ var initialsSizeClasses = {
48
+ sm: "text-caption",
49
+ md: "text-footnote",
50
+ lg: "text-callout",
51
+ xl: "text-title-3"
52
+ };
53
+ var expressiveBackgrounds = [
54
+ "bg-terracota-soft",
55
+ "bg-jardim-soft",
56
+ "bg-ambar-soft",
57
+ "bg-lavanda-soft",
58
+ "bg-ceu-soft"
59
+ ];
60
+ function backgroundFor(name) {
61
+ let sum = 0;
62
+ for (const char of name) {
63
+ sum += char.codePointAt(0) ?? 0;
64
+ }
65
+ return expressiveBackgrounds[sum % expressiveBackgrounds.length] ?? "bg-terracota-soft";
66
+ }
67
+ function initialsFor(name) {
68
+ return name.trim().split(/\s+/).slice(0, 2).map((word) => word[0] ?? "").join("").toUpperCase();
69
+ }
70
+ function Avatar({ name, src, size, className, ...props }) {
71
+ return (
72
+ /*
73
+ * O nome acessível mora no Root: com ou sem foto carregada, o leitor de
74
+ * tela anuncia a pessoa uma vez só — a foto e as iniciais são decorativas.
75
+ */
76
+ /* @__PURE__ */ jsxs(
77
+ Avatar$1.Root,
78
+ {
79
+ role: "img",
80
+ "aria-label": name,
81
+ className: cn(avatar({ size }), className),
82
+ ...props,
83
+ children: [
84
+ src ? /* @__PURE__ */ jsx(Avatar$1.Image, { src, alt: "", className: "size-full object-cover" }) : null,
85
+ /* @__PURE__ */ jsx(
86
+ Avatar$1.Fallback,
87
+ {
88
+ "aria-hidden": "true",
89
+ className: cn(
90
+ "flex size-full items-center justify-center font-friendly font-semibold text-text",
91
+ initialsSizeClasses[size ?? "md"],
92
+ backgroundFor(name)
93
+ ),
94
+ children: initialsFor(name)
95
+ }
96
+ )
97
+ ]
98
+ }
99
+ )
100
+ );
101
+ }
102
+ function AvatarGroup({ max, size, className, children, ...props }) {
103
+ const items = Children.toArray(children);
104
+ const visible = max != null && max >= 0 ? items.slice(0, max) : items;
105
+ const excess = items.length - visible.length;
106
+ return (
107
+ /*
108
+ * A sobreposição conta a cena — "estas pessoas juntas" — e o anel na cor
109
+ * da superfície recorta cada rosto contra o vizinho de trás.
110
+ */
111
+ /* @__PURE__ */ jsxs(
112
+ "div",
113
+ {
114
+ className: cn(
115
+ "flex items-center -space-x-2 *:rounded-full *:ring-2 *:ring-surface",
116
+ className
117
+ ),
118
+ ...props,
119
+ children: [
120
+ visible,
121
+ excess > 0 ? /* @__PURE__ */ jsxs(
122
+ "span",
123
+ {
124
+ className: cn(
125
+ avatar({ size }),
126
+ "bg-surface-sunken text-caption font-semibold text-text-secondary"
127
+ ),
128
+ children: [
129
+ "+",
130
+ excess
131
+ ]
132
+ }
133
+ ) : null
134
+ ]
135
+ }
136
+ )
137
+ );
138
+ }
139
+ var badge = cva(
140
+ "inline-flex items-center gap-1 rounded-pill px-2.5 py-0.5 text-caption font-semibold",
141
+ {
142
+ variants: {
143
+ tone: {
144
+ neutral: "bg-surface-sunken text-text-secondary",
145
+ accent: "bg-accent-soft text-accent-soft-text",
146
+ success: "bg-success-soft text-success-soft-text",
147
+ warning: "bg-warning-soft text-warning-soft-text",
148
+ danger: "bg-danger-soft text-danger-soft-text",
149
+ /*
150
+ * A vila não tem par `-soft-text`: o texto fica na voz secundária —
151
+ * contraste AA garantido pelo próprio tema — e a identidade da cor
152
+ * vem do fundo suave com a bolinha forte, não do texto.
153
+ */
154
+ terracota: "bg-terracota-soft text-text-secondary",
155
+ jardim: "bg-jardim-soft text-text-secondary",
156
+ ambar: "bg-ambar-soft text-text-secondary",
157
+ lavanda: "bg-lavanda-soft text-text-secondary",
158
+ ceu: "bg-ceu-soft text-text-secondary"
159
+ }
160
+ },
161
+ defaultVariants: {
162
+ tone: "neutral"
163
+ }
164
+ }
165
+ );
166
+ var dotToneClasses = {
167
+ neutral: "bg-text-muted",
168
+ accent: "bg-accent",
169
+ success: "bg-success",
170
+ warning: "bg-warning",
171
+ danger: "bg-danger",
172
+ terracota: "bg-terracota",
173
+ jardim: "bg-jardim",
174
+ ambar: "bg-ambar",
175
+ lavanda: "bg-lavanda",
176
+ ceu: "bg-ceu"
177
+ };
178
+ function Badge({ tone, dot = false, className, children, ...props }) {
179
+ return /* @__PURE__ */ jsxs("span", { className: cn(badge({ tone }), className), ...props, children: [
180
+ dot ? /* @__PURE__ */ jsx(
181
+ "span",
182
+ {
183
+ "aria-hidden": "true",
184
+ className: cn("size-1.5 shrink-0 rounded-full", dotToneClasses[tone ?? "neutral"])
185
+ }
186
+ ) : null,
187
+ children
188
+ ] });
189
+ }
190
+ var banner = cva("flex gap-3 rounded-surface squircle p-4 text-subhead", {
191
+ variants: {
192
+ /* Sempre o par soft/soft-text: aviso é fundo calmo, nunca bloco gritado. */
193
+ tone: {
194
+ info: "bg-accent-soft text-accent-soft-text",
195
+ success: "bg-success-soft text-success-soft-text",
196
+ warning: "bg-warning-soft text-warning-soft-text",
197
+ danger: "bg-danger-soft text-danger-soft-text"
198
+ }
199
+ },
200
+ defaultVariants: {
201
+ tone: "info"
202
+ }
203
+ });
204
+ var toneIcon = {
205
+ info: Info,
206
+ success: CircleCheck,
207
+ warning: TriangleAlert,
208
+ danger: CircleAlert
209
+ };
210
+ function Banner({ tone, title, onDismiss, className, children, ...props }) {
211
+ const Icon = toneIcon[tone ?? "info"];
212
+ return /* @__PURE__ */ jsxs(
213
+ "div",
214
+ {
215
+ role: tone === "danger" ? "alert" : "status",
216
+ className: cn(banner({ tone }), className),
217
+ ...props,
218
+ children: [
219
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", className: "mt-0.5 size-5 shrink-0" }),
220
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
221
+ title ? /* @__PURE__ */ jsx("p", { className: "font-semibold", children: title }) : null,
222
+ children
223
+ ] }),
224
+ onDismiss ? /* @__PURE__ */ jsx(
225
+ "button",
226
+ {
227
+ type: "button",
228
+ "aria-label": "Dispensar aviso",
229
+ onClick: onDismiss,
230
+ className: cn(
231
+ "relative -my-1 -mr-1 grid size-8 shrink-0 place-items-center self-start rounded-control-sm",
232
+ "opacity-70 transition-opacity duration-(--motion-quick) ease-(--ease-out-soft) hover:opacity-100",
233
+ // O X herda o tom do banner e ganha 44px de alvo via pseudo-elemento.
234
+ "before:absolute before:-inset-1.5"
235
+ ),
236
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" })
237
+ }
238
+ ) : null
239
+ ]
240
+ }
241
+ );
242
+ }
243
+ var spinner = cva("inline-block animate-spin", {
244
+ variants: {
245
+ size: {
246
+ sm: "size-4",
247
+ md: "size-5",
248
+ lg: "size-7"
249
+ }
250
+ },
251
+ defaultVariants: {
252
+ size: "md"
253
+ }
254
+ });
255
+ function Spinner({ size, label = "Carregando", className }) {
256
+ return /* @__PURE__ */ jsxs("span", { role: "status", className: cn(spinner({ size }), className), children: [
257
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", className: "size-full", children: [
258
+ /* @__PURE__ */ jsx(
259
+ "circle",
260
+ {
261
+ cx: "12",
262
+ cy: "12",
263
+ r: "10",
264
+ stroke: "currentColor",
265
+ strokeWidth: "3",
266
+ className: "opacity-25"
267
+ }
268
+ ),
269
+ /* @__PURE__ */ jsx(
270
+ "path",
271
+ {
272
+ d: "M22 12a10 10 0 0 0-10-10",
273
+ stroke: "currentColor",
274
+ strokeWidth: "3",
275
+ strokeLinecap: "round"
276
+ }
277
+ )
278
+ ] }),
279
+ label ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }) : null
280
+ ] });
281
+ }
282
+ var button = cva(
283
+ [
284
+ "relative inline-flex items-center justify-center gap-2",
285
+ "rounded-control squircle select-none",
286
+ "transition-[transform,background-color,border-color,opacity,box-shadow]",
287
+ "duration-(--motion-quick) ease-(--ease-out-soft)",
288
+ // O aperto: encolher um fio no active é o feedback tátil da Apple.
289
+ "active:scale-[0.97]",
290
+ "disabled:pointer-events-none disabled:opacity-50"
291
+ ],
292
+ {
293
+ variants: {
294
+ variant: {
295
+ primary: "bg-accent text-accent-text hover:bg-accent-hover active:bg-accent-active",
296
+ /* O botão "tingido" do iOS: presença sem gritar. */
297
+ tonal: "bg-accent-soft text-accent-soft-text hover:brightness-97 dark:hover:brightness-110",
298
+ secondary: "bg-surface text-text border border-border-strong hover:bg-surface-sunken",
299
+ ghost: "bg-transparent text-accent-soft-text hover:bg-accent-soft",
300
+ danger: "bg-danger text-danger-text hover:brightness-110",
301
+ "danger-tonal": "bg-danger-soft text-danger-soft-text hover:brightness-97"
302
+ },
303
+ size: {
304
+ sm: "min-h-9 px-3.5 text-subhead font-semibold",
305
+ md: "min-h-touch px-5 text-headline",
306
+ lg: "min-h-13 px-6 text-headline"
307
+ },
308
+ /**
309
+ * Em celular o padrão é ocupar a largura toda: alvo maior e polegar não
310
+ * precisa mirar. `auto` é a exceção, para botão em linha.
311
+ */
312
+ width: {
313
+ full: "w-full",
314
+ auto: "w-auto"
315
+ }
316
+ },
317
+ defaultVariants: {
318
+ variant: "primary",
319
+ size: "md",
320
+ width: "full"
321
+ }
322
+ }
323
+ );
324
+ function Button({
325
+ variant,
326
+ size,
327
+ width,
328
+ loading = false,
329
+ className,
330
+ type,
331
+ disabled,
332
+ children,
333
+ ...props
334
+ }) {
335
+ return (
336
+ // Sem `type` explícito, um botão dentro de formulário submete — que é o
337
+ // default do HTML e quase nunca o que se quis escrever.
338
+ /* @__PURE__ */ jsxs(
339
+ "button",
340
+ {
341
+ type: type ?? "button",
342
+ className: cn(button({ variant, size, width }), className),
343
+ disabled: disabled || loading,
344
+ "aria-busy": loading || void 0,
345
+ ...props,
346
+ children: [
347
+ loading ? /* @__PURE__ */ jsx("span", { className: "absolute inset-0 grid place-items-center", children: /* @__PURE__ */ jsx(Spinner, { size: size === "sm" ? "sm" : "md" }) }) : null,
348
+ /* @__PURE__ */ jsx("span", { className: cn("inline-flex items-center gap-2", loading && "invisible"), children })
349
+ ]
350
+ }
351
+ )
352
+ );
353
+ }
354
+ var card = cva(
355
+ // A superfície branca sobre o fundo cinza: é dessa inversão que nasce a
356
+ // profundidade do sistema — nunca de bordas pesadas.
357
+ ["rounded-surface squircle text-text"],
358
+ {
359
+ variants: {
360
+ variant: {
361
+ /* Sem sombra: para quando o cartão já está dentro de outra superfície. */
362
+ flat: "border border-border bg-surface",
363
+ raised: "bg-surface shadow-level-1",
364
+ /* Acima de tudo na tela — em tema escuro a superfície mais clara é que separa. */
365
+ floating: "bg-surface-raised shadow-level-2"
366
+ },
367
+ padding: {
368
+ /* Para conteúdo que sangra até a borda (imagem, lista interna). */
369
+ none: "p-0",
370
+ md: "p-5",
371
+ lg: "p-6"
372
+ }
373
+ },
374
+ defaultVariants: {
375
+ variant: "raised",
376
+ padding: "md"
377
+ }
378
+ }
379
+ );
380
+ function Card({
381
+ variant,
382
+ padding,
383
+ interactive = false,
384
+ className,
385
+ children,
386
+ ...props
387
+ }) {
388
+ if (interactive) {
389
+ return /* @__PURE__ */ jsx(
390
+ "button",
391
+ {
392
+ type: "button",
393
+ className: cn(
394
+ card({ variant, padding }),
395
+ "block w-full text-left",
396
+ "transition-[transform,box-shadow] duration-(--motion-quick) ease-(--ease-spring)",
397
+ // O aperto tem mola: o cartão afunda um fio e a sombra sobe no hover.
398
+ "hover:shadow-level-2 active:scale-[0.99]",
399
+ "disabled:pointer-events-none disabled:opacity-50",
400
+ className
401
+ ),
402
+ ...props,
403
+ children
404
+ }
405
+ );
406
+ }
407
+ return /* @__PURE__ */ jsx("div", { className: cn(card({ variant, padding }), className), ...props, children });
408
+ }
409
+ var FieldContext = createContext(null);
410
+ function useFieldContext() {
411
+ return useContext(FieldContext);
412
+ }
413
+ function Field({ label, hint, error, required, className, children }) {
414
+ const id = useId();
415
+ const controlId = `${id}-controle`;
416
+ const hintId = hint ? `${id}-dica` : void 0;
417
+ const errorId = error ? `${id}-erro` : void 0;
418
+ return /* @__PURE__ */ jsx(FieldContext.Provider, { value: { controlId, hintId, errorId, invalid: Boolean(error) }, children: /* @__PURE__ */ jsxs("div", { className: cn("flex w-full flex-col gap-1.5", className), children: [
419
+ /* @__PURE__ */ jsxs("label", { htmlFor: controlId, className: "text-subhead font-medium text-text", children: [
420
+ label,
421
+ required ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-danger", children: " *" }) : null
422
+ ] }),
423
+ children,
424
+ hint && !error ? /* @__PURE__ */ jsx("p", { id: hintId, className: "text-footnote text-text-muted", children: hint }) : null,
425
+ /* @__PURE__ */ jsx(
426
+ "p",
427
+ {
428
+ id: errorId,
429
+ "aria-live": "polite",
430
+ className: cn("text-footnote text-danger-soft-text", !error && "hidden"),
431
+ children: error
432
+ }
433
+ )
434
+ ] }) });
435
+ }
436
+ var boxClasses = [
437
+ // `group` é o que deixa o ícone interno saber se o estado é indeterminado.
438
+ "group size-6 shrink-0 rounded-control-sm squircle",
439
+ "bg-surface-sunken border border-border-strong",
440
+ "transition-[background-color,border-color] duration-(--motion-quick) ease-(--ease-out-soft)",
441
+ "data-[state=checked]:bg-accent data-[state=checked]:border-accent data-[state=checked]:text-accent-text",
442
+ "data-[state=indeterminate]:bg-accent data-[state=indeterminate]:border-accent data-[state=indeterminate]:text-accent-text",
443
+ "aria-invalid:border-danger",
444
+ "disabled:pointer-events-none disabled:opacity-50"
445
+ ].join(" ");
446
+ function Checkbox({ label, className, id, ...props }) {
447
+ const field = useFieldContext();
448
+ const generatedId = useId();
449
+ const controlId = id ?? field?.controlId ?? generatedId;
450
+ const box = /* @__PURE__ */ jsx(
451
+ Checkbox$1.Root,
452
+ {
453
+ id: controlId,
454
+ "aria-invalid": field?.invalid || void 0,
455
+ "aria-describedby": field ? [field.errorId, field.hintId].filter(Boolean).join(" ") || void 0 : void 0,
456
+ className: cn(boxClasses, className),
457
+ ...props,
458
+ children: /* @__PURE__ */ jsxs(Checkbox$1.Indicator, { className: "flex items-center justify-center animate-pop-in", children: [
459
+ /* @__PURE__ */ jsx(
460
+ Check,
461
+ {
462
+ "aria-hidden": "true",
463
+ strokeWidth: 3,
464
+ className: "size-4 group-data-[state=indeterminate]:hidden"
465
+ }
466
+ ),
467
+ /* @__PURE__ */ jsx(
468
+ Minus,
469
+ {
470
+ "aria-hidden": "true",
471
+ strokeWidth: 3,
472
+ className: "hidden size-4 group-data-[state=indeterminate]:block"
473
+ }
474
+ )
475
+ ] })
476
+ }
477
+ );
478
+ if (!label) {
479
+ return box;
480
+ }
481
+ return /* @__PURE__ */ jsxs("label", { htmlFor: controlId, className: "flex min-h-touch select-none items-center gap-3", children: [
482
+ box,
483
+ /* @__PURE__ */ jsx("span", { className: "text-body text-text", children: label })
484
+ ] });
485
+ }
486
+ var Dialog = Dialog$1.Root;
487
+ function DialogTrigger({ asChild = true, ...props }) {
488
+ return /* @__PURE__ */ jsx(Dialog$1.Trigger, { asChild, ...props });
489
+ }
490
+ function DialogContent({ className, children, ...props }) {
491
+ return /* @__PURE__ */ jsxs(Dialog$1.Portal, { children: [
492
+ /* @__PURE__ */ jsx(Dialog$1.Overlay, { className: "fixed inset-0 z-(--z-overlay) bg-overlay animate-fade-in" }),
493
+ /* @__PURE__ */ jsxs(
494
+ Dialog$1.Content,
495
+ {
496
+ className: cn(
497
+ "fixed top-1/2 left-1/2 z-(--z-modal) -translate-x-1/2 -translate-y-1/2",
498
+ // Em celular a margem de 1.25rem por lado respira; max-w segura no desktop.
499
+ "w-[calc(100vw-2.5rem)] max-w-md",
500
+ "rounded-surface-lg squircle bg-surface p-6 shadow-level-3",
501
+ "animate-pop-in",
502
+ className
503
+ ),
504
+ ...props,
505
+ children: [
506
+ children,
507
+ /* @__PURE__ */ jsx(
508
+ Dialog$1.Close,
509
+ {
510
+ "aria-label": "Fechar",
511
+ className: cn(
512
+ "absolute top-3 right-3 grid size-9 place-items-center rounded-control-sm squircle",
513
+ "text-text-secondary transition-colors duration-(--motion-quick) ease-(--ease-out-soft)",
514
+ "hover:bg-surface-sunken active:bg-surface-sunken"
515
+ ),
516
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-5" })
517
+ }
518
+ )
519
+ ]
520
+ }
521
+ )
522
+ ] });
523
+ }
524
+ function DialogHeader({ className, children, ...props }) {
525
+ return (
526
+ // pr-6 reserva o canto do botão X — título longo passa por baixo sem colidir.
527
+ /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-1.5 pr-6", className), ...props, children })
528
+ );
529
+ }
530
+ function DialogTitle({ className, ...props }) {
531
+ return /* @__PURE__ */ jsx(Dialog$1.Title, { className: cn("text-title-3", className), ...props });
532
+ }
533
+ function DialogDescription({ className, ...props }) {
534
+ return /* @__PURE__ */ jsx(
535
+ Dialog$1.Description,
536
+ {
537
+ className: cn("text-callout text-text-secondary", className),
538
+ ...props
539
+ }
540
+ );
541
+ }
542
+ function DialogFooter({ className, children, ...props }) {
543
+ return (
544
+ // Em celular os botões empilham com o principal embaixo — onde o polegar já
545
+ // está; no desktop, lado a lado à direita, com o principal por último.
546
+ /* @__PURE__ */ jsx(
547
+ "div",
548
+ {
549
+ className: cn("mt-6 flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", className),
550
+ ...props,
551
+ children
552
+ }
553
+ )
554
+ );
555
+ }
556
+ function DialogClose({ asChild = true, ...props }) {
557
+ return /* @__PURE__ */ jsx(Dialog$1.Close, { asChild, ...props });
558
+ }
559
+ function EmptyState({
560
+ illustration,
561
+ icon,
562
+ title,
563
+ description,
564
+ action,
565
+ className,
566
+ ...props
567
+ }) {
568
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-4 py-10 text-center", className), ...props, children: [
569
+ illustration ?? (icon ? (
570
+ // Decoração pura: o título logo abaixo já diz tudo ao leitor de tela.
571
+ /* @__PURE__ */ jsx(
572
+ "div",
573
+ {
574
+ "aria-hidden": "true",
575
+ className: "grid size-16 place-items-center rounded-expressive squircle bg-accent-soft text-accent-soft-text [&_svg]:size-8",
576
+ children: icon
577
+ }
578
+ )
579
+ ) : null),
580
+ /* @__PURE__ */ jsx("p", { className: "text-title-3", children: title }),
581
+ description ? /* @__PURE__ */ jsx("p", { className: "max-w-sm text-balance text-callout text-text-secondary", children: description }) : null,
582
+ action
583
+ ] });
584
+ }
585
+ var iconButton = cva(
586
+ [
587
+ "inline-flex shrink-0 items-center justify-center",
588
+ "rounded-control squircle select-none",
589
+ // O ícone tem tamanho fixo independente do que o lucide mandar por padrão.
590
+ "[&_svg]:size-5 [&_svg]:shrink-0",
591
+ "transition-[transform,background-color,border-color,opacity,box-shadow]",
592
+ // Mola em vez de curva suave: sem rótulo, o quique é o que confirma o toque.
593
+ "duration-(--motion-quick) ease-(--ease-spring)",
594
+ "active:scale-[0.94]",
595
+ "disabled:pointer-events-none disabled:opacity-50"
596
+ ],
597
+ {
598
+ variants: {
599
+ /* O mesmo vocabulário de cor do Button — quem aprende um, leu os dois. */
600
+ variant: {
601
+ primary: "bg-accent text-accent-text hover:bg-accent-hover active:bg-accent-active",
602
+ tonal: "bg-accent-soft text-accent-soft-text hover:brightness-97 dark:hover:brightness-110",
603
+ secondary: "bg-surface text-text border border-border-strong hover:bg-surface-sunken",
604
+ ghost: "bg-transparent text-accent-soft-text hover:bg-accent-soft",
605
+ "danger-tonal": "bg-danger-soft text-danger-soft-text hover:brightness-97"
606
+ },
607
+ size: {
608
+ sm: "size-9",
609
+ md: "size-touch"
610
+ }
611
+ },
612
+ defaultVariants: {
613
+ variant: "primary",
614
+ size: "md"
615
+ }
616
+ }
617
+ );
618
+ function IconButton({
619
+ label,
620
+ variant,
621
+ size,
622
+ className,
623
+ type,
624
+ children,
625
+ ...props
626
+ }) {
627
+ return (
628
+ // Mesmo cuidado do Button: sem `type`, um botão em formulário submete.
629
+ /* @__PURE__ */ jsx(
630
+ "button",
631
+ {
632
+ type: type ?? "button",
633
+ "aria-label": label,
634
+ className: cn(iconButton({ variant, size }), className),
635
+ ...props,
636
+ children
637
+ }
638
+ )
639
+ );
640
+ }
641
+ var fieldControlClasses = [
642
+ "w-full rounded-control squircle",
643
+ "bg-surface-sunken text-text text-body",
644
+ "placeholder:text-text-muted",
645
+ "border border-transparent",
646
+ "transition-[border-color,box-shadow] duration-(--motion-quick) ease-(--ease-out-soft)",
647
+ "outline-none focus-visible:outline-none",
648
+ "focus:border-accent focus:ring-4 focus:ring-accent/15",
649
+ "aria-invalid:border-danger aria-invalid:ring-4 aria-invalid:ring-danger/15",
650
+ "disabled:pointer-events-none disabled:opacity-50"
651
+ ].join(" ");
652
+ function Input({ className, id, type, ...props }) {
653
+ const field = useFieldContext();
654
+ return /* @__PURE__ */ jsx(
655
+ "input",
656
+ {
657
+ id: id ?? field?.controlId,
658
+ type: type ?? "text",
659
+ "aria-invalid": field?.invalid || void 0,
660
+ "aria-describedby": field ? [field.errorId, field.hintId].filter(Boolean).join(" ") || void 0 : void 0,
661
+ className: cn(fieldControlClasses, "min-h-touch px-4", className),
662
+ ...props
663
+ }
664
+ );
665
+ }
666
+ function Textarea({ className, id, rows, ...props }) {
667
+ const field = useFieldContext();
668
+ return /* @__PURE__ */ jsx(
669
+ "textarea",
670
+ {
671
+ id: id ?? field?.controlId,
672
+ rows: rows ?? 4,
673
+ "aria-invalid": field?.invalid || void 0,
674
+ "aria-describedby": field ? [field.errorId, field.hintId].filter(Boolean).join(" ") || void 0 : void 0,
675
+ className: cn(fieldControlClasses, "min-h-touch resize-y px-4 py-3", className),
676
+ ...props
677
+ }
678
+ );
679
+ }
680
+ function List({ header, footer, className, children }) {
681
+ const headerId = useId();
682
+ return /* @__PURE__ */ jsxs("section", { className: cn("w-full", className), children: [
683
+ header ? /* @__PURE__ */ jsx(
684
+ "p",
685
+ {
686
+ id: headerId,
687
+ className: "px-5 pb-2 text-footnote font-medium text-text-secondary uppercase tracking-wide",
688
+ children: header
689
+ }
690
+ ) : null,
691
+ /* @__PURE__ */ jsx(
692
+ "ul",
693
+ {
694
+ "aria-labelledby": header ? headerId : void 0,
695
+ className: "divide-y divide-border overflow-hidden rounded-surface squircle bg-surface",
696
+ children
697
+ }
698
+ ),
699
+ footer ? /* @__PURE__ */ jsx("p", { className: "px-5 pt-2 text-footnote text-text-muted", children: footer }) : null
700
+ ] });
701
+ }
702
+ var iconBadge = cva(
703
+ // O quadradinho colorido dos Ajustes do iOS: o ícone ganha um chão próprio
704
+ // e a lista ganha ritmo visual sem que nenhum texto dependa dessa cor.
705
+ "grid size-8 shrink-0 place-items-center rounded-control-sm squircle [&_svg]:size-5",
706
+ {
707
+ variants: {
708
+ tone: {
709
+ accent: "bg-accent text-accent-text",
710
+ terracota: "bg-terracota text-white",
711
+ jardim: "bg-jardim text-white",
712
+ ambar: "bg-ambar text-white",
713
+ lavanda: "bg-lavanda text-white",
714
+ ceu: "bg-ceu text-white"
715
+ }
716
+ },
717
+ defaultVariants: {
718
+ tone: "accent"
719
+ }
720
+ }
721
+ );
722
+ function ListItem({
723
+ icon,
724
+ iconTone,
725
+ title,
726
+ subtitle,
727
+ value,
728
+ onClick,
729
+ className
730
+ }) {
731
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
732
+ icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: iconBadge({ tone: iconTone }), children: icon }) : null,
733
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
734
+ /* @__PURE__ */ jsx("span", { className: "truncate text-body", children: title }),
735
+ subtitle ? /* @__PURE__ */ jsx("span", { className: "truncate text-footnote text-text-muted", children: subtitle }) : null
736
+ ] }),
737
+ value ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-body text-text-secondary", children: value }) : null,
738
+ onClick ? (
739
+ /* O chevron é redundante para o leitor de tela — o papel de botão já diz tudo. */
740
+ /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "size-4.5 shrink-0 text-text-muted" })
741
+ ) : null
742
+ ] });
743
+ const rowClasses = "flex min-h-touch w-full items-center gap-3 px-5 py-2";
744
+ return /* @__PURE__ */ jsx("li", { className: cn(className), children: onClick ? /* @__PURE__ */ jsx(
745
+ "button",
746
+ {
747
+ type: "button",
748
+ onClick,
749
+ className: cn(
750
+ rowClasses,
751
+ "text-left transition-colors duration-(--motion-quick) ease-(--ease-out-soft)",
752
+ "hover:bg-surface-sunken active:bg-surface-sunken"
753
+ ),
754
+ children: content
755
+ }
756
+ ) : /* @__PURE__ */ jsx("div", { className: rowClasses, children: content }) });
757
+ }
758
+ var Popover = Popover$1.Root;
759
+ function PopoverTrigger({ asChild = true, ...props }) {
760
+ return /* @__PURE__ */ jsx(Popover$1.Trigger, { asChild, ...props });
761
+ }
762
+ function PopoverContent({
763
+ className,
764
+ sideOffset = 8,
765
+ collisionPadding = 16,
766
+ children,
767
+ ...props
768
+ }) {
769
+ return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(
770
+ Popover$1.Content,
771
+ {
772
+ sideOffset,
773
+ collisionPadding,
774
+ className: cn(
775
+ "z-(--z-overlay) w-72 rounded-surface squircle border border-border",
776
+ "material-thick p-4 shadow-level-3",
777
+ "animate-pop-in",
778
+ className
779
+ ),
780
+ ...props,
781
+ children
782
+ }
783
+ ) });
784
+ }
785
+ function Progress({ value, label, className }) {
786
+ const indeterminate = value === void 0;
787
+ return /* @__PURE__ */ jsx(
788
+ Progress$1.Root,
789
+ {
790
+ value: value ?? null,
791
+ "aria-label": label,
792
+ className: cn(
793
+ "h-2 w-full overflow-hidden rounded-pill bg-surface-sunken",
794
+ // Sem número, o próprio trilho pulsa — movimento sem prometer medida.
795
+ indeterminate && "animate-shimmer",
796
+ className
797
+ ),
798
+ children: indeterminate ? null : /* @__PURE__ */ jsx(
799
+ Progress$1.Indicator,
800
+ {
801
+ className: cn(
802
+ "h-full rounded-pill bg-accent",
803
+ // Gentil de propósito: o quórum sobe conforme confirmações chegam,
804
+ // e o deslizamento comunica "andou" melhor que um salto seco.
805
+ "transition-[width] duration-(--motion-gentle) ease-(--ease-out-soft)"
806
+ ),
807
+ style: { width: `${value}%` }
808
+ }
809
+ )
810
+ }
811
+ );
812
+ }
813
+ function RadioGroup({ className, id, ...props }) {
814
+ const field = useFieldContext();
815
+ return /* @__PURE__ */ jsx(
816
+ RadioGroup$1.Root,
817
+ {
818
+ id: id ?? field?.controlId,
819
+ "aria-invalid": field?.invalid || void 0,
820
+ "aria-describedby": field ? [field.errorId, field.hintId].filter(Boolean).join(" ") || void 0 : void 0,
821
+ className: cn("flex flex-col", className),
822
+ ...props
823
+ }
824
+ );
825
+ }
826
+ var circleClasses = [
827
+ "flex size-6 shrink-0 items-center justify-center rounded-full",
828
+ "bg-surface-sunken border border-border-strong",
829
+ "transition-[border-color,background-color] duration-(--motion-quick) ease-(--ease-out-soft)",
830
+ "data-[state=checked]:border-accent",
831
+ "disabled:pointer-events-none disabled:opacity-50"
832
+ ].join(" ");
833
+ function RadioGroupItem({ label, className, id, ...props }) {
834
+ const generatedId = useId();
835
+ const itemId = id ?? generatedId;
836
+ return (
837
+ // O label embrulha o círculo e aponta para ele por id: clicar no texto
838
+ // seleciona, e a linha inteira vira alvo de toque de 44px sem inflar o
839
+ // controle.
840
+ /* @__PURE__ */ jsxs("label", { htmlFor: itemId, className: "flex min-h-touch select-none items-center gap-3", children: [
841
+ /* @__PURE__ */ jsx(RadioGroup$1.Item, { id: itemId, className: cn(circleClasses, className), ...props, children: /* @__PURE__ */ jsx(RadioGroup$1.Indicator, { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "size-3 rounded-full bg-accent animate-pop-in" }) }) }),
842
+ /* @__PURE__ */ jsx("span", { className: "text-body text-text", children: label })
843
+ ] })
844
+ );
845
+ }
846
+ function SearchField({
847
+ onClear,
848
+ className,
849
+ onChange,
850
+ "aria-label": ariaLabel,
851
+ ...props
852
+ }) {
853
+ const inputRef = useRef(null);
854
+ const [internalValue, setInternalValue] = useState(String(props.defaultValue ?? ""));
855
+ const currentValue = props.value ?? internalValue;
856
+ const hasValue = String(currentValue).length > 0;
857
+ const handleChange = (event) => {
858
+ setInternalValue(event.target.value);
859
+ onChange?.(event);
860
+ };
861
+ const handleClear = () => {
862
+ if (props.value === void 0 && inputRef.current) {
863
+ inputRef.current.value = "";
864
+ setInternalValue("");
865
+ }
866
+ onClear?.();
867
+ inputRef.current?.focus();
868
+ };
869
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
870
+ /* @__PURE__ */ jsx(
871
+ Search,
872
+ {
873
+ "aria-hidden": "true",
874
+ className: "pointer-events-none absolute top-1/2 left-3.5 size-4.5 -translate-y-1/2 text-text-muted"
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsx(
878
+ "input",
879
+ {
880
+ ref: inputRef,
881
+ type: "search",
882
+ "aria-label": ariaLabel ?? "Buscar",
883
+ onChange: handleChange,
884
+ className: cn(
885
+ fieldControlClasses,
886
+ "min-h-touch rounded-pill pl-10 pr-10 [&::-webkit-search-cancel-button]:appearance-none"
887
+ ),
888
+ ...props
889
+ }
890
+ ),
891
+ hasValue ? /* @__PURE__ */ jsx(
892
+ "button",
893
+ {
894
+ type: "button",
895
+ "aria-label": "Limpar busca",
896
+ onClick: handleClear,
897
+ className: cn(
898
+ // A coluna inteira à direita é clicável — 44px de alvo dentro do campo.
899
+ "absolute inset-y-0 right-0 grid w-touch place-items-center rounded-pill",
900
+ "text-text-muted transition-colors duration-(--motion-quick) ease-(--ease-out-soft)",
901
+ "hover:text-text"
902
+ ),
903
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" })
904
+ }
905
+ ) : null
906
+ ] });
907
+ }
908
+ var container = cva("gap-1 rounded-control squircle bg-surface-sunken p-1", {
909
+ variants: {
910
+ /** Como no Button: em celular o padrão é a largura toda. */
911
+ width: {
912
+ full: "flex w-full",
913
+ auto: "inline-flex w-auto"
914
+ }
915
+ },
916
+ defaultVariants: {
917
+ width: "full"
918
+ }
919
+ });
920
+ var segmentClasses = [
921
+ "min-h-9 flex-1 select-none rounded-control-sm squircle px-3.5",
922
+ "text-subhead font-medium text-text-secondary",
923
+ "transition-[background-color,color,box-shadow] duration-(--motion-quick) ease-(--ease-out-soft)",
924
+ // O selecionado "sobe" um nível: superfície clara e sombra de repouso.
925
+ "data-[state=on]:bg-surface data-[state=on]:text-text data-[state=on]:shadow-level-1",
926
+ "disabled:pointer-events-none disabled:opacity-50"
927
+ ].join(" ");
928
+ function SegmentedControl({
929
+ options,
930
+ value,
931
+ defaultValue,
932
+ onValueChange,
933
+ "aria-label": ariaLabel,
934
+ width,
935
+ disabled,
936
+ className
937
+ }) {
938
+ const [internalValue, setInternalValue] = useState(defaultValue ?? options[0]?.value);
939
+ const currentValue = value ?? internalValue;
940
+ const handleValueChange = (next) => {
941
+ if (!next || next === currentValue) {
942
+ return;
943
+ }
944
+ setInternalValue(next);
945
+ onValueChange?.(next);
946
+ };
947
+ return /* @__PURE__ */ jsx(
948
+ ToggleGroup.Root,
949
+ {
950
+ type: "single",
951
+ value: currentValue,
952
+ onValueChange: handleValueChange,
953
+ "aria-label": ariaLabel,
954
+ disabled,
955
+ className: cn(container({ width }), className),
956
+ children: options.map((option) => /* @__PURE__ */ jsx(
957
+ ToggleGroup.Item,
958
+ {
959
+ value: option.value,
960
+ className: segmentClasses,
961
+ children: option.label
962
+ },
963
+ option.value
964
+ ))
965
+ }
966
+ );
967
+ }
968
+ function Select({
969
+ placeholder,
970
+ "aria-label": ariaLabel,
971
+ id,
972
+ className,
973
+ children,
974
+ ...props
975
+ }) {
976
+ const field = useFieldContext();
977
+ return /* @__PURE__ */ jsxs(Select$1.Root, { ...props, children: [
978
+ /* @__PURE__ */ jsxs(
979
+ Select$1.Trigger,
980
+ {
981
+ id: id ?? field?.controlId,
982
+ "aria-label": ariaLabel,
983
+ "aria-invalid": field?.invalid || void 0,
984
+ "aria-describedby": field ? [field.errorId, field.hintId].filter(Boolean).join(" ") || void 0 : void 0,
985
+ className: cn(
986
+ fieldControlClasses,
987
+ "inline-flex min-h-touch items-center justify-between gap-2 px-4 text-left",
988
+ "data-[placeholder]:text-text-muted",
989
+ className
990
+ ),
991
+ children: [
992
+ /* @__PURE__ */ jsx(Select$1.Value, { placeholder }),
993
+ /* @__PURE__ */ jsx(Select$1.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronsUpDown, { "aria-hidden": "true", className: "size-4 shrink-0 text-text-muted" }) })
994
+ ]
995
+ }
996
+ ),
997
+ /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(
998
+ Select$1.Content,
999
+ {
1000
+ position: "popper",
1001
+ sideOffset: 6,
1002
+ className: cn(
1003
+ "z-(--z-overlay) overflow-hidden rounded-surface squircle",
1004
+ "material-thick border border-border shadow-level-3",
1005
+ "animate-pop-in",
1006
+ // A lista nunca fica mais estreita que o campo que a abriu.
1007
+ "min-w-(--radix-select-trigger-width) max-h-(--radix-select-content-available-height)"
1008
+ ),
1009
+ children: /* @__PURE__ */ jsx(Select$1.Viewport, { className: "p-1", children })
1010
+ }
1011
+ ) })
1012
+ ] });
1013
+ }
1014
+ function SelectItem({ className, children, ...props }) {
1015
+ return /* @__PURE__ */ jsxs(
1016
+ Select$1.Item,
1017
+ {
1018
+ className: cn(
1019
+ "flex min-h-touch select-none items-center justify-between gap-2 rounded-control-sm squircle px-3",
1020
+ "text-body text-text",
1021
+ // Aqui o destaque É o anel de foco: o item realçado troca de fundo,
1022
+ // então o outline global só duplicaria o sinal.
1023
+ "outline-none data-[highlighted]:bg-accent-soft data-[highlighted]:text-accent-soft-text",
1024
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
1025
+ className
1026
+ ),
1027
+ ...props,
1028
+ children: [
1029
+ /* @__PURE__ */ jsx(Select$1.ItemText, { children }),
1030
+ /* @__PURE__ */ jsx(Select$1.ItemIndicator, { asChild: true, children: /* @__PURE__ */ jsx(Check, { "aria-hidden": "true", className: "size-4 shrink-0" }) })
1031
+ ]
1032
+ }
1033
+ );
1034
+ }
1035
+ function Separator({
1036
+ orientation = "horizontal",
1037
+ decorative = true,
1038
+ className,
1039
+ ...props
1040
+ }) {
1041
+ return /* @__PURE__ */ jsx(
1042
+ Separator$1.Root,
1043
+ {
1044
+ orientation,
1045
+ decorative,
1046
+ className: cn(
1047
+ "shrink-0 bg-border",
1048
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
1049
+ className
1050
+ ),
1051
+ ...props
1052
+ }
1053
+ );
1054
+ }
1055
+ var Sheet = Dialog$1.Root;
1056
+ function SheetTrigger({ asChild = true, ...props }) {
1057
+ return /* @__PURE__ */ jsx(Dialog$1.Trigger, { asChild, ...props });
1058
+ }
1059
+ function SheetContent({ className, children, ...props }) {
1060
+ return /* @__PURE__ */ jsxs(Dialog$1.Portal, { children: [
1061
+ /* @__PURE__ */ jsx(Dialog$1.Overlay, { className: "fixed inset-0 z-(--z-overlay) bg-overlay animate-fade-in" }),
1062
+ /* @__PURE__ */ jsxs(
1063
+ Dialog$1.Content,
1064
+ {
1065
+ className: cn(
1066
+ "fixed inset-x-0 bottom-0 z-(--z-modal)",
1067
+ "rounded-t-surface-lg squircle material-thick border-t border-border shadow-level-3",
1068
+ // O pb estica até o maior entre o padding do sheet e a safe area — o
1069
+ // conteúdo nunca fica embaixo da barra de gestos do iPhone.
1070
+ "p-6 pb-[max(1.5rem,env(safe-area-inset-bottom))]",
1071
+ "max-h-[85dvh] overflow-y-auto",
1072
+ "animate-slide-up",
1073
+ className
1074
+ ),
1075
+ ...props,
1076
+ children: [
1077
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "mx-auto mb-4 h-1.5 w-10 rounded-pill bg-border-strong" }),
1078
+ children
1079
+ ]
1080
+ }
1081
+ )
1082
+ ] });
1083
+ }
1084
+ function SheetHeader({ className, children, ...props }) {
1085
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-1.5", className), ...props, children });
1086
+ }
1087
+ function SheetTitle({ className, ...props }) {
1088
+ return /* @__PURE__ */ jsx(Dialog$1.Title, { className: cn("text-title-3", className), ...props });
1089
+ }
1090
+ function SheetDescription({ className, ...props }) {
1091
+ return /* @__PURE__ */ jsx(
1092
+ Dialog$1.Description,
1093
+ {
1094
+ className: cn("text-callout text-text-secondary", className),
1095
+ ...props
1096
+ }
1097
+ );
1098
+ }
1099
+ var skeleton = cva("bg-surface-sunken animate-shimmer", {
1100
+ variants: {
1101
+ shape: {
1102
+ text: "h-4 w-full rounded-control-sm",
1103
+ title: "h-6 w-2/5 rounded-control-sm",
1104
+ block: "h-24 rounded-surface",
1105
+ circle: "size-10 rounded-full"
1106
+ }
1107
+ },
1108
+ defaultVariants: {
1109
+ shape: "text"
1110
+ }
1111
+ });
1112
+ function Skeleton({ shape, className, ...props }) {
1113
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: cn(skeleton({ shape }), className), ...props });
1114
+ }
1115
+ var trackClasses = [
1116
+ "inline-flex h-8 w-13 shrink-0 items-center rounded-pill",
1117
+ "bg-surface-sunken border border-border",
1118
+ "transition-[background-color,border-color] duration-(--motion-quick) ease-(--ease-out-soft)",
1119
+ "data-[state=checked]:bg-accent data-[state=checked]:border-accent",
1120
+ "disabled:pointer-events-none disabled:opacity-50"
1121
+ ].join(" ");
1122
+ var thumbClasses = [
1123
+ // Branco fixo nos dois temas, como no iOS: sobre o trilho rebaixado ou sobre
1124
+ // o accent, é a sombra que garante a leitura do polegar.
1125
+ "pointer-events-none block size-7 rounded-full bg-white shadow-level-1",
1126
+ "transition-transform duration-(--motion-quick) ease-(--ease-out-soft)",
1127
+ "translate-x-0.5 data-[state=checked]:translate-x-5"
1128
+ ].join(" ");
1129
+ function Switch({ label, className, id, ...props }) {
1130
+ const field = useFieldContext();
1131
+ const generatedId = useId();
1132
+ const controlId = id ?? field?.controlId ?? generatedId;
1133
+ const control = /* @__PURE__ */ jsx(
1134
+ Switch$1.Root,
1135
+ {
1136
+ id: controlId,
1137
+ "aria-invalid": field?.invalid || void 0,
1138
+ "aria-describedby": field ? [field.errorId, field.hintId].filter(Boolean).join(" ") || void 0 : void 0,
1139
+ className: cn(trackClasses, className),
1140
+ ...props,
1141
+ children: /* @__PURE__ */ jsx(Switch$1.Thumb, { className: thumbClasses })
1142
+ }
1143
+ );
1144
+ if (!label) {
1145
+ return control;
1146
+ }
1147
+ return /* @__PURE__ */ jsxs(
1148
+ "label",
1149
+ {
1150
+ htmlFor: controlId,
1151
+ className: "flex min-h-touch w-full select-none items-center justify-between gap-3",
1152
+ children: [
1153
+ /* @__PURE__ */ jsx("span", { className: "text-body text-text", children: label }),
1154
+ control
1155
+ ]
1156
+ }
1157
+ );
1158
+ }
1159
+ var Tabs = Tabs$1.Root;
1160
+ function TabsList({ className, children, ...props }) {
1161
+ return (
1162
+ // A borda inferior é o trilho onde a sublinha do trigger ativo se apoia.
1163
+ /* @__PURE__ */ jsx(Tabs$1.List, { className: cn("flex gap-1 border-b border-border", className), ...props, children })
1164
+ );
1165
+ }
1166
+ function TabsTrigger({ className, children, ...props }) {
1167
+ return /* @__PURE__ */ jsxs(
1168
+ Tabs$1.Trigger,
1169
+ {
1170
+ className: cn(
1171
+ "group relative min-h-touch select-none px-4 text-subhead font-medium text-text-secondary",
1172
+ "transition-colors duration-(--motion-quick) ease-(--ease-out-soft)",
1173
+ "hover:text-text",
1174
+ "data-[state=active]:text-accent-soft-text",
1175
+ "disabled:pointer-events-none disabled:opacity-50",
1176
+ className
1177
+ ),
1178
+ ...props,
1179
+ children: [
1180
+ children,
1181
+ /* @__PURE__ */ jsx(
1182
+ "span",
1183
+ {
1184
+ "aria-hidden": "true",
1185
+ className: cn(
1186
+ "absolute inset-x-3 -bottom-px h-0.5 rounded-pill bg-accent",
1187
+ "opacity-0 transition-opacity duration-(--motion-quick) ease-(--ease-out-soft)",
1188
+ "group-data-[state=active]:opacity-100"
1189
+ )
1190
+ }
1191
+ )
1192
+ ]
1193
+ }
1194
+ );
1195
+ }
1196
+ function TabsContent({ className, children, ...props }) {
1197
+ return /* @__PURE__ */ jsx(Tabs$1.Content, { className: cn("pt-5", className), ...props, children });
1198
+ }
1199
+ var ToastContext = createContext(null);
1200
+ function useToast() {
1201
+ const context = useContext(ToastContext);
1202
+ if (!context) {
1203
+ throw new Error(
1204
+ "useToast precisa de um <ToastProvider> acima na \xE1rvore \u2014 envolva a app com ele."
1205
+ );
1206
+ }
1207
+ return context;
1208
+ }
1209
+ var variantIcon = {
1210
+ success: { icon: CircleCheck, tint: "text-success" },
1211
+ danger: { icon: CircleAlert, tint: "text-danger" }
1212
+ };
1213
+ function ToastCard({ toast, onClose }) {
1214
+ const visual = variantIcon[toast.variant ?? "default"];
1215
+ return /* @__PURE__ */ jsxs(
1216
+ Toast.Root,
1217
+ {
1218
+ onOpenChange: (open) => {
1219
+ if (!open) onClose();
1220
+ },
1221
+ className: cn(
1222
+ "relative flex w-full gap-3 rounded-surface squircle border border-border material-thick p-4 pr-10 shadow-level-3",
1223
+ "animate-slide-up",
1224
+ // O arrasto acompanha o dedo; soltar aquém do limiar devolve com mola.
1225
+ "data-[swipe=move]:translate-y-(--radix-toast-swipe-move-y) data-[swipe=move]:transition-none",
1226
+ "data-[swipe=cancel]:translate-y-0 data-[swipe=cancel]:transition-transform data-[swipe=cancel]:duration-(--motion-quick) data-[swipe=cancel]:ease-(--ease-out-soft)"
1227
+ ),
1228
+ children: [
1229
+ visual ? /* @__PURE__ */ jsx(visual.icon, { "aria-hidden": "true", className: cn("mt-0.5 size-5 shrink-0", visual.tint) }) : null,
1230
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
1231
+ /* @__PURE__ */ jsx(Toast.Title, { className: "text-subhead font-semibold", children: toast.title }),
1232
+ toast.description ? /* @__PURE__ */ jsx(Toast.Description, { className: "text-footnote text-text-secondary", children: toast.description }) : null
1233
+ ] }),
1234
+ /* @__PURE__ */ jsx(
1235
+ Toast.Close,
1236
+ {
1237
+ "aria-label": "Fechar aviso",
1238
+ className: cn(
1239
+ "absolute top-2 right-2 grid size-8 place-items-center rounded-control-sm text-text-muted",
1240
+ "transition-colors duration-(--motion-quick) ease-(--ease-out-soft) hover:text-text",
1241
+ // O X é discreto no olho, não no dedo: o pseudo-elemento completa 44px.
1242
+ "before:absolute before:-inset-1.5"
1243
+ ),
1244
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" })
1245
+ }
1246
+ )
1247
+ ]
1248
+ }
1249
+ );
1250
+ }
1251
+ function ToastProvider({ children }) {
1252
+ const [toasts, setToasts] = useState([]);
1253
+ const nextId = useRef(0);
1254
+ const toast = useCallback((options) => {
1255
+ nextId.current += 1;
1256
+ const id = nextId.current;
1257
+ setToasts((current) => [...current, { id, ...options }]);
1258
+ }, []);
1259
+ const remove = useCallback((id) => {
1260
+ setToasts((current) => current.filter((item) => item.id !== id));
1261
+ }, []);
1262
+ const value = useMemo(() => ({ toast }), [toast]);
1263
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value, children: [
1264
+ children,
1265
+ /* @__PURE__ */ jsxs(Toast.Provider, { duration: 5e3, swipeDirection: "down", label: "Aviso", children: [
1266
+ toasts.map((item) => /* @__PURE__ */ jsx(ToastCard, { toast: item, onClose: () => remove(item.id) }, item.id)),
1267
+ /* @__PURE__ */ jsx(
1268
+ Toast.Viewport,
1269
+ {
1270
+ label: "Avisos ({hotkey})",
1271
+ className: cn(
1272
+ "fixed bottom-0 inset-x-0 z-(--z-toast) m-0 flex w-full list-none flex-col gap-2 p-5",
1273
+ // Em PWA no iPhone, a barra de gestos comeria o último toast.
1274
+ "pb-[max(1.25rem,env(safe-area-inset-bottom))]",
1275
+ "sm:left-auto sm:max-w-sm"
1276
+ )
1277
+ }
1278
+ )
1279
+ ] })
1280
+ ] });
1281
+ }
1282
+ function Tooltip({ content, side, className, children }) {
1283
+ return (
1284
+ // A dica é só texto, nunca interativa: sem conteúdo "hoverable" ela some
1285
+ // assim que o mouse sai do gatilho, sem área de tolerância fantasma.
1286
+ /* @__PURE__ */ jsx(Tooltip$1.Provider, { delayDuration: 400, skipDelayDuration: 300, disableHoverableContent: true, children: /* @__PURE__ */ jsxs(Tooltip$1.Root, { children: [
1287
+ /* @__PURE__ */ jsx(Tooltip$1.Trigger, { asChild: true, children }),
1288
+ /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(
1289
+ Tooltip$1.Content,
1290
+ {
1291
+ side,
1292
+ sideOffset: 6,
1293
+ className: cn(
1294
+ "z-(--z-tooltip) rounded-control-sm squircle border border-border bg-surface-raised px-3 py-1.5 shadow-level-2",
1295
+ "text-footnote animate-fade-in",
1296
+ className
1297
+ ),
1298
+ children: content
1299
+ }
1300
+ ) })
1301
+ ] }) })
1302
+ );
1303
+ }
1304
+ var band = {
1305
+ terracota: "bg-terracota-soft",
1306
+ jardim: "bg-jardim-soft",
1307
+ ambar: "bg-ambar-soft",
1308
+ lavanda: "bg-lavanda-soft",
1309
+ ceu: "bg-ceu-soft"
1310
+ };
1311
+ var cracha = cva(
1312
+ [
1313
+ "relative flex flex-col items-center overflow-hidden text-center",
1314
+ "rounded-expressive squircle bg-surface shadow-level-1"
1315
+ ],
1316
+ {
1317
+ variants: {
1318
+ size: {
1319
+ md: "w-40 pb-5",
1320
+ lg: "w-48 pb-6"
1321
+ }
1322
+ },
1323
+ defaultVariants: {
1324
+ size: "md"
1325
+ }
1326
+ }
1327
+ );
1328
+ function Cracha({
1329
+ name,
1330
+ unit,
1331
+ src,
1332
+ memberRole,
1333
+ tone = "ceu",
1334
+ size,
1335
+ className
1336
+ }) {
1337
+ return /* @__PURE__ */ jsxs("div", { className: cn(cracha({ size }), className), children: [
1338
+ /* @__PURE__ */ jsx("div", { className: cn("flex h-16 w-full justify-center pt-3", band[tone]), children: /* @__PURE__ */ jsx(
1339
+ "div",
1340
+ {
1341
+ "aria-hidden": "true",
1342
+ className: "h-2.5 w-10 rounded-pill bg-bg shadow-[inset_0_1px_2px_rgb(0_0_0/0.18)]"
1343
+ }
1344
+ ) }),
1345
+ /* @__PURE__ */ jsx("div", { className: "-mt-8 rounded-full ring-4 ring-surface", children: /* @__PURE__ */ jsx(Avatar, { name, src, size: "xl" }) }),
1346
+ /* @__PURE__ */ jsx("p", { className: "mt-3 px-4 font-friendly text-headline text-text", children: name }),
1347
+ unit ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 px-4 text-footnote text-text-secondary", children: unit }) : null,
1348
+ memberRole ? /* @__PURE__ */ jsx("span", { className: "mt-2.5", children: /* @__PURE__ */ jsx(Badge, { tone: "accent", children: memberRole }) }) : null
1349
+ ] });
1350
+ }
1351
+ var cover = {
1352
+ terracota: "bg-terracota-soft",
1353
+ jardim: "bg-jardim-soft",
1354
+ ambar: "bg-ambar-soft",
1355
+ lavanda: "bg-lavanda-soft",
1356
+ ceu: "bg-ceu-soft"
1357
+ };
1358
+ var spine = {
1359
+ terracota: "bg-terracota",
1360
+ jardim: "bg-jardim",
1361
+ ambar: "bg-ambar",
1362
+ lavanda: "bg-lavanda",
1363
+ ceu: "bg-ceu"
1364
+ };
1365
+ var livreto = cva(
1366
+ [
1367
+ "relative flex aspect-[3/4] flex-col overflow-hidden",
1368
+ "rounded-surface squircle shadow-level-1"
1369
+ ],
1370
+ {
1371
+ variants: {
1372
+ size: {
1373
+ sm: "w-28 p-3 pl-5",
1374
+ md: "w-36 p-4 pl-6",
1375
+ lg: "w-44 p-5 pl-7"
1376
+ }
1377
+ },
1378
+ defaultVariants: {
1379
+ size: "md"
1380
+ }
1381
+ }
1382
+ );
1383
+ var titleBySize = {
1384
+ sm: "text-subhead font-semibold",
1385
+ md: "text-headline",
1386
+ lg: "text-title-3"
1387
+ };
1388
+ function Livreto({
1389
+ title,
1390
+ kind = "Pauta",
1391
+ tone = "lavanda",
1392
+ size,
1393
+ footer,
1394
+ className
1395
+ }) {
1396
+ return /* @__PURE__ */ jsxs("div", { className: cn(livreto({ size }), cover[tone], className), children: [
1397
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("absolute inset-y-0 left-0 w-2", spine[tone]) }),
1398
+ /* @__PURE__ */ jsx(
1399
+ "span",
1400
+ {
1401
+ "aria-hidden": "true",
1402
+ className: "absolute inset-y-2 right-0 w-1 rounded-l-pill bg-surface opacity-80"
1403
+ }
1404
+ ),
1405
+ /* @__PURE__ */ jsx("p", { className: "text-caption font-semibold uppercase tracking-[0.12em] text-text-secondary", children: kind }),
1406
+ /* @__PURE__ */ jsx(
1407
+ "p",
1408
+ {
1409
+ lang: "pt-BR",
1410
+ className: cn(
1411
+ "mt-1.5 line-clamp-4 font-friendly text-text",
1412
+ "break-words hyphens-auto",
1413
+ titleBySize[size ?? "md"]
1414
+ ),
1415
+ children: title
1416
+ }
1417
+ ),
1418
+ footer ? /* @__PURE__ */ jsx("div", { className: "mt-auto pt-3", children: footer }) : null
1419
+ ] });
1420
+ }
1421
+ var TONES = ["terracota", "jardim", "ambar", "lavanda", "ceu"];
1422
+ function hashSeed(seed) {
1423
+ let hash = 0;
1424
+ for (const char of seed) {
1425
+ hash = (hash * 31 + (char.codePointAt(0) ?? 0)) % 2147483647;
1426
+ }
1427
+ return hash;
1428
+ }
1429
+ var predio = cva("inline-block", {
1430
+ variants: {
1431
+ size: {
1432
+ sm: "h-20",
1433
+ md: "h-28",
1434
+ lg: "h-40"
1435
+ }
1436
+ },
1437
+ defaultVariants: {
1438
+ size: "md"
1439
+ }
1440
+ });
1441
+ var GROUND_Y = 138;
1442
+ var ROW_HEIGHT = 16;
1443
+ var WINDOW_COLUMNS = [31, 53, 75];
1444
+ var WINDOW_WIDTH = 14;
1445
+ var WINDOW_HEIGHT = 10;
1446
+ var DOOR_HEIGHT = 18;
1447
+ function Predio({ name, tone, floors, size, decorative = false, className }) {
1448
+ const hash = hashSeed(name);
1449
+ const floorCount = Math.min(6, Math.max(3, floors ?? 3 + hash % 4));
1450
+ const chosenTone = tone ?? TONES[hash % TONES.length] ?? "ceu";
1451
+ const bodyTop = GROUND_Y - DOOR_HEIGHT - floorCount * ROW_HEIGHT - 8;
1452
+ const strong = `var(--assembleo-${chosenTone})`;
1453
+ const soft = `var(--assembleo-${chosenTone}-soft)`;
1454
+ const windows = Array.from(
1455
+ { length: floorCount },
1456
+ (_, floor) => WINDOW_COLUMNS.map((x, column) => {
1457
+ const index = floor * WINDOW_COLUMNS.length + column;
1458
+ const lit = (hash >> index % 30 & 1) === 1;
1459
+ return {
1460
+ x,
1461
+ y: bodyTop + 10 + floor * ROW_HEIGHT,
1462
+ lit,
1463
+ key: `${floor}-${column}`
1464
+ };
1465
+ })
1466
+ ).flat();
1467
+ return /* @__PURE__ */ jsxs(
1468
+ "svg",
1469
+ {
1470
+ viewBox: "0 0 120 148",
1471
+ role: decorative ? void 0 : "img",
1472
+ "aria-label": decorative ? void 0 : `Pr\xE9dio do condom\xEDnio ${name}`,
1473
+ "aria-hidden": decorative || void 0,
1474
+ className: cn(predio({ size }), className),
1475
+ children: [
1476
+ /* @__PURE__ */ jsx("rect", { x: "10", y: GROUND_Y, width: "100", height: "4", rx: "2", fill: "var(--assembleo-border)" }),
1477
+ /* @__PURE__ */ jsx("rect", { x: "16", y: bodyTop - 8, width: "88", height: "12", rx: "6", fill: strong }),
1478
+ /* @__PURE__ */ jsx("rect", { x: "20", y: bodyTop, width: "80", height: GROUND_Y - bodyTop, rx: "6", fill: soft }),
1479
+ windows.map((window2) => /* @__PURE__ */ jsx(
1480
+ "rect",
1481
+ {
1482
+ x: window2.x,
1483
+ y: window2.y,
1484
+ width: WINDOW_WIDTH,
1485
+ height: WINDOW_HEIGHT,
1486
+ rx: "3",
1487
+ fill: window2.lit ? "var(--assembleo-ambar)" : "var(--assembleo-surface)"
1488
+ },
1489
+ window2.key
1490
+ )),
1491
+ /* @__PURE__ */ jsx(
1492
+ "path",
1493
+ {
1494
+ d: `M52 ${GROUND_Y} v-${DOOR_HEIGHT - 8} a8 8 0 0 1 16 0 v${DOOR_HEIGHT - 8} z`,
1495
+ fill: strong
1496
+ }
1497
+ ),
1498
+ /* @__PURE__ */ jsx("circle", { cx: "14", cy: GROUND_Y - 5, r: "6", fill: "var(--assembleo-jardim)", opacity: "0.85" }),
1499
+ /* @__PURE__ */ jsx("circle", { cx: "23", cy: GROUND_Y - 3, r: "4", fill: "var(--assembleo-jardim)", opacity: "0.65" })
1500
+ ]
1501
+ }
1502
+ );
1503
+ }
1504
+ function VisuallyHidden({ children }) {
1505
+ return /* @__PURE__ */ jsx("span", { className: "absolute -m-px h-px w-px overflow-hidden whitespace-nowrap border-0 p-0 [clip-path:inset(50%)]", children });
1506
+ }
1507
+ var STORAGE_KEY = "assembleo-theme";
1508
+ function isTheme(value) {
1509
+ return value === "light" || value === "dark" || value === "system";
1510
+ }
1511
+ function readStoredTheme() {
1512
+ try {
1513
+ const stored = window.localStorage.getItem(STORAGE_KEY);
1514
+ return isTheme(stored) ? stored : "system";
1515
+ } catch {
1516
+ return "system";
1517
+ }
1518
+ }
1519
+ function persistTheme(theme) {
1520
+ try {
1521
+ if (theme === "system") {
1522
+ window.localStorage.removeItem(STORAGE_KEY);
1523
+ } else {
1524
+ window.localStorage.setItem(STORAGE_KEY, theme);
1525
+ }
1526
+ } catch {
1527
+ }
1528
+ }
1529
+ function applyTheme(theme) {
1530
+ if (theme === "system") {
1531
+ delete document.documentElement.dataset.theme;
1532
+ } else {
1533
+ document.documentElement.dataset.theme = theme;
1534
+ }
1535
+ }
1536
+ function initTheme() {
1537
+ applyTheme(readStoredTheme());
1538
+ }
1539
+ var ThemeContext = createContext(null);
1540
+ function ThemeProvider({ children }) {
1541
+ const [theme, setThemeState] = useState(() => readStoredTheme());
1542
+ const [systemDark, setSystemDark] = useState(
1543
+ () => window.matchMedia("(prefers-color-scheme: dark)").matches
1544
+ );
1545
+ useEffect(() => {
1546
+ applyTheme(theme);
1547
+ persistTheme(theme);
1548
+ }, [theme]);
1549
+ useEffect(() => {
1550
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
1551
+ const onChange = (event) => setSystemDark(event.matches);
1552
+ media.addEventListener("change", onChange);
1553
+ return () => media.removeEventListener("change", onChange);
1554
+ }, []);
1555
+ useEffect(() => {
1556
+ const onStorage = (event) => {
1557
+ if (event.key === STORAGE_KEY || event.key === null) {
1558
+ setThemeState(readStoredTheme());
1559
+ }
1560
+ };
1561
+ window.addEventListener("storage", onStorage);
1562
+ return () => window.removeEventListener("storage", onStorage);
1563
+ }, []);
1564
+ const resolvedTheme = theme === "system" ? systemDark ? "dark" : "light" : theme;
1565
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme: setThemeState }, children });
1566
+ }
1567
+ function useTheme() {
1568
+ const context = useContext(ThemeContext);
1569
+ if (!context) {
1570
+ throw new Error("useTheme precisa de um <ThemeProvider> acima na \xE1rvore.");
1571
+ }
1572
+ return context;
1573
+ }
1574
+ var OPTIONS = [
1575
+ { value: "light", label: "Claro" },
1576
+ { value: "dark", label: "Escuro" },
1577
+ { value: "system", label: "Sistema" }
1578
+ ];
1579
+ function ThemeToggle({ width, className }) {
1580
+ const { theme, setTheme } = useTheme();
1581
+ return /* @__PURE__ */ jsx(
1582
+ SegmentedControl,
1583
+ {
1584
+ "aria-label": "Tema",
1585
+ options: OPTIONS,
1586
+ value: theme,
1587
+ onValueChange: (value) => setTheme(value),
1588
+ width,
1589
+ className
1590
+ }
1591
+ );
1592
+ }
1593
+
1594
+ export { Avatar, AvatarGroup, Badge, Banner, Button, Card, Checkbox, Cracha, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, EmptyState, Field, IconButton, Input, List, ListItem, Livreto, Popover, PopoverContent, PopoverTrigger, Predio, Progress, RadioGroup, RadioGroupItem, SearchField, SegmentedControl, Select, SelectItem, Separator, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, Skeleton, Spinner, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeProvider, ThemeToggle, ToastProvider, Tooltip, VisuallyHidden, cn, initTheme, useFieldContext, useTheme, useToast };