@strategicos/ui 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -8
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/components/date-input.d.ts +46 -0
- package/dist/components/date-input.d.ts.map +1 -0
- package/dist/components/date-input.js +202 -0
- package/dist/components/date-input.js.map +1 -0
- package/dist/components/date-picker-input.d.ts +49 -0
- package/dist/components/date-picker-input.d.ts.map +1 -0
- package/dist/components/date-picker-input.js +126 -0
- package/dist/components/date-picker-input.js.map +1 -0
- package/dist/components/date-time-picker.d.ts +27 -0
- package/dist/components/date-time-picker.d.ts.map +1 -0
- package/dist/components/date-time-picker.js +77 -0
- package/dist/components/date-time-picker.js.map +1 -0
- package/dist/components/dates.d.ts +94 -0
- package/dist/components/dates.d.ts.map +1 -0
- package/dist/components/dates.js +94 -0
- package/dist/components/dates.js.map +1 -0
- package/dist/components/popover.d.ts +1 -1
- package/dist/components/popover.d.ts.map +1 -1
- package/dist/components/popover.js +9 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/time-input.d.ts +16 -0
- package/dist/components/time-input.d.ts.map +1 -0
- package/dist/components/time-input.js +18 -0
- package/dist/components/time-input.js.map +1 -0
- package/package.json +1 -1
- package/src/components/date-input.tsx +287 -0
- package/src/components/date-picker-input.tsx +287 -0
- package/src/components/date-time-picker.tsx +152 -0
- package/src/components/dates.tsx +274 -0
- package/src/components/popover.tsx +10 -0
- package/src/components/time-input.tsx +34 -0
- package/dist/components/date-picker.d.ts +0 -78
- package/dist/components/date-picker.d.ts.map +0 -1
- package/dist/components/date-picker.js +0 -142
- package/dist/components/date-picker.js.map +0 -1
- package/src/components/date-picker.tsx +0 -414
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
// Derivado do DatePicker do Tremor (Apache-2.0) e modificado pela Strategicos.
|
|
4
|
-
// Ver THIRD_PARTY_NOTICES.md.
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import { CalendarIcon, XIcon } from "lucide-react";
|
|
7
|
-
import { useIsMobile } from "../hooks/use-mobile.js";
|
|
8
|
-
import { cn } from "../lib/utils.js";
|
|
9
|
-
import { Calendar } from "./calendar.js";
|
|
10
|
-
import { useFieldControl } from "./field.js";
|
|
11
|
-
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
12
|
-
import { selectTriggerVariants } from "./select.js";
|
|
13
|
-
// ── Valores ───────────────────────────────────────────────────────────────────
|
|
14
|
-
// As datas entram e saem como "AAAA-MM-DD", como no Mantine 8 e no
|
|
15
|
-
// <input type="date">: sem hora e sem fuso, 2026-10-15 é 15/10 em qualquer
|
|
16
|
-
// lugar. Um Date à meia-noite UTC vira o dia 14 no Brasil.
|
|
17
|
-
/** "AAAA-MM-DD" → Date local à meia-noite. Valor vazio ou inválido → undefined. */
|
|
18
|
-
function parseDateValue(value) {
|
|
19
|
-
const m = value ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(value) : null;
|
|
20
|
-
if (!m)
|
|
21
|
-
return undefined;
|
|
22
|
-
const date = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
23
|
-
// 2026-02-31 não existe: o Date rolaria para março.
|
|
24
|
-
return date.getMonth() === Number(m[2]) - 1 ? date : undefined;
|
|
25
|
-
}
|
|
26
|
-
/** Date → "AAAA-MM-DD" no dia local. */
|
|
27
|
-
function toDateValue(date) {
|
|
28
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
29
|
-
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`;
|
|
30
|
-
}
|
|
31
|
-
const formatBR = (date) => date.toLocaleDateString("pt-BR");
|
|
32
|
-
function clampDate(date, min, max) {
|
|
33
|
-
if (min && date < min)
|
|
34
|
-
return min;
|
|
35
|
-
if (max && date > max)
|
|
36
|
-
return max;
|
|
37
|
-
return date;
|
|
38
|
-
}
|
|
39
|
-
function outOfRange(min, max) {
|
|
40
|
-
const m = [];
|
|
41
|
-
if (min)
|
|
42
|
-
m.push({ before: min });
|
|
43
|
-
if (max)
|
|
44
|
-
m.push({ after: max });
|
|
45
|
-
return m;
|
|
46
|
-
}
|
|
47
|
-
function inRange(value, minDate, maxDate) {
|
|
48
|
-
return (!minDate || value >= minDate) && (!maxDate || value <= maxDate);
|
|
49
|
-
}
|
|
50
|
-
function PickerTrigger({ text, placeholder, open, size = "default", disabled, clearable, onClear, className, wrapperClassName, slot, controlProps, }) {
|
|
51
|
-
const showClear = clearable && !!text && !disabled;
|
|
52
|
-
const trigger = (_jsx(PopoverTrigger, { asChild: true, disabled: disabled, children: _jsxs("button", { type: "button", "aria-haspopup": "dialog", "aria-expanded": open, "data-slot": slot, "data-size": size, "data-placeholder": text ? undefined : "", ...controlProps, className: cn(selectTriggerVariants({ size }), "w-full min-w-0 cursor-pointer justify-start text-left tabular-nums", showClear && "pr-9", className), children: [_jsx(CalendarIcon, { className: "text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: text ?? placeholder })] }) }));
|
|
53
|
-
if (!clearable)
|
|
54
|
-
return trigger;
|
|
55
|
-
return (_jsxs("div", { "data-slot": `${slot}-wrapper`, className: cn("relative w-full min-w-0", wrapperClassName), children: [trigger, showClear && (
|
|
56
|
-
// Irmão do gatilho, não filho: botão dentro de botão não é HTML válido.
|
|
57
|
-
_jsx("button", { type: "button", onClick: onClear, "aria-label": "Limpar data", 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", children: _jsx(XIcon, { className: "size-4" }) }))] }));
|
|
58
|
-
}
|
|
59
|
-
/** Atalhos ao lado do calendário (no celular, uma faixa em cima). */
|
|
60
|
-
function PresetList({ presets, isActive, isDisabled, onSelect, }) {
|
|
61
|
-
return (_jsx("ul", { "data-slot": "date-picker-presets", className: "flex gap-1 overflow-x-auto border-b border-border p-2 sm:w-40 sm:flex-col sm:overflow-visible sm:border-r sm:border-b-0", children: presets.map(p => (_jsx("li", { className: "shrink-0", children: _jsx("button", { type: "button", disabled: isDisabled(p.value), "aria-pressed": isActive(p.value), onClick: () => onSelect(p.value), className: "w-full cursor-pointer truncate rounded-md px-2.5 py-1.5 text-left text-sm whitespace-nowrap outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-pressed:bg-accent aria-pressed:font-medium", children: p.label }) }, p.label))) }));
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Escolha de uma data num calendário, como o DatePickerInput do Mantine. Base:
|
|
65
|
-
* o DatePicker do Tremor, no visual do kit (gatilho igual ao do Select).
|
|
66
|
-
*
|
|
67
|
-
* <Field label="Prazo">
|
|
68
|
-
* <DatePicker value={prazo} onValueChange={setPrazo} minDate={inicio} clearable />
|
|
69
|
-
* </Field>
|
|
70
|
-
*
|
|
71
|
-
* O valor é "AAAA-MM-DD" ou `null` (ver `parseDateValue`/`toDateValue`).
|
|
72
|
-
* Escolher o dia já fecha. `presets` põe atalhos ao lado ("Hoje", "Fim do
|
|
73
|
-
* mês"…); os que caem fora de `minDate`/`maxDate` ficam desabilitados.
|
|
74
|
-
*
|
|
75
|
-
* Teclado: o calendário abre com o foco no dia escolhido (ou hoje); setas
|
|
76
|
-
* andam pelos dias, PageUp/PageDown pelos meses, Enter escolhe, Esc fecha.
|
|
77
|
-
*/
|
|
78
|
-
function DatePicker({ value, onValueChange, placeholder = "Selecione a data", minDate, maxDate, clearable = false, presets, formatValue = formatBR, captionLayout, size, disabled, className, wrapperClassName, align = "start", onOpenChange, ...props }) {
|
|
79
|
-
const controlProps = useFieldControl(props, { withRequired: false });
|
|
80
|
-
const [open, setOpen] = React.useState(false);
|
|
81
|
-
const selected = parseDateValue(value);
|
|
82
|
-
const min = parseDateValue(minDate);
|
|
83
|
-
const max = parseDateValue(maxDate);
|
|
84
|
-
function handleOpenChange(o) {
|
|
85
|
-
setOpen(o);
|
|
86
|
-
onOpenChange?.(o);
|
|
87
|
-
}
|
|
88
|
-
function pick(v) {
|
|
89
|
-
onValueChange(v);
|
|
90
|
-
handleOpenChange(false);
|
|
91
|
-
}
|
|
92
|
-
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PickerTrigger, { slot: "date-picker-trigger", text: selected ? formatValue(selected) : null, placeholder: placeholder, open: open, size: size, disabled: disabled, clearable: clearable, onClear: () => onValueChange(null), className: className, wrapperClassName: wrapperClassName, controlProps: controlProps }), _jsxs(PopoverContent, { align: align, className: "flex w-auto max-w-[calc(100vw-2rem)] flex-col p-0 sm:flex-row",
|
|
93
|
-
// O foco vai para o dia (autoFocus do calendário), não para o 1º botão.
|
|
94
|
-
onOpenAutoFocus: e => e.preventDefault(), children: [presets && presets.length > 0 && (_jsx(PresetList, { presets: presets, isActive: v => v === value, isDisabled: v => !inRange(v, minDate, maxDate), onSelect: pick })), _jsx(Calendar, { mode: "single", required: true, autoFocus: true, selected: selected, onSelect: d => pick(toDateValue(d)), defaultMonth: selected ?? clampDate(new Date(), min, max), startMonth: min, endMonth: max, disabled: outOfRange(min, max), captionLayout: captionLayout })] })] }));
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Escolha de um período (início e fim) num calendário de dois meses — um só no
|
|
98
|
-
* celular. Base: o DateRangePicker do Tremor, no visual do kit.
|
|
99
|
-
*
|
|
100
|
-
* <DateRangePicker value={[inicio, fim]} onValueChange={([i, f]) => { … }} />
|
|
101
|
-
*
|
|
102
|
-
* O primeiro clique marca o início e o segundo, o fim; aí fecha e grava. Se o
|
|
103
|
-
* segundo dia vier antes do primeiro, os dois trocam de lugar. Fechar no meio
|
|
104
|
-
* descarta o período começado e mantém o anterior.
|
|
105
|
-
*/
|
|
106
|
-
function DateRangePicker({ value, onValueChange, placeholder = "Selecione o período", minDate, maxDate, clearable = false, presets, formatValue = formatBR, captionLayout, size, disabled, className, wrapperClassName, align = "start", onOpenChange, ...props }) {
|
|
107
|
-
const controlProps = useFieldControl(props, { withRequired: false });
|
|
108
|
-
const isMobile = useIsMobile();
|
|
109
|
-
const [open, setOpen] = React.useState(false);
|
|
110
|
-
// Início escolhido nesta abertura, à espera do fim.
|
|
111
|
-
const [pendingFrom, setPendingFrom] = React.useState(null);
|
|
112
|
-
const from = parseDateValue(value[0]);
|
|
113
|
-
const to = parseDateValue(value[1]);
|
|
114
|
-
const min = parseDateValue(minDate);
|
|
115
|
-
const max = parseDateValue(maxDate);
|
|
116
|
-
function handleOpenChange(o) {
|
|
117
|
-
setOpen(o);
|
|
118
|
-
setPendingFrom(null);
|
|
119
|
-
onOpenChange?.(o);
|
|
120
|
-
}
|
|
121
|
-
function commit(range) {
|
|
122
|
-
onValueChange(range);
|
|
123
|
-
handleOpenChange(false);
|
|
124
|
-
}
|
|
125
|
-
function pickDay(day) {
|
|
126
|
-
if (!pendingFrom) {
|
|
127
|
-
setPendingFrom(day);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const [s, e] = day < pendingFrom ? [day, pendingFrom] : [pendingFrom, day];
|
|
131
|
-
commit([toDateValue(s), toDateValue(e)]);
|
|
132
|
-
}
|
|
133
|
-
const text = from
|
|
134
|
-
? `${formatValue(from)} – ${to ? formatValue(to) : "…"}`
|
|
135
|
-
: null;
|
|
136
|
-
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PickerTrigger, { slot: "date-range-picker-trigger", text: text, placeholder: placeholder, open: open, size: size, disabled: disabled, clearable: clearable, onClear: () => onValueChange([null, null]), className: className, wrapperClassName: wrapperClassName, controlProps: controlProps }), _jsxs(PopoverContent, { align: align, className: "flex w-auto max-w-[calc(100vw-2rem)] flex-col p-0 sm:flex-row", onOpenAutoFocus: e => e.preventDefault(), children: [presets && presets.length > 0 && (_jsx(PresetList, { presets: presets, isActive: v => v[0] === value[0] && v[1] === value[1], isDisabled: v => !inRange(v[0], minDate, maxDate) || !inRange(v[1], minDate, maxDate), onSelect: commit })), _jsxs("div", { className: "flex flex-col", children: [_jsx(Calendar, { mode: "range", autoFocus: true, numberOfMonths: isMobile ? 1 : 2,
|
|
137
|
-
// O cálculo de intervalo é nosso (pickDay): o do DayPicker já
|
|
138
|
-
// fecha um intervalo de um dia no primeiro clique.
|
|
139
|
-
selected: pendingFrom ? { from: pendingFrom, to: undefined } : { from, to }, onSelect: (_, day) => pickDay(day), defaultMonth: from ?? clampDate(new Date(), min, max), startMonth: min, endMonth: max, disabled: outOfRange(min, max), captionLayout: captionLayout }), _jsx("p", { "aria-live": "polite", className: "border-t border-border px-3 py-2 text-xs text-muted-foreground", children: pendingFrom ? "Agora escolha o último dia." : "Escolha o primeiro dia." })] })] })] }));
|
|
140
|
-
}
|
|
141
|
-
export { DatePicker, DateRangePicker, parseDateValue, toDateValue };
|
|
142
|
-
//# sourceMappingURL=date-picker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"date-picker.js","sourceRoot":"","sources":["../../src/components/date-picker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
// Derivado do DatePicker do Tremor (Apache-2.0) e modificado pela Strategicos.
|
|
4
|
-
// Ver THIRD_PARTY_NOTICES.md.
|
|
5
|
-
|
|
6
|
-
import * as React from "react"
|
|
7
|
-
import { CalendarIcon, XIcon } from "lucide-react"
|
|
8
|
-
import type { Matcher } from "react-day-picker"
|
|
9
|
-
|
|
10
|
-
import { useIsMobile } from "../hooks/use-mobile.js"
|
|
11
|
-
import { cn } from "../lib/utils.js"
|
|
12
|
-
import { Calendar } from "./calendar.js"
|
|
13
|
-
import { useFieldControl } from "./field.js"
|
|
14
|
-
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js"
|
|
15
|
-
import { selectTriggerVariants } from "./select.js"
|
|
16
|
-
|
|
17
|
-
// ── Valores ───────────────────────────────────────────────────────────────────
|
|
18
|
-
// As datas entram e saem como "AAAA-MM-DD", como no Mantine 8 e no
|
|
19
|
-
// <input type="date">: sem hora e sem fuso, 2026-10-15 é 15/10 em qualquer
|
|
20
|
-
// lugar. Um Date à meia-noite UTC vira o dia 14 no Brasil.
|
|
21
|
-
|
|
22
|
-
/** "AAAA-MM-DD" → Date local à meia-noite. Valor vazio ou inválido → undefined. */
|
|
23
|
-
function parseDateValue(value: string | null | undefined): Date | undefined {
|
|
24
|
-
const m = value ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(value) : null
|
|
25
|
-
if (!m) return undefined
|
|
26
|
-
const date = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))
|
|
27
|
-
// 2026-02-31 não existe: o Date rolaria para março.
|
|
28
|
-
return date.getMonth() === Number(m[2]) - 1 ? date : undefined
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Date → "AAAA-MM-DD" no dia local. */
|
|
32
|
-
function toDateValue(date: Date): string {
|
|
33
|
-
const p = (n: number) => String(n).padStart(2, "0")
|
|
34
|
-
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const formatBR = (date: Date) => date.toLocaleDateString("pt-BR")
|
|
38
|
-
|
|
39
|
-
function clampDate(date: Date, min?: Date, max?: Date) {
|
|
40
|
-
if (min && date < min) return min
|
|
41
|
-
if (max && date > max) return max
|
|
42
|
-
return date
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function outOfRange(min?: Date, max?: Date): Matcher[] {
|
|
46
|
-
const m: Matcher[] = []
|
|
47
|
-
if (min) m.push({ before: min })
|
|
48
|
-
if (max) m.push({ after: max })
|
|
49
|
-
return m
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function inRange(value: string, minDate?: string, maxDate?: string) {
|
|
53
|
-
return (!minDate || value >= minDate) && (!maxDate || value <= maxDate)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ── Peças comuns ──────────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
type FieldProps = {
|
|
59
|
-
id?: string
|
|
60
|
-
"aria-label"?: string
|
|
61
|
-
"aria-describedby"?: string
|
|
62
|
-
"aria-invalid"?: React.AriaAttributes["aria-invalid"]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
type CommonProps = FieldProps & {
|
|
66
|
-
placeholder?: string
|
|
67
|
-
/** Primeiro dia que pode ser escolhido ("AAAA-MM-DD"). */
|
|
68
|
-
minDate?: string
|
|
69
|
-
/** Último dia que pode ser escolhido ("AAAA-MM-DD"). */
|
|
70
|
-
maxDate?: string
|
|
71
|
-
/** Um X no gatilho limpa o valor. */
|
|
72
|
-
clearable?: boolean
|
|
73
|
-
/** Como a data aparece no gatilho. Padrão: dd/mm/aaaa. */
|
|
74
|
-
formatValue?: (date: Date) => string
|
|
75
|
-
/** "dropdown" troca o título do mês por seletores de mês e ano. */
|
|
76
|
-
captionLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years"
|
|
77
|
-
size?: "sm" | "default"
|
|
78
|
-
disabled?: boolean
|
|
79
|
-
/** Classes do gatilho. */
|
|
80
|
-
className?: string
|
|
81
|
-
/** Classes do invólucro, que só existe com `clearable`. */
|
|
82
|
-
wrapperClassName?: string
|
|
83
|
-
align?: "start" | "center" | "end"
|
|
84
|
-
onOpenChange?: (open: boolean) => void
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function PickerTrigger({
|
|
88
|
-
text,
|
|
89
|
-
placeholder,
|
|
90
|
-
open,
|
|
91
|
-
size = "default",
|
|
92
|
-
disabled,
|
|
93
|
-
clearable,
|
|
94
|
-
onClear,
|
|
95
|
-
className,
|
|
96
|
-
wrapperClassName,
|
|
97
|
-
slot,
|
|
98
|
-
controlProps,
|
|
99
|
-
}: {
|
|
100
|
-
text: string | null
|
|
101
|
-
placeholder: string
|
|
102
|
-
open: boolean
|
|
103
|
-
size?: "sm" | "default"
|
|
104
|
-
disabled?: boolean
|
|
105
|
-
clearable?: boolean
|
|
106
|
-
onClear: () => void
|
|
107
|
-
className?: string
|
|
108
|
-
wrapperClassName?: string
|
|
109
|
-
slot: string
|
|
110
|
-
controlProps: FieldProps
|
|
111
|
-
}) {
|
|
112
|
-
const showClear = clearable && !!text && !disabled
|
|
113
|
-
|
|
114
|
-
const trigger = (
|
|
115
|
-
<PopoverTrigger asChild disabled={disabled}>
|
|
116
|
-
<button
|
|
117
|
-
type="button"
|
|
118
|
-
aria-haspopup="dialog"
|
|
119
|
-
aria-expanded={open}
|
|
120
|
-
data-slot={slot}
|
|
121
|
-
data-size={size}
|
|
122
|
-
data-placeholder={text ? undefined : ""}
|
|
123
|
-
{...controlProps}
|
|
124
|
-
className={cn(
|
|
125
|
-
selectTriggerVariants({ size }),
|
|
126
|
-
"w-full min-w-0 cursor-pointer justify-start text-left tabular-nums",
|
|
127
|
-
showClear && "pr-9",
|
|
128
|
-
className
|
|
129
|
-
)}
|
|
130
|
-
>
|
|
131
|
-
<CalendarIcon className="text-muted-foreground" />
|
|
132
|
-
<span className="min-w-0 flex-1 truncate">{text ?? placeholder}</span>
|
|
133
|
-
</button>
|
|
134
|
-
</PopoverTrigger>
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
if (!clearable) return trigger
|
|
138
|
-
return (
|
|
139
|
-
<div data-slot={`${slot}-wrapper`} className={cn("relative w-full min-w-0", wrapperClassName)}>
|
|
140
|
-
{trigger}
|
|
141
|
-
{showClear && (
|
|
142
|
-
// Irmão do gatilho, não filho: botão dentro de botão não é HTML válido.
|
|
143
|
-
<button
|
|
144
|
-
type="button"
|
|
145
|
-
onClick={onClear}
|
|
146
|
-
aria-label="Limpar data"
|
|
147
|
-
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"
|
|
148
|
-
>
|
|
149
|
-
<XIcon className="size-4" />
|
|
150
|
-
</button>
|
|
151
|
-
)}
|
|
152
|
-
</div>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Atalhos ao lado do calendário (no celular, uma faixa em cima). */
|
|
157
|
-
function PresetList<T>({
|
|
158
|
-
presets,
|
|
159
|
-
isActive,
|
|
160
|
-
isDisabled,
|
|
161
|
-
onSelect,
|
|
162
|
-
}: {
|
|
163
|
-
presets: { label: string; value: T }[]
|
|
164
|
-
isActive: (value: T) => boolean
|
|
165
|
-
isDisabled: (value: T) => boolean
|
|
166
|
-
onSelect: (value: T) => void
|
|
167
|
-
}) {
|
|
168
|
-
return (
|
|
169
|
-
<ul
|
|
170
|
-
data-slot="date-picker-presets"
|
|
171
|
-
className="flex gap-1 overflow-x-auto border-b border-border p-2 sm:w-40 sm:flex-col sm:overflow-visible sm:border-r sm:border-b-0"
|
|
172
|
-
>
|
|
173
|
-
{presets.map(p => (
|
|
174
|
-
<li key={p.label} className="shrink-0">
|
|
175
|
-
<button
|
|
176
|
-
type="button"
|
|
177
|
-
disabled={isDisabled(p.value)}
|
|
178
|
-
aria-pressed={isActive(p.value)}
|
|
179
|
-
onClick={() => onSelect(p.value)}
|
|
180
|
-
className="w-full cursor-pointer truncate rounded-md px-2.5 py-1.5 text-left text-sm whitespace-nowrap outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-pressed:bg-accent aria-pressed:font-medium"
|
|
181
|
-
>
|
|
182
|
-
{p.label}
|
|
183
|
-
</button>
|
|
184
|
-
</li>
|
|
185
|
-
))}
|
|
186
|
-
</ul>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ── DatePicker ────────────────────────────────────────────────────────────────
|
|
191
|
-
|
|
192
|
-
export type DatePreset = { label: string; value: string }
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Escolha de uma data num calendário, como o DatePickerInput do Mantine. Base:
|
|
196
|
-
* o DatePicker do Tremor, no visual do kit (gatilho igual ao do Select).
|
|
197
|
-
*
|
|
198
|
-
* <Field label="Prazo">
|
|
199
|
-
* <DatePicker value={prazo} onValueChange={setPrazo} minDate={inicio} clearable />
|
|
200
|
-
* </Field>
|
|
201
|
-
*
|
|
202
|
-
* O valor é "AAAA-MM-DD" ou `null` (ver `parseDateValue`/`toDateValue`).
|
|
203
|
-
* Escolher o dia já fecha. `presets` põe atalhos ao lado ("Hoje", "Fim do
|
|
204
|
-
* mês"…); os que caem fora de `minDate`/`maxDate` ficam desabilitados.
|
|
205
|
-
*
|
|
206
|
-
* Teclado: o calendário abre com o foco no dia escolhido (ou hoje); setas
|
|
207
|
-
* andam pelos dias, PageUp/PageDown pelos meses, Enter escolhe, Esc fecha.
|
|
208
|
-
*/
|
|
209
|
-
function DatePicker({
|
|
210
|
-
value,
|
|
211
|
-
onValueChange,
|
|
212
|
-
placeholder = "Selecione a data",
|
|
213
|
-
minDate,
|
|
214
|
-
maxDate,
|
|
215
|
-
clearable = false,
|
|
216
|
-
presets,
|
|
217
|
-
formatValue = formatBR,
|
|
218
|
-
captionLayout,
|
|
219
|
-
size,
|
|
220
|
-
disabled,
|
|
221
|
-
className,
|
|
222
|
-
wrapperClassName,
|
|
223
|
-
align = "start",
|
|
224
|
-
onOpenChange,
|
|
225
|
-
...props
|
|
226
|
-
}: CommonProps & {
|
|
227
|
-
value: string | null
|
|
228
|
-
onValueChange: (value: string | null) => void
|
|
229
|
-
presets?: DatePreset[]
|
|
230
|
-
}) {
|
|
231
|
-
const controlProps = useFieldControl(props, { withRequired: false })
|
|
232
|
-
const [open, setOpen] = React.useState(false)
|
|
233
|
-
const selected = parseDateValue(value)
|
|
234
|
-
const min = parseDateValue(minDate)
|
|
235
|
-
const max = parseDateValue(maxDate)
|
|
236
|
-
|
|
237
|
-
function handleOpenChange(o: boolean) {
|
|
238
|
-
setOpen(o)
|
|
239
|
-
onOpenChange?.(o)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function pick(v: string) {
|
|
243
|
-
onValueChange(v)
|
|
244
|
-
handleOpenChange(false)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
249
|
-
<PickerTrigger
|
|
250
|
-
slot="date-picker-trigger"
|
|
251
|
-
text={selected ? formatValue(selected) : null}
|
|
252
|
-
placeholder={placeholder}
|
|
253
|
-
open={open}
|
|
254
|
-
size={size}
|
|
255
|
-
disabled={disabled}
|
|
256
|
-
clearable={clearable}
|
|
257
|
-
onClear={() => onValueChange(null)}
|
|
258
|
-
className={className}
|
|
259
|
-
wrapperClassName={wrapperClassName}
|
|
260
|
-
controlProps={controlProps}
|
|
261
|
-
/>
|
|
262
|
-
<PopoverContent
|
|
263
|
-
align={align}
|
|
264
|
-
className="flex w-auto max-w-[calc(100vw-2rem)] flex-col p-0 sm:flex-row"
|
|
265
|
-
// O foco vai para o dia (autoFocus do calendário), não para o 1º botão.
|
|
266
|
-
onOpenAutoFocus={e => e.preventDefault()}
|
|
267
|
-
>
|
|
268
|
-
{presets && presets.length > 0 && (
|
|
269
|
-
<PresetList
|
|
270
|
-
presets={presets}
|
|
271
|
-
isActive={v => v === value}
|
|
272
|
-
isDisabled={v => !inRange(v, minDate, maxDate)}
|
|
273
|
-
onSelect={pick}
|
|
274
|
-
/>
|
|
275
|
-
)}
|
|
276
|
-
<Calendar
|
|
277
|
-
mode="single"
|
|
278
|
-
required
|
|
279
|
-
autoFocus
|
|
280
|
-
selected={selected}
|
|
281
|
-
onSelect={d => pick(toDateValue(d))}
|
|
282
|
-
defaultMonth={selected ?? clampDate(new Date(), min, max)}
|
|
283
|
-
startMonth={min}
|
|
284
|
-
endMonth={max}
|
|
285
|
-
disabled={outOfRange(min, max)}
|
|
286
|
-
captionLayout={captionLayout}
|
|
287
|
-
/>
|
|
288
|
-
</PopoverContent>
|
|
289
|
-
</Popover>
|
|
290
|
-
)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// ── DateRangePicker ───────────────────────────────────────────────────────────
|
|
294
|
-
|
|
295
|
-
export type DateRangeValue = [string | null, string | null]
|
|
296
|
-
export type DateRangePreset = { label: string; value: [string, string] }
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Escolha de um período (início e fim) num calendário de dois meses — um só no
|
|
300
|
-
* celular. Base: o DateRangePicker do Tremor, no visual do kit.
|
|
301
|
-
*
|
|
302
|
-
* <DateRangePicker value={[inicio, fim]} onValueChange={([i, f]) => { … }} />
|
|
303
|
-
*
|
|
304
|
-
* O primeiro clique marca o início e o segundo, o fim; aí fecha e grava. Se o
|
|
305
|
-
* segundo dia vier antes do primeiro, os dois trocam de lugar. Fechar no meio
|
|
306
|
-
* descarta o período começado e mantém o anterior.
|
|
307
|
-
*/
|
|
308
|
-
function DateRangePicker({
|
|
309
|
-
value,
|
|
310
|
-
onValueChange,
|
|
311
|
-
placeholder = "Selecione o período",
|
|
312
|
-
minDate,
|
|
313
|
-
maxDate,
|
|
314
|
-
clearable = false,
|
|
315
|
-
presets,
|
|
316
|
-
formatValue = formatBR,
|
|
317
|
-
captionLayout,
|
|
318
|
-
size,
|
|
319
|
-
disabled,
|
|
320
|
-
className,
|
|
321
|
-
wrapperClassName,
|
|
322
|
-
align = "start",
|
|
323
|
-
onOpenChange,
|
|
324
|
-
...props
|
|
325
|
-
}: CommonProps & {
|
|
326
|
-
value: DateRangeValue
|
|
327
|
-
onValueChange: (value: DateRangeValue) => void
|
|
328
|
-
presets?: DateRangePreset[]
|
|
329
|
-
}) {
|
|
330
|
-
const controlProps = useFieldControl(props, { withRequired: false })
|
|
331
|
-
const isMobile = useIsMobile()
|
|
332
|
-
const [open, setOpen] = React.useState(false)
|
|
333
|
-
// Início escolhido nesta abertura, à espera do fim.
|
|
334
|
-
const [pendingFrom, setPendingFrom] = React.useState<Date | null>(null)
|
|
335
|
-
|
|
336
|
-
const from = parseDateValue(value[0])
|
|
337
|
-
const to = parseDateValue(value[1])
|
|
338
|
-
const min = parseDateValue(minDate)
|
|
339
|
-
const max = parseDateValue(maxDate)
|
|
340
|
-
|
|
341
|
-
function handleOpenChange(o: boolean) {
|
|
342
|
-
setOpen(o)
|
|
343
|
-
setPendingFrom(null)
|
|
344
|
-
onOpenChange?.(o)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function commit(range: [string, string]) {
|
|
348
|
-
onValueChange(range)
|
|
349
|
-
handleOpenChange(false)
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function pickDay(day: Date) {
|
|
353
|
-
if (!pendingFrom) { setPendingFrom(day); return }
|
|
354
|
-
const [s, e] = day < pendingFrom ? [day, pendingFrom] : [pendingFrom, day]
|
|
355
|
-
commit([toDateValue(s), toDateValue(e)])
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const text = from
|
|
359
|
-
? `${formatValue(from)} – ${to ? formatValue(to) : "…"}`
|
|
360
|
-
: null
|
|
361
|
-
|
|
362
|
-
return (
|
|
363
|
-
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
364
|
-
<PickerTrigger
|
|
365
|
-
slot="date-range-picker-trigger"
|
|
366
|
-
text={text}
|
|
367
|
-
placeholder={placeholder}
|
|
368
|
-
open={open}
|
|
369
|
-
size={size}
|
|
370
|
-
disabled={disabled}
|
|
371
|
-
clearable={clearable}
|
|
372
|
-
onClear={() => onValueChange([null, null])}
|
|
373
|
-
className={className}
|
|
374
|
-
wrapperClassName={wrapperClassName}
|
|
375
|
-
controlProps={controlProps}
|
|
376
|
-
/>
|
|
377
|
-
<PopoverContent
|
|
378
|
-
align={align}
|
|
379
|
-
className="flex w-auto max-w-[calc(100vw-2rem)] flex-col p-0 sm:flex-row"
|
|
380
|
-
onOpenAutoFocus={e => e.preventDefault()}
|
|
381
|
-
>
|
|
382
|
-
{presets && presets.length > 0 && (
|
|
383
|
-
<PresetList
|
|
384
|
-
presets={presets}
|
|
385
|
-
isActive={v => v[0] === value[0] && v[1] === value[1]}
|
|
386
|
-
isDisabled={v => !inRange(v[0], minDate, maxDate) || !inRange(v[1], minDate, maxDate)}
|
|
387
|
-
onSelect={commit}
|
|
388
|
-
/>
|
|
389
|
-
)}
|
|
390
|
-
<div className="flex flex-col">
|
|
391
|
-
<Calendar
|
|
392
|
-
mode="range"
|
|
393
|
-
autoFocus
|
|
394
|
-
numberOfMonths={isMobile ? 1 : 2}
|
|
395
|
-
// O cálculo de intervalo é nosso (pickDay): o do DayPicker já
|
|
396
|
-
// fecha um intervalo de um dia no primeiro clique.
|
|
397
|
-
selected={pendingFrom ? { from: pendingFrom, to: undefined } : { from, to }}
|
|
398
|
-
onSelect={(_, day) => pickDay(day)}
|
|
399
|
-
defaultMonth={from ?? clampDate(new Date(), min, max)}
|
|
400
|
-
startMonth={min}
|
|
401
|
-
endMonth={max}
|
|
402
|
-
disabled={outOfRange(min, max)}
|
|
403
|
-
captionLayout={captionLayout}
|
|
404
|
-
/>
|
|
405
|
-
<p aria-live="polite" className="border-t border-border px-3 py-2 text-xs text-muted-foreground">
|
|
406
|
-
{pendingFrom ? "Agora escolha o último dia." : "Escolha o primeiro dia."}
|
|
407
|
-
</p>
|
|
408
|
-
</div>
|
|
409
|
-
</PopoverContent>
|
|
410
|
-
</Popover>
|
|
411
|
-
)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
export { DatePicker, DateRangePicker, parseDateValue, toDateValue }
|