@skalfa/skalfa-component 1.0.7 → 1.0.8

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