@eintrek/erp-theme 1.4.10 → 1.4.11
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/components/ui/calendar.d.ts +6 -2
- package/dist/components/ui/date-picker.d.ts +5 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +145 -95
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +144 -94
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import require$$1, { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { ChevronDownIcon, ChevronRight, ChevronLeftIcon, ChevronRightIcon, Calendar as Calendar$1, ArrowRight, ArrowLeft,
|
|
2
|
+
import { ChevronDownIcon, ChevronRight, CheckIcon, ChevronUpIcon, ChevronLeftIcon, ChevronRightIcon, Calendar as Calendar$1, ArrowRight, ArrowLeft, XIcon, SearchIcon, CircleIcon, ChevronsUpDown, Check, Paperclip, X as X$1, AlertCircle, MoreHorizontalIcon, GripVerticalIcon, PanelLeftIcon, ChevronLeft, MoreHorizontal, ChevronDown, ChevronUp, EyeOff, Filter, Settings2, ListChecks, ArrowDownUp, Trash2, GripVertical, ListFilter, CalendarIcon, Copy, Code2, FileSpreadsheetIcon, CommandIcon } from 'lucide-react';
|
|
3
3
|
import '@radix-ui/react-accessible-icon';
|
|
4
4
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
5
5
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
@@ -213,6 +213,80 @@ function BreadcrumbSeparator({ children, className, ...props }) {
|
|
|
213
213
|
return (jsx("li", { "data-slot": "breadcrumb-separator", role: "presentation", "aria-hidden": "true", className: cn$1("[&>svg]:size-3.5", className), ...props, children: children ?? jsx(ChevronRight, {}) }));
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
function Select({ onValueChange, onOpenChange, open, defaultOpen, ...props }) {
|
|
217
|
+
// Tracked in a ref, not state: this only needs to be readable inside the
|
|
218
|
+
// callback below, and putting it in state would re-render on every open.
|
|
219
|
+
const isOpenRef = React.useRef(defaultOpen ?? false);
|
|
220
|
+
if (open !== undefined)
|
|
221
|
+
isOpenRef.current = open;
|
|
222
|
+
return (jsx(SelectPrimitive.Root, { "data-slot": "select", ...props, open: open, defaultOpen: defaultOpen, onOpenChange: next => {
|
|
223
|
+
isOpenRef.current = next;
|
|
224
|
+
onOpenChange?.(next);
|
|
225
|
+
}, onValueChange: value => {
|
|
226
|
+
// Drop ONLY the empty value Radix echoes back on its own.
|
|
227
|
+
//
|
|
228
|
+
// Radix keeps a hidden native <select> so the control works inside
|
|
229
|
+
// real forms. Whenever the controlled value changes it assigns that
|
|
230
|
+
// value to the native node and dispatches a synthetic change event,
|
|
231
|
+
// which comes straight back out through onValueChange:
|
|
232
|
+
//
|
|
233
|
+
// setValue.call(select, selectValue);
|
|
234
|
+
// select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
235
|
+
// ...
|
|
236
|
+
// onChange: (event) => onValueChange(event.target.value)
|
|
237
|
+
//
|
|
238
|
+
// The native <option> list is registered by SelectItem, and the items
|
|
239
|
+
// live inside SelectContent — portalled, and only mounted while the
|
|
240
|
+
// menu is open. On a closed select the option for the incoming value
|
|
241
|
+
// usually does not exist yet, the DOM refuses the assignment,
|
|
242
|
+
// `select.value` collapses to "", and that empty string reaches the
|
|
243
|
+
// consumer. In a form this lands exactly when saved data arrives: the
|
|
244
|
+
// field is set to its stored value, Radix echoes "", and the handler
|
|
245
|
+
// writes that back — enum fields then snap to their fallback and zod
|
|
246
|
+
// rejects the submit with "received ''".
|
|
247
|
+
//
|
|
248
|
+
// The open check is what keeps a real "none" option working. Radix
|
|
249
|
+
// does NOT forbid <SelectItem value="">, and picking one is a
|
|
250
|
+
// legitimate way to clear a selection. A user pick always arrives
|
|
251
|
+
// while the menu is still open — SelectItem's handleSelect calls
|
|
252
|
+
// onValueChange(value) BEFORE onOpenChange(false) — whereas the echo
|
|
253
|
+
// above fires from an effect with the menu closed. So only the closed
|
|
254
|
+
// case is suppressed.
|
|
255
|
+
if (value === "" && !isOpenRef.current)
|
|
256
|
+
return;
|
|
257
|
+
onValueChange?.(value);
|
|
258
|
+
} }));
|
|
259
|
+
}
|
|
260
|
+
function SelectGroup({ ...props }) {
|
|
261
|
+
return jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
262
|
+
}
|
|
263
|
+
function SelectValue({ ...props }) {
|
|
264
|
+
return jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
265
|
+
}
|
|
266
|
+
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
267
|
+
return (jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn$1("border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, jsx(SelectPrimitive.Icon, { asChild: true, children: jsx(ChevronDownIcon, { className: "size-4 opacity-50" }) })] }));
|
|
268
|
+
}
|
|
269
|
+
function SelectContent({ className, children, position = "popper", ...props }) {
|
|
270
|
+
return (jsx(SelectPrimitive.Portal, { children: jsxs(SelectPrimitive.Content, { "data-slot": "select-content", className: cn$1("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", position === "popper" &&
|
|
271
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, ...props, children: [jsx(SelectScrollUpButton, {}), jsx(SelectPrimitive.Viewport, { className: cn$1("p-1", position === "popper" &&
|
|
272
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"), children: children }), jsx(SelectScrollDownButton, {})] }) }));
|
|
273
|
+
}
|
|
274
|
+
function SelectLabel({ className, ...props }) {
|
|
275
|
+
return (jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn$1("text-muted-foreground px-2 py-1.5 text-xs", className), ...props }));
|
|
276
|
+
}
|
|
277
|
+
function SelectItem({ className, children, ...props }) {
|
|
278
|
+
return (jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn$1("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className), ...props, children: [jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: jsx(SelectPrimitive.ItemIndicator, { children: jsx(CheckIcon, { className: "size-4" }) }) }), jsx(SelectPrimitive.ItemText, { children: children })] }));
|
|
279
|
+
}
|
|
280
|
+
function SelectSeparator({ className, ...props }) {
|
|
281
|
+
return (jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn$1("bg-border pointer-events-none -mx-1 my-1 h-px", className), ...props }));
|
|
282
|
+
}
|
|
283
|
+
function SelectScrollUpButton({ className, ...props }) {
|
|
284
|
+
return (jsx(SelectPrimitive.ScrollUpButton, { "data-slot": "select-scroll-up-button", className: cn$1("flex cursor-default items-center justify-center py-1", className), ...props, children: jsx(ChevronUpIcon, { className: "size-4" }) }));
|
|
285
|
+
}
|
|
286
|
+
function SelectScrollDownButton({ className, ...props }) {
|
|
287
|
+
return (jsx(SelectPrimitive.ScrollDownButton, { "data-slot": "select-scroll-down-button", className: cn$1("flex cursor-default items-center justify-center py-1", className), ...props, children: jsx(ChevronDownIcon, { className: "size-4" }) }));
|
|
288
|
+
}
|
|
289
|
+
|
|
216
290
|
/**
|
|
217
291
|
* @module constants
|
|
218
292
|
* @summary Useful constants
|
|
@@ -659,11 +733,35 @@ function getThaiMonthName(monthIndex) {
|
|
|
659
733
|
|
|
660
734
|
/** Thai weekday abbreviations (Sunday-first, matching JS getDay()). */
|
|
661
735
|
const WEEK_DAYS_TH = ["อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส."];
|
|
662
|
-
function
|
|
663
|
-
|
|
736
|
+
function getInitialMonth(selected) {
|
|
737
|
+
if (selected instanceof Date && !Number.isNaN(selected.getTime())) {
|
|
738
|
+
return new Date(selected.getFullYear(), selected.getMonth(), 1);
|
|
739
|
+
}
|
|
740
|
+
if (Array.isArray(selected) && selected[0] instanceof Date) {
|
|
741
|
+
return new Date(selected[0].getFullYear(), selected[0].getMonth(), 1);
|
|
742
|
+
}
|
|
743
|
+
if (selected &&
|
|
744
|
+
typeof selected === "object" &&
|
|
745
|
+
"from" in selected &&
|
|
746
|
+
selected.from instanceof Date) {
|
|
747
|
+
return new Date(selected.from.getFullYear(), selected.from.getMonth(), 1);
|
|
748
|
+
}
|
|
749
|
+
return new Date();
|
|
664
750
|
}
|
|
665
|
-
function Calendar({ className, selected, onSelect, mode = "single", disabled, }) {
|
|
666
|
-
const
|
|
751
|
+
function Calendar({ className, selected, onSelect, mode = "single", disabled, fromYear = 1900, toYear, }) {
|
|
752
|
+
const maxYear = toYear ?? new Date().getFullYear() + 10;
|
|
753
|
+
const [currentMonth, setCurrentMonth] = React.useState(() => getInitialMonth(selected));
|
|
754
|
+
// Jump the grid to the selected date when it changes (e.g. form load).
|
|
755
|
+
React.useEffect(() => {
|
|
756
|
+
const next = getInitialMonth(selected);
|
|
757
|
+
setCurrentMonth((prev) => {
|
|
758
|
+
if (prev.getFullYear() === next.getFullYear() &&
|
|
759
|
+
prev.getMonth() === next.getMonth()) {
|
|
760
|
+
return prev;
|
|
761
|
+
}
|
|
762
|
+
return next;
|
|
763
|
+
});
|
|
764
|
+
}, [selected]);
|
|
667
765
|
const today = new Date();
|
|
668
766
|
const year = currentMonth.getFullYear();
|
|
669
767
|
const month = currentMonth.getMonth();
|
|
@@ -671,13 +769,20 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
671
769
|
const lastDayOfMonth = new Date(year, month + 1, 0);
|
|
672
770
|
const firstDayOfWeek = firstDayOfMonth.getDay();
|
|
673
771
|
const daysInMonth = lastDayOfMonth.getDate();
|
|
772
|
+
const yearOptions = React.useMemo(() => {
|
|
773
|
+
const years = [];
|
|
774
|
+
const start = Math.min(fromYear, maxYear);
|
|
775
|
+
const end = Math.max(fromYear, maxYear);
|
|
776
|
+
for (let y = end; y >= start; y--) {
|
|
777
|
+
years.push(y);
|
|
778
|
+
}
|
|
779
|
+
return years;
|
|
780
|
+
}, [fromYear, maxYear]);
|
|
674
781
|
const getDaysInMonth = () => {
|
|
675
782
|
const days = [];
|
|
676
|
-
// Add empty cells for days before the first day of the month
|
|
677
783
|
for (let i = 0; i < firstDayOfWeek; i++) {
|
|
678
784
|
days.push(null);
|
|
679
785
|
}
|
|
680
|
-
// Add days of the month
|
|
681
786
|
for (let day = 1; day <= daysInMonth; day++) {
|
|
682
787
|
days.push(new Date(year, month, day));
|
|
683
788
|
}
|
|
@@ -689,7 +794,10 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
689
794
|
if (mode === "single" && selected instanceof Date) {
|
|
690
795
|
return date.toDateString() === selected.toDateString();
|
|
691
796
|
}
|
|
692
|
-
if (mode === "range" &&
|
|
797
|
+
if (mode === "range" &&
|
|
798
|
+
selected &&
|
|
799
|
+
typeof selected === "object" &&
|
|
800
|
+
"from" in selected) {
|
|
693
801
|
const range = selected;
|
|
694
802
|
if (range.from && range.to) {
|
|
695
803
|
return date >= range.from && date <= range.to;
|
|
@@ -699,7 +807,7 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
699
807
|
}
|
|
700
808
|
}
|
|
701
809
|
if (mode === "multiple" && Array.isArray(selected)) {
|
|
702
|
-
return selected.some(d => d.toDateString() === date.toDateString());
|
|
810
|
+
return selected.some((d) => d.toDateString() === date.toDateString());
|
|
703
811
|
}
|
|
704
812
|
return false;
|
|
705
813
|
};
|
|
@@ -718,7 +826,6 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
718
826
|
onSelect?.(date);
|
|
719
827
|
}
|
|
720
828
|
else if (mode === "range") {
|
|
721
|
-
// Simple range selection - click to set start, click again to set end
|
|
722
829
|
if (!selected || typeof selected !== "object" || !("from" in selected)) {
|
|
723
830
|
onSelect?.({ from: date, to: undefined });
|
|
724
831
|
}
|
|
@@ -742,9 +849,9 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
742
849
|
}
|
|
743
850
|
else if (mode === "multiple") {
|
|
744
851
|
const currentSelection = Array.isArray(selected) ? selected : [];
|
|
745
|
-
const isAlreadySelected = currentSelection.some(d => d.toDateString() === date.toDateString());
|
|
852
|
+
const isAlreadySelected = currentSelection.some((d) => d.toDateString() === date.toDateString());
|
|
746
853
|
if (isAlreadySelected) {
|
|
747
|
-
onSelect?.(currentSelection.filter(d => d.toDateString() !== date.toDateString()));
|
|
854
|
+
onSelect?.(currentSelection.filter((d) => d.toDateString() !== date.toDateString()));
|
|
748
855
|
}
|
|
749
856
|
else {
|
|
750
857
|
onSelect?.([...currentSelection, date]);
|
|
@@ -752,7 +859,7 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
752
859
|
}
|
|
753
860
|
};
|
|
754
861
|
const navigateMonth = (direction) => {
|
|
755
|
-
setCurrentMonth(prev => {
|
|
862
|
+
setCurrentMonth((prev) => {
|
|
756
863
|
const newMonth = new Date(prev);
|
|
757
864
|
if (direction === "prev") {
|
|
758
865
|
newMonth.setMonth(prev.getMonth() - 1);
|
|
@@ -763,18 +870,34 @@ function Calendar({ className, selected, onSelect, mode = "single", disabled, })
|
|
|
763
870
|
return newMonth;
|
|
764
871
|
});
|
|
765
872
|
};
|
|
873
|
+
const handleMonthChange = (monthStr) => {
|
|
874
|
+
const nextMonth = Number.parseInt(monthStr, 10);
|
|
875
|
+
if (Number.isNaN(nextMonth))
|
|
876
|
+
return;
|
|
877
|
+
setCurrentMonth((prev) => new Date(prev.getFullYear(), nextMonth, 1));
|
|
878
|
+
};
|
|
879
|
+
const handleYearChange = (buddhistYearStr) => {
|
|
880
|
+
const gregorian = toGregorianYear(Number.parseInt(buddhistYearStr, 10));
|
|
881
|
+
if (Number.isNaN(gregorian))
|
|
882
|
+
return;
|
|
883
|
+
setCurrentMonth((prev) => new Date(gregorian, prev.getMonth(), 1));
|
|
884
|
+
};
|
|
766
885
|
const days = getDaysInMonth();
|
|
767
|
-
|
|
886
|
+
const canGoPrev = year > fromYear || (year === fromYear && month > 0);
|
|
887
|
+
const canGoNext = year < maxYear || (year === maxYear && month < 11);
|
|
888
|
+
return (jsxs("div", { "data-slot": "calendar", className: cn$1("bg-background group/calendar p-3 w-fit", className), children: [jsxs("div", { className: "mb-4 flex items-center justify-between gap-1", children: [jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => navigateMonth("prev"), disabled: !canGoPrev, className: "h-8 w-8 shrink-0 p-0", "aria-label": "\u0E40\u0E14\u0E37\u0E2D\u0E19\u0E01\u0E48\u0E2D\u0E19\u0E2B\u0E19\u0E49\u0E32", children: jsx(ChevronLeftIcon, { className: "h-4 w-4" }) }), jsxs("div", { className: "flex min-w-0 flex-1 items-center justify-center gap-1", children: [jsxs(Select, { value: String(month), onValueChange: handleMonthChange, children: [jsx(SelectTrigger, { size: "sm", className: "h-8 w-[7.5rem] px-2 text-xs", "aria-label": "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E40\u0E14\u0E37\u0E2D\u0E19", children: jsx(SelectValue, { children: THAI_MONTH_NAMES[month] }) }), jsx(SelectContent, { className: "z-[100] max-h-60", children: THAI_MONTH_NAMES.map((name, index) => (jsx(SelectItem, { value: String(index), children: name }, name))) })] }), jsxs(Select, { value: String(toBuddhistYear(year)), onValueChange: handleYearChange, children: [jsx(SelectTrigger, { size: "sm", className: "h-8 w-[5.5rem] px-2 text-xs", "aria-label": "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E1B\u0E35", children: jsx(SelectValue, { children: toBuddhistYear(year) }) }), jsx(SelectContent, { className: "z-[100] max-h-60", children: yearOptions.map((y) => (jsx(SelectItem, { value: String(toBuddhistYear(y)), children: toBuddhistYear(y) }, y))) })] })] }), jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => navigateMonth("next"), disabled: !canGoNext, className: "h-8 w-8 shrink-0 p-0", "aria-label": "\u0E40\u0E14\u0E37\u0E2D\u0E19\u0E16\u0E31\u0E14\u0E44\u0E1B", children: jsx(ChevronRightIcon, { className: "h-4 w-4" }) })] }), jsx("div", { className: "mb-2 grid grid-cols-7 gap-1", children: WEEK_DAYS_TH.map((day) => (jsx("div", { className: "text-muted-foreground flex h-8 items-center justify-center text-center text-xs font-normal", children: day }, day))) }), jsx("div", { className: "grid grid-cols-7 gap-1", children: days.map((date, index) => {
|
|
768
889
|
if (!date) {
|
|
769
890
|
return jsx("div", { className: "h-8" }, index);
|
|
770
891
|
}
|
|
771
892
|
const isDateSelected = isSelected(date);
|
|
772
893
|
const isDateToday = isToday(date);
|
|
773
894
|
const isDateDisabled = isDisabled(date);
|
|
774
|
-
return (jsx(Button, { variant: "ghost", size: "icon", className: cn$1("h-8 w-8 p-0 text-sm font-normal", isDateSelected && "bg-primary text-primary-foreground", isDateToday &&
|
|
895
|
+
return (jsx(Button, { type: "button", variant: "ghost", size: "icon", className: cn$1("h-8 w-8 p-0 text-sm font-normal", isDateSelected && "bg-primary text-primary-foreground", isDateToday &&
|
|
896
|
+
!isDateSelected &&
|
|
897
|
+
"bg-accent text-accent-foreground", isDateDisabled && "cursor-not-allowed opacity-50", "hover:bg-accent hover:text-accent-foreground"), onClick: () => handleDateClick(date), disabled: isDateDisabled, children: date.getDate() }, date.toISOString()));
|
|
775
898
|
}) })] }));
|
|
776
899
|
}
|
|
777
|
-
function DateInput({ value, onChange, placeholder = "Select date", className, disabled = false }) {
|
|
900
|
+
function DateInput({ value, onChange, placeholder = "Select date", className, disabled = false, }) {
|
|
778
901
|
const handleChange = (event) => {
|
|
779
902
|
const dateValue = event.target.value;
|
|
780
903
|
if (dateValue) {
|
|
@@ -785,9 +908,9 @@ function DateInput({ value, onChange, placeholder = "Select date", className, di
|
|
|
785
908
|
}
|
|
786
909
|
};
|
|
787
910
|
const formatDateForInput = (date) => {
|
|
788
|
-
return date.toISOString().split(
|
|
911
|
+
return date.toISOString().split("T")[0];
|
|
789
912
|
};
|
|
790
|
-
return (jsx("input", { type: "date", value: value ? formatDateForInput(value) : "", onChange: handleChange, placeholder: placeholder, disabled: disabled, className: cn$1("flex h-10 w-full rounded-md border
|
|
913
|
+
return (jsx("input", { type: "date", value: value ? formatDateForInput(value) : "", onChange: handleChange, placeholder: placeholder, disabled: disabled, className: cn$1("border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className) }));
|
|
791
914
|
}
|
|
792
915
|
|
|
793
916
|
const formatDistanceLocale = {
|
|
@@ -1289,10 +1412,11 @@ const defaultFormatDate = (date) => new Intl.DateTimeFormat("th-TH", {
|
|
|
1289
1412
|
year: "numeric",
|
|
1290
1413
|
timeZone: "Asia/Bangkok",
|
|
1291
1414
|
}).format(date);
|
|
1292
|
-
function DatePicker({ date, onDateChange, placeholder = "เลือกวันที่", disabled = false, disabledDates, formatDate = defaultFormatDate, className, id, ariaLabel, "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }) {
|
|
1415
|
+
function DatePicker({ date, onDateChange, placeholder = "เลือกวันที่", disabled = false, disabledDates, formatDate = defaultFormatDate, fromYear = 1900, toYear, className, id, ariaLabel, "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }) {
|
|
1293
1416
|
const [open, setOpen] = React.useState(false);
|
|
1294
1417
|
const isDateDisabled = disabledDates ?? defaultDisabledDates;
|
|
1295
|
-
|
|
1418
|
+
const resolvedToYear = toYear ?? new Date().getFullYear() + 10;
|
|
1419
|
+
return (jsxs(Popover, { open: open && !disabled, onOpenChange: setOpen, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs("div", { className: "relative w-full", children: [jsx(Input, { readOnly: true, disabled: disabled, value: date ? formatDate(date) : "", placeholder: placeholder, className: cn$1("border-border bg-background text-foreground placeholder:text-muted-foreground w-full pr-9", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", className), id: id, "aria-label": date ? undefined : (ariaLabel ?? placeholder), "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby }), jsx(Calendar$1, { className: "text-muted-foreground pointer-events-none absolute top-1/2 right-3 h-4 w-4 -translate-y-1/2" })] }) }), jsx(PopoverContent, { className: "w-auto overflow-visible p-0", align: "start", children: jsx(Calendar, { ...{ locale: th }, mode: "single", selected: date, onSelect: (selectedDate) => {
|
|
1296
1420
|
if (selectedDate instanceof Date) {
|
|
1297
1421
|
onDateChange?.(selectedDate);
|
|
1298
1422
|
setOpen(false);
|
|
@@ -1301,7 +1425,7 @@ function DatePicker({ date, onDateChange, placeholder = "เลือกวั
|
|
|
1301
1425
|
onDateChange?.(undefined);
|
|
1302
1426
|
setOpen(false);
|
|
1303
1427
|
}
|
|
1304
|
-
}, disabled: isDateDisabled, initialFocus: true }) })] }));
|
|
1428
|
+
}, disabled: isDateDisabled, fromYear: fromYear, toYear: resolvedToYear, initialFocus: true }) })] }));
|
|
1305
1429
|
}
|
|
1306
1430
|
|
|
1307
1431
|
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
|
|
@@ -16384,80 +16508,6 @@ function ScrollBar({ className, orientation = "vertical", ...props }) {
|
|
|
16384
16508
|
"h-2.5 flex-col border-t border-t-transparent", className), ...props, children: jsx(ScrollAreaPrimitive.ScrollAreaThumb, { "data-slot": "scroll-area-thumb", className: "bg-border relative flex-1 rounded-full" }) }));
|
|
16385
16509
|
}
|
|
16386
16510
|
|
|
16387
|
-
function Select({ onValueChange, onOpenChange, open, defaultOpen, ...props }) {
|
|
16388
|
-
// Tracked in a ref, not state: this only needs to be readable inside the
|
|
16389
|
-
// callback below, and putting it in state would re-render on every open.
|
|
16390
|
-
const isOpenRef = React.useRef(defaultOpen ?? false);
|
|
16391
|
-
if (open !== undefined)
|
|
16392
|
-
isOpenRef.current = open;
|
|
16393
|
-
return (jsx(SelectPrimitive.Root, { "data-slot": "select", ...props, open: open, defaultOpen: defaultOpen, onOpenChange: next => {
|
|
16394
|
-
isOpenRef.current = next;
|
|
16395
|
-
onOpenChange?.(next);
|
|
16396
|
-
}, onValueChange: value => {
|
|
16397
|
-
// Drop ONLY the empty value Radix echoes back on its own.
|
|
16398
|
-
//
|
|
16399
|
-
// Radix keeps a hidden native <select> so the control works inside
|
|
16400
|
-
// real forms. Whenever the controlled value changes it assigns that
|
|
16401
|
-
// value to the native node and dispatches a synthetic change event,
|
|
16402
|
-
// which comes straight back out through onValueChange:
|
|
16403
|
-
//
|
|
16404
|
-
// setValue.call(select, selectValue);
|
|
16405
|
-
// select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
16406
|
-
// ...
|
|
16407
|
-
// onChange: (event) => onValueChange(event.target.value)
|
|
16408
|
-
//
|
|
16409
|
-
// The native <option> list is registered by SelectItem, and the items
|
|
16410
|
-
// live inside SelectContent — portalled, and only mounted while the
|
|
16411
|
-
// menu is open. On a closed select the option for the incoming value
|
|
16412
|
-
// usually does not exist yet, the DOM refuses the assignment,
|
|
16413
|
-
// `select.value` collapses to "", and that empty string reaches the
|
|
16414
|
-
// consumer. In a form this lands exactly when saved data arrives: the
|
|
16415
|
-
// field is set to its stored value, Radix echoes "", and the handler
|
|
16416
|
-
// writes that back — enum fields then snap to their fallback and zod
|
|
16417
|
-
// rejects the submit with "received ''".
|
|
16418
|
-
//
|
|
16419
|
-
// The open check is what keeps a real "none" option working. Radix
|
|
16420
|
-
// does NOT forbid <SelectItem value="">, and picking one is a
|
|
16421
|
-
// legitimate way to clear a selection. A user pick always arrives
|
|
16422
|
-
// while the menu is still open — SelectItem's handleSelect calls
|
|
16423
|
-
// onValueChange(value) BEFORE onOpenChange(false) — whereas the echo
|
|
16424
|
-
// above fires from an effect with the menu closed. So only the closed
|
|
16425
|
-
// case is suppressed.
|
|
16426
|
-
if (value === "" && !isOpenRef.current)
|
|
16427
|
-
return;
|
|
16428
|
-
onValueChange?.(value);
|
|
16429
|
-
} }));
|
|
16430
|
-
}
|
|
16431
|
-
function SelectGroup({ ...props }) {
|
|
16432
|
-
return jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
16433
|
-
}
|
|
16434
|
-
function SelectValue({ ...props }) {
|
|
16435
|
-
return jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
16436
|
-
}
|
|
16437
|
-
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
16438
|
-
return (jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn$1("border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, jsx(SelectPrimitive.Icon, { asChild: true, children: jsx(ChevronDownIcon, { className: "size-4 opacity-50" }) })] }));
|
|
16439
|
-
}
|
|
16440
|
-
function SelectContent({ className, children, position = "popper", ...props }) {
|
|
16441
|
-
return (jsx(SelectPrimitive.Portal, { children: jsxs(SelectPrimitive.Content, { "data-slot": "select-content", className: cn$1("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", position === "popper" &&
|
|
16442
|
-
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, ...props, children: [jsx(SelectScrollUpButton, {}), jsx(SelectPrimitive.Viewport, { className: cn$1("p-1", position === "popper" &&
|
|
16443
|
-
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"), children: children }), jsx(SelectScrollDownButton, {})] }) }));
|
|
16444
|
-
}
|
|
16445
|
-
function SelectLabel({ className, ...props }) {
|
|
16446
|
-
return (jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn$1("text-muted-foreground px-2 py-1.5 text-xs", className), ...props }));
|
|
16447
|
-
}
|
|
16448
|
-
function SelectItem({ className, children, ...props }) {
|
|
16449
|
-
return (jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn$1("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className), ...props, children: [jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: jsx(SelectPrimitive.ItemIndicator, { children: jsx(CheckIcon, { className: "size-4" }) }) }), jsx(SelectPrimitive.ItemText, { children: children })] }));
|
|
16450
|
-
}
|
|
16451
|
-
function SelectSeparator({ className, ...props }) {
|
|
16452
|
-
return (jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn$1("bg-border pointer-events-none -mx-1 my-1 h-px", className), ...props }));
|
|
16453
|
-
}
|
|
16454
|
-
function SelectScrollUpButton({ className, ...props }) {
|
|
16455
|
-
return (jsx(SelectPrimitive.ScrollUpButton, { "data-slot": "select-scroll-up-button", className: cn$1("flex cursor-default items-center justify-center py-1", className), ...props, children: jsx(ChevronUpIcon, { className: "size-4" }) }));
|
|
16456
|
-
}
|
|
16457
|
-
function SelectScrollDownButton({ className, ...props }) {
|
|
16458
|
-
return (jsx(SelectPrimitive.ScrollDownButton, { "data-slot": "select-scroll-down-button", className: cn$1("flex cursor-default items-center justify-center py-1", className), ...props, children: jsx(ChevronDownIcon, { className: "size-4" }) }));
|
|
16459
|
-
}
|
|
16460
|
-
|
|
16461
16511
|
function SelectField({ id, label, helperText, error, required, placeholder, value, defaultValue, onValueChange, disabled, options, children, containerClassName, labelClassName, triggerClassName, messageClassName, size, "aria-label": ariaLabel, "aria-describedby": ariaDescribedby, }) {
|
|
16462
16512
|
const reactId = React.useId();
|
|
16463
16513
|
const inputId = id ?? reactId;
|