@strategicos/ui 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +51 -2
  2. package/THIRD_PARTY_NOTICES.md +187 -0
  3. package/dist/components/calendar.d.ts +24 -0
  4. package/dist/components/calendar.d.ts.map +1 -0
  5. package/dist/components/calendar.js +81 -0
  6. package/dist/components/calendar.js.map +1 -0
  7. package/dist/components/combobox.d.ts +73 -0
  8. package/dist/components/combobox.d.ts.map +1 -0
  9. package/dist/components/combobox.js +193 -0
  10. package/dist/components/combobox.js.map +1 -0
  11. package/dist/components/date-input.d.ts +46 -0
  12. package/dist/components/date-input.d.ts.map +1 -0
  13. package/dist/components/date-input.js +202 -0
  14. package/dist/components/date-input.js.map +1 -0
  15. package/dist/components/date-picker-input.d.ts +49 -0
  16. package/dist/components/date-picker-input.d.ts.map +1 -0
  17. package/dist/components/date-picker-input.js +126 -0
  18. package/dist/components/date-picker-input.js.map +1 -0
  19. package/dist/components/date-time-picker.d.ts +27 -0
  20. package/dist/components/date-time-picker.d.ts.map +1 -0
  21. package/dist/components/date-time-picker.js +77 -0
  22. package/dist/components/date-time-picker.js.map +1 -0
  23. package/dist/components/dates.d.ts +94 -0
  24. package/dist/components/dates.d.ts.map +1 -0
  25. package/dist/components/dates.js +94 -0
  26. package/dist/components/dates.js.map +1 -0
  27. package/dist/components/multi-select.d.ts.map +1 -1
  28. package/dist/components/multi-select.js +2 -1
  29. package/dist/components/multi-select.js.map +1 -1
  30. package/dist/components/select.d.ts +16 -1
  31. package/dist/components/select.d.ts.map +1 -1
  32. package/dist/components/select.js +24 -2
  33. package/dist/components/select.js.map +1 -1
  34. package/dist/components/time-input.d.ts +16 -0
  35. package/dist/components/time-input.d.ts.map +1 -0
  36. package/dist/components/time-input.js +18 -0
  37. package/dist/components/time-input.js.map +1 -0
  38. package/package.json +2 -1
  39. package/src/components/calendar.tsx +139 -0
  40. package/src/components/combobox.tsx +390 -0
  41. package/src/components/date-input.tsx +287 -0
  42. package/src/components/date-picker-input.tsx +287 -0
  43. package/src/components/date-time-picker.tsx +152 -0
  44. package/src/components/dates.tsx +274 -0
  45. package/src/components/multi-select.tsx +3 -1
  46. package/src/components/select.tsx +29 -1
  47. package/src/components/time-input.tsx +34 -0
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Select as SelectPrimitive } from "radix-ui";
4
+ import { cva } from "class-variance-authority";
4
5
  import { cn } from "../lib/utils.js";
5
6
  import { useFieldControl } from "./field.js";
6
7
  import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
@@ -13,8 +14,29 @@ function SelectGroup({ className, ...props }) {
13
14
  function SelectValue({ ...props }) {
14
15
  return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
15
16
  }
17
+ /**
18
+ * Visual do gatilho de seleção: borda, foco, aberto, desabilitado, erro e
19
+ * tamanhos. É o mesmo do SelectTrigger, exportado para os gatilhos que não são
20
+ * o Select do Radix (MultiSelect, Combobox, DatePicker ou um Popover próprio)
21
+ * não precisarem copiar as classes — cópia que fica para trás quando o kit
22
+ * muda.
23
+ *
24
+ * Placeholder: marque o gatilho com `data-placeholder` enquanto estiver vazio,
25
+ * como o Radix faz.
26
+ *
27
+ * <button className={cn(selectTriggerVariants({ size }), "w-full")} …>
28
+ */
29
+ const selectTriggerVariants = cva("flex items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent pr-2.5 pl-3 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring data-[state=open]:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
30
+ variants: {
31
+ size: {
32
+ default: "h-9",
33
+ sm: "h-7.5 rounded-[min(var(--radius-md),10px)]",
34
+ },
35
+ },
36
+ defaultVariants: { size: "default" },
37
+ });
16
38
  function SelectTrigger({ className, size = "default", children, ...props }) {
17
- return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn("flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2.5 pl-3 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring data-[state=open]:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-7.5 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...useFieldControl(props, { withRequired: false }), children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 opacity-70" }) })] }));
39
+ return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(selectTriggerVariants({ size }), "w-fit py-2 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5", className), ...useFieldControl(props, { withRequired: false }), children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 opacity-70" }) })] }));
18
40
  }
19
41
  function SelectContent({ className, children, position = "popper", align = "start", ...props }) {
20
42
  return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, align: align, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { "data-position": position, className: cn("p-1 data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)", position === "popper" && ""), children: children }), _jsx(SelectScrollDownButton, {})] }) }));
@@ -34,5 +56,5 @@ function SelectScrollUpButton({ className, ...props }) {
34
56
  function SelectScrollDownButton({ className, ...props }) {
35
57
  return (_jsx(SelectPrimitive.ScrollDownButton, { "data-slot": "select-scroll-down-button", className: cn("z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: _jsx(ChevronDownIcon, {}) }));
36
58
  }
37
- export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };
59
+ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, selectTriggerVariants, };
38
60
  //# sourceMappingURL=select.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAGZ,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAA;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAA;AAE9C,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAExE,SAAS,MAAM,CAAC,EACd,GAAG,KAAK,EAC0C;IAClD,OAAO,KAAC,eAAe,CAAC,IAAI,iBAAW,QAAQ,KAAK,KAAK,GAAI,CAAA;AAC/D,CAAC;AAED,SAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EAC2C;IACnD,OAAO,CACL,KAAC,eAAe,CAAC,KAAK,iBACV,cAAc,EACxB,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC,KACvC,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EAC2C;IACnD,OAAO,KAAC,eAAe,CAAC,KAAK,iBAAW,cAAc,KAAK,KAAK,GAAI,CAAA;AACtE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,qBAAqB,GAAG,GAAG,CAC/B,qeAAqe,EACre;IACE,QAAQ,EAAE;QACR,IAAI,EAAE;YACJ,OAAO,EAAE,KAAK;YACd,EAAE,EAAE,4CAA4C;SACjD;KACF;IACD,eAAe,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;CACrC,CACF,CAAA;AAED,SAAS,aAAa,CAAC,EACrB,SAAS,EACT,IAAI,GAAG,SAAS,EAChB,QAAQ,EACR,GAAG,KAAK,EAGT;IACC,OAAO,CACL,MAAC,eAAe,CAAC,OAAO,iBACZ,gBAAgB,eACf,IAAI,EACf,SAAS,EAAE,EAAE,CACX,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC,EAC/B,+JAA+J,EAC/J,SAAS,CACV,KACG,eAAe,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,aAElD,QAAQ,EACT,KAAC,eAAe,CAAC,IAAI,IAAC,OAAO,kBAK3B,KAAC,eAAe,IAAC,SAAS,EAAC,uCAAuC,GAAG,GAChD,IACC,CAC3B,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAQ,GAAG,QAAQ,EACnB,KAAK,GAAG,OAAO,EACf,GAAG,KAAK,EAC6C;IACrD,OAAO,CACL,KAAC,eAAe,CAAC,MAAM,cACrB,MAAC,eAAe,CAAC,OAAO,iBACZ,gBAAgB,wBACN,QAAQ,KAAK,cAAc,EAC/C,SAAS,EAAE,EAAE,CAAC,kkBAAkkB,EAAE,QAAQ,KAAI,QAAQ,IAAE,iIAAiI,EAAE,SAAS,CAAE,EACtvB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,KAAK,KACR,KAAK,aAET,KAAC,oBAAoB,KAAG,EACxB,KAAC,eAAe,CAAC,QAAQ,qBACR,QAAQ,EACvB,SAAS,EAAE,EAAE,CACX,wJAAwJ,EACxJ,QAAQ,KAAK,QAAQ,IAAI,EAAE,CAC5B,YAEA,QAAQ,GACgB,EAC3B,KAAC,sBAAsB,KAAG,IACF,GACH,CAC1B,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EAC2C;IACnD,OAAO,CACL,KAAC,eAAe,CAAC,KAAK,iBACV,cAAc,EACxB,SAAS,EAAE,EAAE,CAAC,yDAAyD,EAAE,SAAS,CAAC,KAC/E,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,WAAW,EACX,GAAG,KAAK,EAKT;IACC,OAAO,CACL,MAAC,eAAe,CAAC,IAAI,iBACT,aAAa,EACvB,SAAS,EAAE,EAAE,CACX,obAAob,EACpb,SAAS,CACV,KACG,KAAK,aAET,eAAM,SAAS,EAAC,8EAA8E,YAC5F,KAAC,eAAe,CAAC,aAAa,cAC5B,KAAC,SAAS,IAAC,SAAS,EAAC,qBAAqB,GAAG,GACf,GAC3B,EACN,WAAW,CAAC,CAAC,CAAC,CACb,eAAK,SAAS,EAAC,eAAe,aAC5B,KAAC,eAAe,CAAC,QAAQ,cAAE,QAAQ,GAA4B,EAC/D,eAAM,SAAS,EAAC,mCAAmC,YAAE,WAAW,GAAQ,IACpE,CACP,CAAC,CAAC,CAAC,CACF,KAAC,eAAe,CAAC,QAAQ,cAAE,QAAQ,GAA4B,CAChE,IACoB,CACxB,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EAC+C;IACvD,OAAO,CACL,KAAC,eAAe,CAAC,SAAS,iBACd,kBAAkB,EAC5B,SAAS,EAAE,EAAE,CAAC,+CAA+C,EAAE,SAAS,CAAC,KACrE,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,EAC5B,SAAS,EACT,GAAG,KAAK,EACoD;IAC5D,OAAO,CACL,KAAC,eAAe,CAAC,cAAc,iBACnB,yBAAyB,EACnC,SAAS,EAAE,EAAE,CACX,2GAA2G,EAC3G,SAAS,CACV,KACG,KAAK,YAET,KAAC,aAAa,KACZ,GAC6B,CAClC,CAAA;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACsD;IAC9D,OAAO,CACL,KAAC,eAAe,CAAC,gBAAgB,iBACrB,2BAA2B,EACrC,SAAS,EAAE,EAAE,CACX,2GAA2G,EAC3G,SAAS,CACV,KACG,KAAK,YAET,KAAC,eAAe,KACd,GAC+B,CACpC,CAAA;AACH,CAAC;AAED,OAAO,EACL,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,WAAW,EACX,qBAAqB,GACtB,CAAA"}
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { type InputProps } from "./input.js";
3
+ /**
4
+ * Hora, como o TimeInput do Mantine: o <input type="time"> do navegador, no
5
+ * visual do Input. O valor é "HH:mm" (ou "HH:mm:ss" com `withSeconds`).
6
+ *
7
+ * <Field label="Horário"><TimeInput value={hora} onChange={e => setHora(e.target.value)} /></Field>
8
+ *
9
+ * O formato de 12 ou 24 horas segue o sistema do usuário — no Brasil, 24 horas.
10
+ * O relógio à direita é o do navegador (abre a lista de horas no Chrome).
11
+ */
12
+ declare function TimeInput({ withSeconds, className, ...props }: Omit<InputProps, "type"> & {
13
+ withSeconds?: boolean;
14
+ }): React.JSX.Element;
15
+ export { TimeInput };
16
+ //# sourceMappingURL=time-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-input.d.ts","sourceRoot":"","sources":["../../src/components/time-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;;;;GAQG;AACH,iBAAS,SAAS,CAAC,EACjB,WAAmB,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,qBASA;AAED,OAAO,EAAE,SAAS,EAAE,CAAA"}
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Input } from "./input.js";
5
+ /**
6
+ * Hora, como o TimeInput do Mantine: o <input type="time"> do navegador, no
7
+ * visual do Input. O valor é "HH:mm" (ou "HH:mm:ss" com `withSeconds`).
8
+ *
9
+ * <Field label="Horário"><TimeInput value={hora} onChange={e => setHora(e.target.value)} /></Field>
10
+ *
11
+ * O formato de 12 ou 24 horas segue o sistema do usuário — no Brasil, 24 horas.
12
+ * O relógio à direita é o do navegador (abre a lista de horas no Chrome).
13
+ */
14
+ function TimeInput({ withSeconds = false, className, ...props }) {
15
+ return (_jsx(Input, { type: "time", step: withSeconds ? 1 : 60, className: cn("tabular-nums", className), ...props }));
16
+ }
17
+ export { TimeInput };
18
+ //# sourceMappingURL=time-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-input.js","sourceRoot":"","sources":["../../src/components/time-input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAIZ,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAA;AAEnD;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,EACjB,WAAW,GAAG,KAAK,EACnB,SAAS,EACT,GAAG,KAAK,EAGT;IACC,OAAO,CACL,KAAC,KAAK,IACJ,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAC1B,SAAS,EAAE,EAAE,CAAC,cAAc,EAAE,SAAS,CAAC,KACpC,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strategicos/ui",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Componentes React da Strategicos (shadcn + Radix + Tailwind v4).",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -43,6 +43,7 @@
43
43
  "clsx": "^2.1.1",
44
44
  "lucide-react": "^1.12.0",
45
45
  "radix-ui": "^1.4.3",
46
+ "react-day-picker": "^10.0.2",
46
47
  "tailwind-merge": "^3.5.0",
47
48
  "tw-animate-css": "^1.4.0"
48
49
  },
@@ -0,0 +1,139 @@
1
+ "use client"
2
+
3
+ // Derivado do Calendar do Tremor (Apache-2.0) e modificado pela Strategicos.
4
+ // Ver THIRD_PARTY_NOTICES.md.
5
+
6
+ import * as React from "react"
7
+ import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
8
+ import { DayPicker, type DayButtonProps } from "react-day-picker"
9
+ import { ptBR } from "react-day-picker/locale"
10
+
11
+ import { cn } from "../lib/utils.js"
12
+ import { buttonVariants } from "./button.js"
13
+
14
+ /**
15
+ * Calendário (react-day-picker), com o desenho do Calendar do Tremor nos tokens
16
+ * do kit. É a peça de dentro do DatePicker; use solto quando o calendário fica
17
+ * aberto na tela.
18
+ *
19
+ * <Calendar mode="single" selected={dia} onSelect={setDia} />
20
+ *
21
+ * Padrões: português do Brasil (semana começando no domingo) e dias de fora do
22
+ * mês visíveis. `captionLayout="dropdown"` troca o título por seletores de mês
23
+ * e ano — útil para datas distantes, como nascimento. Aceita todas as props do
24
+ * DayPicker.
25
+ */
26
+ function Calendar({
27
+ className,
28
+ classNames,
29
+ captionLayout = "label",
30
+ showOutsideDays = true,
31
+ locale = ptBR,
32
+ components,
33
+ ...props
34
+ }: React.ComponentProps<typeof DayPicker>) {
35
+ return (
36
+ <DayPicker
37
+ data-slot="calendar"
38
+ locale={locale}
39
+ showOutsideDays={showOutsideDays}
40
+ captionLayout={captionLayout}
41
+ className={cn("w-fit p-3", className)}
42
+ classNames={{
43
+ months: "relative flex flex-col gap-4 sm:flex-row",
44
+ month: "flex w-full flex-col gap-3",
45
+ nav: "absolute inset-x-0 top-0 flex items-center justify-between",
46
+ button_previous: cn(buttonVariants({ variant: "ghost", size: "icon-sm" }), "aria-disabled:pointer-events-none aria-disabled:opacity-40"),
47
+ button_next: cn(buttonVariants({ variant: "ghost", size: "icon-sm" }), "aria-disabled:pointer-events-none aria-disabled:opacity-40"),
48
+ month_caption: "flex h-7.5 items-center justify-center px-8",
49
+ // Os meses vêm em minúscula no pt-BR ("outubro 2026"): só a primeira
50
+ // letra sobe — `capitalize` faria "Outubro De 2026" em outros formatos.
51
+ caption_label: cn(
52
+ "text-sm font-semibold select-none first-letter:uppercase",
53
+ captionLayout !== "label" && "flex h-7.5 items-center gap-1 rounded-md pr-1 pl-2 font-medium [&>svg]:size-3.5 [&>svg]:text-muted-foreground"
54
+ ),
55
+ dropdowns: "flex items-center gap-1.5 text-sm",
56
+ dropdown_root: "relative rounded-md border border-input transition-colors has-focus-visible:border-ring",
57
+ dropdown: "absolute inset-0 cursor-pointer opacity-0",
58
+ month_grid: "w-full border-collapse",
59
+ weekdays: "flex",
60
+ weekday: "w-9 pb-1 text-xs font-medium text-muted-foreground select-none",
61
+ week: "mt-0.5 flex w-full",
62
+ day: "relative size-9 p-0 text-center select-none",
63
+ // Faixa do intervalo: o fundo fica na célula, para os dias do meio
64
+ // emendarem; as pontas arredondam do lado de fora.
65
+ range_start: "rounded-l-md bg-accent",
66
+ range_middle: "bg-accent",
67
+ range_end: "rounded-r-md bg-accent",
68
+ today: "",
69
+ outside: "text-muted-foreground aria-selected:text-muted-foreground",
70
+ disabled: "text-muted-foreground opacity-50",
71
+ hidden: "invisible",
72
+ ...classNames,
73
+ }}
74
+ components={{
75
+ Chevron: ({ className, orientation, ...rest }) => {
76
+ const Icon = orientation === "left" ? ChevronLeftIcon : orientation === "right" ? ChevronRightIcon : ChevronDownIcon
77
+ return <Icon className={cn("size-4", className)} {...rest} />
78
+ },
79
+ DayButton: CalendarDayButton,
80
+ ...components,
81
+ }}
82
+ {...props}
83
+ />
84
+ )
85
+ }
86
+
87
+ /** Dia do calendário. Os estados viram data-* para o estilo, e "hoje" ganha o
88
+ * traço embaixo do número, como no Tremor.
89
+ *
90
+ * O dia escolhido é `data-picked`, e não `data-selected-*`: o styles.css
91
+ * registra a variante `data-selected`, e o Tailwind lê `data-selected-x:` como
92
+ * essa variante com um valor — a classe é descartada sem aviso. */
93
+ function CalendarDayButton({ className, day, modifiers, children, ...props }: DayButtonProps) {
94
+ const ref = React.useRef<HTMLButtonElement>(null)
95
+ React.useEffect(() => {
96
+ if (modifiers.focused) ref.current?.focus()
97
+ }, [modifiers.focused])
98
+
99
+ const single = modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
100
+
101
+ return (
102
+ <button
103
+ ref={ref}
104
+ type="button"
105
+ data-picked={single || undefined}
106
+ data-range-start={modifiers.range_start || undefined}
107
+ data-range-end={modifiers.range_end || undefined}
108
+ data-range-middle={modifiers.range_middle || undefined}
109
+ data-today={modifiers.today || undefined}
110
+ className={cn(
111
+ "relative flex size-9 cursor-pointer items-center justify-center rounded-md text-sm tabular-nums outline-none transition-colors",
112
+ "hover:bg-accent hover:text-accent-foreground focus-visible:z-10 focus-visible:ring-3 focus-visible:ring-ring/50",
113
+ "disabled:pointer-events-none",
114
+ "data-today:font-semibold",
115
+ // Dourado só como preenchimento, com o texto escuro do primary-foreground.
116
+ "data-picked:bg-primary data-picked:text-primary-foreground data-picked:hover:bg-primary-hover",
117
+ "data-range-start:bg-primary data-range-start:text-primary-foreground data-range-start:hover:bg-primary-hover",
118
+ "data-range-end:bg-primary data-range-end:text-primary-foreground data-range-end:hover:bg-primary-hover",
119
+ "data-range-middle:rounded-none data-range-middle:hover:bg-transparent data-range-middle:hover:underline",
120
+ className
121
+ )}
122
+ {...props}
123
+ >
124
+ {children}
125
+ {modifiers.today && (
126
+ <span
127
+ aria-hidden
128
+ className={cn(
129
+ "absolute bottom-1.5 left-1/2 h-0.5 w-4 -translate-x-1/2 rounded-full",
130
+ // primary-strong: o dourado claro não chega a 3:1 sobre o branco.
131
+ single || modifiers.range_start || modifiers.range_end ? "bg-primary-foreground" : "bg-primary-strong"
132
+ )}
133
+ />
134
+ )}
135
+ </button>
136
+ )
137
+ }
138
+
139
+ export { Calendar, CalendarDayButton }
@@ -0,0 +1,390 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { CheckIcon, ChevronDownIcon, Loader2Icon, PlusIcon, SearchIcon, XIcon } from "lucide-react"
5
+
6
+ import { cn } from "../lib/utils.js"
7
+ import { Button } from "./button.js"
8
+ import { useFieldControl } from "./field.js"
9
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js"
10
+ import { selectTriggerVariants } from "./select.js"
11
+
12
+ export type ComboboxOption = {
13
+ value: string
14
+ label: string
15
+ /** Texto secundário (ex.: e-mail), só na lista. */
16
+ description?: string
17
+ /** Agrupa as opções sob um título. */
18
+ group?: string
19
+ /** Bolinha de cor antes do rótulo. */
20
+ color?: string
21
+ disabled?: boolean
22
+ }
23
+
24
+ type Entry = { kind: "option"; option: ComboboxOption } | { kind: "create" }
25
+
26
+ function errorMessage(err: unknown, fallback: string) {
27
+ return err instanceof Error && err.message ? err.message : fallback
28
+ }
29
+
30
+ /**
31
+ * Seleção de um item com busca, como o Select `searchable` do Mantine. Use no
32
+ * lugar do Select quando a lista é longa ou quando a tela precisa criar ou
33
+ * excluir opções ali mesmo.
34
+ *
35
+ * <Combobox
36
+ * options={setores.map(s => ({ value: s.id, label: s.name }))}
37
+ * value={setorId}
38
+ * onValueChange={setSetorId}
39
+ * clearable
40
+ * onCreate={async nome => (await criarSetor(nome)).id}
41
+ * />
42
+ *
43
+ * - `clearable`: um X no gatilho volta o valor para `null`.
44
+ * - `onCreate`: com texto na busca que não bate com nenhuma opção, aparece
45
+ * "Criar “texto”". Se a função devolver um valor, ele fica selecionado.
46
+ * - `onRemove`: cada opção ganha um X que pede confirmação na própria linha.
47
+ * Se a opção excluída era a selecionada, o valor volta para `null`.
48
+ *
49
+ * Erros de `onCreate`/`onRemove` (promessa rejeitada) aparecem no rodapé da
50
+ * lista, com a mensagem do erro.
51
+ *
52
+ * Teclado: digite para filtrar, ↑/↓ para andar, Enter para escolher, Delete
53
+ * (com a busca vazia) para excluir a opção ativa, Esc para fechar. Dentro de
54
+ * um <Field>, o gatilho recebe o id e os aria-*.
55
+ */
56
+ function Combobox({
57
+ options,
58
+ value,
59
+ onValueChange,
60
+ placeholder = "Selecione",
61
+ searchPlaceholder = "Buscar…",
62
+ emptyText = "Nada encontrado.",
63
+ searchable = true,
64
+ clearable = false,
65
+ onCreate,
66
+ createLabel = q => <>Criar &ldquo;{q}&rdquo;</>,
67
+ onRemove,
68
+ removeLabel = o => `Excluir ${o.label}`,
69
+ removeConfirmLabel = o => <>Excluir &ldquo;{o.label}&rdquo;?</>,
70
+ size = "default",
71
+ disabled = false,
72
+ className,
73
+ wrapperClassName,
74
+ contentClassName,
75
+ onOpenChange,
76
+ ...props
77
+ }: {
78
+ options: ComboboxOption[]
79
+ value: string | null
80
+ onValueChange: (value: string | null) => void
81
+ placeholder?: string
82
+ searchPlaceholder?: string
83
+ emptyText?: string
84
+ searchable?: boolean
85
+ clearable?: boolean
86
+ /** Cria uma opção com o texto da busca. Devolva o valor dela para
87
+ * selecioná-la. */
88
+ onCreate?: (label: string) => string | void | Promise<string | void>
89
+ createLabel?: (query: string) => React.ReactNode
90
+ /** Exclui a opção (o X na linha, depois de confirmar). */
91
+ onRemove?: (option: ComboboxOption) => void | Promise<void>
92
+ /** Rótulo acessível do X de cada opção. */
93
+ removeLabel?: (option: ComboboxOption) => string
94
+ /** Pergunta mostrada na linha ao confirmar a exclusão. */
95
+ removeConfirmLabel?: (option: ComboboxOption) => React.ReactNode
96
+ size?: "sm" | "default"
97
+ disabled?: boolean
98
+ /** Classes do gatilho. */
99
+ className?: string
100
+ /** Classes do invólucro, que só existe com `clearable`. Use para largura e
101
+ * posição no layout. */
102
+ wrapperClassName?: string
103
+ contentClassName?: string
104
+ onOpenChange?: (open: boolean) => void
105
+ id?: string
106
+ "aria-label"?: string
107
+ "aria-describedby"?: string
108
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"]
109
+ }) {
110
+ const controlProps = useFieldControl(props, { withRequired: false })
111
+ const [open, setOpen] = React.useState(false)
112
+ const [query, setQuery] = React.useState("")
113
+ const [active, setActive] = React.useState(0)
114
+ const [creating, setCreating] = React.useState(false)
115
+ const [confirming, setConfirming] = React.useState<string | null>(null)
116
+ const [removing, setRemoving] = React.useState<string | null>(null)
117
+ const [error, setError] = React.useState<string | null>(null)
118
+ const listId = React.useId()
119
+ const searchRef = React.useRef<HTMLInputElement>(null)
120
+
121
+ const selected = options.find(o => o.value === value)
122
+ const q = query.trim().toLowerCase()
123
+ const filtered = q ? options.filter(o => o.label.toLowerCase().includes(q)) : options
124
+ const canCreate = !!onCreate && !!q && !options.some(o => o.label.trim().toLowerCase() === q)
125
+
126
+ // Ordem do teclado: as opções habilitadas e, por último, o "Criar".
127
+ const entries: Entry[] = filtered.filter(o => !o.disabled).map(option => ({ kind: "option" as const, option }))
128
+ if (canCreate) entries.push({ kind: "create" })
129
+ const activeIndex = Math.min(active, entries.length - 1)
130
+ const entryId = (i: number) => `${listId}-${i}`
131
+
132
+ // Grupos na ordem em que aparecem nas opções.
133
+ const groups: { name: string | undefined; items: ComboboxOption[] }[] = []
134
+ for (const o of filtered) {
135
+ const g = groups.find(x => x.name === o.group)
136
+ if (g) g.items.push(o)
137
+ else groups.push({ name: o.group, items: [o] })
138
+ }
139
+
140
+ // Mantém a opção ativa à vista ao andar com as setas.
141
+ React.useEffect(() => {
142
+ if (!open || activeIndex < 0) return
143
+ document.getElementById(entryId(activeIndex))?.scrollIntoView({ block: "nearest" })
144
+ // eslint-disable-next-line react-hooks/exhaustive-deps
145
+ }, [open, activeIndex])
146
+
147
+ function handleOpenChange(o: boolean) {
148
+ setOpen(o)
149
+ if (!o) {
150
+ setQuery("")
151
+ setActive(0)
152
+ setConfirming(null)
153
+ setError(null)
154
+ } else {
155
+ // Abre com a opção selecionada ativa.
156
+ const i = entries.findIndex(e => e.kind === "option" && e.option.value === value)
157
+ setActive(Math.max(i, 0))
158
+ }
159
+ onOpenChange?.(o)
160
+ }
161
+
162
+ function choose(v: string) {
163
+ onValueChange(v)
164
+ handleOpenChange(false)
165
+ }
166
+
167
+ async function create() {
168
+ if (!onCreate || creating) return
169
+ setCreating(true)
170
+ setError(null)
171
+ try {
172
+ const created = await onCreate(query.trim())
173
+ if (typeof created === "string") onValueChange(created)
174
+ handleOpenChange(false)
175
+ } catch (err) {
176
+ setError(errorMessage(err, "Não foi possível criar."))
177
+ } finally {
178
+ setCreating(false)
179
+ }
180
+ }
181
+
182
+ async function remove(option: ComboboxOption) {
183
+ if (!onRemove) return
184
+ setRemoving(option.value)
185
+ setError(null)
186
+ try {
187
+ await onRemove(option)
188
+ if (option.value === value) onValueChange(null)
189
+ } catch (err) {
190
+ setError(errorMessage(err, "Não foi possível excluir."))
191
+ } finally {
192
+ setRemoving(null)
193
+ setConfirming(null)
194
+ searchRef.current?.focus()
195
+ }
196
+ }
197
+
198
+ function cancelConfirm() {
199
+ setConfirming(null)
200
+ searchRef.current?.focus()
201
+ }
202
+
203
+ function activate(entry: Entry | undefined) {
204
+ if (!entry) return
205
+ if (entry.kind === "create") void create()
206
+ else choose(entry.option.value)
207
+ }
208
+
209
+ function onKeyDown(e: React.KeyboardEvent) {
210
+ // Na confirmação, o teclado é dos botões Excluir/Cancelar.
211
+ if (confirming) return
212
+ if (e.key === "ArrowDown") { e.preventDefault(); setActive(Math.min(activeIndex + 1, entries.length - 1)) }
213
+ else if (e.key === "ArrowUp") { e.preventDefault(); setActive(Math.max(activeIndex - 1, 0)) }
214
+ else if (e.key === "Enter") { e.preventDefault(); activate(entries[activeIndex]) }
215
+ else if (e.key === "Delete" && onRemove && !query) {
216
+ const entry = entries[activeIndex]
217
+ if (entry?.kind === "option") { e.preventDefault(); setConfirming(entry.option.value) }
218
+ }
219
+ }
220
+
221
+ const showClear = clearable && !!selected && !disabled
222
+
223
+ const trigger = (
224
+ <PopoverTrigger asChild disabled={disabled}>
225
+ <button
226
+ type="button"
227
+ role="combobox"
228
+ aria-expanded={open}
229
+ aria-controls={listId}
230
+ aria-haspopup="listbox"
231
+ data-slot="combobox-trigger"
232
+ data-size={size}
233
+ data-placeholder={selected ? undefined : ""}
234
+ {...controlProps}
235
+ className={cn(
236
+ selectTriggerVariants({ size }),
237
+ "w-full min-w-0 cursor-pointer text-left",
238
+ showClear && "pr-9",
239
+ className
240
+ )}
241
+ >
242
+ <span className="flex min-w-0 flex-1 items-center gap-2">
243
+ {selected?.color && <span className="size-2 shrink-0 rounded-full" style={{ background: selected.color }} />}
244
+ <span className="truncate">{selected?.label ?? placeholder}</span>
245
+ </span>
246
+ {!showClear && <ChevronDownIcon className="size-4 shrink-0 opacity-70" />}
247
+ </button>
248
+ </PopoverTrigger>
249
+ )
250
+
251
+ return (
252
+ <Popover open={open} onOpenChange={handleOpenChange}>
253
+ {clearable ? (
254
+ <div data-slot="combobox-wrapper" className={cn("relative w-full min-w-0", wrapperClassName)}>
255
+ {trigger}
256
+ {showClear && (
257
+ // Irmão do gatilho, não filho: botão dentro de botão não é HTML válido.
258
+ <button
259
+ type="button"
260
+ onClick={() => onValueChange(null)}
261
+ aria-label="Limpar"
262
+ className="absolute inset-y-0 right-1.5 my-auto flex size-6 cursor-pointer items-center justify-center rounded text-muted-foreground outline-none hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
263
+ >
264
+ <XIcon className="size-4" />
265
+ </button>
266
+ )}
267
+ </div>
268
+ ) : trigger}
269
+
270
+ <PopoverContent
271
+ className={cn("w-(--radix-popover-trigger-width) min-w-60 p-0", contentClassName)}
272
+ onKeyDown={onKeyDown}
273
+ onEscapeKeyDown={e => { if (confirming) { e.preventDefault(); cancelConfirm() } }}
274
+ >
275
+ {searchable && (
276
+ <div className="flex items-center gap-2 border-b border-border px-3">
277
+ <SearchIcon className="size-4 shrink-0 text-muted-foreground" />
278
+ <input
279
+ ref={searchRef}
280
+ autoFocus
281
+ value={query}
282
+ onChange={e => { setQuery(e.target.value); setActive(0); setError(null) }}
283
+ placeholder={searchPlaceholder}
284
+ aria-label={searchPlaceholder}
285
+ aria-controls={listId}
286
+ aria-activedescendant={activeIndex >= 0 && !confirming ? entryId(activeIndex) : undefined}
287
+ aria-autocomplete="list"
288
+ className="h-9 w-full bg-transparent text-base outline-none placeholder:text-muted-foreground md:text-sm"
289
+ />
290
+ </div>
291
+ )}
292
+
293
+ <div id={listId} role="listbox" className="max-h-64 overflow-y-auto overscroll-contain p-1">
294
+ {filtered.length === 0 && !canCreate && (
295
+ <p className="px-2.5 py-6 text-center text-sm text-muted-foreground">{emptyText}</p>
296
+ )}
297
+
298
+ {groups.map(g => (
299
+ <div key={g.name ?? "_"} role="group" aria-label={g.name}>
300
+ {g.name && <p className="px-2.5 pt-2 pb-1 text-xs font-medium text-muted-foreground">{g.name}</p>}
301
+ {g.items.map(o => {
302
+ const index = entries.findIndex(e => e.kind === "option" && e.option.value === o.value)
303
+ const isSelected = o.value === value
304
+ const isActive = index === activeIndex
305
+
306
+ if (confirming === o.value) return (
307
+ <div key={o.value} role="alert" className="flex items-center gap-1.5 rounded-md bg-destructive/10 py-1 pr-1 pl-2.5 text-sm">
308
+ <span className="min-w-0 flex-1 truncate">{removeConfirmLabel(o)}</span>
309
+ <Button
310
+ type="button"
311
+ size="sm"
312
+ variant="destructive"
313
+ autoFocus
314
+ disabled={removing === o.value}
315
+ onClick={() => remove(o)}
316
+ >
317
+ {removing === o.value ? <Loader2Icon className="animate-spin" /> : "Excluir"}
318
+ </Button>
319
+ <Button type="button" size="sm" variant="ghost" disabled={removing === o.value} onClick={cancelConfirm}>
320
+ Cancelar
321
+ </Button>
322
+ </div>
323
+ )
324
+
325
+ return (
326
+ <div
327
+ key={o.value}
328
+ id={index >= 0 ? entryId(index) : undefined}
329
+ role="option"
330
+ aria-selected={isSelected}
331
+ aria-disabled={o.disabled || undefined}
332
+ data-active={isActive || undefined}
333
+ onMouseEnter={() => { if (index >= 0) setActive(index) }}
334
+ onClick={() => { if (!o.disabled) choose(o.value) }}
335
+ className="group/option flex cursor-pointer items-center gap-2 rounded-md py-1.5 pr-2 pl-2.5 text-sm select-none data-active:bg-accent data-active:text-accent-foreground aria-disabled:cursor-not-allowed aria-disabled:opacity-50"
336
+ >
337
+ {o.color && <span className="size-2 shrink-0 rounded-full" style={{ background: o.color }} />}
338
+ <span className="flex min-w-0 flex-1 flex-col">
339
+ <span className="truncate">{o.label}</span>
340
+ {o.description && <span className="truncate text-[11px] text-muted-foreground">{o.description}</span>}
341
+ </span>
342
+ {isSelected && <CheckIcon className="size-4 shrink-0" />}
343
+ {onRemove && !o.disabled && (
344
+ // Fora da ordem do Tab (a lista anda pelas setas); no
345
+ // teclado, a exclusão é o Delete na opção ativa.
346
+ <button
347
+ type="button"
348
+ tabIndex={-1}
349
+ aria-label={removeLabel(o)}
350
+ title={removeLabel(o)}
351
+ onClick={e => { e.stopPropagation(); setConfirming(o.value) }}
352
+ className="-m-1 flex size-6 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground opacity-60 outline-none hover:text-destructive hover:opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover/option:opacity-60 [@media(hover:hover)]:group-data-[active]/option:opacity-60"
353
+ >
354
+ <XIcon className="size-3.5" />
355
+ </button>
356
+ )}
357
+ </div>
358
+ )
359
+ })}
360
+ </div>
361
+ ))}
362
+
363
+ {canCreate && (() => {
364
+ const index = entries.length - 1
365
+ return (
366
+ <div
367
+ id={entryId(index)}
368
+ role="option"
369
+ aria-selected={false}
370
+ data-active={index === activeIndex || undefined}
371
+ onMouseEnter={() => setActive(index)}
372
+ onClick={() => void create()}
373
+ className="flex cursor-pointer items-center gap-2 rounded-md py-1.5 pr-2 pl-2.5 text-sm font-medium text-primary-strong select-none data-active:bg-primary/10"
374
+ >
375
+ {creating ? <Loader2Icon className="size-4 shrink-0 animate-spin" /> : <PlusIcon className="size-4 shrink-0" />}
376
+ <span className="truncate">{createLabel(query.trim())}</span>
377
+ </div>
378
+ )
379
+ })()}
380
+ </div>
381
+
382
+ {error && (
383
+ <p role="alert" className="border-t border-border px-3 py-2 text-xs text-destructive">{error}</p>
384
+ )}
385
+ </PopoverContent>
386
+ </Popover>
387
+ )
388
+ }
389
+
390
+ export { Combobox }