@skybin-tech/nebula-ui 0.0.30 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/Form/DatePicker.cjs +8 -2
- package/dist/cjs/components/Form/DatePicker.cjs.map +1 -1
- package/dist/cjs/components/Form/DateTimePicker.cjs +202 -0
- package/dist/cjs/components/Form/DateTimePicker.cjs.map +1 -0
- package/dist/cjs/components/Form/PasswordRevealToggle.cjs +21 -0
- package/dist/cjs/components/Form/PasswordRevealToggle.cjs.map +1 -0
- package/dist/cjs/components/Form/TextBox.cjs +29 -14
- package/dist/cjs/components/Form/TextBox.cjs.map +1 -1
- package/dist/cjs/components/Form/TimePicker.cjs +111 -0
- package/dist/cjs/components/Form/TimePicker.cjs.map +1 -0
- package/dist/cjs/components/Form/context.cjs +1 -1
- package/dist/cjs/components/Form/context.cjs.map +1 -1
- package/dist/cjs/components/Form/index.cjs +7 -0
- package/dist/cjs/components/Input/Input.cjs +32 -4
- package/dist/cjs/components/Input/Input.cjs.map +1 -1
- package/dist/cjs/index.cjs +7 -0
- package/dist/components/Form/DatePicker.d.ts.map +1 -1
- package/dist/components/Form/DatePicker.js +9 -3
- package/dist/components/Form/DatePicker.js.map +1 -1
- package/dist/components/Form/DateTimePicker.d.ts +28 -0
- package/dist/components/Form/DateTimePicker.d.ts.map +1 -0
- package/dist/components/Form/DateTimePicker.js +202 -0
- package/dist/components/Form/DateTimePicker.js.map +1 -0
- package/dist/components/Form/PasswordRevealToggle.d.ts +7 -0
- package/dist/components/Form/PasswordRevealToggle.d.ts.map +1 -0
- package/dist/components/Form/PasswordRevealToggle.js +21 -0
- package/dist/components/Form/PasswordRevealToggle.js.map +1 -0
- package/dist/components/Form/TextBox.d.ts +5 -0
- package/dist/components/Form/TextBox.d.ts.map +1 -1
- package/dist/components/Form/TextBox.js +30 -15
- package/dist/components/Form/TextBox.js.map +1 -1
- package/dist/components/Form/TimePicker.d.ts +14 -0
- package/dist/components/Form/TimePicker.d.ts.map +1 -0
- package/dist/components/Form/TimePicker.js +111 -0
- package/dist/components/Form/TimePicker.js.map +1 -0
- package/dist/components/Form/context.d.ts +1 -1
- package/dist/components/Form/context.d.ts.map +1 -1
- package/dist/components/Form/context.js +1 -1
- package/dist/components/Form/context.js.map +1 -1
- package/dist/components/Form/index.d.ts +6 -0
- package/dist/components/Form/index.d.ts.map +1 -1
- package/dist/components/Form/index.js +4 -1
- package/dist/components/Input/Input.d.ts +14 -2
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Input/Input.js +34 -6
- package/dist/components/Input/Input.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/package.json +1 -1
|
@@ -13,6 +13,12 @@ let react_hook_form = require("react-hook-form");
|
|
|
13
13
|
let lucide_react = require("lucide-react");
|
|
14
14
|
let date_fns = require("date-fns");
|
|
15
15
|
//#region src/components/Form/DatePicker.tsx
|
|
16
|
+
function toStartOfDay(value) {
|
|
17
|
+
if (!value || value === "") return;
|
|
18
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
19
|
+
if (Number.isNaN(date.getTime())) return;
|
|
20
|
+
return (0, date_fns.startOfDay)(date);
|
|
21
|
+
}
|
|
16
22
|
var datePickerVariants = (0, class_variance_authority.cva)("w-full justify-start text-left font-normal", {
|
|
17
23
|
variants: {
|
|
18
24
|
size: {
|
|
@@ -66,7 +72,7 @@ function DatePicker({ name, label, helperText, showError = true, error: customEr
|
|
|
66
72
|
name,
|
|
67
73
|
control
|
|
68
74
|
});
|
|
69
|
-
const selectedDate =
|
|
75
|
+
const selectedDate = toStartOfDay(field.value);
|
|
70
76
|
const fieldError = fieldState.error?.message;
|
|
71
77
|
const errorMessage = customError ?? fieldError;
|
|
72
78
|
const hasError = !!errorMessage;
|
|
@@ -107,7 +113,7 @@ function DatePicker({ name, label, helperText, showError = true, error: customEr
|
|
|
107
113
|
mode: "single",
|
|
108
114
|
selected: selectedDate,
|
|
109
115
|
onSelect: (date) => {
|
|
110
|
-
field.onChange(date
|
|
116
|
+
field.onChange(date ? (0, date_fns.startOfDay)(date) : "");
|
|
111
117
|
setOpen(false);
|
|
112
118
|
}
|
|
113
119
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePicker.cjs","names":[],"sources":["../../../../src/components/Form/DatePicker.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useContext, useEffect, useId, useState } from \"react\";\r\nimport { format } from \"date-fns\";\r\nimport { CalendarIcon } from \"lucide-react\";\r\nimport { useController, useFormContext as useRHFFormContext, type Control, type FieldPath, type FieldValues } from \"react-hook-form\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { Button } from \"../../primitives/button\";\r\nimport { Calendar } from \"../../primitives/calendar\";\r\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../primitives/popover\";\r\nimport { FormConfigContext, type FieldValidationRules, type FormConfig } from \"./context\";\r\nimport { FieldLayout } from \"./FieldLayout\";\r\n\r\nconst datePickerVariants = cva(\"w-full justify-start text-left font-normal\", {\r\n variants: {\r\n size: {\r\n sm: \"h-8 text-xs\",\r\n md: \"h-10 text-sm\",\r\n lg: \"h-12 text-base\",\r\n },\r\n variant: {\r\n default: \"border-input\",\r\n error: \"border-destructive\",\r\n success: \"border-green-500\",\r\n },\r\n },\r\n defaultVariants: {\r\n size: \"md\",\r\n variant: \"default\",\r\n },\r\n});\r\n\r\nexport interface DatePickerProps<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n> extends VariantProps<typeof datePickerVariants> {\r\n name: TName;\r\n label?: string;\r\n helperText?: string;\r\n showError?: boolean;\r\n error?: string;\r\n fullWidth?: boolean;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n className?: string;\r\n id?: string;\r\n control?: Control<TFieldValues>;\r\n required?: boolean | string;\r\n validate?: (value: unknown) => boolean | string | Promise<boolean | string>;\r\n}\r\n\r\nexport function DatePicker<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>({\r\n name,\r\n label,\r\n helperText,\r\n showError = true,\r\n error: customError,\r\n size,\r\n variant,\r\n fullWidth = true,\r\n placeholder = \"Pick a date\",\r\n disabled,\r\n className,\r\n id: providedId,\r\n control: externalControl,\r\n required,\r\n validate,\r\n}: DatePickerProps<TFieldValues, TName>) {\r\n const generatedId = useId();\r\n const inputId = providedId ?? generatedId;\r\n const [open, setOpen] = useState(false);\r\n const formConfigContext = useContext(FormConfigContext);\r\n const formConfig: FormConfig = formConfigContext ?? {};\r\n const rhfContext = useRHFFormContext<TFieldValues>();\r\n const control = externalControl ?? rhfContext?.control;\r\n\r\n const registerFieldValidation = formConfigContext?.registerFieldValidation;\r\n const unregisterFieldValidation = formConfigContext?.unregisterFieldValidation;\r\n\r\n useEffect(() => {\r\n if (registerFieldValidation) {\r\n const rules: FieldValidationRules = {};\r\n if (required !== undefined) rules.required = required;\r\n if (validate !== undefined) rules.validate = validate;\r\n\r\n registerFieldValidation({\r\n name: name as string,\r\n type: \"date\",\r\n rules,\r\n });\r\n\r\n return () => {\r\n unregisterFieldValidation?.(name as string);\r\n };\r\n }\r\n }, [\r\n registerFieldValidation,\r\n unregisterFieldValidation,\r\n name,\r\n required,\r\n validate,\r\n ]);\r\n\r\n const { field, fieldState } = useController<TFieldValues, TName>({\r\n name,\r\n control,\r\n });\r\n\r\n const selectedDate =
|
|
1
|
+
{"version":3,"file":"DatePicker.cjs","names":[],"sources":["../../../../src/components/Form/DatePicker.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useContext, useEffect, useId, useState } from \"react\";\r\nimport { format, startOfDay } from \"date-fns\";\r\nimport { CalendarIcon } from \"lucide-react\";\r\nimport { useController, useFormContext as useRHFFormContext, type Control, type FieldPath, type FieldValues } from \"react-hook-form\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { Button } from \"../../primitives/button\";\r\nimport { Calendar } from \"../../primitives/calendar\";\r\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../primitives/popover\";\r\nimport { FormConfigContext, type FieldValidationRules, type FormConfig } from \"./context\";\r\nimport { FieldLayout } from \"./FieldLayout\";\r\n\r\nfunction toStartOfDay(value: unknown): Date | undefined {\r\n if (!value || value === \"\") {\r\n return undefined;\r\n }\r\n\r\n const date = value instanceof Date ? value : new Date(value as string | number);\r\n if (Number.isNaN(date.getTime())) {\r\n return undefined;\r\n }\r\n\r\n return startOfDay(date);\r\n}\r\n\r\nconst datePickerVariants = cva(\"w-full justify-start text-left font-normal\", {\r\n variants: {\r\n size: {\r\n sm: \"h-8 text-xs\",\r\n md: \"h-10 text-sm\",\r\n lg: \"h-12 text-base\",\r\n },\r\n variant: {\r\n default: \"border-input\",\r\n error: \"border-destructive\",\r\n success: \"border-green-500\",\r\n },\r\n },\r\n defaultVariants: {\r\n size: \"md\",\r\n variant: \"default\",\r\n },\r\n});\r\n\r\nexport interface DatePickerProps<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n> extends VariantProps<typeof datePickerVariants> {\r\n name: TName;\r\n label?: string;\r\n helperText?: string;\r\n showError?: boolean;\r\n error?: string;\r\n fullWidth?: boolean;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n className?: string;\r\n id?: string;\r\n control?: Control<TFieldValues>;\r\n required?: boolean | string;\r\n validate?: (value: unknown) => boolean | string | Promise<boolean | string>;\r\n}\r\n\r\nexport function DatePicker<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>({\r\n name,\r\n label,\r\n helperText,\r\n showError = true,\r\n error: customError,\r\n size,\r\n variant,\r\n fullWidth = true,\r\n placeholder = \"Pick a date\",\r\n disabled,\r\n className,\r\n id: providedId,\r\n control: externalControl,\r\n required,\r\n validate,\r\n}: DatePickerProps<TFieldValues, TName>) {\r\n const generatedId = useId();\r\n const inputId = providedId ?? generatedId;\r\n const [open, setOpen] = useState(false);\r\n const formConfigContext = useContext(FormConfigContext);\r\n const formConfig: FormConfig = formConfigContext ?? {};\r\n const rhfContext = useRHFFormContext<TFieldValues>();\r\n const control = externalControl ?? rhfContext?.control;\r\n\r\n const registerFieldValidation = formConfigContext?.registerFieldValidation;\r\n const unregisterFieldValidation = formConfigContext?.unregisterFieldValidation;\r\n\r\n useEffect(() => {\r\n if (registerFieldValidation) {\r\n const rules: FieldValidationRules = {};\r\n if (required !== undefined) rules.required = required;\r\n if (validate !== undefined) rules.validate = validate;\r\n\r\n registerFieldValidation({\r\n name: name as string,\r\n type: \"date\",\r\n rules,\r\n });\r\n\r\n return () => {\r\n unregisterFieldValidation?.(name as string);\r\n };\r\n }\r\n }, [\r\n registerFieldValidation,\r\n unregisterFieldValidation,\r\n name,\r\n required,\r\n validate,\r\n ]);\r\n\r\n const { field, fieldState } = useController<TFieldValues, TName>({\r\n name,\r\n control,\r\n });\r\n\r\n const selectedDate = toStartOfDay(field.value);\r\n const fieldError = fieldState.error?.message;\r\n const errorMessage = customError ?? fieldError;\r\n const hasError = !!errorMessage;\r\n const effectiveSize = size ?? formConfig.size ?? \"md\";\r\n const effectiveDisabled = disabled ?? formConfig.disabled;\r\n const effectiveVariant = hasError ? \"error\" : variant;\r\n\r\n return (\r\n <FieldLayout\r\n inputId={inputId}\r\n label={label}\r\n required={!!required}\r\n helperText={helperText}\r\n errorMessage={showError ? errorMessage : undefined}\r\n showError={showError}\r\n fullWidth={fullWidth}\r\n formConfig={formConfig}\r\n >\r\n <Popover open={open} onOpenChange={setOpen}>\r\n <PopoverTrigger asChild>\r\n <Button\r\n id={inputId}\r\n type=\"button\"\r\n variant=\"outline\"\r\n disabled={effectiveDisabled}\r\n aria-invalid={hasError}\r\n aria-describedby={\r\n hasError\r\n ? `${inputId}-error`\r\n : helperText\r\n ? `${inputId}-helper`\r\n : undefined\r\n }\r\n className={cn(\r\n datePickerVariants({ size: effectiveSize, variant: effectiveVariant }),\r\n !selectedDate && \"text-muted-foreground\",\r\n className\r\n )}\r\n >\r\n <CalendarIcon className=\"mr-2 h-4 w-4\" />\r\n {selectedDate ? format(selectedDate, \"PPP\") : placeholder}\r\n </Button>\r\n </PopoverTrigger>\r\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\r\n <Calendar\r\n mode=\"single\"\r\n selected={selectedDate}\r\n onSelect={(date) => {\r\n field.onChange(date ? startOfDay(date) : \"\");\r\n setOpen(false);\r\n }}\r\n />\r\n </PopoverContent>\r\n </Popover>\r\n </FieldLayout>\r\n );\r\n}\r\n\r\nDatePicker.displayName = \"DatePicker\";\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
const require_cn = require("../../utils/cn.cjs");
|
|
4
|
+
const require_button = require("../../primitives/button.cjs");
|
|
5
|
+
const require_context = require("./context.cjs");
|
|
6
|
+
const require_FieldLayout = require("./FieldLayout.cjs");
|
|
7
|
+
const require_popover = require("../../primitives/popover.cjs");
|
|
8
|
+
const require_calendar = require("../../primitives/calendar.cjs");
|
|
9
|
+
const require_separator = require("../../primitives/separator.cjs");
|
|
10
|
+
const require_TimePicker = require("./TimePicker.cjs");
|
|
11
|
+
let react = require("react");
|
|
12
|
+
let class_variance_authority = require("class-variance-authority");
|
|
13
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
14
|
+
let react_hook_form = require("react-hook-form");
|
|
15
|
+
let lucide_react = require("lucide-react");
|
|
16
|
+
let date_fns = require("date-fns");
|
|
17
|
+
//#region src/components/Form/DateTimePicker.tsx
|
|
18
|
+
var dateTimePickerVariants = (0, class_variance_authority.cva)("w-full justify-start text-left font-normal", {
|
|
19
|
+
variants: {
|
|
20
|
+
size: {
|
|
21
|
+
sm: "h-8 text-xs",
|
|
22
|
+
md: "h-10 text-sm",
|
|
23
|
+
lg: "h-12 text-base"
|
|
24
|
+
},
|
|
25
|
+
variant: {
|
|
26
|
+
default: "border-input",
|
|
27
|
+
error: "border-destructive",
|
|
28
|
+
success: "border-green-500"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
size: "md",
|
|
33
|
+
variant: "default"
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
function toValidDate(value) {
|
|
37
|
+
if (!value || value === "") return;
|
|
38
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
39
|
+
if (Number.isNaN(date.getTime())) return;
|
|
40
|
+
return date;
|
|
41
|
+
}
|
|
42
|
+
function hasTimePart(date) {
|
|
43
|
+
return date.getHours() !== 0 || date.getMinutes() !== 0 || date.getSeconds() !== 0 || date.getMilliseconds() !== 0;
|
|
44
|
+
}
|
|
45
|
+
function withPreservedTime(nextDay, previous) {
|
|
46
|
+
const next = new Date(nextDay);
|
|
47
|
+
next.setHours(previous.getHours(), previous.getMinutes(), previous.getSeconds(), previous.getMilliseconds());
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
function isSelectEventTarget(target) {
|
|
51
|
+
return target instanceof Element && Boolean(target.closest("[data-radix-select-content]"));
|
|
52
|
+
}
|
|
53
|
+
function DateTimePicker({ name, label, helperText, showError = true, error: customError, size, variant, fullWidth = true, placeholder = "Pick a date", disabled, className, id: providedId, control: externalControl, required, validate, minuteStep = 5 }) {
|
|
54
|
+
const generatedId = (0, react.useId)();
|
|
55
|
+
const inputId = providedId ?? generatedId;
|
|
56
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
57
|
+
const [timeOpen, setTimeOpen] = (0, react.useState)(false);
|
|
58
|
+
const [timeSet, setTimeSet] = (0, react.useState)(false);
|
|
59
|
+
const formConfigContext = (0, react.useContext)(require_context.FormConfigContext);
|
|
60
|
+
const formConfig = formConfigContext ?? {};
|
|
61
|
+
const rhfContext = (0, react_hook_form.useFormContext)();
|
|
62
|
+
const control = externalControl ?? rhfContext?.control;
|
|
63
|
+
const registerFieldValidation = formConfigContext?.registerFieldValidation;
|
|
64
|
+
const unregisterFieldValidation = formConfigContext?.unregisterFieldValidation;
|
|
65
|
+
(0, react.useEffect)(() => {
|
|
66
|
+
if (registerFieldValidation) {
|
|
67
|
+
const rules = {};
|
|
68
|
+
if (required !== void 0) rules.required = required;
|
|
69
|
+
if (validate !== void 0) rules.validate = validate;
|
|
70
|
+
registerFieldValidation({
|
|
71
|
+
name,
|
|
72
|
+
type: "datetime",
|
|
73
|
+
rules
|
|
74
|
+
});
|
|
75
|
+
return () => {
|
|
76
|
+
unregisterFieldValidation?.(name);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}, [
|
|
80
|
+
registerFieldValidation,
|
|
81
|
+
unregisterFieldValidation,
|
|
82
|
+
name,
|
|
83
|
+
required,
|
|
84
|
+
validate
|
|
85
|
+
]);
|
|
86
|
+
const { field, fieldState } = (0, react_hook_form.useController)({
|
|
87
|
+
name,
|
|
88
|
+
control
|
|
89
|
+
});
|
|
90
|
+
const selectedDate = toValidDate(field.value);
|
|
91
|
+
(0, react.useEffect)(() => {
|
|
92
|
+
const date = toValidDate(field.value);
|
|
93
|
+
if (!date) {
|
|
94
|
+
setTimeSet(false);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (hasTimePart(date)) setTimeSet(true);
|
|
98
|
+
}, [field.value]);
|
|
99
|
+
const fieldError = fieldState.error?.message;
|
|
100
|
+
const errorMessage = customError ?? fieldError;
|
|
101
|
+
const hasError = !!errorMessage;
|
|
102
|
+
const effectiveSize = size ?? formConfig.size ?? "md";
|
|
103
|
+
const effectiveDisabled = disabled ?? formConfig.disabled;
|
|
104
|
+
const effectiveVariant = hasError ? "error" : variant;
|
|
105
|
+
const displayHasTime = timeSet && !!selectedDate;
|
|
106
|
+
const handleOpenChange = (nextOpen) => {
|
|
107
|
+
setOpen(nextOpen);
|
|
108
|
+
if (nextOpen && timeSet) {
|
|
109
|
+
setTimeOpen(true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!nextOpen) setTimeOpen(false);
|
|
113
|
+
};
|
|
114
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_FieldLayout.FieldLayout, {
|
|
115
|
+
inputId,
|
|
116
|
+
label,
|
|
117
|
+
required: !!required,
|
|
118
|
+
helperText,
|
|
119
|
+
errorMessage: showError ? errorMessage : void 0,
|
|
120
|
+
showError,
|
|
121
|
+
fullWidth,
|
|
122
|
+
formConfig,
|
|
123
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_popover.Popover, {
|
|
124
|
+
open,
|
|
125
|
+
onOpenChange: handleOpenChange,
|
|
126
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_popover.PopoverTrigger, {
|
|
127
|
+
asChild: true,
|
|
128
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_button.Button, {
|
|
129
|
+
id: inputId,
|
|
130
|
+
type: "button",
|
|
131
|
+
variant: "outline",
|
|
132
|
+
disabled: effectiveDisabled,
|
|
133
|
+
"aria-invalid": hasError,
|
|
134
|
+
"aria-describedby": hasError ? `${inputId}-error` : helperText ? `${inputId}-helper` : void 0,
|
|
135
|
+
className: require_cn.cn(dateTimePickerVariants({
|
|
136
|
+
size: effectiveSize,
|
|
137
|
+
variant: effectiveVariant
|
|
138
|
+
}), !selectedDate && "text-muted-foreground", className),
|
|
139
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CalendarIcon, { className: "mr-2 h-4 w-4" }), selectedDate ? (0, date_fns.format)(selectedDate, displayHasTime ? "PPP p" : "PPP") : placeholder]
|
|
140
|
+
})
|
|
141
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_popover.PopoverContent, {
|
|
142
|
+
className: "w-auto p-0",
|
|
143
|
+
align: "start",
|
|
144
|
+
onPointerDownOutside: (event) => {
|
|
145
|
+
if (isSelectEventTarget(event.target)) event.preventDefault();
|
|
146
|
+
},
|
|
147
|
+
onFocusOutside: (event) => {
|
|
148
|
+
if (isSelectEventTarget(event.target)) event.preventDefault();
|
|
149
|
+
},
|
|
150
|
+
children: [
|
|
151
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_calendar.Calendar, {
|
|
152
|
+
mode: "single",
|
|
153
|
+
selected: selectedDate,
|
|
154
|
+
onSelect: (date) => {
|
|
155
|
+
if (!date) {
|
|
156
|
+
field.onChange("");
|
|
157
|
+
setTimeSet(false);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (selectedDate && timeSet) {
|
|
161
|
+
field.onChange(withPreservedTime(date, selectedDate));
|
|
162
|
+
setOpen(false);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
field.onChange((0, date_fns.startOfDay)(date));
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_separator.Separator, {}),
|
|
169
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
170
|
+
className: "p-3",
|
|
171
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_button.Button, {
|
|
172
|
+
type: "button",
|
|
173
|
+
variant: "ghost",
|
|
174
|
+
disabled: effectiveDisabled,
|
|
175
|
+
className: "h-8 w-full justify-start px-2 font-normal",
|
|
176
|
+
onClick: () => setTimeOpen((current) => !current),
|
|
177
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Clock, { className: "mr-2 h-4 w-4" }), displayHasTime && selectedDate ? (0, date_fns.format)(selectedDate, "p") : "Add time"]
|
|
178
|
+
}), timeOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_TimePicker.TimePicker, {
|
|
179
|
+
className: "mt-2",
|
|
180
|
+
value: selectedDate,
|
|
181
|
+
minuteStep,
|
|
182
|
+
disabled: effectiveDisabled,
|
|
183
|
+
onChange: (date) => {
|
|
184
|
+
field.onChange(date);
|
|
185
|
+
setTimeSet(true);
|
|
186
|
+
},
|
|
187
|
+
onClear: () => {
|
|
188
|
+
if (selectedDate) field.onChange((0, date_fns.startOfDay)(selectedDate));
|
|
189
|
+
setTimeSet(false);
|
|
190
|
+
}
|
|
191
|
+
}) : null]
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
})]
|
|
195
|
+
})
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
DateTimePicker.displayName = "DateTimePicker";
|
|
199
|
+
//#endregion
|
|
200
|
+
exports.DateTimePicker = DateTimePicker;
|
|
201
|
+
|
|
202
|
+
//# sourceMappingURL=DateTimePicker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DateTimePicker.cjs","names":[],"sources":["../../../../src/components/Form/DateTimePicker.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useContext, useEffect, useId, useState } from \"react\";\r\nimport { format, startOfDay } from \"date-fns\";\r\nimport { CalendarIcon, Clock } from \"lucide-react\";\r\nimport { useController, useFormContext as useRHFFormContext, type Control, type FieldPath, type FieldValues } from \"react-hook-form\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { Button } from \"../../primitives/button\";\r\nimport { Calendar } from \"../../primitives/calendar\";\r\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../primitives/popover\";\r\nimport { Separator } from \"../../primitives/separator\";\r\nimport { FormConfigContext, type FieldValidationRules, type FormConfig } from \"./context\";\r\nimport { FieldLayout } from \"./FieldLayout\";\r\nimport { TimePicker } from \"./TimePicker\";\r\n\r\nconst dateTimePickerVariants = cva(\"w-full justify-start text-left font-normal\", {\r\n variants: {\r\n size: {\r\n sm: \"h-8 text-xs\",\r\n md: \"h-10 text-sm\",\r\n lg: \"h-12 text-base\",\r\n },\r\n variant: {\r\n default: \"border-input\",\r\n error: \"border-destructive\",\r\n success: \"border-green-500\",\r\n },\r\n },\r\n defaultVariants: {\r\n size: \"md\",\r\n variant: \"default\",\r\n },\r\n});\r\n\r\nexport interface DateTimePickerProps<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n> extends VariantProps<typeof dateTimePickerVariants> {\r\n name: TName;\r\n label?: string;\r\n helperText?: string;\r\n showError?: boolean;\r\n error?: string;\r\n fullWidth?: boolean;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n className?: string;\r\n id?: string;\r\n control?: Control<TFieldValues>;\r\n required?: boolean | string;\r\n validate?: (value: unknown) => boolean | string | Promise<boolean | string>;\r\n minuteStep?: number;\r\n}\r\n\r\nfunction toValidDate(value: unknown): Date | undefined {\r\n if (!value || value === \"\") {\r\n return undefined;\r\n }\r\n\r\n const date = value instanceof Date ? value : new Date(value as string | number);\r\n if (Number.isNaN(date.getTime())) {\r\n return undefined;\r\n }\r\n\r\n return date;\r\n}\r\n\r\nfunction hasTimePart(date: Date): boolean {\r\n return date.getHours() !== 0 || date.getMinutes() !== 0 || date.getSeconds() !== 0 || date.getMilliseconds() !== 0;\r\n}\r\n\r\nfunction withPreservedTime(nextDay: Date, previous: Date): Date {\r\n const next = new Date(nextDay);\r\n next.setHours(\r\n previous.getHours(),\r\n previous.getMinutes(),\r\n previous.getSeconds(),\r\n previous.getMilliseconds()\r\n );\r\n return next;\r\n}\r\n\r\nfunction isSelectEventTarget(target: EventTarget | null): boolean {\r\n return target instanceof Element && Boolean(target.closest(\"[data-radix-select-content]\"));\r\n}\r\n\r\nexport function DateTimePicker<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>({\r\n name,\r\n label,\r\n helperText,\r\n showError = true,\r\n error: customError,\r\n size,\r\n variant,\r\n fullWidth = true,\r\n placeholder = \"Pick a date\",\r\n disabled,\r\n className,\r\n id: providedId,\r\n control: externalControl,\r\n required,\r\n validate,\r\n minuteStep = 5,\r\n}: DateTimePickerProps<TFieldValues, TName>) {\r\n const generatedId = useId();\r\n const inputId = providedId ?? generatedId;\r\n const [open, setOpen] = useState(false);\r\n const [timeOpen, setTimeOpen] = useState(false);\r\n const [timeSet, setTimeSet] = useState(false);\r\n const formConfigContext = useContext(FormConfigContext);\r\n const formConfig: FormConfig = formConfigContext ?? {};\r\n const rhfContext = useRHFFormContext<TFieldValues>();\r\n const control = externalControl ?? rhfContext?.control;\r\n\r\n const registerFieldValidation = formConfigContext?.registerFieldValidation;\r\n const unregisterFieldValidation = formConfigContext?.unregisterFieldValidation;\r\n\r\n useEffect(() => {\r\n if (registerFieldValidation) {\r\n const rules: FieldValidationRules = {};\r\n if (required !== undefined) rules.required = required;\r\n if (validate !== undefined) rules.validate = validate;\r\n\r\n registerFieldValidation({\r\n name: name as string,\r\n type: \"datetime\",\r\n rules,\r\n });\r\n\r\n return () => {\r\n unregisterFieldValidation?.(name as string);\r\n };\r\n }\r\n }, [\r\n registerFieldValidation,\r\n unregisterFieldValidation,\r\n name,\r\n required,\r\n validate,\r\n ]);\r\n\r\n const { field, fieldState } = useController<TFieldValues, TName>({\r\n name,\r\n control,\r\n });\r\n\r\n const selectedDate = toValidDate(field.value);\r\n\r\n useEffect(() => {\r\n const date = toValidDate(field.value);\r\n if (!date) {\r\n setTimeSet(false);\r\n return;\r\n }\r\n\r\n if (hasTimePart(date)) {\r\n setTimeSet(true);\r\n }\r\n }, [field.value]);\r\n\r\n const fieldError = fieldState.error?.message;\r\n const errorMessage = customError ?? fieldError;\r\n const hasError = !!errorMessage;\r\n const effectiveSize = size ?? formConfig.size ?? \"md\";\r\n const effectiveDisabled = disabled ?? formConfig.disabled;\r\n const effectiveVariant = hasError ? \"error\" : variant;\r\n const displayHasTime = timeSet && !!selectedDate;\r\n\r\n const handleOpenChange = (nextOpen: boolean) => {\r\n setOpen(nextOpen);\r\n if (nextOpen && timeSet) {\r\n setTimeOpen(true);\r\n return;\r\n }\r\n\r\n if (!nextOpen) {\r\n setTimeOpen(false);\r\n }\r\n };\r\n\r\n return (\r\n <FieldLayout\r\n inputId={inputId}\r\n label={label}\r\n required={!!required}\r\n helperText={helperText}\r\n errorMessage={showError ? errorMessage : undefined}\r\n showError={showError}\r\n fullWidth={fullWidth}\r\n formConfig={formConfig}\r\n >\r\n <Popover open={open} onOpenChange={handleOpenChange}>\r\n <PopoverTrigger asChild>\r\n <Button\r\n id={inputId}\r\n type=\"button\"\r\n variant=\"outline\"\r\n disabled={effectiveDisabled}\r\n aria-invalid={hasError}\r\n aria-describedby={\r\n hasError\r\n ? `${inputId}-error`\r\n : helperText\r\n ? `${inputId}-helper`\r\n : undefined\r\n }\r\n className={cn(\r\n dateTimePickerVariants({ size: effectiveSize, variant: effectiveVariant }),\r\n !selectedDate && \"text-muted-foreground\",\r\n className\r\n )}\r\n >\r\n <CalendarIcon className=\"mr-2 h-4 w-4\" />\r\n {selectedDate\r\n ? format(selectedDate, displayHasTime ? \"PPP p\" : \"PPP\")\r\n : placeholder}\r\n </Button>\r\n </PopoverTrigger>\r\n <PopoverContent\r\n className=\"w-auto p-0\"\r\n align=\"start\"\r\n onPointerDownOutside={(event) => {\r\n if (isSelectEventTarget(event.target)) {\r\n event.preventDefault();\r\n }\r\n }}\r\n onFocusOutside={(event) => {\r\n if (isSelectEventTarget(event.target)) {\r\n event.preventDefault();\r\n }\r\n }}\r\n >\r\n <Calendar\r\n mode=\"single\"\r\n selected={selectedDate}\r\n onSelect={(date) => {\r\n if (!date) {\r\n field.onChange(\"\");\r\n setTimeSet(false);\r\n return;\r\n }\r\n\r\n if (selectedDate && timeSet) {\r\n field.onChange(withPreservedTime(date, selectedDate));\r\n setOpen(false);\r\n return;\r\n }\r\n\r\n field.onChange(startOfDay(date));\r\n }}\r\n />\r\n <Separator />\r\n <div className=\"p-3\">\r\n <Button\r\n type=\"button\"\r\n variant=\"ghost\"\r\n disabled={effectiveDisabled}\r\n className=\"h-8 w-full justify-start px-2 font-normal\"\r\n onClick={() => setTimeOpen((current) => !current)}\r\n >\r\n <Clock className=\"mr-2 h-4 w-4\" />\r\n {displayHasTime && selectedDate ? format(selectedDate, \"p\") : \"Add time\"}\r\n </Button>\r\n {timeOpen ? (\r\n <TimePicker\r\n className=\"mt-2\"\r\n value={selectedDate}\r\n minuteStep={minuteStep}\r\n disabled={effectiveDisabled}\r\n onChange={(date) => {\r\n field.onChange(date);\r\n setTimeSet(true);\r\n }}\r\n onClear={() => {\r\n if (selectedDate) {\r\n field.onChange(startOfDay(selectedDate));\r\n }\r\n setTimeSet(false);\r\n }}\r\n />\r\n ) : null}\r\n </div>\r\n </PopoverContent>\r\n </Popover>\r\n </FieldLayout>\r\n );\r\n}\r\n\r\nDateTimePicker.displayName = \"DateTimePicker\";\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
let lucide_react = require("lucide-react");
|
|
5
|
+
//#region src/components/Form/PasswordRevealToggle.tsx
|
|
6
|
+
function PasswordRevealToggle({ visible, onToggle, disabled = false }) {
|
|
7
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8
|
+
type: "button",
|
|
9
|
+
onClick: onToggle,
|
|
10
|
+
disabled,
|
|
11
|
+
className: "text-muted-foreground hover:text-foreground transition-colors disabled:pointer-events-none disabled:opacity-50",
|
|
12
|
+
tabIndex: -1,
|
|
13
|
+
"aria-label": visible ? "Hide password" : "Show password",
|
|
14
|
+
"aria-pressed": visible,
|
|
15
|
+
children: visible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Eye, { className: "h-4 w-4" })
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
exports.PasswordRevealToggle = PasswordRevealToggle;
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=PasswordRevealToggle.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordRevealToggle.cjs","names":[],"sources":["../../../../src/components/Form/PasswordRevealToggle.tsx"],"sourcesContent":["\"use client\";\n\nimport { Eye, EyeOff } from \"lucide-react\";\n\nexport interface PasswordRevealToggleProps {\n\tvisible: boolean;\n\tonToggle: () => void;\n\tdisabled?: boolean;\n}\n\nexport function PasswordRevealToggle({\n\tvisible,\n\tonToggle,\n\tdisabled = false,\n}: PasswordRevealToggleProps) {\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tonClick={onToggle}\n\t\t\tdisabled={disabled}\n\t\t\tclassName=\"text-muted-foreground hover:text-foreground transition-colors disabled:pointer-events-none disabled:opacity-50\"\n\t\t\ttabIndex={-1}\n\t\t\taria-label={visible ? \"Hide password\" : \"Show password\"}\n\t\t\taria-pressed={visible}\n\t\t>\n\t\t\t{visible ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n\t\t</button>\n\t);\n}\n"],"mappings":";;;;;AAUA,SAAgB,qBAAqB,EACpC,SACA,UACA,WAAW,SACkB;CAC7B,OACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACC,MAAK;EACL,SAAS;EACC;EACV,WAAU;EACV,UAAU;EACV,cAAY,UAAU,kBAAkB;EACxC,gBAAc;EAEb,UAAA,UAAU,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,QAAD,EAAQ,WAAU,UAAW,CAAA,IAAI,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAA,KAAD,EAAK,WAAU,UAAW,CAAA;CAC/D,CAAA;AAEV"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const require_cn = require("../../utils/cn.cjs");
|
|
4
4
|
const require_context = require("./context.cjs");
|
|
5
5
|
const require_FieldLayout = require("./FieldLayout.cjs");
|
|
6
|
+
const require_PasswordRevealToggle = require("./PasswordRevealToggle.cjs");
|
|
6
7
|
const require_textbox = require("../../primitives/textbox.cjs");
|
|
7
8
|
let react = require("react");
|
|
8
9
|
let class_variance_authority = require("class-variance-authority");
|
|
@@ -37,6 +38,7 @@ var textBoxVariants = (0, class_variance_authority.cva)("", {
|
|
|
37
38
|
* - Label, helper text, and error message support
|
|
38
39
|
* - Prefix/suffix elements
|
|
39
40
|
* - Clear button functionality
|
|
41
|
+
* - Built-in password reveal toggle when `type="password"`
|
|
40
42
|
*
|
|
41
43
|
* @example
|
|
42
44
|
* ```tsx
|
|
@@ -44,14 +46,18 @@ var textBoxVariants = (0, class_variance_authority.cva)("", {
|
|
|
44
46
|
* <Form onSubmit={handleSubmit} defaultValues={{ username: "", email: "" }}>
|
|
45
47
|
* <TextBox name="username" label="Username" required minLength={3} maxLength={50} />
|
|
46
48
|
* <TextBox name="email" label="Email" type="email" required email />
|
|
49
|
+
* <TextBox name="password" label="Password" type="password" required />
|
|
47
50
|
* <TextBox name="website" label="Website" url />
|
|
48
51
|
* <Button type="submit">Submit</Button>
|
|
49
52
|
* </Form>
|
|
50
53
|
* ```
|
|
51
54
|
*/
|
|
52
|
-
function TextBoxInner({ name, label, helperText, showError = true, error: customError, size, variant, fullWidth = true, className, disabled, prefix, suffix, allowClear, onClear, id: providedId, control: externalControl, required, minLength, maxLength, minValue, maxValue, pattern, email, url, validate, ...props }, ref) {
|
|
55
|
+
function TextBoxInner({ name, label, helperText, showError = true, error: customError, size, variant, fullWidth = true, className, disabled, prefix, suffix, allowClear, onClear, passwordToggle, id: providedId, control: externalControl, required, minLength, maxLength, minValue, maxValue, pattern, email, url, validate, ...props }, ref) {
|
|
53
56
|
const generatedId = (0, react.useId)();
|
|
54
57
|
const inputId = providedId ?? generatedId;
|
|
58
|
+
const [passwordVisible, setPasswordVisible] = (0, react.useState)(false);
|
|
59
|
+
const showPasswordToggle = props.type === "password" && passwordToggle !== false;
|
|
60
|
+
const resolvedType = showPasswordToggle && passwordVisible ? "text" : props.type;
|
|
55
61
|
const formConfigContext = (0, react.useContext)(require_context.FormConfigContext);
|
|
56
62
|
const formConfig = formConfigContext ?? {};
|
|
57
63
|
const rhfContext = (0, react_hook_form.useFormContext)();
|
|
@@ -137,6 +143,7 @@ function TextBoxInner({ name, label, helperText, showError = true, error: custom
|
|
|
137
143
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_textbox.TextBoxPrimitive, {
|
|
138
144
|
...props,
|
|
139
145
|
...field,
|
|
146
|
+
type: resolvedType,
|
|
140
147
|
ref: (node) => {
|
|
141
148
|
if (typeof ref === "function") ref(node);
|
|
142
149
|
else if (ref) ref.current = node;
|
|
@@ -149,21 +156,29 @@ function TextBoxInner({ name, label, helperText, showError = true, error: custom
|
|
|
149
156
|
className: require_cn.cn(textBoxVariants({
|
|
150
157
|
size: effectiveSize,
|
|
151
158
|
variant: effectiveVariant
|
|
152
|
-
}), prefix && "pl-10", (suffix || allowClear) && "pr-10", className)
|
|
159
|
+
}), prefix && "pl-10", (suffix || allowClear || showPasswordToggle) && "pr-10", className)
|
|
153
160
|
}),
|
|
154
|
-
(suffix || allowClear && field.value) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
161
|
+
(suffix || allowClear && field.value || showPasswordToggle) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
155
162
|
className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1",
|
|
156
|
-
children: [
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
children: [
|
|
164
|
+
allowClear && field.value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
165
|
+
type: "button",
|
|
166
|
+
onClick: handleClear,
|
|
167
|
+
className: "text-muted-foreground hover:text-foreground transition-colors",
|
|
168
|
+
tabIndex: -1,
|
|
169
|
+
"aria-label": "Clear",
|
|
170
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "h-4 w-4" })
|
|
171
|
+
}),
|
|
172
|
+
showPasswordToggle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_PasswordRevealToggle.PasswordRevealToggle, {
|
|
173
|
+
visible: passwordVisible,
|
|
174
|
+
onToggle: () => setPasswordVisible((visible) => !visible),
|
|
175
|
+
disabled: effectiveDisabled
|
|
176
|
+
}),
|
|
177
|
+
suffix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
178
|
+
className: "text-muted-foreground",
|
|
179
|
+
children: suffix
|
|
180
|
+
})
|
|
181
|
+
]
|
|
167
182
|
})
|
|
168
183
|
]
|
|
169
184
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextBox.cjs","names":[],"sources":["../../../../src/components/Form/TextBox.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { forwardRef, useId, useContext, useEffect } from \"react\";\r\nimport type { InputHTMLAttributes, ReactNode } from \"react\";\r\nimport { useController, useFormContext as useRHFFormContext, type FieldValues, type FieldPath, type Control } from \"react-hook-form\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { FormConfigContext, type FormConfig, type FieldValidationRules } from \"../Form/context\";\r\nimport { FieldLayout } from \"./FieldLayout\";\r\nimport { TextBoxPrimitive } from \"../../primitives/textbox\";\r\nimport { X } from \"lucide-react\";\r\n\r\nconst textBoxVariants = cva(\r\n \"\",\r\n {\r\n variants: {\r\n size: {\r\n sm: \"h-8 text-xs px-2\",\r\n md: \"h-10 text-sm px-3\",\r\n lg: \"h-12 text-base px-4\",\r\n },\r\n variant: {\r\n default: \"border-input focus-visible:ring-ring\",\r\n error: \"border-destructive focus-visible:ring-destructive\",\r\n success: \"border-green-500 focus-visible:ring-green-500\",\r\n },\r\n },\r\n defaultVariants: {\r\n size: \"md\",\r\n variant: \"default\",\r\n },\r\n }\r\n);\r\n\r\n/**\r\n * Validation rule with optional custom message\r\n */\r\ntype ValidationRule<T> = T | { value: T; message: string };\r\n\r\nexport interface TextBoxProps<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n> extends Omit<InputHTMLAttributes<HTMLInputElement>, \"size\" | \"name\" | \"prefix\" | \"pattern\" | \"required\" | \"minLength\" | \"maxLength\">,\r\n VariantProps<typeof textBoxVariants> {\r\n /** Field name - required for form integration */\r\n name: TName;\r\n /** Label text for the input */\r\n label?: string;\r\n /** Helper text displayed below the input */\r\n helperText?: string;\r\n /** Whether to show the error message */\r\n showError?: boolean;\r\n /** Custom error message (overrides form error) */\r\n error?: string;\r\n /** Whether the input should take full width */\r\n fullWidth?: boolean;\r\n /** Prefix element */\r\n prefix?: ReactNode;\r\n /** Suffix element */\r\n suffix?: ReactNode;\r\n /** Allow clear button */\r\n allowClear?: boolean;\r\n /** Callback when clear is clicked */\r\n onClear?: () => void;\r\n /** External control (for use outside Form) */\r\n control?: Control<TFieldValues>;\r\n \r\n // Validation props\r\n /** Field is required */\r\n required?: boolean | string;\r\n /** Minimum length for strings */\r\n minLength?: ValidationRule<number>;\r\n /** Maximum length for strings */\r\n maxLength?: ValidationRule<number>;\r\n /** Minimum value for numbers */\r\n minValue?: ValidationRule<number>;\r\n /** Maximum value for numbers */\r\n maxValue?: ValidationRule<number>;\r\n /** Regex pattern for validation */\r\n pattern?: ValidationRule<RegExp>;\r\n /** Email validation */\r\n email?: boolean | string;\r\n /** URL validation */\r\n url?: boolean | string;\r\n /** Custom validation function */\r\n validate?: (value: unknown) => boolean | string | Promise<boolean | string>;\r\n}\r\n\r\n/**\r\n * TextBox component with form integration and automatic validation registration\r\n * \r\n * This is a wrapper around the shadcn/ui Input primitive that adds:\r\n * - Form integration with react-hook-form\r\n * - Automatic validation registration\r\n * - Label, helper text, and error message support\r\n * - Prefix/suffix elements\r\n * - Clear button functionality\r\n * \r\n * @example\r\n * ```tsx\r\n * // Inside a Form component - validation is automatically registered\r\n * <Form onSubmit={handleSubmit} defaultValues={{ username: \"\", email: \"\" }}>\r\n * <TextBox name=\"username\" label=\"Username\" required minLength={3} maxLength={50} />\r\n * <TextBox name=\"email\" label=\"Email\" type=\"email\" required email />\r\n * <TextBox name=\"website\" label=\"Website\" url />\r\n * <Button type=\"submit\">Submit</Button>\r\n * </Form>\r\n * ```\r\n */\r\nfunction TextBoxInner<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>(\r\n {\r\n name,\r\n label,\r\n helperText,\r\n showError = true,\r\n error: customError,\r\n size,\r\n variant,\r\n fullWidth = true,\r\n className,\r\n disabled,\r\n prefix,\r\n suffix,\r\n allowClear,\r\n onClear,\r\n id: providedId,\r\n control: externalControl,\r\n // Validation props\r\n required,\r\n minLength,\r\n maxLength,\r\n minValue,\r\n maxValue,\r\n pattern,\r\n email,\r\n url,\r\n validate,\r\n ...props\r\n }: TextBoxProps<TFieldValues, TName>,\r\n ref: React.ForwardedRef<HTMLInputElement>\r\n) {\r\n const generatedId = useId();\r\n const inputId = providedId ?? generatedId;\r\n \r\n // Try to get form context\r\n const formConfigContext = useContext(FormConfigContext);\r\n const formConfig: FormConfig = formConfigContext ?? {};\r\n\r\n // Get form context from react-hook-form\r\n const rhfContext = useRHFFormContext<TFieldValues>();\r\n const control = externalControl ?? rhfContext?.control;\r\n\r\n // Destructure stable function references so the effect doesn't depend on the\r\n // context object identity (which changes every render due to inline construction).\r\n const registerFieldValidation = formConfigContext?.registerFieldValidation;\r\n const unregisterFieldValidation = formConfigContext?.unregisterFieldValidation;\r\n\r\n // Register validation rules with the form\r\n useEffect(() => {\r\n if (registerFieldValidation) {\r\n const rules: FieldValidationRules = {};\r\n\r\n if (required !== undefined) rules.required = required;\r\n if (minLength !== undefined) rules.minLength = minLength;\r\n if (maxLength !== undefined) rules.maxLength = maxLength;\r\n if (minValue !== undefined) rules.min = minValue;\r\n if (maxValue !== undefined) rules.max = maxValue;\r\n if (pattern !== undefined) rules.pattern = pattern;\r\n if (email !== undefined) rules.email = email;\r\n if (url !== undefined) rules.url = url;\r\n if (validate !== undefined) rules.validate = validate;\r\n\r\n // Determine field type and auto-infer rules from input type\r\n const inputType = props.type ?? \"text\";\r\n let fieldType: \"string\" | \"number\" | \"date\" = \"string\";\r\n\r\n if (inputType === \"number\") {\r\n fieldType = \"number\";\r\n } else if (inputType === \"date\") {\r\n fieldType = \"date\";\r\n } else if (inputType === \"email\" && rules.email === undefined) {\r\n rules.email = true;\r\n } else if (inputType === \"url\" && rules.url === undefined) {\r\n rules.url = true;\r\n } else if (inputType === \"tel\" && rules.pattern === undefined) {\r\n rules.pattern = { value: /^\\+?[\\d\\s\\-().]{7,}$/, message: \"Invalid phone number\" };\r\n }\r\n\r\n registerFieldValidation({\r\n name: name as string,\r\n type: fieldType,\r\n rules,\r\n });\r\n\r\n return () => {\r\n unregisterFieldValidation?.(name as string);\r\n };\r\n }\r\n }, [\r\n registerFieldValidation,\r\n unregisterFieldValidation,\r\n name,\r\n required,\r\n minLength,\r\n maxLength,\r\n minValue,\r\n maxValue,\r\n pattern,\r\n email,\r\n url,\r\n validate,\r\n props.type,\r\n ]);\r\n\r\n // Use controller for form integration\r\n const { field, fieldState } = useController<TFieldValues, TName>({\r\n name,\r\n control,\r\n });\r\n\r\n const fieldError = fieldState.error?.message;\r\n const errorMessage = customError ?? fieldError;\r\n const hasError = !!errorMessage;\r\n \r\n // Merge sizes - prop takes precedence over form config\r\n const effectiveSize = size ?? formConfig.size ?? \"md\";\r\n const effectiveDisabled = disabled ?? formConfig.disabled;\r\n \r\n // Determine variant based on error state\r\n const effectiveVariant = hasError ? \"error\" : variant;\r\n\r\n const handleClear = () => {\r\n field.onChange(\"\");\r\n onClear?.();\r\n };\r\n\r\n return (\r\n <FieldLayout\r\n inputId={inputId}\r\n label={label}\r\n required={!!required}\r\n helperText={helperText}\r\n errorMessage={showError ? errorMessage : undefined}\r\n showError={showError}\r\n fullWidth={fullWidth}\r\n formConfig={formConfig}\r\n >\r\n <div className=\"relative\">\r\n {prefix && (\r\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\r\n {prefix}\r\n </div>\r\n )}\r\n\r\n <TextBoxPrimitive\r\n {...props}\r\n {...field}\r\n ref={(node) => {\r\n if (typeof ref === \"function\") {\r\n ref(node);\r\n } else if (ref) {\r\n ref.current = node;\r\n }\r\n field.ref(node);\r\n }}\r\n id={inputId}\r\n disabled={effectiveDisabled}\r\n aria-invalid={hasError}\r\n aria-describedby={\r\n hasError\r\n ? `${inputId}-error`\r\n : helperText\r\n ? `${inputId}-helper`\r\n : undefined\r\n }\r\n className={cn(\r\n textBoxVariants({ size: effectiveSize, variant: effectiveVariant }),\r\n prefix && \"pl-10\",\r\n (suffix || allowClear) && \"pr-10\",\r\n className\r\n )}\r\n />\r\n\r\n {(suffix || (allowClear && field.value)) && (\r\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1\">\r\n {allowClear && field.value && (\r\n <button\r\n type=\"button\"\r\n onClick={handleClear}\r\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\r\n tabIndex={-1}\r\n aria-label=\"Clear\"\r\n >\r\n <X className=\"h-4 w-4\" />\r\n </button>\r\n )}\r\n {suffix && (\r\n <span className=\"text-muted-foreground\">{suffix}</span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </FieldLayout>\r\n );\r\n}\r\n\r\n// Use forwardRef with generic support\r\nexport const TextBox = forwardRef(TextBoxInner) as <\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>(\r\n props: TextBoxProps<TFieldValues, TName> & { ref?: React.ForwardedRef<HTMLInputElement> }\r\n) => React.ReactElement;\r\n\r\n(TextBox as React.FC).displayName = \"TextBox\";\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TextBox.cjs","names":[],"sources":["../../../../src/components/Form/TextBox.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { forwardRef, useId, useContext, useEffect, useState } from \"react\";\r\nimport type { InputHTMLAttributes, ReactNode } from \"react\";\r\nimport { useController, useFormContext as useRHFFormContext, type FieldValues, type FieldPath, type Control } from \"react-hook-form\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { FormConfigContext, type FormConfig, type FieldValidationRules } from \"../Form/context\";\r\nimport { FieldLayout } from \"./FieldLayout\";\r\nimport { PasswordRevealToggle } from \"./PasswordRevealToggle\";\r\nimport { TextBoxPrimitive } from \"../../primitives/textbox\";\r\nimport { X } from \"lucide-react\";\r\n\r\nconst textBoxVariants = cva(\r\n \"\",\r\n {\r\n variants: {\r\n size: {\r\n sm: \"h-8 text-xs px-2\",\r\n md: \"h-10 text-sm px-3\",\r\n lg: \"h-12 text-base px-4\",\r\n },\r\n variant: {\r\n default: \"border-input focus-visible:ring-ring\",\r\n error: \"border-destructive focus-visible:ring-destructive\",\r\n success: \"border-green-500 focus-visible:ring-green-500\",\r\n },\r\n },\r\n defaultVariants: {\r\n size: \"md\",\r\n variant: \"default\",\r\n },\r\n }\r\n);\r\n\r\n/**\r\n * Validation rule with optional custom message\r\n */\r\ntype ValidationRule<T> = T | { value: T; message: string };\r\n\r\nexport interface TextBoxProps<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n> extends Omit<InputHTMLAttributes<HTMLInputElement>, \"size\" | \"name\" | \"prefix\" | \"pattern\" | \"required\" | \"minLength\" | \"maxLength\">,\r\n VariantProps<typeof textBoxVariants> {\r\n /** Field name - required for form integration */\r\n name: TName;\r\n /** Label text for the input */\r\n label?: string;\r\n /** Helper text displayed below the input */\r\n helperText?: string;\r\n /** Whether to show the error message */\r\n showError?: boolean;\r\n /** Custom error message (overrides form error) */\r\n error?: string;\r\n /** Whether the input should take full width */\r\n fullWidth?: boolean;\r\n /** Prefix element */\r\n prefix?: ReactNode;\r\n /** Suffix element */\r\n suffix?: ReactNode;\r\n /** Allow clear button */\r\n allowClear?: boolean;\r\n /** Callback when clear is clicked */\r\n onClear?: () => void;\r\n /**\r\n * Show an eye toggle for password fields.\r\n * Defaults to true when `type=\"password\"`.\r\n */\r\n passwordToggle?: boolean;\r\n /** External control (for use outside Form) */\r\n control?: Control<TFieldValues>;\r\n \r\n // Validation props\r\n /** Field is required */\r\n required?: boolean | string;\r\n /** Minimum length for strings */\r\n minLength?: ValidationRule<number>;\r\n /** Maximum length for strings */\r\n maxLength?: ValidationRule<number>;\r\n /** Minimum value for numbers */\r\n minValue?: ValidationRule<number>;\r\n /** Maximum value for numbers */\r\n maxValue?: ValidationRule<number>;\r\n /** Regex pattern for validation */\r\n pattern?: ValidationRule<RegExp>;\r\n /** Email validation */\r\n email?: boolean | string;\r\n /** URL validation */\r\n url?: boolean | string;\r\n /** Custom validation function */\r\n validate?: (value: unknown) => boolean | string | Promise<boolean | string>;\r\n}\r\n\r\n/**\r\n * TextBox component with form integration and automatic validation registration\r\n * \r\n * This is a wrapper around the shadcn/ui Input primitive that adds:\r\n * - Form integration with react-hook-form\r\n * - Automatic validation registration\r\n * - Label, helper text, and error message support\r\n * - Prefix/suffix elements\r\n * - Clear button functionality\r\n * - Built-in password reveal toggle when `type=\"password\"`\r\n * \r\n * @example\r\n * ```tsx\r\n * // Inside a Form component - validation is automatically registered\r\n * <Form onSubmit={handleSubmit} defaultValues={{ username: \"\", email: \"\" }}>\r\n * <TextBox name=\"username\" label=\"Username\" required minLength={3} maxLength={50} />\r\n * <TextBox name=\"email\" label=\"Email\" type=\"email\" required email />\r\n * <TextBox name=\"password\" label=\"Password\" type=\"password\" required />\r\n * <TextBox name=\"website\" label=\"Website\" url />\r\n * <Button type=\"submit\">Submit</Button>\r\n * </Form>\r\n * ```\r\n */\r\nfunction TextBoxInner<\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>(\r\n {\r\n name,\r\n label,\r\n helperText,\r\n showError = true,\r\n error: customError,\r\n size,\r\n variant,\r\n fullWidth = true,\r\n className,\r\n disabled,\r\n prefix,\r\n suffix,\r\n allowClear,\r\n onClear,\r\n passwordToggle,\r\n id: providedId,\r\n control: externalControl,\r\n // Validation props\r\n required,\r\n minLength,\r\n maxLength,\r\n minValue,\r\n maxValue,\r\n pattern,\r\n email,\r\n url,\r\n validate,\r\n ...props\r\n }: TextBoxProps<TFieldValues, TName>,\r\n ref: React.ForwardedRef<HTMLInputElement>\r\n) {\r\n const generatedId = useId();\r\n const inputId = providedId ?? generatedId;\r\n const [passwordVisible, setPasswordVisible] = useState(false);\r\n const isPasswordType = props.type === \"password\";\r\n const showPasswordToggle = isPasswordType && passwordToggle !== false;\r\n const resolvedType = showPasswordToggle && passwordVisible ? \"text\" : props.type;\r\n \r\n // Try to get form context\r\n const formConfigContext = useContext(FormConfigContext);\r\n const formConfig: FormConfig = formConfigContext ?? {};\r\n\r\n // Get form context from react-hook-form\r\n const rhfContext = useRHFFormContext<TFieldValues>();\r\n const control = externalControl ?? rhfContext?.control;\r\n\r\n // Destructure stable function references so the effect doesn't depend on the\r\n // context object identity (which changes every render due to inline construction).\r\n const registerFieldValidation = formConfigContext?.registerFieldValidation;\r\n const unregisterFieldValidation = formConfigContext?.unregisterFieldValidation;\r\n\r\n // Register validation rules with the form\r\n useEffect(() => {\r\n if (registerFieldValidation) {\r\n const rules: FieldValidationRules = {};\r\n\r\n if (required !== undefined) rules.required = required;\r\n if (minLength !== undefined) rules.minLength = minLength;\r\n if (maxLength !== undefined) rules.maxLength = maxLength;\r\n if (minValue !== undefined) rules.min = minValue;\r\n if (maxValue !== undefined) rules.max = maxValue;\r\n if (pattern !== undefined) rules.pattern = pattern;\r\n if (email !== undefined) rules.email = email;\r\n if (url !== undefined) rules.url = url;\r\n if (validate !== undefined) rules.validate = validate;\r\n\r\n // Determine field type and auto-infer rules from input type\r\n const inputType = props.type ?? \"text\";\r\n let fieldType: \"string\" | \"number\" | \"date\" = \"string\";\r\n\r\n if (inputType === \"number\") {\r\n fieldType = \"number\";\r\n } else if (inputType === \"date\") {\r\n fieldType = \"date\";\r\n } else if (inputType === \"email\" && rules.email === undefined) {\r\n rules.email = true;\r\n } else if (inputType === \"url\" && rules.url === undefined) {\r\n rules.url = true;\r\n } else if (inputType === \"tel\" && rules.pattern === undefined) {\r\n rules.pattern = { value: /^\\+?[\\d\\s\\-().]{7,}$/, message: \"Invalid phone number\" };\r\n }\r\n\r\n registerFieldValidation({\r\n name: name as string,\r\n type: fieldType,\r\n rules,\r\n });\r\n\r\n return () => {\r\n unregisterFieldValidation?.(name as string);\r\n };\r\n }\r\n }, [\r\n registerFieldValidation,\r\n unregisterFieldValidation,\r\n name,\r\n required,\r\n minLength,\r\n maxLength,\r\n minValue,\r\n maxValue,\r\n pattern,\r\n email,\r\n url,\r\n validate,\r\n props.type,\r\n ]);\r\n\r\n // Use controller for form integration\r\n const { field, fieldState } = useController<TFieldValues, TName>({\r\n name,\r\n control,\r\n });\r\n\r\n const fieldError = fieldState.error?.message;\r\n const errorMessage = customError ?? fieldError;\r\n const hasError = !!errorMessage;\r\n \r\n // Merge sizes - prop takes precedence over form config\r\n const effectiveSize = size ?? formConfig.size ?? \"md\";\r\n const effectiveDisabled = disabled ?? formConfig.disabled;\r\n \r\n // Determine variant based on error state\r\n const effectiveVariant = hasError ? \"error\" : variant;\r\n\r\n const handleClear = () => {\r\n field.onChange(\"\");\r\n onClear?.();\r\n };\r\n\r\n return (\r\n <FieldLayout\r\n inputId={inputId}\r\n label={label}\r\n required={!!required}\r\n helperText={helperText}\r\n errorMessage={showError ? errorMessage : undefined}\r\n showError={showError}\r\n fullWidth={fullWidth}\r\n formConfig={formConfig}\r\n >\r\n <div className=\"relative\">\r\n {prefix && (\r\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\r\n {prefix}\r\n </div>\r\n )}\r\n\r\n <TextBoxPrimitive\r\n {...props}\r\n {...field}\r\n type={resolvedType}\r\n ref={(node) => {\r\n if (typeof ref === \"function\") {\r\n ref(node);\r\n } else if (ref) {\r\n ref.current = node;\r\n }\r\n field.ref(node);\r\n }}\r\n id={inputId}\r\n disabled={effectiveDisabled}\r\n aria-invalid={hasError}\r\n aria-describedby={\r\n hasError\r\n ? `${inputId}-error`\r\n : helperText\r\n ? `${inputId}-helper`\r\n : undefined\r\n }\r\n className={cn(\r\n textBoxVariants({ size: effectiveSize, variant: effectiveVariant }),\r\n prefix && \"pl-10\",\r\n (suffix || allowClear || showPasswordToggle) && \"pr-10\",\r\n className\r\n )}\r\n />\r\n\r\n {(suffix || (allowClear && field.value) || showPasswordToggle) && (\r\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1\">\r\n {allowClear && field.value && (\r\n <button\r\n type=\"button\"\r\n onClick={handleClear}\r\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\r\n tabIndex={-1}\r\n aria-label=\"Clear\"\r\n >\r\n <X className=\"h-4 w-4\" />\r\n </button>\r\n )}\r\n {showPasswordToggle && (\r\n <PasswordRevealToggle\r\n visible={passwordVisible}\r\n onToggle={() => setPasswordVisible((visible) => !visible)}\r\n disabled={effectiveDisabled}\r\n />\r\n )}\r\n {suffix && (\r\n <span className=\"text-muted-foreground\">{suffix}</span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </FieldLayout>\r\n );\r\n}\r\n\r\n// Use forwardRef with generic support\r\nexport const TextBox = forwardRef(TextBoxInner) as <\r\n TFieldValues extends FieldValues = FieldValues,\r\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\r\n>(\r\n props: TextBoxProps<TFieldValues, TName> & { ref?: React.ForwardedRef<HTMLInputElement> }\r\n) => React.ReactElement;\r\n\r\n(TextBox as React.FC).displayName = \"TextBox\";\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
const require_cn = require("../../utils/cn.cjs");
|
|
4
|
+
const require_button = require("../../primitives/button.cjs");
|
|
5
|
+
const require_select = require("../../primitives/select.cjs");
|
|
6
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
let date_fns = require("date-fns");
|
|
8
|
+
//#region src/components/Form/TimePicker.tsx
|
|
9
|
+
function getHour12(date) {
|
|
10
|
+
const hour = date.getHours() % 12;
|
|
11
|
+
return hour === 0 ? 12 : hour;
|
|
12
|
+
}
|
|
13
|
+
function getPeriod(date) {
|
|
14
|
+
return date.getHours() >= 12 ? "PM" : "AM";
|
|
15
|
+
}
|
|
16
|
+
function toHours24(hour12, period) {
|
|
17
|
+
const hour = hour12 % 12;
|
|
18
|
+
return period === "PM" ? hour + 12 : hour;
|
|
19
|
+
}
|
|
20
|
+
function applyTime(base, hour12, minute, period) {
|
|
21
|
+
const next = base ? new Date(base) : (0, date_fns.startOfDay)(/* @__PURE__ */ new Date());
|
|
22
|
+
return (0, date_fns.setMinutes)((0, date_fns.setHours)(next, toHours24(hour12, period)), minute);
|
|
23
|
+
}
|
|
24
|
+
function minuteOptions(minuteStep, currentMinute) {
|
|
25
|
+
const steps = [];
|
|
26
|
+
for (let minute = 0; minute < 60; minute += minuteStep) steps.push(minute);
|
|
27
|
+
if (currentMinute !== void 0 && !steps.includes(currentMinute)) return [...steps, currentMinute].sort((a, b) => a - b);
|
|
28
|
+
return steps;
|
|
29
|
+
}
|
|
30
|
+
function TimePicker({ value, onChange, onClear, minuteStep = 5, disabled, className }) {
|
|
31
|
+
const hour = value ? getHour12(value) : void 0;
|
|
32
|
+
const minute = value ? value.getMinutes() : void 0;
|
|
33
|
+
const period = value ? getPeriod(value) : void 0;
|
|
34
|
+
const minutes = minuteOptions(minuteStep, minute);
|
|
35
|
+
const commit = (nextHour, nextMinute, nextPeriod) => {
|
|
36
|
+
onChange(applyTime(value, nextHour, nextMinute, nextPeriod));
|
|
37
|
+
};
|
|
38
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
39
|
+
className: require_cn.cn("flex flex-col gap-2", className),
|
|
40
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
41
|
+
className: "flex items-center gap-2",
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_select.Select, {
|
|
44
|
+
value: hour !== void 0 ? String(hour) : void 0,
|
|
45
|
+
onValueChange: (next) => {
|
|
46
|
+
commit(Number(next), minute ?? 0, period ?? "AM");
|
|
47
|
+
},
|
|
48
|
+
disabled,
|
|
49
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectTrigger, {
|
|
50
|
+
"aria-label": "Hour",
|
|
51
|
+
className: "h-8 w-[4.5rem] text-xs",
|
|
52
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectValue, { placeholder: "HH" })
|
|
53
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectContent, { children: Array.from({ length: 12 }, (_, index) => index + 1).map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectItem, {
|
|
54
|
+
value: String(option),
|
|
55
|
+
children: option
|
|
56
|
+
}, option)) })]
|
|
57
|
+
}),
|
|
58
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59
|
+
className: "text-muted-foreground text-sm",
|
|
60
|
+
children: ":"
|
|
61
|
+
}),
|
|
62
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_select.Select, {
|
|
63
|
+
value: minute !== void 0 ? String(minute) : void 0,
|
|
64
|
+
onValueChange: (next) => {
|
|
65
|
+
commit(hour ?? 12, Number(next), period ?? "AM");
|
|
66
|
+
},
|
|
67
|
+
disabled,
|
|
68
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectTrigger, {
|
|
69
|
+
"aria-label": "Minute",
|
|
70
|
+
className: "h-8 w-[4.5rem] text-xs",
|
|
71
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectValue, { placeholder: "MM" })
|
|
72
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectContent, { children: minutes.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectItem, {
|
|
73
|
+
value: String(option),
|
|
74
|
+
children: String(option).padStart(2, "0")
|
|
75
|
+
}, option)) })]
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_select.Select, {
|
|
78
|
+
value: period,
|
|
79
|
+
onValueChange: (next) => {
|
|
80
|
+
commit(hour ?? 12, minute ?? 0, next);
|
|
81
|
+
},
|
|
82
|
+
disabled,
|
|
83
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectTrigger, {
|
|
84
|
+
"aria-label": "AM or PM",
|
|
85
|
+
className: "h-8 w-[4.75rem] text-xs",
|
|
86
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectValue, { placeholder: "AM" })
|
|
87
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_select.SelectContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectItem, {
|
|
88
|
+
value: "AM",
|
|
89
|
+
children: "AM"
|
|
90
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_select.SelectItem, {
|
|
91
|
+
value: "PM",
|
|
92
|
+
children: "PM"
|
|
93
|
+
})] })]
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
}), onClear && value ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
97
|
+
type: "button",
|
|
98
|
+
variant: "link",
|
|
99
|
+
size: "sm",
|
|
100
|
+
disabled,
|
|
101
|
+
className: "h-auto self-start px-0",
|
|
102
|
+
onClick: onClear,
|
|
103
|
+
children: "Clear time"
|
|
104
|
+
}) : null]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
TimePicker.displayName = "TimePicker";
|
|
108
|
+
//#endregion
|
|
109
|
+
exports.TimePicker = TimePicker;
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=TimePicker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimePicker.cjs","names":[],"sources":["../../../../src/components/Form/TimePicker.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { setHours, setMinutes, startOfDay } from \"date-fns\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { Button } from \"../../primitives/button\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"../../primitives/select\";\r\n\r\nexport type DayPeriod = \"AM\" | \"PM\";\r\n\r\nexport interface TimePickerProps {\r\n value?: Date;\r\n onChange: (date: Date) => void;\r\n onClear?: () => void;\r\n minuteStep?: number;\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nfunction getHour12(date: Date): number {\r\n const hour = date.getHours() % 12;\r\n return hour === 0 ? 12 : hour;\r\n}\r\n\r\nfunction getPeriod(date: Date): DayPeriod {\r\n return date.getHours() >= 12 ? \"PM\" : \"AM\";\r\n}\r\n\r\nfunction toHours24(hour12: number, period: DayPeriod): number {\r\n const hour = hour12 % 12;\r\n return period === \"PM\" ? hour + 12 : hour;\r\n}\r\n\r\nfunction applyTime(\r\n base: Date | undefined,\r\n hour12: number,\r\n minute: number,\r\n period: DayPeriod\r\n): Date {\r\n const next = base ? new Date(base) : startOfDay(new Date());\r\n return setMinutes(setHours(next, toHours24(hour12, period)), minute);\r\n}\r\n\r\nfunction minuteOptions(minuteStep: number, currentMinute?: number): number[] {\r\n const steps: number[] = [];\r\n for (let minute = 0; minute < 60; minute += minuteStep) {\r\n steps.push(minute);\r\n }\r\n\r\n if (currentMinute !== undefined && !steps.includes(currentMinute)) {\r\n return [...steps, currentMinute].sort((a, b) => a - b);\r\n }\r\n\r\n return steps;\r\n}\r\n\r\nexport function TimePicker({\r\n value,\r\n onChange,\r\n onClear,\r\n minuteStep = 5,\r\n disabled,\r\n className,\r\n}: TimePickerProps) {\r\n const hour = value ? getHour12(value) : undefined;\r\n const minute = value ? value.getMinutes() : undefined;\r\n const period = value ? getPeriod(value) : undefined;\r\n const minutes = minuteOptions(minuteStep, minute);\r\n\r\n const commit = (nextHour: number, nextMinute: number, nextPeriod: DayPeriod) => {\r\n onChange(applyTime(value, nextHour, nextMinute, nextPeriod));\r\n };\r\n\r\n return (\r\n <div className={cn(\"flex flex-col gap-2\", className)}>\r\n <div className=\"flex items-center gap-2\">\r\n <Select\r\n value={hour !== undefined ? String(hour) : undefined}\r\n onValueChange={(next) => {\r\n commit(Number(next), minute ?? 0, period ?? \"AM\");\r\n }}\r\n disabled={disabled}\r\n >\r\n <SelectTrigger aria-label=\"Hour\" className=\"h-8 w-[4.5rem] text-xs\">\r\n <SelectValue placeholder=\"HH\" />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {Array.from({ length: 12 }, (_, index) => index + 1).map((option) => (\r\n <SelectItem key={option} value={String(option)}>\r\n {option}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n\r\n <span className=\"text-muted-foreground text-sm\">:</span>\r\n\r\n <Select\r\n value={minute !== undefined ? String(minute) : undefined}\r\n onValueChange={(next) => {\r\n commit(hour ?? 12, Number(next), period ?? \"AM\");\r\n }}\r\n disabled={disabled}\r\n >\r\n <SelectTrigger aria-label=\"Minute\" className=\"h-8 w-[4.5rem] text-xs\">\r\n <SelectValue placeholder=\"MM\" />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {minutes.map((option) => (\r\n <SelectItem key={option} value={String(option)}>\r\n {String(option).padStart(2, \"0\")}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n\r\n <Select\r\n value={period}\r\n onValueChange={(next) => {\r\n commit(hour ?? 12, minute ?? 0, next as DayPeriod);\r\n }}\r\n disabled={disabled}\r\n >\r\n <SelectTrigger aria-label=\"AM or PM\" className=\"h-8 w-[4.75rem] text-xs\">\r\n <SelectValue placeholder=\"AM\" />\r\n </SelectTrigger>\r\n <SelectContent>\r\n <SelectItem value=\"AM\">AM</SelectItem>\r\n <SelectItem value=\"PM\">PM</SelectItem>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n\r\n {onClear && value ? (\r\n <Button\r\n type=\"button\"\r\n variant=\"link\"\r\n size=\"sm\"\r\n disabled={disabled}\r\n className=\"h-auto self-start px-0\"\r\n onClick={onClear}\r\n >\r\n Clear time\r\n </Button>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n\r\nTimePicker.displayName = \"TimePicker\";\r\n"],"mappings":"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"}
|