@strategicos/ui 0.8.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.
- 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/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/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
|
@@ -0,0 +1,77 @@
|
|
|
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 { Calendar } from "./calendar.js";
|
|
7
|
+
import { clampDate, formatDateTime, outOfRange, parseDateValue, toDateValue, PickerFooter, PickerTrigger, } from "./dates.js";
|
|
8
|
+
import { useFieldControl } from "./field.js";
|
|
9
|
+
import { Popover, PopoverContent } from "./popover.js";
|
|
10
|
+
import { TimeInput } from "./time-input.js";
|
|
11
|
+
/**
|
|
12
|
+
* Data e hora num calendário com campo de hora, como o DateTimePicker do
|
|
13
|
+
* Mantine. Para digitar a data e a hora direto, `DateInput withTime`.
|
|
14
|
+
*
|
|
15
|
+
* <Field label="Reunião">
|
|
16
|
+
* <DateTimePicker value={inicio} onValueChange={setInicio} />
|
|
17
|
+
* </Field>
|
|
18
|
+
*
|
|
19
|
+
* - Valor: "AAAA-MM-DD HH:mm:ss" ou `null`.
|
|
20
|
+
* - `withApplyButton` vem LIGADO (o botão de confirmar do Mantine): escolhe-se
|
|
21
|
+
* o dia e a hora e grava-se no Aplicar. Desligado, cada mudança já grava e o
|
|
22
|
+
* calendário fica aberto até sair dele.
|
|
23
|
+
* - `defaultTime`: hora usada quando ainda não há valor. Padrão: 00:00.
|
|
24
|
+
* - `minDate`/`maxDate` limitam o DIA ("AAAA-MM-DD").
|
|
25
|
+
*/
|
|
26
|
+
function DateTimePicker({ value, onValueChange, placeholder = "Selecione data e hora", minDate, maxDate, clearable = false, withApplyButton = true, withSeconds = false, defaultTime = "00:00", valueFormatter = formatDateTime, captionLayout, size, disabled, className, wrapperClassName, align = "start", onOpenChange, ...props }) {
|
|
27
|
+
const controlProps = useFieldControl(props, { withRequired: false });
|
|
28
|
+
const [open, setOpen] = React.useState(false);
|
|
29
|
+
const [draft, setDraft] = React.useState(value);
|
|
30
|
+
// A hora existe antes do dia: dá para acertá-la e escolher o dia depois.
|
|
31
|
+
const [time, setTime] = React.useState(() => timeOf(value, defaultTime));
|
|
32
|
+
const shown = withApplyButton ? draft : value;
|
|
33
|
+
const selected = parseDateValue(shown);
|
|
34
|
+
const committed = parseDateValue(value);
|
|
35
|
+
const min = parseDateValue(minDate);
|
|
36
|
+
const max = parseDateValue(maxDate);
|
|
37
|
+
function handleOpenChange(o) {
|
|
38
|
+
setOpen(o);
|
|
39
|
+
if (o) {
|
|
40
|
+
setDraft(value);
|
|
41
|
+
setTime(timeOf(value, defaultTime));
|
|
42
|
+
}
|
|
43
|
+
onOpenChange?.(o);
|
|
44
|
+
}
|
|
45
|
+
function update(v) {
|
|
46
|
+
if (withApplyButton)
|
|
47
|
+
setDraft(v);
|
|
48
|
+
else
|
|
49
|
+
onValueChange(v);
|
|
50
|
+
}
|
|
51
|
+
// `time` é sempre "HH:mm:ss" (timeOf e changeTime completam).
|
|
52
|
+
function pickDay(day) {
|
|
53
|
+
update(`${toDateValue(day)} ${time}`);
|
|
54
|
+
}
|
|
55
|
+
function changeTime(t) {
|
|
56
|
+
// O campo nativo devolve "" enquanto a hora está pela metade.
|
|
57
|
+
if (!/^\d{2}:\d{2}(:\d{2})?$/.test(t))
|
|
58
|
+
return;
|
|
59
|
+
const full = t.length === 5 ? `${t}:00` : t;
|
|
60
|
+
setTime(full);
|
|
61
|
+
if (shown)
|
|
62
|
+
update(`${shown.slice(0, 10)} ${full}`);
|
|
63
|
+
}
|
|
64
|
+
function apply() {
|
|
65
|
+
if (draft !== value)
|
|
66
|
+
onValueChange(draft);
|
|
67
|
+
handleOpenChange(false);
|
|
68
|
+
}
|
|
69
|
+
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PickerTrigger, { slot: "date-time-picker", text: committed ? valueFormatter(committed) : 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: "w-auto max-w-[calc(100vw-2rem)] p-0", onOpenAutoFocus: e => e.preventDefault(), children: [_jsx(Calendar, { mode: "single", required: true, autoFocus: true, selected: selected, onSelect: pickDay, defaultMonth: selected ?? clampDate(new Date(), min, max), startMonth: min, endMonth: max, disabled: outOfRange(min, max), captionLayout: captionLayout }), _jsx(PickerFooter, { onCancel: withApplyButton ? () => handleOpenChange(false) : undefined, onApply: withApplyButton ? apply : undefined, applyDisabled: !draft, children: _jsx(TimeInput, { "aria-label": "Hora", withSeconds: withSeconds, value: withSeconds ? time : time.slice(0, 5), onChange: e => changeTime(e.target.value), className: "w-32" }) })] })] }));
|
|
70
|
+
}
|
|
71
|
+
/** "HH:mm:ss" do valor, ou a hora padrão. */
|
|
72
|
+
function timeOf(value, defaultTime) {
|
|
73
|
+
const t = value && value.length >= 16 ? value.slice(11, 19) : defaultTime;
|
|
74
|
+
return t.length === 5 ? `${t}:00` : t;
|
|
75
|
+
}
|
|
76
|
+
export { DateTimePicker };
|
|
77
|
+
//# sourceMappingURL=date-time-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-time-picker.js","sourceRoot":"","sources":["../../src/components/date-time-picker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { Matcher } from "react-day-picker";
|
|
3
|
+
/** "AAAA-MM-DD" ou "AAAA-MM-DD HH:mm[:ss]" → Date local. Vazio ou inválido →
|
|
4
|
+
* undefined. */
|
|
5
|
+
declare function parseDateValue(value: string | null | undefined): Date | undefined;
|
|
6
|
+
/** Date → "AAAA-MM-DD" (dia local). */
|
|
7
|
+
declare function toDateValue(date: Date): string;
|
|
8
|
+
/** Date → "AAAA-MM-DD HH:mm:ss" (hora local). */
|
|
9
|
+
declare function toDateTimeValue(date: Date): string;
|
|
10
|
+
/** 15/10/2026 */
|
|
11
|
+
declare const formatDate: (date: Date) => string;
|
|
12
|
+
/** 15/10/2026 14:30 */
|
|
13
|
+
declare const formatDateTime: (date: Date) => string;
|
|
14
|
+
declare function clampDate(date: Date, min?: Date, max?: Date): Date;
|
|
15
|
+
/** Dias fora de [min, max] para o `disabled` do Calendar. */
|
|
16
|
+
declare function outOfRange(min?: Date, max?: Date): Matcher[];
|
|
17
|
+
/** Compara só o dia ("AAAA-MM-DD…" ordena como texto). */
|
|
18
|
+
declare function inRange(value: string, minDate?: string, maxDate?: string): boolean;
|
|
19
|
+
type FieldControlProps = {
|
|
20
|
+
id?: string;
|
|
21
|
+
"aria-label"?: string;
|
|
22
|
+
"aria-describedby"?: string;
|
|
23
|
+
"aria-invalid"?: React.AriaAttributes["aria-invalid"];
|
|
24
|
+
};
|
|
25
|
+
/** Gatilho dos pickers: o visual do SelectTrigger, com ícone de calendário e,
|
|
26
|
+
* opcionalmente, um X para limpar. Vai dentro de um <Popover>. */
|
|
27
|
+
declare function PickerTrigger({ text, placeholder, open, size, disabled, clearable, onClear, className, wrapperClassName, slot, controlProps, }: {
|
|
28
|
+
text: string | null;
|
|
29
|
+
placeholder: string;
|
|
30
|
+
open: boolean;
|
|
31
|
+
size?: "sm" | "default" | null;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
clearable?: boolean;
|
|
34
|
+
onClear: () => void;
|
|
35
|
+
className?: string;
|
|
36
|
+
wrapperClassName?: string;
|
|
37
|
+
slot: string;
|
|
38
|
+
controlProps: FieldControlProps;
|
|
39
|
+
}): React.JSX.Element;
|
|
40
|
+
/** X de limpar. Fica ao lado do gatilho, não dentro: botão dentro de botão não
|
|
41
|
+
* é HTML válido. */
|
|
42
|
+
declare function ClearButton({ onClick, className }: {
|
|
43
|
+
onClick: () => void;
|
|
44
|
+
className?: string;
|
|
45
|
+
}): React.JSX.Element;
|
|
46
|
+
/** Atalhos ao lado do calendário (no celular, uma faixa em cima). */
|
|
47
|
+
declare function PresetList<T>({ presets, isActive, isDisabled, onSelect, }: {
|
|
48
|
+
presets: {
|
|
49
|
+
label: string;
|
|
50
|
+
value: T;
|
|
51
|
+
}[];
|
|
52
|
+
isActive: (value: T) => boolean;
|
|
53
|
+
isDisabled: (value: T) => boolean;
|
|
54
|
+
onSelect: (value: T) => void;
|
|
55
|
+
}): React.JSX.Element;
|
|
56
|
+
/** Rodapé do calendário: algo à esquerda (dica, hora) e, com
|
|
57
|
+
* `withApplyButton`, Cancelar e Aplicar à direita. Para texto de dica, use
|
|
58
|
+
* <PickerHint>; o rodapé não tinge o que vai nele (a hora é um campo). */
|
|
59
|
+
declare function PickerFooter({ children, onCancel, onApply, applyDisabled, }: {
|
|
60
|
+
children?: React.ReactNode;
|
|
61
|
+
onCancel?: () => void;
|
|
62
|
+
onApply?: () => void;
|
|
63
|
+
applyDisabled?: boolean;
|
|
64
|
+
}): React.JSX.Element;
|
|
65
|
+
declare function PickerHint({ children }: {
|
|
66
|
+
children: React.ReactNode;
|
|
67
|
+
}): React.JSX.Element;
|
|
68
|
+
/** Props comuns aos pickers (DatePickerInput e DateTimePicker). */
|
|
69
|
+
type PickerBaseProps = FieldControlProps & {
|
|
70
|
+
placeholder?: string;
|
|
71
|
+
/** Primeiro dia que pode ser escolhido ("AAAA-MM-DD"). */
|
|
72
|
+
minDate?: string;
|
|
73
|
+
/** Último dia que pode ser escolhido ("AAAA-MM-DD"). */
|
|
74
|
+
maxDate?: string;
|
|
75
|
+
/** Um X no gatilho limpa o valor. */
|
|
76
|
+
clearable?: boolean;
|
|
77
|
+
/** Rodapé com Cancelar e Aplicar: a escolha só vale ao aplicar, e fechar
|
|
78
|
+
* de outro jeito descarta. Sem ele, escolher já grava. */
|
|
79
|
+
withApplyButton?: boolean;
|
|
80
|
+
/** Como o valor aparece no gatilho. */
|
|
81
|
+
valueFormatter?: (date: Date) => string;
|
|
82
|
+
/** "dropdown" troca o título do mês por seletores de mês e ano. */
|
|
83
|
+
captionLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years";
|
|
84
|
+
size?: "sm" | "default";
|
|
85
|
+
disabled?: boolean;
|
|
86
|
+
/** Classes do gatilho. */
|
|
87
|
+
className?: string;
|
|
88
|
+
/** Classes do invólucro, que só existe com `clearable`. */
|
|
89
|
+
wrapperClassName?: string;
|
|
90
|
+
align?: "start" | "center" | "end";
|
|
91
|
+
onOpenChange?: (open: boolean) => void;
|
|
92
|
+
};
|
|
93
|
+
export { parseDateValue, toDateValue, toDateTimeValue, formatDate, formatDateTime, clampDate, outOfRange, inRange, PickerTrigger, ClearButton, PresetList, PickerFooter, PickerHint, type FieldControlProps, type PickerBaseProps, };
|
|
94
|
+
//# sourceMappingURL=dates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../../src/components/dates.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CalendarIcon, XIcon } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { Button } from "./button.js";
|
|
6
|
+
import { PopoverTrigger } from "./popover.js";
|
|
7
|
+
import { selectTriggerVariants } from "./select.js";
|
|
8
|
+
/**
|
|
9
|
+
* Utilitários dos campos de data (DateInput, DatePickerInput, DateTimePicker).
|
|
10
|
+
*
|
|
11
|
+
* Os valores seguem o Mantine 8: texto, nunca `Date`.
|
|
12
|
+
* - data: "AAAA-MM-DD" (como o <input type="date">)
|
|
13
|
+
* - data e hora: "AAAA-MM-DD HH:mm:ss"
|
|
14
|
+
* Sem fuso: 2026-10-15 é 15/10 em qualquer lugar. Um Date à meia-noite UTC
|
|
15
|
+
* vira o dia 14 no Brasil.
|
|
16
|
+
*/
|
|
17
|
+
const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/;
|
|
18
|
+
/** "AAAA-MM-DD" ou "AAAA-MM-DD HH:mm[:ss]" → Date local. Vazio ou inválido →
|
|
19
|
+
* undefined. */
|
|
20
|
+
function parseDateValue(value) {
|
|
21
|
+
const m = value ? DATE_RE.exec(value) : null;
|
|
22
|
+
if (!m)
|
|
23
|
+
return undefined;
|
|
24
|
+
const [y, mo, d, h = "0", mi = "0", s = "0"] = m.slice(1);
|
|
25
|
+
const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));
|
|
26
|
+
// 2026-02-31 não existe: o Date rolaria para março.
|
|
27
|
+
if (date.getMonth() !== Number(mo) - 1 || Number(h) > 23 || Number(mi) > 59 || Number(s) > 59)
|
|
28
|
+
return undefined;
|
|
29
|
+
return date;
|
|
30
|
+
}
|
|
31
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
32
|
+
/** Date → "AAAA-MM-DD" (dia local). */
|
|
33
|
+
function toDateValue(date) {
|
|
34
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
35
|
+
}
|
|
36
|
+
/** Date → "AAAA-MM-DD HH:mm:ss" (hora local). */
|
|
37
|
+
function toDateTimeValue(date) {
|
|
38
|
+
return `${toDateValue(date)} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
39
|
+
}
|
|
40
|
+
/** 15/10/2026 */
|
|
41
|
+
const formatDate = (date) => date.toLocaleDateString("pt-BR");
|
|
42
|
+
/** 15/10/2026 14:30 */
|
|
43
|
+
const formatDateTime = (date) => `${formatDate(date)} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
44
|
+
// ── Limites ───────────────────────────────────────────────────────────────────
|
|
45
|
+
function clampDate(date, min, max) {
|
|
46
|
+
if (min && date < min)
|
|
47
|
+
return min;
|
|
48
|
+
if (max && date > max)
|
|
49
|
+
return max;
|
|
50
|
+
return date;
|
|
51
|
+
}
|
|
52
|
+
/** Dias fora de [min, max] para o `disabled` do Calendar. */
|
|
53
|
+
function outOfRange(min, max) {
|
|
54
|
+
const m = [];
|
|
55
|
+
if (min)
|
|
56
|
+
m.push({ before: min });
|
|
57
|
+
if (max)
|
|
58
|
+
m.push({ after: max });
|
|
59
|
+
return m;
|
|
60
|
+
}
|
|
61
|
+
/** Compara só o dia ("AAAA-MM-DD…" ordena como texto). */
|
|
62
|
+
function inRange(value, minDate, maxDate) {
|
|
63
|
+
const day = value.slice(0, 10);
|
|
64
|
+
return (!minDate || day >= minDate.slice(0, 10)) && (!maxDate || day <= maxDate.slice(0, 10));
|
|
65
|
+
}
|
|
66
|
+
/** Gatilho dos pickers: o visual do SelectTrigger, com ícone de calendário e,
|
|
67
|
+
* opcionalmente, um X para limpar. Vai dentro de um <Popover>. */
|
|
68
|
+
function PickerTrigger({ text, placeholder, open, size = "default", disabled, clearable, onClear, className, wrapperClassName, slot, controlProps, }) {
|
|
69
|
+
const showClear = clearable && !!text && !disabled;
|
|
70
|
+
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 ?? "default", "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 })] }) }));
|
|
71
|
+
if (!clearable)
|
|
72
|
+
return trigger;
|
|
73
|
+
return (_jsxs("div", { "data-slot": `${slot}-wrapper`, className: cn("relative w-full min-w-0", wrapperClassName), children: [trigger, showClear && _jsx(ClearButton, { onClick: onClear })] }));
|
|
74
|
+
}
|
|
75
|
+
/** X de limpar. Fica ao lado do gatilho, não dentro: botão dentro de botão não
|
|
76
|
+
* é HTML válido. */
|
|
77
|
+
function ClearButton({ onClick, className }) {
|
|
78
|
+
return (_jsx("button", { type: "button", onClick: onClick, "aria-label": "Limpar data", className: cn("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", className), children: _jsx(XIcon, { className: "size-4" }) }));
|
|
79
|
+
}
|
|
80
|
+
/** Atalhos ao lado do calendário (no celular, uma faixa em cima). */
|
|
81
|
+
function PresetList({ presets, isActive, isDisabled, onSelect, }) {
|
|
82
|
+
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))) }));
|
|
83
|
+
}
|
|
84
|
+
/** Rodapé do calendário: algo à esquerda (dica, hora) e, com
|
|
85
|
+
* `withApplyButton`, Cancelar e Aplicar à direita. Para texto de dica, use
|
|
86
|
+
* <PickerHint>; o rodapé não tinge o que vai nele (a hora é um campo). */
|
|
87
|
+
function PickerFooter({ children, onCancel, onApply, applyDisabled, }) {
|
|
88
|
+
return (_jsxs("div", { "data-slot": "date-picker-footer", className: "flex flex-wrap items-center gap-2 border-t border-border p-3", children: [_jsx("div", { className: "min-w-0 flex-1", children: children }), onApply && (_jsxs("div", { className: "flex shrink-0 gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: "Cancelar" }), _jsx(Button, { type: "button", onClick: onApply, disabled: applyDisabled, children: "Aplicar" })] }))] }));
|
|
89
|
+
}
|
|
90
|
+
function PickerHint({ children }) {
|
|
91
|
+
return _jsx("span", { "aria-live": "polite", className: "text-xs text-muted-foreground", children: children });
|
|
92
|
+
}
|
|
93
|
+
export { parseDateValue, toDateValue, toDateTimeValue, formatDate, formatDateTime, clampDate, outOfRange, inRange, PickerTrigger, ClearButton, PresetList, PickerFooter, PickerHint, };
|
|
94
|
+
//# sourceMappingURL=dates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.js","sourceRoot":"","sources":["../../src/components/dates.tsx"],"names":[],"mappings":"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"}
|
|
@@ -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
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { CalendarIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils.js"
|
|
7
|
+
import { Calendar } from "./calendar.js"
|
|
8
|
+
import {
|
|
9
|
+
clampDate, ClearButton, formatDate, formatDateTime, inRange, outOfRange,
|
|
10
|
+
parseDateValue, toDateValue, type FieldControlProps,
|
|
11
|
+
} from "./dates.js"
|
|
12
|
+
import { Input } from "./input.js"
|
|
13
|
+
import { Popover, PopoverAnchor, PopoverContent } from "./popover.js"
|
|
14
|
+
|
|
15
|
+
/** Põe as barras (e o espaço e os dois-pontos da hora) entre os dígitos.
|
|
16
|
+
* Separador só ENTRE grupos já começados: "15" e não "15/" — assim apagar
|
|
17
|
+
* para trás nunca esbarra num separador que volta sozinho. */
|
|
18
|
+
function mask(digits: string, withTime: boolean) {
|
|
19
|
+
const d = digits.slice(0, withTime ? 12 : 8)
|
|
20
|
+
let out = d.slice(0, 2)
|
|
21
|
+
if (d.length > 2) out += "/" + d.slice(2, 4)
|
|
22
|
+
if (d.length > 4) out += "/" + d.slice(4, 8)
|
|
23
|
+
if (d.length > 8) out += " " + d.slice(8, 10)
|
|
24
|
+
if (d.length > 10) out += ":" + d.slice(10, 12)
|
|
25
|
+
return out
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const countDigits = (s: string) => s.replace(/\D/g, "").length
|
|
29
|
+
|
|
30
|
+
/** Posição do cursor logo depois do n-ésimo dígito do texto mascarado. */
|
|
31
|
+
function caretAfterDigits(masked: string, n: number) {
|
|
32
|
+
if (n === 0) return 0
|
|
33
|
+
let seen = 0
|
|
34
|
+
for (let i = 0; i < masked.length; i++) {
|
|
35
|
+
if (/\d/.test(masked[i]!) && ++seen === n) return i + 1
|
|
36
|
+
}
|
|
37
|
+
return masked.length
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Texto digitado → valor. Data completa → "AAAA-MM-DD"; com hora →
|
|
42
|
+
* "AAAA-MM-DD HH:mm:00". Incompleto ou inexistente (31/02) → undefined.
|
|
43
|
+
* `dateOnlyOk`: com hora, aceita só a data (hora 00:00) — vale ao sair do
|
|
44
|
+
* campo, não enquanto se digita (a hora pode estar vindo).
|
|
45
|
+
*/
|
|
46
|
+
function parseTyped(text: string, withTime: boolean, dateOnlyOk: boolean): string | undefined {
|
|
47
|
+
const d = text.replace(/\D/g, "")
|
|
48
|
+
if (d.length < 8) return undefined
|
|
49
|
+
const date = `${d.slice(4, 8)}-${d.slice(2, 4)}-${d.slice(0, 2)}`
|
|
50
|
+
let v: string
|
|
51
|
+
if (!withTime) v = date
|
|
52
|
+
else if (d.length === 12) v = `${date} ${d.slice(8, 10)}:${d.slice(10, 12)}:00`
|
|
53
|
+
else if (dateOnlyOk && d.length === 8) v = `${date} 00:00:00`
|
|
54
|
+
else return undefined
|
|
55
|
+
return parseDateValue(v) ? v : undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Data digitada, como o DateInput do Mantine: dd/mm/aaaa com as barras postas
|
|
60
|
+
* sozinhas, e um calendário que abre no clique (ou ↓) para quem prefere
|
|
61
|
+
* escolher. `withTime` acrescenta a hora: dd/mm/aaaa hh:mm.
|
|
62
|
+
*
|
|
63
|
+
* <Field label="Início">
|
|
64
|
+
* <DateInput value={inicio} onValueChange={setInicio} maxDate={fim} />
|
|
65
|
+
* </Field>
|
|
66
|
+
*
|
|
67
|
+
* - Valor: "AAAA-MM-DD" (com `withTime`, "AAAA-MM-DD HH:mm:ss") ou `null`.
|
|
68
|
+
* - Grava assim que o texto vira uma data válida dentro de `minDate`/`maxDate`.
|
|
69
|
+
* Ao sair do campo, texto incompleto ou fora dos limites volta ao último
|
|
70
|
+
* valor válido; campo vazio grava `null`.
|
|
71
|
+
* - Colar "2026-10-15" (ISO) também funciona.
|
|
72
|
+
*
|
|
73
|
+
* Teclado: ↓ abre o calendário e põe o foco nele; Enter confirma o texto; Esc
|
|
74
|
+
* fecha o calendário.
|
|
75
|
+
*/
|
|
76
|
+
function DateInput({
|
|
77
|
+
value,
|
|
78
|
+
onValueChange,
|
|
79
|
+
withTime = false,
|
|
80
|
+
minDate,
|
|
81
|
+
maxDate,
|
|
82
|
+
placeholder = withTime ? "dd/mm/aaaa hh:mm" : "dd/mm/aaaa",
|
|
83
|
+
clearable = false,
|
|
84
|
+
withCalendar = true,
|
|
85
|
+
captionLayout,
|
|
86
|
+
disabled,
|
|
87
|
+
required,
|
|
88
|
+
name,
|
|
89
|
+
className,
|
|
90
|
+
wrapperClassName,
|
|
91
|
+
onBlur,
|
|
92
|
+
"aria-invalid": ariaInvalid,
|
|
93
|
+
...props
|
|
94
|
+
}: FieldControlProps & {
|
|
95
|
+
value: string | null
|
|
96
|
+
onValueChange: (value: string | null) => void
|
|
97
|
+
/** Data e hora (dd/mm/aaaa hh:mm). */
|
|
98
|
+
withTime?: boolean
|
|
99
|
+
/** Primeiro dia aceito ("AAAA-MM-DD"). */
|
|
100
|
+
minDate?: string
|
|
101
|
+
/** Último dia aceito ("AAAA-MM-DD"). */
|
|
102
|
+
maxDate?: string
|
|
103
|
+
placeholder?: string
|
|
104
|
+
/** Um X no campo limpa o valor. */
|
|
105
|
+
clearable?: boolean
|
|
106
|
+
/** Calendário no clique. Sem ele, o campo é só de digitar. */
|
|
107
|
+
withCalendar?: boolean
|
|
108
|
+
captionLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years"
|
|
109
|
+
disabled?: boolean
|
|
110
|
+
required?: boolean
|
|
111
|
+
name?: string
|
|
112
|
+
/** Classes do <input>. */
|
|
113
|
+
className?: string
|
|
114
|
+
/** Classes do invólucro (largura e posição no layout). */
|
|
115
|
+
wrapperClassName?: string
|
|
116
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement>
|
|
117
|
+
}) {
|
|
118
|
+
const display = React.useCallback((v: string | null) => {
|
|
119
|
+
const d = parseDateValue(v)
|
|
120
|
+
return d ? (withTime ? formatDateTime(d) : formatDate(d)) : ""
|
|
121
|
+
}, [withTime])
|
|
122
|
+
|
|
123
|
+
const [text, setText] = React.useState(() => display(value))
|
|
124
|
+
const [open, setOpen] = React.useState(false)
|
|
125
|
+
// Pedido de foco nos dias (↓). Contador, e não booleano: a ↓ com o
|
|
126
|
+
// calendário já aberto também precisa levar o foco.
|
|
127
|
+
const [focusRequest, setFocusRequest] = React.useState(0)
|
|
128
|
+
const [month, setMonth] = React.useState<Date | undefined>(() => parseDateValue(value))
|
|
129
|
+
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
130
|
+
const anchorRef = React.useRef<HTMLDivElement>(null)
|
|
131
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
132
|
+
const caret = React.useRef<number | null>(null)
|
|
133
|
+
|
|
134
|
+
// Valor trocado por fora (outro campo, reset do formulário): o texto segue,
|
|
135
|
+
// a menos que já represente esse valor (é o que se está digitando).
|
|
136
|
+
const [prevValue, setPrevValue] = React.useState(value)
|
|
137
|
+
if (value !== prevValue) {
|
|
138
|
+
setPrevValue(value)
|
|
139
|
+
if (parseTyped(text, withTime, true) !== value) setText(display(value))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Recoloca o cursor onde estava depois que a máscara reescreveu o texto.
|
|
143
|
+
React.useLayoutEffect(() => {
|
|
144
|
+
if (caret.current === null || !inputRef.current) return
|
|
145
|
+
inputRef.current.setSelectionRange(caret.current, caret.current)
|
|
146
|
+
caret.current = null
|
|
147
|
+
}, [text])
|
|
148
|
+
|
|
149
|
+
const accepts = (v: string | undefined): v is string => !!v && inRange(v, minDate, maxDate)
|
|
150
|
+
|
|
151
|
+
function setTyped(next: string, caretAt: number | null) {
|
|
152
|
+
caret.current = caretAt
|
|
153
|
+
setText(next)
|
|
154
|
+
const v = parseTyped(next, withTime, false)
|
|
155
|
+
if (accepts(v)) {
|
|
156
|
+
setMonth(parseDateValue(v))
|
|
157
|
+
if (v !== value) onValueChange(v)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
162
|
+
const raw = e.target.value
|
|
163
|
+
const before = countDigits(raw.slice(0, e.target.selectionStart ?? raw.length))
|
|
164
|
+
const next = mask(raw.replace(/\D/g, ""), withTime)
|
|
165
|
+
setTyped(next, caretAfterDigits(next, before))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handlePaste(e: React.ClipboardEvent<HTMLInputElement>) {
|
|
169
|
+
// ISO colado ("2026-10-15", "2026-10-15 14:30") vira o texto brasileiro.
|
|
170
|
+
const d = parseDateValue(e.clipboardData.getData("text").trim())
|
|
171
|
+
if (!d) return
|
|
172
|
+
e.preventDefault()
|
|
173
|
+
setTyped(withTime ? formatDateTime(d) : formatDate(d), null)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Sair do campo (ou Enter): grava o texto válido ou volta ao último valor. */
|
|
177
|
+
function settle() {
|
|
178
|
+
if (!text.trim()) {
|
|
179
|
+
if (value !== null) onValueChange(null)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
const v = parseTyped(text, withTime, true)
|
|
183
|
+
if (accepts(v)) {
|
|
184
|
+
if (v !== value) onValueChange(v)
|
|
185
|
+
setText(display(v))
|
|
186
|
+
} else {
|
|
187
|
+
setText(display(value))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// O dia focável do DayPicker é o único com tabindex=0 (o escolhido ou hoje).
|
|
192
|
+
React.useEffect(() => {
|
|
193
|
+
if (!focusRequest) return
|
|
194
|
+
contentRef.current?.querySelector<HTMLButtonElement>('[role=grid] button[tabindex="0"]')?.focus()
|
|
195
|
+
}, [focusRequest])
|
|
196
|
+
|
|
197
|
+
function openCalendar(focus: boolean) {
|
|
198
|
+
if (!withCalendar || disabled) return
|
|
199
|
+
if (!open) setMonth(parseDateValue(value))
|
|
200
|
+
setOpen(true)
|
|
201
|
+
if (focus) setFocusRequest(n => n + 1)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function pickDay(day: Date) {
|
|
205
|
+
// Com hora, mantém a hora já digitada (ou a do valor, ou 00:00).
|
|
206
|
+
const current = parseTyped(text, withTime, true) ?? value
|
|
207
|
+
const time = current && current.length >= 19 ? current.slice(11, 19) : "00:00:00"
|
|
208
|
+
const v = withTime ? `${toDateValue(day)} ${time}` : toDateValue(day)
|
|
209
|
+
onValueChange(v)
|
|
210
|
+
setText(display(v))
|
|
211
|
+
setOpen(false)
|
|
212
|
+
inputRef.current?.focus()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Digitado e completo, mas inválido ou fora dos limites: borda de erro já
|
|
216
|
+
// enquanto se digita (o Field, se houver, ainda manda no aria-invalid).
|
|
217
|
+
const typedInvalid = countDigits(text) === (withTime ? 12 : 8) && !accepts(parseTyped(text, withTime, false))
|
|
218
|
+
const min = parseDateValue(minDate)
|
|
219
|
+
const max = parseDateValue(maxDate)
|
|
220
|
+
const selected = parseDateValue(value)
|
|
221
|
+
const showClear = clearable && !!text && !disabled
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
225
|
+
<PopoverAnchor asChild>
|
|
226
|
+
<div ref={anchorRef} data-slot="date-input-wrapper" className={cn("relative w-full min-w-0", wrapperClassName)}>
|
|
227
|
+
<Input
|
|
228
|
+
ref={inputRef}
|
|
229
|
+
data-slot="date-input"
|
|
230
|
+
value={text}
|
|
231
|
+
placeholder={placeholder}
|
|
232
|
+
disabled={disabled}
|
|
233
|
+
required={required}
|
|
234
|
+
name={name}
|
|
235
|
+
inputMode="numeric"
|
|
236
|
+
autoComplete="off"
|
|
237
|
+
aria-haspopup={withCalendar ? "dialog" : undefined}
|
|
238
|
+
aria-expanded={withCalendar ? open : undefined}
|
|
239
|
+
aria-invalid={ariaInvalid ?? (typedInvalid || undefined)}
|
|
240
|
+
leftSection={<CalendarIcon />}
|
|
241
|
+
className={cn("tabular-nums", showClear && "pr-9", className)}
|
|
242
|
+
onChange={handleChange}
|
|
243
|
+
onPaste={handlePaste}
|
|
244
|
+
onClick={() => { if (!open) openCalendar(false) }}
|
|
245
|
+
onKeyDown={e => {
|
|
246
|
+
if (e.key === "ArrowDown" && withCalendar) { e.preventDefault(); openCalendar(true) }
|
|
247
|
+
else if (e.key === "Enter") { e.preventDefault(); settle(); setOpen(false) }
|
|
248
|
+
}}
|
|
249
|
+
onBlur={e => { settle(); onBlur?.(e) }}
|
|
250
|
+
{...props}
|
|
251
|
+
/>
|
|
252
|
+
{showClear && (
|
|
253
|
+
<ClearButton onClick={() => { setText(""); onValueChange(null); inputRef.current?.focus() }} />
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
</PopoverAnchor>
|
|
257
|
+
|
|
258
|
+
{withCalendar && (
|
|
259
|
+
<PopoverContent
|
|
260
|
+
ref={contentRef}
|
|
261
|
+
className="w-auto p-0"
|
|
262
|
+
// O foco fica no campo: dá para continuar digitando com o calendário
|
|
263
|
+
// aberto. Só a ↓ leva o foco para os dias.
|
|
264
|
+
onOpenAutoFocus={e => e.preventDefault()}
|
|
265
|
+
onCloseAutoFocus={e => e.preventDefault()}
|
|
266
|
+
// Clicar no próprio campo não conta como "fora".
|
|
267
|
+
onInteractOutside={e => { if (anchorRef.current?.contains(e.target as Node)) e.preventDefault() }}
|
|
268
|
+
>
|
|
269
|
+
<Calendar
|
|
270
|
+
mode="single"
|
|
271
|
+
required
|
|
272
|
+
selected={selected}
|
|
273
|
+
onSelect={pickDay}
|
|
274
|
+
month={month ?? clampDate(new Date(), min, max)}
|
|
275
|
+
onMonthChange={setMonth}
|
|
276
|
+
startMonth={min}
|
|
277
|
+
endMonth={max}
|
|
278
|
+
disabled={outOfRange(min, max)}
|
|
279
|
+
captionLayout={captionLayout}
|
|
280
|
+
/>
|
|
281
|
+
</PopoverContent>
|
|
282
|
+
)}
|
|
283
|
+
</Popover>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export { DateInput }
|