@skalfa/skalfa-component 1.0.52 → 1.0.54
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/input/InputDate.component.d.ts +1 -0
- package/dist/input/InputDate.component.js +47 -29
- package/dist/input/InputDatetime.component.js +2 -2
- package/dist/input/InputImage.component.d.ts +2 -0
- package/dist/input/InputImage.component.js +54 -18
- package/dist/input/InputTime.component.d.ts +5 -3
- package/dist/input/InputTime.component.js +109 -28
- package/dist/input/Select.component.d.ts +7 -2
- package/dist/input/Select.component.js +91 -38
- package/package.json +1 -1
- package/src/input/InputDate.component.tsx +95 -55
- package/src/input/InputDatetime.component.tsx +2 -0
- package/src/input/InputImage.component.tsx +71 -21
- package/src/input/InputTime.component.tsx +218 -58
- package/src/input/Select.component.tsx +111 -53
|
@@ -16,6 +16,7 @@ export interface InputDateProps extends Omit<InputHTMLAttributes<HTMLInputElemen
|
|
|
16
16
|
}
|
|
17
17
|
export declare function InputDateComponent({ label, tip, leftIcon, rightIcon, value, invalid, validations, register, unregister, onChange, className, ...props }: InputDateProps): import("@types/react").JSX.Element;
|
|
18
18
|
export interface InputDatePickerProps {
|
|
19
|
+
value?: string;
|
|
19
20
|
onChange?: (date: string) => void;
|
|
20
21
|
minDate?: string;
|
|
21
22
|
maxDate?: string;
|
|
@@ -27,45 +27,62 @@ function InputDateComponent({ label, tip, leftIcon, rightIcon, value, invalid, v
|
|
|
27
27
|
const [invalidMessage] = (0, _utils_1.useValidation)(inputHandler.value, validations, invalid, inputHandler.idle);
|
|
28
28
|
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-container", children: [(0, jsx_runtime_1.jsxs)("label", { htmlFor: randomId, className: (0, _utils_1.cn)("input-label", props.disabled && "input-label-disabled", inputHandler.focus && "input-label-focus", !!invalidMessage && "input-label-error", (0, _utils_1.pcn)(className, "label"), props.disabled && (0, _utils_1.pcn)(className, "label", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "label", "focus"), !!invalidMessage && (0, _utils_1.pcn)(className, "label", "error")), children: [label, validations && _utils_1.validation.hasRules(validations, "required") && (0, jsx_runtime_1.jsx)("span", { className: "text-danger ml-1", children: "*" })] }), tip && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip", props.disabled && "input-tip-disabled", (0, _utils_1.pcn)(className, "tip"), props.disabled && (0, _utils_1.pcn)(className, "tip", "disabled")), children: tip })), (0, jsx_runtime_1.jsx)(OutsideClick_component_1.OutsideClickComponent, { onOutsideClick: !isSm ? () => inputHandler.setFocus(false) : undefined, children: (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { ...props, id: randomId, className: (0, _utils_1.cn)("input", leftIcon && "input-with-left-icon", rightIcon && "input-with-right-icon", (0, _utils_1.pcn)(className, "input"), !!invalidMessage && "input-error", !!invalidMessage && (0, _utils_1.pcn)(className, "input", "error")), value: inputHandler.value, onChange: (e) => {
|
|
29
29
|
inputHandler.setValue(e.target.value);
|
|
30
|
-
inputHandler.
|
|
30
|
+
inputHandler.setIdle(false);
|
|
31
31
|
onChange?.(e.target.value);
|
|
32
32
|
}, onFocus: (e) => {
|
|
33
33
|
props.onFocus?.(e);
|
|
34
34
|
inputHandler.setFocus(true);
|
|
35
35
|
}, onBlur: (e) => {
|
|
36
36
|
props.onBlur?.(e);
|
|
37
|
-
}, autoComplete: "off", inputMode: isSm ? "none" : undefined }), leftIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-left", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon"), props.disabled && (0, _utils_1.pcn)(className, "icon", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "icon", "focus")), icon: leftIcon })), rightIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-right", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon"), props.disabled && (0, _utils_1.pcn)(className, "icon", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "icon", "focus")), icon: rightIcon })), !isSm && inputHandler.focus && ((0, jsx_runtime_1.jsx)("div", { className: "input-date-picker-popover", children: (0, jsx_runtime_1.jsx)(exports.InputDatePickerComponent, { onChange: (e) => {
|
|
37
|
+
}, autoComplete: "off", inputMode: isSm ? "none" : undefined }), leftIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-left", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon"), props.disabled && (0, _utils_1.pcn)(className, "icon", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "icon", "focus")), icon: leftIcon })), rightIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-right", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon"), props.disabled && (0, _utils_1.pcn)(className, "icon", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "icon", "focus")), icon: rightIcon })), !isSm && inputHandler.focus && ((0, jsx_runtime_1.jsx)("div", { className: "input-date-picker-popover", children: (0, jsx_runtime_1.jsx)(exports.InputDatePickerComponent, { value: inputHandler.value, onChange: (e) => {
|
|
38
38
|
inputHandler.setValue(e);
|
|
39
|
+
inputHandler.setIdle(false);
|
|
39
40
|
onChange?.(e);
|
|
40
|
-
|
|
41
|
+
inputHandler.setFocus(false);
|
|
42
|
+
} }) }))] }) }), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage }))] }), isSm && ((0, jsx_runtime_1.jsx)(BottomSheet_component_1.BottomSheetComponent, { show: inputHandler.focus, onClose: () => inputHandler.setFocus(false), size: 380, footer: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { label: "Selesai", variant: "outline", onClick: () => inputHandler.setFocus(false), block: true }) }), children: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.InputDatePickerComponent, { value: inputHandler.value, onChange: (e) => {
|
|
41
43
|
inputHandler.setValue(e);
|
|
44
|
+
inputHandler.setIdle(false);
|
|
42
45
|
onChange?.(e);
|
|
43
46
|
} }) }) }))] }));
|
|
44
47
|
}
|
|
45
48
|
;
|
|
46
|
-
const InputDatePickerComponent = ({ onChange, minDate, maxDate, rightElement, }) => {
|
|
49
|
+
const InputDatePickerComponent = ({ value, onChange, minDate, maxDate, rightElement, }) => {
|
|
47
50
|
const activeYearRef = (0, react_1.useRef)(null);
|
|
48
51
|
const containerYearRef = (0, react_1.useRef)(null);
|
|
49
|
-
const
|
|
50
|
-
|
|
52
|
+
const initialDate = (0, react_1.useMemo)(() => {
|
|
53
|
+
return value && (0, moment_1.default)(value).isValid() ? (0, moment_1.default)(value) : (0, moment_1.default)();
|
|
54
|
+
}, [value]);
|
|
55
|
+
const [currentDate, setCurrentDate] = (0, react_1.useState)(initialDate);
|
|
56
|
+
const [selectedDate, setSelectedDate] = (0, react_1.useState)(initialDate);
|
|
57
|
+
(0, react_1.useEffect)(() => {
|
|
58
|
+
if (value && (0, moment_1.default)(value).isValid()) {
|
|
59
|
+
const m = (0, moment_1.default)(value);
|
|
60
|
+
setCurrentDate(m);
|
|
61
|
+
setSelectedDate(m);
|
|
62
|
+
}
|
|
63
|
+
}, [value]);
|
|
51
64
|
const startDate = (0, moment_1.default)(currentDate).startOf("month").startOf("week");
|
|
52
65
|
const endDate = (0, moment_1.default)(currentDate).endOf("month").endOf("week");
|
|
53
|
-
const handlePrevMonth = () =>
|
|
54
|
-
|
|
66
|
+
const handlePrevMonth = (e) => {
|
|
67
|
+
e?.preventDefault();
|
|
68
|
+
e?.stopPropagation();
|
|
69
|
+
setCurrentDate((0, moment_1.default)(currentDate).subtract(1, "month"));
|
|
70
|
+
};
|
|
71
|
+
const handleNextMonth = (e) => {
|
|
72
|
+
e?.preventDefault();
|
|
73
|
+
e?.stopPropagation();
|
|
74
|
+
setCurrentDate((0, moment_1.default)(currentDate).add(1, "month"));
|
|
75
|
+
};
|
|
55
76
|
const handleDateClick = (date) => {
|
|
56
|
-
if ((minDate && date.isBefore((0, moment_1.default)(minDate))) || (maxDate && date.isAfter((0, moment_1.default)(maxDate)))) {
|
|
77
|
+
if ((minDate && date.isBefore((0, moment_1.default)(minDate), "day")) || (maxDate && date.isAfter((0, moment_1.default)(maxDate), "day"))) {
|
|
57
78
|
return;
|
|
58
79
|
}
|
|
59
80
|
setSelectedDate(date);
|
|
60
81
|
onChange?.(date.format("YYYY-MM-DD"));
|
|
61
82
|
};
|
|
62
83
|
const renderDays = () => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < 7; i++) {
|
|
66
|
-
days.push((0, jsx_runtime_1.jsx)("div", { className: "text-center font-bold", children: startDay.add(i, "days").format("dd") }, i));
|
|
67
|
-
}
|
|
68
|
-
return days;
|
|
84
|
+
const dayNames = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
85
|
+
return dayNames.map((d, i) => ((0, jsx_runtime_1.jsx)("div", { className: "text-center text-[11px] font-semibold text-light-foreground", children: d }, i)));
|
|
69
86
|
};
|
|
70
87
|
const renderCells = () => {
|
|
71
88
|
const rows = [];
|
|
@@ -74,35 +91,36 @@ const InputDatePickerComponent = ({ onChange, minDate, maxDate, rightElement, })
|
|
|
74
91
|
while (day.isBefore(endDate) || day.isSame(endDate, "day")) {
|
|
75
92
|
for (let i = 0; i < 7; i++) {
|
|
76
93
|
const cloneDay = (0, moment_1.default)(day);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
const isCurrentMonth = day.isSame(currentDate, "month");
|
|
95
|
+
const isSelected = day.isSame(selectedDate, "day");
|
|
96
|
+
const isToday = day.isSame((0, moment_1.default)(), "day");
|
|
97
|
+
const isDisabled = (minDate && day.isBefore((0, moment_1.default)(minDate), "day")) ||
|
|
98
|
+
(maxDate && day.isAfter((0, moment_1.default)(maxDate), "day"));
|
|
99
|
+
days.push((0, jsx_runtime_1.jsx)("button", { type: "button", className: (0, _utils_1.cn)("w-8 h-8 text-xs flex items-center justify-center text-center rounded-lg transition-all border-none select-none", isCurrentMonth ? "text-foreground font-medium" : "text-light-foreground/40", isSelected ? "bg-primary text-white font-bold hover:bg-primary" : (isToday ? "border border-primary font-semibold hover:bg-light-primary" : "hover:bg-light-primary"), isDisabled ? "opacity-20 cursor-not-allowed pointer-events-none" : "cursor-pointer"), onClick: () => !isDisabled && handleDateClick(cloneDay), children: day.format("D") }, day.format("YYYY-MM-DD")));
|
|
82
100
|
day.add(1, "day");
|
|
83
101
|
}
|
|
84
|
-
rows.push((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-7 gap-1", children: days }, day.
|
|
102
|
+
rows.push((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-7 gap-1", children: days }, day.format("YYYY-MM-DD-row")));
|
|
85
103
|
days = [];
|
|
86
104
|
}
|
|
87
105
|
return rows;
|
|
88
106
|
};
|
|
89
107
|
const years = (0, react_1.useMemo)(() => {
|
|
90
108
|
const dumpYears = [];
|
|
91
|
-
for (let i =
|
|
109
|
+
for (let i = 1940; i <= (0, moment_1.default)().year() + 10; i++) {
|
|
92
110
|
dumpYears.push(i);
|
|
93
111
|
}
|
|
94
112
|
return dumpYears;
|
|
95
113
|
}, []);
|
|
96
114
|
(0, react_1.useEffect)(() => {
|
|
97
115
|
if (activeYearRef.current && containerYearRef.current) {
|
|
98
|
-
containerYearRef.current
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
const container = containerYearRef.current;
|
|
117
|
+
const activeEl = activeYearRef.current;
|
|
118
|
+
container.scrollTop = activeEl.offsetTop - container.offsetTop - (container.clientHeight / 2) + (activeEl.clientHeight / 2);
|
|
101
119
|
}
|
|
102
|
-
}, []);
|
|
103
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full flex gap-2
|
|
120
|
+
}, [currentDate.year()]);
|
|
121
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full flex gap-2.5 h-[260px] select-none", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-[68px] shrink-0 h-full overflow-y-auto input-scroll pr-1 border-r border-stroke", ref: containerYearRef, children: (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-0.5", children: years?.map((item) => {
|
|
104
122
|
const isActive = currentDate?.year() === item;
|
|
105
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
106
|
-
}) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "
|
|
123
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: isActive ? activeYearRef : null, className: (0, _utils_1.cn)("py-1 px-2 font-semibold rounded-md cursor-pointer transition-colors text-center select-none", isActive ? "bg-primary text-white" : "hover:bg-light-primary text-foreground"), onClick: () => setCurrentDate((0, moment_1.default)(currentDate).set("year", item)), children: item }, item));
|
|
124
|
+
}) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 flex flex-col justify-between pl-0.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center mb-1.5", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handlePrevMonth, className: "w-7 h-7 flex items-center justify-center text-xs rounded-full hover:bg-light-primary transition-colors cursor-pointer border-none bg-transparent", children: (0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/chevron-left", className: "w-3.5 h-3.5" }) }), (0, jsx_runtime_1.jsx)("h2", { className: "font-semibold", children: currentDate.format("MMMM YYYY") }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleNextMonth, className: "w-7 h-7 flex items-center justify-center text-xs rounded-full hover:bg-light-primary transition-colors cursor-pointer border-none bg-transparent", children: (0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/chevron-right", className: "w-3.5 h-3.5" }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-7 gap-1 mb-1", children: renderDays() }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-1", children: renderCells() })] }), rightElement && (0, jsx_runtime_1.jsx)("div", { children: rightElement })] }));
|
|
107
125
|
};
|
|
108
126
|
exports.InputDatePickerComponent = InputDatePickerComponent;
|
|
@@ -55,7 +55,7 @@ function InputDatetimeComponent({ label, tip, leftIcon, rightIcon, value, invali
|
|
|
55
55
|
label: "Jam",
|
|
56
56
|
value: 'time'
|
|
57
57
|
},
|
|
58
|
-
], active: pickerType, onChange: (e) => setPickerType(e), className: "mb-4" }), pickerType === "date" ? ((0, jsx_runtime_1.jsx)(InputDate_component_1.InputDatePickerComponent, { onChange: (e) => {
|
|
58
|
+
], active: pickerType, onChange: (e) => setPickerType(e), className: "mb-4" }), pickerType === "date" ? ((0, jsx_runtime_1.jsx)(InputDate_component_1.InputDatePickerComponent, { value: dateValue, onChange: (e) => {
|
|
59
59
|
setDateValue(e);
|
|
60
60
|
handleChange(e, timeValue);
|
|
61
61
|
} })) : ((0, jsx_runtime_1.jsx)(InputTime_component_1.InputTimePickerComponent, { onChange: (e) => {
|
|
@@ -70,7 +70,7 @@ function InputDatetimeComponent({ label, tip, leftIcon, rightIcon, value, invali
|
|
|
70
70
|
label: "Jam",
|
|
71
71
|
value: 'time'
|
|
72
72
|
},
|
|
73
|
-
], active: pickerType, onChange: (e) => setPickerType(e), className: "mb-4" }), pickerType === "date" ? ((0, jsx_runtime_1.jsx)(InputDate_component_1.InputDatePickerComponent, { onChange: (e) => {
|
|
73
|
+
], active: pickerType, onChange: (e) => setPickerType(e), className: "mb-4" }), pickerType === "date" ? ((0, jsx_runtime_1.jsx)(InputDate_component_1.InputDatePickerComponent, { value: dateValue, onChange: (e) => {
|
|
74
74
|
setDateValue(e);
|
|
75
75
|
handleChange(e, timeValue);
|
|
76
76
|
} })) : ((0, jsx_runtime_1.jsx)(InputTime_component_1.InputTimePickerComponent, { onChange: (e) => {
|
|
@@ -17,6 +17,8 @@ export declare const InputImageComponent: React.FC<InputImageProps>;
|
|
|
17
17
|
interface CropperProps {
|
|
18
18
|
src: string;
|
|
19
19
|
aspect: number;
|
|
20
|
+
fileName?: string;
|
|
21
|
+
fileType?: string;
|
|
20
22
|
onDone?: (file: File) => void;
|
|
21
23
|
}
|
|
22
24
|
export declare const CanvasCropper: React.FC<CropperProps>;
|
|
@@ -16,6 +16,7 @@ const InputImageComponent = ({ name, label, tip, value, disabled, aspect, invali
|
|
|
16
16
|
const [preview, setPreview] = (0, react_1.useState)("");
|
|
17
17
|
const [drag, setDrag] = (0, react_1.useState)(false);
|
|
18
18
|
const [cropSrc, setCropSrc] = (0, react_1.useState)(null);
|
|
19
|
+
const [cropFile, setCropFile] = (0, react_1.useState)(null);
|
|
19
20
|
const [openCrop, setOpenCrop] = (0, react_1.useState)(false);
|
|
20
21
|
const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, true, unregister);
|
|
21
22
|
const [invalidMessage, setInvalidMessage] = (0, _utils_1.useValidation)(inputHandler.value, validations, invalid, inputHandler.idle);
|
|
@@ -35,6 +36,7 @@ const InputImageComponent = ({ name, label, tip, value, disabled, aspect, invali
|
|
|
35
36
|
}
|
|
36
37
|
}, [value]);
|
|
37
38
|
const openCropper = (file) => {
|
|
39
|
+
setCropFile(file);
|
|
38
40
|
const reader = new FileReader();
|
|
39
41
|
reader.onload = () => {
|
|
40
42
|
setCropSrc(reader.result);
|
|
@@ -68,10 +70,14 @@ const InputImageComponent = ({ name, label, tip, value, disabled, aspect, invali
|
|
|
68
70
|
};
|
|
69
71
|
const onCropCancel = () => {
|
|
70
72
|
setOpenCrop(false);
|
|
73
|
+
setCropFile(null);
|
|
74
|
+
setCropSrc(null);
|
|
71
75
|
inputRef.current.value = "";
|
|
72
76
|
};
|
|
73
77
|
const remove = () => {
|
|
74
78
|
setPreview("");
|
|
79
|
+
setCropFile(null);
|
|
80
|
+
setCropSrc(null);
|
|
75
81
|
onChange?.(null);
|
|
76
82
|
inputRef.current && (inputRef.current.value = "");
|
|
77
83
|
};
|
|
@@ -99,12 +105,11 @@ const InputImageComponent = ({ name, label, tip, value, disabled, aspect, invali
|
|
|
99
105
|
}, onDragOver: (e) => e.preventDefault(), onDragEnter: (e) => {
|
|
100
106
|
e.preventDefault();
|
|
101
107
|
setDrag(true);
|
|
102
|
-
}, onDragLeave: () => setDrag(false), onDrop: onDropFile, children: [preview && !containerAspect ? ((0, jsx_runtime_1.jsxs)("div", { className: "relative group w-full h-full flex items-center justify-center", children: [(0, jsx_runtime_1.jsx)("img", { src: preview, alt: "Preview", className: "w-full h-auto max-h-[400px] object-contain rounded-lg" }), (0, jsx_runtime_1.jsxs)("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col items-center justify-center text-white rounded-lg", children: [(0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/images", className: "text-3xl mb-2" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-center", children: "Klik atau seret untuk ganti Gambar" })] })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "input-image-dropzone-content", children: [(0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: drag ? "solid/hand-holding" : "solid/images", className: "text-3xl" }), (0, jsx_runtime_1.jsx)("p", { className: "input-image-dropzone-text", children: drag ? "Letakkan di sini" : preview ? "Klik atau seret untuk ganti Gambar" : "Klik atau seret gambar" })] })), (0, jsx_runtime_1.jsx)("input", { id: randomId, ref: inputRef, type: "file", accept: "image/*", className: "hidden", disabled: disabled, onChange: onUpload })] }) }), preview && !disabled && ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { icon: "solid/times", paint: "danger", size: "sm", className: "absolute top-10 right-4", onClick: remove })), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage })), !isSm ? ((0, jsx_runtime_1.jsx)(Modal_component_1.ModalComponent, { show: openCrop, onClose: onCropCancel, title: "Sesuaikan Gambar", className: "w-fit max-w-[90vw]", children: cropSrc && ((0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.CanvasCropper, { src: cropSrc, aspect: eval(currentAspect.replace(":", "/")), onDone: onCropDone }) })) })) : ((0, jsx_runtime_1.jsx)(BottomSheet_component_1.BottomSheetComponent, { show: openCrop, onClose: onCropCancel, size: 400, children: cropSrc && ((0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.CanvasCropper, { src: cropSrc, aspect: eval(currentAspect.replace(":", "/")), onDone: onCropDone }) })) }))] }));
|
|
108
|
+
}, onDragLeave: () => setDrag(false), onDrop: onDropFile, children: [preview && !containerAspect ? ((0, jsx_runtime_1.jsxs)("div", { className: "relative group w-full h-full flex items-center justify-center", children: [(0, jsx_runtime_1.jsx)("img", { src: preview, alt: "Preview", className: "w-full h-auto max-h-[400px] object-contain rounded-lg" }), (0, jsx_runtime_1.jsxs)("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col items-center justify-center text-white rounded-lg", children: [(0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/images", className: "text-3xl mb-2" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-center", children: "Klik atau seret untuk ganti Gambar" })] })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "input-image-dropzone-content", children: [(0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: drag ? "solid/hand-holding" : "solid/images", className: "text-3xl" }), (0, jsx_runtime_1.jsx)("p", { className: "input-image-dropzone-text", children: drag ? "Letakkan di sini" : preview ? "Klik atau seret untuk ganti Gambar" : "Klik atau seret gambar" })] })), (0, jsx_runtime_1.jsx)("input", { id: randomId, ref: inputRef, type: "file", accept: "image/*", className: "hidden", disabled: disabled, onChange: onUpload })] }) }), preview && !disabled && ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { icon: "solid/times", paint: "danger", size: "sm", className: "absolute top-10 right-4", onClick: remove })), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage })), !isSm ? ((0, jsx_runtime_1.jsx)(Modal_component_1.ModalComponent, { show: openCrop, onClose: onCropCancel, title: "Sesuaikan Gambar", className: "w-fit max-w-[90vw]", children: cropSrc && ((0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.CanvasCropper, { src: cropSrc, aspect: eval(currentAspect.replace(":", "/")), fileName: cropFile?.name, fileType: cropFile?.type, onDone: onCropDone }) })) })) : ((0, jsx_runtime_1.jsx)(BottomSheet_component_1.BottomSheetComponent, { show: openCrop, onClose: onCropCancel, size: 400, children: cropSrc && ((0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.CanvasCropper, { src: cropSrc, aspect: eval(currentAspect.replace(":", "/")), fileName: cropFile?.name, fileType: cropFile?.type, onDone: onCropDone }) })) }))] }));
|
|
103
109
|
};
|
|
104
110
|
exports.InputImageComponent = InputImageComponent;
|
|
105
|
-
const CanvasCropper = ({ src, aspect, onDone, }) => {
|
|
111
|
+
const CanvasCropper = ({ src, aspect, fileName, fileType, onDone, }) => {
|
|
106
112
|
const canvasRef = (0, react_1.useRef)(null);
|
|
107
|
-
const previewRef = (0, react_1.useRef)(null);
|
|
108
113
|
const pinchDistRef = (0, react_1.useRef)(0);
|
|
109
114
|
const pinchStartZoomRef = (0, react_1.useRef)(1);
|
|
110
115
|
const [img, setImg] = (0, react_1.useState)(null);
|
|
@@ -130,8 +135,8 @@ const CanvasCropper = ({ src, aspect, onDone, }) => {
|
|
|
130
135
|
canvas.width = CROP_W;
|
|
131
136
|
canvas.height = CROP_SIZE;
|
|
132
137
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
133
|
-
const scaledW = img.
|
|
134
|
-
const scaledH = img.
|
|
138
|
+
const scaledW = img.naturalWidth * zoom;
|
|
139
|
+
const scaledH = img.naturalHeight * zoom;
|
|
135
140
|
const x = pos.x - scaledW / 2 + CROP_W / 2;
|
|
136
141
|
const y = pos.y - scaledH / 2 + CROP_SIZE / 2;
|
|
137
142
|
ctx.drawImage(img, x, y, scaledW, scaledH);
|
|
@@ -178,19 +183,50 @@ const CanvasCropper = ({ src, aspect, onDone, }) => {
|
|
|
178
183
|
const performCrop = () => {
|
|
179
184
|
if (!img)
|
|
180
185
|
return;
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
const scaleFactor = 1 / zoom;
|
|
187
|
+
const scaledW = img.naturalWidth * zoom;
|
|
188
|
+
const scaledH = img.naturalHeight * zoom;
|
|
189
|
+
const cropDisplayX = (scaledW - CROP_W) / 2 - pos.x;
|
|
190
|
+
const cropDisplayY = (scaledH - CROP_SIZE) / 2 - pos.y;
|
|
191
|
+
const sourceX = cropDisplayX * scaleFactor;
|
|
192
|
+
const sourceY = cropDisplayY * scaleFactor;
|
|
193
|
+
const sourceW = CROP_W * scaleFactor;
|
|
194
|
+
const sourceH = CROP_SIZE * scaleFactor;
|
|
195
|
+
const sx = Math.max(0, Math.min(img.naturalWidth - 1, sourceX));
|
|
196
|
+
const sy = Math.max(0, Math.min(img.naturalHeight - 1, sourceY));
|
|
197
|
+
const sw = Math.min(sourceW, img.naturalWidth - sx);
|
|
198
|
+
const sh = Math.min(sourceH, img.naturalHeight - sy);
|
|
199
|
+
let targetW = Math.round(sw);
|
|
200
|
+
let targetH = Math.round(sh);
|
|
201
|
+
const MAX_DIMENSION = 2560;
|
|
202
|
+
if (targetW > MAX_DIMENSION || targetH > MAX_DIMENSION) {
|
|
203
|
+
if (targetW >= targetH) {
|
|
204
|
+
targetH = Math.round((targetH * MAX_DIMENSION) / targetW);
|
|
205
|
+
targetW = MAX_DIMENSION;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
targetW = Math.round((targetW * MAX_DIMENSION) / targetH);
|
|
209
|
+
targetH = MAX_DIMENSION;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const outputCanvas = document.createElement("canvas");
|
|
213
|
+
outputCanvas.width = targetW;
|
|
214
|
+
outputCanvas.height = targetH;
|
|
215
|
+
const octx = outputCanvas.getContext("2d");
|
|
216
|
+
if (!octx)
|
|
217
|
+
return;
|
|
218
|
+
octx.imageSmoothingEnabled = true;
|
|
219
|
+
octx.imageSmoothingQuality = "high";
|
|
220
|
+
octx.drawImage(img, sx, sy, sw, sh, 0, 0, targetW, targetH);
|
|
221
|
+
const type = fileType || "image/jpeg";
|
|
222
|
+
const name = fileName || (type === "image/png" ? "cropped.png" : "cropped.jpg");
|
|
223
|
+
const quality = type === "image/png" ? undefined : 0.92;
|
|
224
|
+
outputCanvas.toBlob((blob) => {
|
|
225
|
+
if (!blob)
|
|
226
|
+
return;
|
|
227
|
+
const file = new File([blob], name, { type, lastModified: Date.now() });
|
|
192
228
|
onDone?.(file);
|
|
193
|
-
});
|
|
229
|
+
}, type, quality);
|
|
194
230
|
};
|
|
195
231
|
const handleWheel = (e) => {
|
|
196
232
|
e.preventDefault();
|
|
@@ -276,6 +312,6 @@ const CanvasCropper = ({ src, aspect, onDone, }) => {
|
|
|
276
312
|
setDragging(false);
|
|
277
313
|
pinchDistRef.current = 0;
|
|
278
314
|
};
|
|
279
|
-
return ((0, jsx_runtime_1.jsxs)("div", { style: { touchAction: "none" }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-image-cropper-wrapper", style: { width: CROP_W, height: CROP_SIZE }, onMouseMove: handleMove, onMouseUp: handleUp, onMouseLeave: handleUp, onWheel: handleWheel, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [(0, jsx_runtime_1.jsx)("canvas", { ref: canvasRef, width: CROP_W, height: CROP_SIZE, onMouseDown: handleDown, className: "cursor-move" }), (0, jsx_runtime_1.jsxs)("div", { className: "input-image-cropper-grid-container", style: { width: CROP_W, height: CROP_SIZE }, children: [(0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-border" }), (0, jsx_runtime_1.jsxs)("div", { className: "absolute inset-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-h input-image-cropper-grid-line-h-1" }), (0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-h input-image-cropper-grid-line-h-2" }), (0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-v input-image-cropper-grid-line-v-1" }), (0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-v input-image-cropper-grid-line-v-2" })] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-4 pb-2", children: (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { label: "Selesai", variant: "outline", onClick: performCrop, block: true }) })
|
|
315
|
+
return ((0, jsx_runtime_1.jsxs)("div", { style: { touchAction: "none" }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-image-cropper-wrapper", style: { width: CROP_W, height: CROP_SIZE }, onMouseMove: handleMove, onMouseUp: handleUp, onMouseLeave: handleUp, onWheel: handleWheel, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [(0, jsx_runtime_1.jsx)("canvas", { ref: canvasRef, width: CROP_W, height: CROP_SIZE, onMouseDown: handleDown, className: "cursor-move" }), (0, jsx_runtime_1.jsxs)("div", { className: "input-image-cropper-grid-container", style: { width: CROP_W, height: CROP_SIZE }, children: [(0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-border" }), (0, jsx_runtime_1.jsxs)("div", { className: "absolute inset-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-h input-image-cropper-grid-line-h-1" }), (0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-h input-image-cropper-grid-line-h-2" }), (0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-v input-image-cropper-grid-line-v-1" }), (0, jsx_runtime_1.jsx)("div", { className: "input-image-cropper-grid-line-v input-image-cropper-grid-line-v-2" })] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-4 pb-2", children: (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { label: "Selesai", variant: "outline", onClick: performCrop, block: true }) })] }));
|
|
280
316
|
};
|
|
281
317
|
exports.CanvasCropper = CanvasCropper;
|
|
@@ -8,16 +8,18 @@ export interface InputTimeProps extends Omit<InputHTMLAttributes<HTMLInputElemen
|
|
|
8
8
|
value?: string;
|
|
9
9
|
invalid?: string;
|
|
10
10
|
validations?: ValidationRules;
|
|
11
|
+
showSeconds?: boolean;
|
|
11
12
|
onChange?: (value: string) => any;
|
|
12
13
|
register?: (name: string, validations?: ValidationRules) => void;
|
|
13
14
|
unregister?: (name: string) => void;
|
|
14
15
|
/** Use custom class with: "label::", "tip::", "error::", "icon::". */
|
|
15
16
|
className?: string;
|
|
16
17
|
}
|
|
17
|
-
export declare function InputTimeComponent({ label, tip, leftIcon, rightIcon, value, invalid, validations, register, unregister, onChange, className, ...props }: InputTimeProps): import("@types/react").JSX.Element;
|
|
18
|
-
interface InputTimePickerProps {
|
|
18
|
+
export declare function InputTimeComponent({ label, tip, leftIcon, rightIcon, value, invalid, validations, showSeconds, register, unregister, onChange, className, ...props }: InputTimeProps): import("@types/react").JSX.Element;
|
|
19
|
+
export interface InputTimePickerProps {
|
|
20
|
+
value?: string;
|
|
21
|
+
showSeconds?: boolean;
|
|
19
22
|
onChange?: (time: string) => void;
|
|
20
23
|
rightElement?: ReactNode;
|
|
21
24
|
}
|
|
22
25
|
export declare const InputTimePickerComponent: FC<InputTimePickerProps>;
|
|
23
|
-
export {};
|
|
@@ -10,7 +10,7 @@ const _utils_1 = require("@utils");
|
|
|
10
10
|
const BottomSheet_component_1 = require("../modal/BottomSheet.component");
|
|
11
11
|
const Button_component_1 = require("../button/Button.component");
|
|
12
12
|
const OutsideClick_component_1 = require("../wrap/OutsideClick.component");
|
|
13
|
-
function InputTimeComponent({ label, tip, leftIcon, rightIcon, value, invalid, validations, register, unregister, onChange, className = "", ...props }) {
|
|
13
|
+
function InputTimeComponent({ label, tip, leftIcon, rightIcon, value, invalid, validations, showSeconds, register, unregister, onChange, className = "", ...props }) {
|
|
14
14
|
const { isSm } = (0, _utils_1.useResponsive)();
|
|
15
15
|
// =========================>
|
|
16
16
|
// ## Initial
|
|
@@ -28,48 +28,129 @@ function InputTimeComponent({ label, tip, leftIcon, rightIcon, value, invalid, v
|
|
|
28
28
|
}, onFocus: (e) => {
|
|
29
29
|
props.onFocus?.(e);
|
|
30
30
|
inputHandler.setFocus(true);
|
|
31
|
-
}, autoComplete: "off", inputMode: isSm ? "none" : undefined }), leftIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-left", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon")), icon: leftIcon })), rightIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-right", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon")), icon: rightIcon })), !isSm && inputHandler.focus && ((0, jsx_runtime_1.jsx)("div", { className: "input-time-picker-popover", children: (0, jsx_runtime_1.jsx)(exports.InputTimePickerComponent, { onChange: (time) => {
|
|
31
|
+
}, autoComplete: "off", inputMode: isSm ? "none" : undefined }), leftIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-left", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon")), icon: leftIcon })), rightIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-right", props.disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon")), icon: rightIcon })), !isSm && inputHandler.focus && ((0, jsx_runtime_1.jsx)("div", { className: "input-time-picker-popover", children: (0, jsx_runtime_1.jsx)(exports.InputTimePickerComponent, { value: inputHandler.value, showSeconds: showSeconds, onChange: (time) => {
|
|
32
32
|
inputHandler.setValue(time);
|
|
33
|
+
inputHandler.setIdle(false);
|
|
33
34
|
onChange?.(time);
|
|
34
|
-
} }) }))] }) }), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage }))] }), isSm && ((0, jsx_runtime_1.jsx)(BottomSheet_component_1.BottomSheetComponent, { show: inputHandler.focus, onClose: () => inputHandler.setFocus(false), size: 380, footer: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { label: "Selesai", variant: "outline", onClick: () => inputHandler.setFocus(false), block: true }) }), children: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.InputTimePickerComponent, { onChange: (time) => {
|
|
35
|
+
} }) }))] }) }), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage }))] }), isSm && ((0, jsx_runtime_1.jsx)(BottomSheet_component_1.BottomSheetComponent, { show: inputHandler.focus, onClose: () => inputHandler.setFocus(false), size: 380, footer: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { label: "Selesai", variant: "outline", onClick: () => inputHandler.setFocus(false), block: true }) }), children: (0, jsx_runtime_1.jsx)("div", { className: "p-4", children: (0, jsx_runtime_1.jsx)(exports.InputTimePickerComponent, { value: inputHandler.value, showSeconds: showSeconds, onChange: (time) => {
|
|
35
36
|
inputHandler.setValue(time);
|
|
37
|
+
inputHandler.setIdle(false);
|
|
36
38
|
onChange?.(time);
|
|
37
39
|
} }) }) }))] }));
|
|
38
40
|
}
|
|
39
41
|
;
|
|
40
|
-
const InputTimePickerComponent = ({ onChange, rightElement, }) => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const InputTimePickerComponent = ({ value, showSeconds = false, onChange, rightElement, }) => {
|
|
43
|
+
const parsed = (0, react_1.useMemo)(() => {
|
|
44
|
+
if (!value || typeof value !== "string") {
|
|
45
|
+
return { h: 0, m: 0, s: 0, hasValue: false };
|
|
46
|
+
}
|
|
47
|
+
const parts = value.split(":").map((p) => parseInt(p, 10));
|
|
48
|
+
return {
|
|
49
|
+
h: isNaN(parts[0]) ? 0 : Math.min(23, Math.max(0, parts[0])),
|
|
50
|
+
m: isNaN(parts[1]) ? 0 : Math.min(59, Math.max(0, parts[1])),
|
|
51
|
+
s: isNaN(parts[2]) ? 0 : Math.min(59, Math.max(0, parts[2])),
|
|
52
|
+
hasValue: true,
|
|
53
|
+
};
|
|
54
|
+
}, [value]);
|
|
55
|
+
const [hour, setHour] = (0, react_1.useState)(parsed.h);
|
|
56
|
+
const [minute, setMinute] = (0, react_1.useState)(parsed.m);
|
|
57
|
+
const [second, setSecond] = (0, react_1.useState)(parsed.s);
|
|
58
|
+
const shouldShowSeconds = showSeconds || (typeof value === "string" && value.split(":").length >= 3 && value.split(":")[2] !== "");
|
|
59
|
+
(0, react_1.useEffect)(() => {
|
|
60
|
+
if (parsed.hasValue) {
|
|
61
|
+
setHour(parsed.h);
|
|
62
|
+
setMinute(parsed.m);
|
|
63
|
+
setSecond(parsed.s);
|
|
64
|
+
}
|
|
65
|
+
}, [parsed]);
|
|
44
66
|
const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
|
|
45
67
|
const minutes = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
|
|
46
68
|
const seconds = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
const presetContainerRef = (0, react_1.useRef)(null);
|
|
70
|
+
const activePresetRef = (0, react_1.useRef)(null);
|
|
71
|
+
const hourContainerRef = (0, react_1.useRef)(null);
|
|
72
|
+
const activeHourRef = (0, react_1.useRef)(null);
|
|
73
|
+
const minuteContainerRef = (0, react_1.useRef)(null);
|
|
74
|
+
const activeMinuteRef = (0, react_1.useRef)(null);
|
|
75
|
+
const secondContainerRef = (0, react_1.useRef)(null);
|
|
76
|
+
const activeSecondRef = (0, react_1.useRef)(null);
|
|
77
|
+
const scrollToActive = (container, active) => {
|
|
78
|
+
if (container && active) {
|
|
79
|
+
container.scrollTop = active.offsetTop - container.offsetTop - (container.clientHeight / 2) + (active.clientHeight / 2);
|
|
80
|
+
}
|
|
54
81
|
};
|
|
55
82
|
(0, react_1.useEffect)(() => {
|
|
56
|
-
|
|
83
|
+
scrollToActive(hourContainerRef.current, activeHourRef.current);
|
|
84
|
+
scrollToActive(minuteContainerRef.current, activeMinuteRef.current);
|
|
85
|
+
if (shouldShowSeconds) {
|
|
86
|
+
scrollToActive(secondContainerRef.current, activeSecondRef.current);
|
|
87
|
+
}
|
|
88
|
+
scrollToActive(presetContainerRef.current, activePresetRef.current);
|
|
89
|
+
}, []);
|
|
90
|
+
const emitChange = (newH, newM, newS) => {
|
|
91
|
+
const hh = String(newH).padStart(2, "0");
|
|
92
|
+
const mm = String(newM).padStart(2, "0");
|
|
93
|
+
const ss = String(newS).padStart(2, "0");
|
|
94
|
+
const formatted = shouldShowSeconds ? `${hh}:${mm}:${ss}` : `${hh}:${mm}`;
|
|
57
95
|
onChange?.(formatted);
|
|
58
|
-
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
};
|
|
97
|
+
const handleSelect = (type, val) => {
|
|
98
|
+
let newH = hour;
|
|
99
|
+
let newM = minute;
|
|
100
|
+
let newS = second;
|
|
101
|
+
if (type === "h") {
|
|
102
|
+
newH = val;
|
|
103
|
+
setHour(val);
|
|
104
|
+
}
|
|
105
|
+
else if (type === "m") {
|
|
106
|
+
newM = val;
|
|
107
|
+
setMinute(val);
|
|
108
|
+
}
|
|
109
|
+
else if (type === "s") {
|
|
110
|
+
newS = val;
|
|
111
|
+
setSecond(val);
|
|
112
|
+
}
|
|
113
|
+
emitChange(newH, newM, newS);
|
|
114
|
+
};
|
|
115
|
+
const handlePresetSelect = (presetStr) => {
|
|
116
|
+
const parts = presetStr.split(":").map(Number);
|
|
117
|
+
const newH = parts[0] ?? 0;
|
|
118
|
+
const newM = parts[1] ?? 0;
|
|
119
|
+
const newS = 0;
|
|
120
|
+
setHour(newH);
|
|
121
|
+
setMinute(newM);
|
|
122
|
+
setSecond(newS);
|
|
123
|
+
emitChange(newH, newM, newS);
|
|
124
|
+
};
|
|
63
125
|
const timeSlots = (0, react_1.useMemo)(() => {
|
|
64
|
-
const
|
|
65
|
-
for (let i = 0; i
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
newTimeSlots.push(label);
|
|
126
|
+
const slots = [];
|
|
127
|
+
for (let i = 0; i < 24 * 60; i += 30) {
|
|
128
|
+
const h = Math.floor(i / 60);
|
|
129
|
+
const m = i % 60;
|
|
130
|
+
slots.push(`${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`);
|
|
70
131
|
}
|
|
71
|
-
return
|
|
132
|
+
return slots;
|
|
72
133
|
}, []);
|
|
73
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full
|
|
134
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full flex gap-2 h-[260px] select-none text-xs", children: [(0, jsx_runtime_1.jsxs)("div", { className: "w-20 shrink-0 h-full flex flex-col border-r border-stroke pr-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke", children: "Preset" }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5", ref: presetContainerRef, children: timeSlots.map((time) => {
|
|
135
|
+
const isPresetActive = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}` === time;
|
|
136
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: isPresetActive ? activePresetRef : null, className: (0, _utils_1.cn)("py-1.5 px-1 text-center font-medium rounded-md cursor-pointer transition-colors", isPresetActive
|
|
137
|
+
? "bg-primary text-white font-semibold"
|
|
138
|
+
: "hover:bg-light-primary text-foreground"), onClick: () => handlePresetSelect(time), children: time }, time));
|
|
139
|
+
}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 h-full flex flex-col", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke", children: "Jam" }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5", ref: hourContainerRef, children: hours.map((item) => {
|
|
140
|
+
const active = Number(item) === hour;
|
|
141
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: active ? activeHourRef : null, onClick: () => handleSelect("h", Number(item)), className: (0, _utils_1.cn)("py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors", active
|
|
142
|
+
? "bg-primary text-white font-semibold"
|
|
143
|
+
: "hover:bg-light-primary text-foreground"), children: item }, item));
|
|
144
|
+
}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 h-full flex flex-col", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke", children: "Menit" }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5", ref: minuteContainerRef, children: minutes.map((item) => {
|
|
145
|
+
const active = Number(item) === minute;
|
|
146
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: active ? activeMinuteRef : null, onClick: () => handleSelect("m", Number(item)), className: (0, _utils_1.cn)("py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors", active
|
|
147
|
+
? "bg-primary text-white font-semibold"
|
|
148
|
+
: "hover:bg-light-primary text-foreground"), children: item }, item));
|
|
149
|
+
}) })] }), shouldShowSeconds && ((0, jsx_runtime_1.jsxs)("div", { className: "flex-1 h-full flex flex-col", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke", children: "Detik" }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5", ref: secondContainerRef, children: seconds.map((item) => {
|
|
150
|
+
const active = Number(item) === second;
|
|
151
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: active ? activeSecondRef : null, onClick: () => handleSelect("s", Number(item)), className: (0, _utils_1.cn)("py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors", active
|
|
152
|
+
? "bg-primary text-white font-semibold"
|
|
153
|
+
: "hover:bg-light-primary text-foreground"), children: item }, item));
|
|
154
|
+
}) })] })), rightElement && (0, jsx_runtime_1.jsx)("div", { children: rightElement })] }));
|
|
74
155
|
};
|
|
75
156
|
exports.InputTimePickerComponent = InputTimePickerComponent;
|
|
@@ -5,6 +5,8 @@ export interface SelectOptionProps {
|
|
|
5
5
|
value: string | number;
|
|
6
6
|
searchable?: string[];
|
|
7
7
|
customLabel?: ReactNode;
|
|
8
|
+
customOption?: ((option: any) => ReactNode) | ReactNode;
|
|
9
|
+
[key: string]: any;
|
|
8
10
|
}
|
|
9
11
|
export interface SelectProps {
|
|
10
12
|
name: string;
|
|
@@ -23,7 +25,8 @@ export interface SelectProps {
|
|
|
23
25
|
options?: SelectOptionProps[];
|
|
24
26
|
searchable?: boolean;
|
|
25
27
|
serverOptionControl?: ApiType & {
|
|
26
|
-
cacheName?: string |
|
|
28
|
+
cacheName?: string | boolean;
|
|
29
|
+
selectableOption?: string[];
|
|
27
30
|
};
|
|
28
31
|
idbOptionControl?: {
|
|
29
32
|
store: string;
|
|
@@ -38,6 +41,8 @@ export interface SelectProps {
|
|
|
38
41
|
maxShowOption?: number;
|
|
39
42
|
creatable?: boolean | string;
|
|
40
43
|
creatableLabel?: string;
|
|
44
|
+
customOption?: (option: any) => ReactNode;
|
|
45
|
+
selectableOption?: string[];
|
|
41
46
|
onChange?: (value: any, data?: any) => any;
|
|
42
47
|
register?: (name: string, validations?: ValidationRules) => void;
|
|
43
48
|
unregister?: (name: string) => void;
|
|
@@ -46,4 +51,4 @@ export interface SelectProps {
|
|
|
46
51
|
/** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
|
|
47
52
|
className?: string;
|
|
48
53
|
}
|
|
49
|
-
export declare function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, value, invalid, disabled, validations, multiple, autoFocus, clearable, options, searchable, serverOptionControl, idbOptionControl, serverSearchable, includedOptions, exceptOptions, tempOptions, newOption, maxShowOption, creatable, creatableLabel, register, unregister, onChange, onFocus, onBlur, className, }: SelectProps): import("@types/react").JSX.Element;
|
|
54
|
+
export declare function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, value, invalid, disabled, validations, multiple, autoFocus, clearable, options, searchable, serverOptionControl, idbOptionControl, serverSearchable, includedOptions, exceptOptions, tempOptions, newOption, maxShowOption, creatable, creatableLabel, customOption, selectableOption, register, unregister, onChange, onFocus, onBlur, className, }: SelectProps): import("@types/react").JSX.Element;
|