@skalfa/skalfa-component 1.0.7 → 1.0.9

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.
Files changed (107) hide show
  1. package/dist/accordion/Accordion.component.js +2 -3
  2. package/dist/breadcrumb/Breadcrumb.component.js +2 -3
  3. package/dist/button/Button.component.d.ts +2 -1
  4. package/dist/button/Button.component.js +2 -2
  5. package/dist/carousel/Carousel.component.js +2 -3
  6. package/dist/chip/Chip.component.js +2 -3
  7. package/dist/contexts-mock.d.ts +2 -0
  8. package/dist/contexts-mock.js +10 -0
  9. package/dist/input/Checkbox.component.js +2 -3
  10. package/dist/input/Input.component.d.ts +3 -2
  11. package/dist/input/Input.component.js +3 -3
  12. package/dist/input/InputCheckbox.component.js +1 -1
  13. package/dist/input/InputCurrency.component.d.ts +3 -2
  14. package/dist/input/InputCurrency.component.js +3 -3
  15. package/dist/input/InputDate.component.js +4 -5
  16. package/dist/input/InputDatetime.component.d.ts +3 -2
  17. package/dist/input/InputDatetime.component.js +3 -3
  18. package/dist/input/InputDocument.component.js +7 -8
  19. package/dist/input/InputImage.component.js +3 -4
  20. package/dist/input/InputNumber.component.js +3 -4
  21. package/dist/input/InputPassword.component.js +3 -3
  22. package/dist/input/InputRadio.component.js +1 -1
  23. package/dist/input/InputTime.component.js +3 -3
  24. package/dist/input/InputValues.component.js +3 -4
  25. package/dist/input/Select.component.js +6 -7
  26. package/dist/modal/FloatingPage.component.d.ts +3 -1
  27. package/dist/modal/FloatingPage.component.js +7 -3
  28. package/dist/modal/Modal.component.js +1 -2
  29. package/dist/modal/ModalConfirm.component.d.ts +2 -2
  30. package/dist/modal/ModalConfirm.component.js +14 -12
  31. package/dist/modal/Toast.component.js +1 -2
  32. package/dist/nav/Bottombar.component.d.ts +2 -2
  33. package/dist/nav/Bottombar.component.js +7 -8
  34. package/dist/nav/Footer.component.d.ts +2 -2
  35. package/dist/nav/Footer.component.js +6 -7
  36. package/dist/nav/Headbar.component.js +2 -3
  37. package/dist/nav/Navbar.component.d.ts +2 -2
  38. package/dist/nav/Navbar.component.js +3 -4
  39. package/dist/nav/Sidebar.component.d.ts +3 -7
  40. package/dist/nav/Sidebar.component.js +14 -17
  41. package/dist/supervision/FormSupervision.component.d.ts +4 -29
  42. package/dist/supervision/FormSupervision.component.js +84 -136
  43. package/dist/supervision/TableSupervision.component.d.ts +17 -26
  44. package/dist/supervision/TableSupervision.component.js +207 -193
  45. package/dist/table/ControlBar.component.js +15 -16
  46. package/dist/table/FilterComponent.js +4 -6
  47. package/dist/table/Pagination.component.js +2 -3
  48. package/dist/table/Table.component.js +3 -4
  49. package/dist/wrap/Swipe.component.js +2 -2
  50. package/package.json +18 -10
  51. package/src/accordion/Accordion.component.tsx +87 -0
  52. package/src/breadcrumb/Breadcrumb.component.tsx +79 -0
  53. package/src/button/Button.component.tsx +89 -0
  54. package/src/card/AlertCard.component.tsx +69 -0
  55. package/src/card/Card.component.tsx +25 -0
  56. package/src/card/DashboardCard.component.tsx +44 -0
  57. package/src/card/GalleryCard.component.tsx +50 -0
  58. package/src/card/ProductCard.component.tsx +65 -0
  59. package/src/card/ProfileCard.component.tsx +71 -0
  60. package/src/carousel/Carousel.component.tsx +111 -0
  61. package/src/chip/Chip.component.tsx +38 -0
  62. package/src/contexts-mock.ts +6 -0
  63. package/src/index.ts +70 -0
  64. package/src/input/Checkbox.component.tsx +102 -0
  65. package/src/input/Input.component.tsx +334 -0
  66. package/src/input/InputCheckbox.component.tsx +174 -0
  67. package/src/input/InputCurrency.component.tsx +165 -0
  68. package/src/input/InputDate.component.tsx +355 -0
  69. package/src/input/InputDatetime.component.tsx +267 -0
  70. package/src/input/InputDocument.component.tsx +360 -0
  71. package/src/input/InputImage.component.tsx +534 -0
  72. package/src/input/InputNumber.component.tsx +193 -0
  73. package/src/input/InputOtp.component.tsx +169 -0
  74. package/src/input/InputPassword.component.tsx +245 -0
  75. package/src/input/InputRadio.component.tsx +174 -0
  76. package/src/input/InputTime.component.tsx +280 -0
  77. package/src/input/InputValues.component.tsx +70 -0
  78. package/src/input/Radio.component.tsx +98 -0
  79. package/src/input/Select.component.tsx +557 -0
  80. package/src/modal/BottomSheet.component.tsx +246 -0
  81. package/src/modal/FloatingPage.component.tsx +133 -0
  82. package/src/modal/Modal.component.tsx +94 -0
  83. package/src/modal/ModalConfirm.component.tsx +204 -0
  84. package/src/modal/Toast.component.tsx +124 -0
  85. package/src/nav/Bottombar.component.tsx +71 -0
  86. package/src/nav/Footer.component.tsx +176 -0
  87. package/src/nav/Headbar.component.tsx +33 -0
  88. package/src/nav/Navbar.component.tsx +137 -0
  89. package/src/nav/Sidebar.component.tsx +307 -0
  90. package/src/nav/Tabbar.component.tsx +61 -0
  91. package/src/nav/Wizard.component.tsx +80 -0
  92. package/src/supervision/FormSupervision.component.tsx +404 -0
  93. package/src/supervision/TableSupervision.component.tsx +653 -0
  94. package/src/table/ControlBar.component.tsx +501 -0
  95. package/src/table/FilterComponent.tsx +517 -0
  96. package/src/table/Pagination.component.tsx +152 -0
  97. package/src/table/Table.component.tsx +436 -0
  98. package/src/typography/TypographyArticle.component.tsx +26 -0
  99. package/src/typography/TypographyColumn.component.tsx +20 -0
  100. package/src/typography/TypographyContent.component.tsx +20 -0
  101. package/src/typography/TypographyTips.component.tsx +20 -0
  102. package/src/wrap/Draggable.component.tsx +303 -0
  103. package/src/wrap/Image.component.tsx +10 -0
  104. package/src/wrap/OutsideClick.component.tsx +48 -0
  105. package/src/wrap/ScrollContainer.component.tsx +107 -0
  106. package/src/wrap/ShortcutProvider.tsx +57 -0
  107. package/src/wrap/Swipe.component.tsx +121 -0
@@ -0,0 +1,355 @@
1
+ "use client"
2
+
3
+ import { InputHTMLAttributes, ReactNode, useEffect, useMemo, useRef, useState } from "react";
4
+ import moment from "moment";
5
+ import { Icon } from "@skalfa/skalfa-icon";
6
+ import { cn, pcn, useInputHandler, useInputRandomId, useResponsive, useValidation, validation, ValidationRules } from "@utils";
7
+ import { BottomSheetComponent } from "../modal/BottomSheet.component";
8
+ import { ButtonComponent } from "../button/Button.component";
9
+ import { OutsideClickComponent } from "../wrap/OutsideClick.component";
10
+
11
+
12
+
13
+ type CT = "label" | "tip" | "error" | "input" | "icon";
14
+
15
+ export interface InputDateProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
16
+ label ?: string;
17
+ tip ?: string | ReactNode;
18
+ leftIcon ?: any;
19
+ rightIcon ?: any;
20
+
21
+ value ?: string;
22
+ invalid ?: string;
23
+ validations ?: ValidationRules;
24
+
25
+ onChange ?: (value: string) => any;
26
+ register ?: (name: string, validations?: ValidationRules) => void;
27
+ unregister ?: (name: string) => void;
28
+
29
+ /** Use custom class with: "label::", "tip::", "error::", "icon::". */
30
+ className ?: string;
31
+ }
32
+
33
+
34
+
35
+ export function InputDateComponent({
36
+ label,
37
+ tip,
38
+ leftIcon,
39
+ rightIcon,
40
+
41
+ value,
42
+ invalid,
43
+ validations,
44
+
45
+ register,
46
+ unregister,
47
+ onChange,
48
+
49
+ className = "",
50
+ ...props
51
+ }: InputDateProps) {
52
+ const { isSm } = useResponsive();
53
+
54
+ // =========================>
55
+ // ## Initial
56
+ // =========================>
57
+ const inputHandler = useInputHandler(props.name, value, validations, register, false)
58
+ const randomId = useInputRandomId()
59
+
60
+
61
+ // =========================>
62
+ // ## Invalid handler
63
+ // =========================>
64
+ const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
65
+
66
+
67
+ return (
68
+ <>
69
+ <div className="input-container">
70
+ <label
71
+ htmlFor={randomId}
72
+ className={cn(
73
+ "input-label",
74
+ props.disabled && "input-label-disabled",
75
+ inputHandler.focus && "input-label-focus",
76
+ !!invalidMessage && "input-label-error",
77
+ pcn<CT>(className, "label"),
78
+ props.disabled && pcn<CT>(className, "label", "disabled"),
79
+ inputHandler.focus && pcn<CT>(className, "label", "focus"),
80
+ !!invalidMessage && pcn<CT>(className, "label", "error")
81
+ )}
82
+ >
83
+ {label}
84
+ {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
85
+ </label>
86
+
87
+ {tip && (
88
+ <small
89
+ className={cn(
90
+ "input-tip",
91
+ props.disabled && "input-tip-disabled",
92
+ pcn<CT>(className, "tip"),
93
+ props.disabled && pcn<CT>(className, "tip", "disabled")
94
+ )}
95
+ >{tip}</small>
96
+ )}
97
+
98
+ <OutsideClickComponent onOutsideClick={!isSm ? () => inputHandler.setFocus(false) : undefined}>
99
+ <div className="relative">
100
+ <input
101
+ {...props}
102
+ id={randomId}
103
+ className={cn(
104
+ "input",
105
+ leftIcon && "input-with-left-icon",
106
+ rightIcon && "input-with-right-icon",
107
+ pcn<CT>(className, "input"),
108
+ !!invalidMessage && "input-error",
109
+ !!invalidMessage && pcn<CT>(className, "input", "error")
110
+ )}
111
+ value={inputHandler.value}
112
+ onChange={(e) => {
113
+ inputHandler.setValue(e.target.value);
114
+ inputHandler.setValue(false);
115
+ onChange?.(e.target.value);
116
+ }}
117
+ onFocus={(e) => {
118
+ props.onFocus?.(e);
119
+ inputHandler.setFocus(true);
120
+ }}
121
+ onBlur={(e) => {
122
+ props.onBlur?.(e);
123
+ }}
124
+ autoComplete="off"
125
+ inputMode={isSm ? "none" : undefined}
126
+ />
127
+
128
+ {leftIcon && (
129
+ <Icon
130
+ className={cn(
131
+ "input-icon",
132
+ "input-icon-left",
133
+ props.disabled && "input-icon-disabled",
134
+ inputHandler.focus && "input-icon-focus",
135
+ pcn<CT>(className, "icon"),
136
+ props.disabled && pcn<CT>(className, "icon", "disabled"),
137
+ inputHandler.focus && pcn<CT>(className, "icon", "focus")
138
+ )}
139
+ icon={leftIcon}
140
+ />
141
+ )}
142
+
143
+ {rightIcon && (
144
+ <Icon
145
+ className={cn(
146
+ "input-icon",
147
+ "input-icon-right",
148
+ props.disabled && "input-icon-disabled",
149
+ inputHandler.focus && "input-icon-focus",
150
+ pcn<CT>(className, "icon"),
151
+ props.disabled && pcn<CT>(className, "icon", "disabled"),
152
+ inputHandler.focus && pcn<CT>(className, "icon", "focus")
153
+ )}
154
+ icon={rightIcon}
155
+ />
156
+ )}
157
+
158
+ {!isSm && inputHandler.focus && (
159
+ <div className="input-date-picker-popover">
160
+ <InputDatePickerComponent
161
+ onChange={(e) => {
162
+ inputHandler.setValue(e);
163
+ onChange?.(e);
164
+ }}
165
+ />
166
+ </div>
167
+ )}
168
+ </div>
169
+ </OutsideClickComponent>
170
+
171
+ {invalidMessage && (
172
+ <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
173
+ )}
174
+ </div>
175
+
176
+ {isSm && (
177
+ <BottomSheetComponent
178
+ show={inputHandler.focus}
179
+ onClose={() => inputHandler.setFocus(false)}
180
+ size={380}
181
+ footer={
182
+ <div className="p-4">
183
+ <ButtonComponent
184
+ label="Selesai"
185
+ variant="outline"
186
+ onClick={() => inputHandler.setFocus(false)}
187
+ block
188
+ />
189
+ </div>
190
+ }
191
+ >
192
+ <div className="p-4">
193
+ <InputDatePickerComponent
194
+ onChange={(e) => {
195
+ inputHandler.setValue(e);
196
+ onChange?.(e);
197
+ }}
198
+ />
199
+ </div>
200
+ </BottomSheetComponent>
201
+ )}
202
+ </>
203
+ );
204
+ }
205
+
206
+
207
+
208
+ export interface InputDatePickerProps {
209
+ onChange ?: (date: string) => void;
210
+ minDate ?: string;
211
+ maxDate ?: string;
212
+ rightElement ?: ReactNode;
213
+ };
214
+
215
+
216
+
217
+ export const InputDatePickerComponent: React.FC<InputDatePickerProps> = ({
218
+ onChange,
219
+ minDate,
220
+ maxDate,
221
+ rightElement,
222
+ }) => {
223
+ const activeYearRef = useRef<HTMLDivElement | null>(null);
224
+ const containerYearRef = useRef<HTMLDivElement | null>(null);
225
+
226
+ const [currentDate, setCurrentDate] = useState(moment());
227
+ const [selectedDate, setSelectedDate] = useState(moment());
228
+
229
+ const startDate = moment(currentDate).startOf("month").startOf("week");
230
+ const endDate = moment(currentDate).endOf("month").endOf("week");
231
+
232
+ const handlePrevMonth = () => setCurrentDate(moment(currentDate).subtract(1, "month"));
233
+ const handleNextMonth = () => setCurrentDate(moment(currentDate).add(1, "month"));
234
+
235
+ const handleDateClick = (date: moment.Moment) => {
236
+ if ((minDate && date.isBefore(moment(minDate))) || (maxDate && date.isAfter(moment(maxDate)))) { return; }
237
+
238
+ setSelectedDate(date);
239
+ onChange?.(date.format("YYYY-MM-DD"));
240
+ };
241
+
242
+ const renderDays = () => {
243
+ const days = [];
244
+ const startDay = moment(startDate);
245
+
246
+ for (let i = 0; i < 7; i++) {
247
+ days.push(
248
+ <div key={i} className="text-center font-bold">
249
+ {startDay.add(i, "days").format("dd")}
250
+ </div>
251
+ );
252
+ }
253
+
254
+ return days;
255
+ };
256
+
257
+ const renderCells = () => {
258
+ const rows = [];
259
+ let days = [];
260
+ const day = moment(startDate);
261
+
262
+ while (day.isBefore(endDate) || day.isSame(endDate, "day")) {
263
+ for (let i = 0; i < 7; i++) {
264
+ const cloneDay = moment(day);
265
+
266
+ days.push(
267
+ <div
268
+ key={day.toString()}
269
+ className={`w-8 aspect-square flex items-center justify-center text-center rounded-lg transition-all
270
+ ${day.isSame(currentDate, "month") ? "text-foreground" : "text-light-foreground"}
271
+ ${day.isSame(selectedDate, "day") ? "bg-primary text-background" : "hover:bg-light-primary"}
272
+ ${day.isSame(moment(), "day") ? "border !border-primary" : "hover:bg-light-primary"}
273
+ ${(minDate && day.isBefore(moment(minDate))) || (maxDate && day.isAfter(moment(maxDate))) ? "opacity-10 cursor-not-allowed" : "cursor-pointer"}`}
274
+ onClick={() => handleDateClick(cloneDay)}
275
+ >{day.format("D")}</div>
276
+ );
277
+
278
+ day.add(1, "day");
279
+ }
280
+
281
+ rows.push(<div key={day.toString()} className="grid grid-cols-7 gap-1">{days}</div>);
282
+
283
+ days = [];
284
+ }
285
+
286
+ return rows;
287
+ };
288
+
289
+ const years = useMemo(() => {
290
+ const dumpYears = [];
291
+
292
+ for (let i = 1945; i <= moment().year(); i++) {
293
+ dumpYears.push(i);
294
+ }
295
+
296
+ return dumpYears;
297
+ }, []);
298
+
299
+ useEffect(() => {
300
+ if (activeYearRef.current && containerYearRef.current) {
301
+ containerYearRef.current.scrollTo({
302
+ top: activeYearRef.current.offsetTop - containerYearRef.current.offsetTop,
303
+ });
304
+ }
305
+ }, []);
306
+
307
+ return (
308
+ <div className="w-full flex gap-2 max-h-[260]">
309
+ <div
310
+ className="w-1/5 overflow-y-auto input-scroll pr-1"
311
+ ref={containerYearRef}
312
+ >
313
+ <div className="flex flex-col">
314
+ {years?.map((item) => {
315
+ const isActive = currentDate?.year() === item;
316
+
317
+ return (
318
+ <>
319
+ <div
320
+ key={item}
321
+ ref={isActive ? activeYearRef : null}
322
+ className={`py-1 px-2 font-semibold rounded-[6px] cursor-pointer ${isActive && "bg-light-primary"}`}
323
+ onClick={() => setCurrentDate(moment().set("year", item))}
324
+ >
325
+ {item}
326
+ </div>
327
+ </>
328
+ );
329
+ })}
330
+ </div>
331
+ </div>
332
+ <div className="w-4/5">
333
+ <div className="flex justify-between items-center mb-2">
334
+ <button
335
+ onClick={handlePrevMonth}
336
+ className="w-8 text-sm aspect-square rounded-full cursor-pointer"
337
+ >
338
+ <Icon icon="solid/chevron-left" />
339
+ </button>
340
+ <h2 className="font-semibold">{currentDate.format("MMMM")}</h2>
341
+ <button
342
+ onClick={handleNextMonth}
343
+ className="w-8 text-sm aspect-square rounded-full cursor-pointer"
344
+ >
345
+ <Icon icon="solid/chevron-right" />
346
+ </button>
347
+ </div>
348
+ <div className="grid grid-cols-7 gap-1 mb-2">{renderDays()}</div>
349
+ <div>{renderCells()}</div>
350
+ </div>
351
+
352
+ {rightElement && <div>{rightElement}</div>}
353
+ </div>
354
+ );
355
+ };
@@ -0,0 +1,267 @@
1
+ "use client"
2
+
3
+ import { InputHTMLAttributes, ReactNode, useEffect, useState } from "react";
4
+ import { Icon, type IconName } from "@skalfa/skalfa-icon";
5
+ import { cn, pcn, useInputHandler, useInputRandomId, useResponsive, useValidation, validation, ValidationRules } from "@utils";
6
+ import { OutsideClickComponent } from "../wrap/OutsideClick.component";
7
+ import { InputDatePickerComponent } from "./InputDate.component";
8
+ import { InputTimePickerComponent } from "./InputTime.component";
9
+ import { BottomSheetComponent } from "../modal/BottomSheet.component";
10
+ import { ButtonComponent } from "../button/Button.component";
11
+ import { TabbarComponent } from "../nav/Tabbar.component";
12
+
13
+
14
+
15
+ type CT = "label" | "tip" | "error" | "input" | "icon";
16
+
17
+ export interface InputDateTimeProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
18
+ label ?: string;
19
+ tip ?: string | ReactNode;
20
+ leftIcon ?: IconName;
21
+ rightIcon ?: IconName;
22
+
23
+ value ?: string;
24
+ invalid ?: string;
25
+ validations ?: ValidationRules;
26
+
27
+ onChange ?: (value: string) => any;
28
+ register ?: (name: string, validations?: ValidationRules) => void;
29
+ unregister ?: (name: string) => void;
30
+
31
+ /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
32
+ className ?: string;
33
+ }
34
+
35
+
36
+
37
+ export function InputDatetimeComponent({
38
+ label,
39
+ tip,
40
+ leftIcon,
41
+ rightIcon,
42
+
43
+ value,
44
+ invalid,
45
+ validations,
46
+
47
+ register,
48
+ unregister,
49
+ onChange,
50
+
51
+ className = "",
52
+ ...props
53
+ }: InputDateTimeProps) {
54
+ const { isSm } = useResponsive();
55
+
56
+ const [pickerType, setPickerType] = useState<"date" | "time">("date");
57
+ const [dateValue, setDateValue] = useState("");
58
+ const [timeValue, setTimeValue] = useState("");
59
+
60
+
61
+ // =========================>
62
+ // ## Initial
63
+ // =========================>
64
+ const inputHandler = useInputHandler(props.name, value, validations, register, false)
65
+ const randomId = useInputRandomId()
66
+
67
+
68
+ // =========================>
69
+ // ## Invalid handler
70
+ // =========================>
71
+ const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
72
+
73
+
74
+ // =========================>
75
+ // ## change value handler
76
+ // =========================>
77
+ useEffect(() => {
78
+ inputHandler.setValue(value || "");
79
+ value && inputHandler.setValue(false);
80
+
81
+ if (value) {
82
+ const [d, t] = value.split(" ");
83
+ setDateValue(d || "");
84
+ setTimeValue(t || "");
85
+ }
86
+ }, [value]);
87
+
88
+
89
+ const handleChange = (date: string, time: string) => {
90
+ const newVal = `${date} ${time}`;
91
+ inputHandler.setValue(newVal.trim());
92
+ onChange?.(newVal.trim());
93
+ };
94
+
95
+
96
+ return (
97
+ <>
98
+ <div className="input-container">
99
+ <label
100
+ htmlFor={randomId}
101
+ className={cn(
102
+ "input-label",
103
+ props.disabled && "input-label-disabled",
104
+ inputHandler.focus && "input-label-focus",
105
+ !!invalidMessage && "input-label-error",
106
+ pcn<CT>(className, "label"),
107
+ )}
108
+ >
109
+ {label}
110
+ {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
111
+ </label>
112
+
113
+ {tip && (
114
+ <small
115
+ className={cn(
116
+ "input-tip",
117
+ props.disabled && "input-tip-disabled",
118
+ pcn<CT>(className, "tip"),
119
+ )}
120
+ >{tip}</small>
121
+ )}
122
+
123
+ <OutsideClickComponent onOutsideClick={!isSm ? () => inputHandler.setFocus(false) : undefined}>
124
+ <div className="relative">
125
+ <input
126
+ {...props}
127
+ id={randomId}
128
+ readOnly
129
+ className={cn(
130
+ "input",
131
+ leftIcon && "input-with-left-icon",
132
+ rightIcon && "input-with-right-icon",
133
+ pcn<CT>(className, "input"),
134
+ !!invalidMessage && "input-error"
135
+ )}
136
+ value={inputHandler.value}
137
+ onFocus={(e) => {
138
+ props.onFocus?.(e);
139
+ inputHandler.setFocus(true);
140
+ }}
141
+ autoComplete="off"
142
+ inputMode={isSm ? "none" : undefined}
143
+ />
144
+
145
+ {leftIcon && (
146
+ <Icon
147
+ className={cn(
148
+ "input-icon",
149
+ "input-icon-left",
150
+ props.disabled && "input-icon-disabled",
151
+ inputHandler.focus && "input-icon-focus",
152
+ pcn<CT>(className, "icon"),
153
+ )}
154
+ icon={leftIcon}
155
+ />
156
+ )}
157
+
158
+ {rightIcon && (
159
+ <Icon
160
+ className={cn(
161
+ "input-icon",
162
+ "input-icon-right",
163
+ props.disabled && "input-icon-disabled",
164
+ inputHandler.focus && "input-icon-focus",
165
+ pcn<CT>(className, "icon"),
166
+ )}
167
+ icon={rightIcon}
168
+ />
169
+ )}
170
+
171
+ {!isSm && inputHandler.focus && (
172
+ <>
173
+ <div className="input-datetime-picker-popover">
174
+ <TabbarComponent
175
+ items={[
176
+ {
177
+ label: "Tanggal",
178
+ value: 'date'
179
+ },
180
+ {
181
+ label: "Jam",
182
+ value: 'time'
183
+ },
184
+ ]}
185
+ active={pickerType}
186
+ onChange={(e) => setPickerType(e as "time" | "date")}
187
+ className="mb-4"
188
+ />
189
+ {pickerType === "date" ? (
190
+ <InputDatePickerComponent
191
+ onChange={(e) => {
192
+ setDateValue(e);
193
+ handleChange(e, timeValue);
194
+ }}
195
+ />
196
+ ) : (
197
+ <InputTimePickerComponent
198
+ onChange={(e) => {
199
+ setTimeValue(e);
200
+ handleChange(dateValue, e);
201
+ }}
202
+ />
203
+ )}
204
+ </div>
205
+ </>
206
+ )}
207
+ </div>
208
+ </OutsideClickComponent>
209
+
210
+ {invalidMessage && (
211
+ <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
212
+ )}
213
+ </div>
214
+
215
+ {isSm && (
216
+ <BottomSheetComponent
217
+ show={inputHandler.focus}
218
+ onClose={() => inputHandler.setFocus(false)}
219
+ size={430}
220
+ footer={
221
+ <div className="p-4">
222
+ <ButtonComponent
223
+ label="Selesai"
224
+ variant="outline"
225
+ onClick={() => inputHandler.setFocus(false)}
226
+ block
227
+ />
228
+ </div>
229
+ }
230
+ >
231
+ <div className="p-4">
232
+ <TabbarComponent
233
+ items={[
234
+ {
235
+ label: "Tanggal",
236
+ value: 'date'
237
+ },
238
+ {
239
+ label: "Jam",
240
+ value: 'time'
241
+ },
242
+ ]}
243
+ active={pickerType}
244
+ onChange={(e) => setPickerType(e as "time" | "date")}
245
+ className="mb-4"
246
+ />
247
+ {pickerType === "date" ? (
248
+ <InputDatePickerComponent
249
+ onChange={(e) => {
250
+ setDateValue(e);
251
+ handleChange(e, timeValue);
252
+ }}
253
+ />
254
+ ) : (
255
+ <InputTimePickerComponent
256
+ onChange={(e) => {
257
+ setTimeValue(e);
258
+ handleChange(dateValue, e);
259
+ }}
260
+ />
261
+ )}
262
+ </div>
263
+ </BottomSheetComponent>
264
+ )}
265
+ </>
266
+ );
267
+ }