@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,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 { RadioComponent } from "./Radio.component";
6
+
7
+
8
+
9
+ type CT = "label" | "tip" | "error" | "input" | "icon";
10
+
11
+ export type InputRadioOptionProps = {
12
+ value: string | number;
13
+ label: string;
14
+ };
15
+
16
+ export type InputRadioProps = {
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
+ options ?: InputRadioOptionProps[];
26
+ serverOptionControl ?: ApiType;
27
+ customOptions ?: any;
28
+ validations ?: ValidationRules;
29
+
30
+ onChange ?: (value: string | number) => any;
31
+ register ?: (name: string, validations?: ValidationRules) => void;
32
+ unregister ?: (name: string) => void;
33
+
34
+ /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
35
+ className ?: string;
36
+ /** Use custom class with: "label::", "checked::", "error::". */
37
+ classNameCheckbox ?: string;
38
+ };
39
+
40
+
41
+
42
+ export function InputRadioComponent({
43
+ name,
44
+ label,
45
+ tip,
46
+ vertical,
47
+
48
+ value,
49
+ disabled,
50
+ invalid,
51
+
52
+ options,
53
+ serverOptionControl,
54
+ customOptions,
55
+ validations,
56
+
57
+ onChange,
58
+ register,
59
+ unregister,
60
+
61
+ className = "",
62
+ classNameCheckbox = "",
63
+ }: InputRadioProps) {
64
+ const [dataOptions, setDataOptions] = useState<InputRadioOptionProps[]>([]);
65
+ const [loading, setLoading] = useState(false);
66
+
67
+
68
+ // =========================>
69
+ // ## Initial
70
+ // =========================>
71
+ const inputHandler = useInputHandler(name, value, validations, register, false)
72
+
73
+
74
+ // =========================>
75
+ // ## Invalid handler
76
+ // =========================>
77
+ const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
78
+
79
+
80
+ // =========================>
81
+ // ## Fetch option
82
+ // =========================>
83
+ useEffect(() => {
84
+ const fetchOptions = async () => {
85
+ setLoading(true);
86
+ const mutateOptions = await api(serverOptionControl || {});
87
+ if (mutateOptions?.status == 200) {
88
+ customOptions
89
+ ? setDataOptions([customOptions, ...mutateOptions.data])
90
+ : 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-radio-list",
134
+ vertical && "input-radio-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) &&
145
+ (options || dataOptions)?.map((option, key) => {
146
+ return (
147
+ <RadioComponent
148
+ key={key}
149
+ label={option.label}
150
+ name={`option[${option.value}]#${name}`}
151
+ checked={inputHandler.value == option.value}
152
+ disabled={disabled}
153
+ className={classNameCheckbox}
154
+ onChange={() => {
155
+ if (inputHandler.value == option.value) {
156
+ inputHandler.setValue("");
157
+ onChange?.("");
158
+ } else {
159
+ inputHandler.setValue(option.value || "");
160
+ onChange?.(option.value || "");
161
+ }
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,280 @@
1
+ "use client"
2
+
3
+ import { FC, InputHTMLAttributes, ReactNode, useEffect, useMemo, useState } from "react";
4
+ import { Icon } from "@skalfa/skalfa-icon";
5
+ import { cn, pcn, useInputHandler, useInputRandomId, useResponsive, useValidation, validation, ValidationRules } from "@utils";
6
+ import { BottomSheetComponent } from "../modal/BottomSheet.component";
7
+ import { ButtonComponent } from "../button/Button.component";
8
+ import { OutsideClickComponent } from "../wrap/OutsideClick.component";
9
+
10
+
11
+
12
+ type CT = "label" | "tip" | "error" | "input"| "icon";
13
+
14
+ export interface InputTimeProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
15
+ label ?: string;
16
+ tip ?: string | ReactNode;
17
+ leftIcon ?: any;
18
+ rightIcon ?: any;
19
+
20
+ value ?: string;
21
+ invalid ?: string;
22
+ validations ?: ValidationRules;
23
+
24
+ onChange ?: (value: string) => any;
25
+ register ?: (name: string, validations?: ValidationRules) => void;
26
+ unregister ?: (name: string) => void;
27
+
28
+ /** Use custom class with: "label::", "tip::", "error::", "icon::". */
29
+ className ?: string;
30
+ }
31
+
32
+
33
+
34
+ export function InputTimeComponent({
35
+ label,
36
+ tip,
37
+ leftIcon,
38
+ rightIcon,
39
+
40
+ value,
41
+ invalid,
42
+ validations,
43
+
44
+ register,
45
+ unregister,
46
+ onChange,
47
+
48
+ className = "",
49
+ ...props
50
+ }: InputTimeProps) {
51
+ const { isSm } = useResponsive();
52
+
53
+ // =========================>
54
+ // ## Initial
55
+ // =========================>
56
+ const inputHandler = useInputHandler(props.name, value, validations, register, false)
57
+ const randomId = useInputRandomId()
58
+
59
+
60
+ // =========================>
61
+ // ## Invalid handler
62
+ // =========================>
63
+ const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
64
+
65
+
66
+ return (
67
+ <>
68
+ <div className="input-container">
69
+ <label
70
+ htmlFor={randomId}
71
+ className={cn(
72
+ "input-label",
73
+ props.disabled && "input-label-disabled",
74
+ inputHandler.focus && "input-label-focus",
75
+ !!invalidMessage && "input-label-error",
76
+ pcn<CT>(className, "label"),
77
+ )}
78
+ >
79
+ {label}
80
+ {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
81
+ </label>
82
+
83
+ {tip && (
84
+ <small
85
+ className={cn(
86
+ "input-tip",
87
+ props.disabled && "input-tip-disabled",
88
+ pcn<CT>(className, "tip"),
89
+ )}
90
+ >{tip}</small>
91
+ )}
92
+
93
+ <OutsideClickComponent onOutsideClick={!isSm ? () => inputHandler.setFocus(false) : undefined}>
94
+ <div className="relative">
95
+ <input
96
+ {...props}
97
+ id={randomId}
98
+ className={cn(
99
+ "input",
100
+ leftIcon && "input-with-left-icon",
101
+ rightIcon && "input-with-right-icon",
102
+ pcn<CT>(className, "input"),
103
+ !!invalidMessage && "input-error"
104
+ )}
105
+ value={inputHandler.value}
106
+ onChange={(e) => {
107
+ inputHandler.setValue(e.target.value);
108
+ inputHandler.setIdle(false);
109
+ onChange?.(e.target.value);
110
+ }}
111
+ onFocus={(e) => {
112
+ props.onFocus?.(e);
113
+ inputHandler.setFocus(true);
114
+ }}
115
+ autoComplete="off"
116
+ inputMode={isSm ? "none" : undefined}
117
+ />
118
+
119
+ {leftIcon && (
120
+ <Icon
121
+ className={cn(
122
+ "input-icon",
123
+ "input-icon-left",
124
+ props.disabled && "input-icon-disabled",
125
+ inputHandler.focus && "input-icon-focus",
126
+ pcn<CT>(className, "icon"),
127
+ )}
128
+ icon={leftIcon}
129
+ />
130
+ )}
131
+
132
+ {rightIcon && (
133
+ <Icon
134
+ className={cn(
135
+ "input-icon",
136
+ "input-icon-right",
137
+ props.disabled && "input-icon-disabled",
138
+ inputHandler.focus && "input-icon-focus",
139
+ pcn<CT>(className, "icon"),
140
+ )}
141
+ icon={rightIcon}
142
+ />
143
+ )}
144
+
145
+ {!isSm && inputHandler.focus && (
146
+ <div className="input-time-picker-popover">
147
+ <InputTimePickerComponent
148
+ onChange={(time) => {
149
+ inputHandler.setValue(time);
150
+ onChange?.(time);
151
+ }}
152
+ />
153
+ </div>
154
+ )}
155
+ </div>
156
+ </OutsideClickComponent>
157
+
158
+ {invalidMessage && (
159
+ <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
160
+ )}
161
+ </div>
162
+
163
+ {isSm && (
164
+ <BottomSheetComponent
165
+ show={inputHandler.focus}
166
+ onClose={() => inputHandler.setFocus(false)}
167
+ size={380}
168
+ footer={
169
+ <div className="p-4">
170
+ <ButtonComponent
171
+ label="Selesai"
172
+ variant="outline"
173
+ onClick={() => inputHandler.setFocus(false)}
174
+ block
175
+ />
176
+ </div>
177
+ }
178
+ >
179
+ <div className="p-4">
180
+ <InputTimePickerComponent
181
+ onChange={(time) => {
182
+ inputHandler.setValue(time);
183
+ onChange?.(time);
184
+ }}
185
+ />
186
+ </div>
187
+ </BottomSheetComponent>
188
+ )}
189
+ </>
190
+ );
191
+ }
192
+
193
+
194
+
195
+ interface InputTimePickerProps {
196
+ onChange ?: (time: string) => void;
197
+ rightElement ?: ReactNode;
198
+ };
199
+
200
+
201
+
202
+
203
+ export const InputTimePickerComponent: FC<InputTimePickerProps> = ({
204
+ onChange,
205
+ rightElement,
206
+ }) => {
207
+ const [hour, setHour] = useState(0);
208
+ const [minute, setMinute] = useState(0);
209
+ const [second, setSecond] = useState(0);
210
+
211
+ const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
212
+ const minutes = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
213
+ const seconds = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
214
+
215
+ const handleSelect = (type: "h" | "m" | "s", val: string) => {
216
+ if (type === "h") setHour(Number(val));
217
+ if (type === "m") setMinute(Number(val));
218
+ if (type === "s") setSecond(Number(val));
219
+ };
220
+
221
+ useEffect(() => {
222
+ const formatted = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}:${String(second).padStart(2, "0")}`;
223
+ onChange?.(formatted);
224
+ }, [hour, minute, second]);
225
+
226
+ const renderColumn = (items: string[], type: "h" | "m" | "s", activeValue: number) => (
227
+ <div className="flex-1 overflow-y-auto text-center input-scroll">
228
+ {items.map((item) => {
229
+ const active = Number(item) === activeValue;
230
+
231
+ return (
232
+ <div
233
+ key={item}
234
+ onClick={() => handleSelect(type, item)}
235
+ className={cn(
236
+ "p-2 cursor-pointer rounded text-sm",
237
+ active ? "bg-primary text-background font-semibold" : "hover:bg-light-primary"
238
+ )}
239
+ >{item}</div>
240
+ );
241
+ })}
242
+ </div>
243
+ );
244
+
245
+ const timeSlots = useMemo(() => {
246
+ const newTimeSlots = [];
247
+
248
+ for (let i = 0; i <= 24 * 60; i += 30) {
249
+ const hours = Math.floor(i / 60);
250
+ const minutes = i % 60;
251
+ const label = `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
252
+
253
+ newTimeSlots.push(label);
254
+ }
255
+
256
+ return newTimeSlots;
257
+ }, [])
258
+
259
+ return (
260
+ <div className="w-full max-h-[260] flex gap-2">
261
+ <div className="w-1/3 overflow-y-auto bg-background rounded-[6px] input-scroll">
262
+ {timeSlots.map((time) => (
263
+ <div
264
+ key={time}
265
+ className="p-2 text-sm rounded cursor-pointer hover:bg-light-primary text-center"
266
+ onClick={() => onChange?.(time)}
267
+ >{time}</div>
268
+ ))}
269
+ </div>
270
+
271
+ <div className="w-2/3 flex gap-2">
272
+ {renderColumn(hours, "h", hour)}
273
+ {renderColumn(minutes, "m", minute)}
274
+ {renderColumn(seconds, "s", second)}
275
+ </div>
276
+
277
+ {rightElement && <div>{rightElement}</div>}
278
+ </div>
279
+ );
280
+ };
@@ -0,0 +1,70 @@
1
+ import { Icon, type IconName } from "@skalfa/skalfa-icon";
2
+ import { CSSProperties } from 'react';
3
+ import { cn } from '@utils';
4
+
5
+ export function InputValues({
6
+ value,
7
+ className,
8
+ onDelete,
9
+ isFocus,
10
+ onFocus,
11
+ style,
12
+ } : {
13
+ value?: string[] | number[];
14
+ className?: string;
15
+ onDelete?: (val: string | number, index: string | number) => void;
16
+ isFocus?: boolean,
17
+ onFocus?: () => void;
18
+ style?: CSSProperties
19
+ }) {
20
+ if(isFocus) {
21
+ return (
22
+ <>
23
+ {!!value?.length && (
24
+ <div
25
+ className={cn("input-values-dropdown", className)}
26
+ onClick={() => onFocus?.()}
27
+ >
28
+ {value?.map((item, key) => {
29
+ return (
30
+ <div key={key} className="input-values-item">
31
+ <span className="">{item}</span>
32
+ <Icon
33
+ icon="solid/times"
34
+ className="input-values-delete"
35
+ onClick={() => onDelete?.(item, key)}
36
+ />
37
+ </div>
38
+ );
39
+ })}
40
+ </div>
41
+ )}
42
+ </>
43
+ )
44
+ } else {
45
+ return (
46
+ <>
47
+ <div
48
+ className={cn("input-values-preview", className)}
49
+ style={style}
50
+ onClick={() => onFocus?.()}
51
+ >
52
+ <div className="input-values-container">
53
+ {value?.map((item, key) => {
54
+ return (
55
+ <div key={key} className="input-values-item">
56
+ <span>{item}</span>
57
+ <Icon
58
+ icon="solid/times"
59
+ className="input-values-delete"
60
+ onClick={() => onDelete?.(item, key)}
61
+ />
62
+ </div>
63
+ );
64
+ })}
65
+ </div>
66
+ </div>
67
+ </>
68
+ )
69
+ }
70
+ }
@@ -0,0 +1,98 @@
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { cn, pcn, useInputRandomId } from "@utils";
5
+
6
+ type CT = "label" | "checked" | "error" | "input";
7
+
8
+ export interface RadioProps {
9
+ name : string;
10
+ label ?: string;
11
+
12
+ value ?: string;
13
+ disabled ?: boolean;
14
+ checked ?: boolean;
15
+ invalid ?: string;
16
+
17
+ onChange ?: () => void;
18
+
19
+ /** Use custom class with: "label::", "checked::", "error::". */
20
+ className ?: string;
21
+ };
22
+
23
+ export function RadioComponent({
24
+ name,
25
+ label,
26
+
27
+ value,
28
+ disabled,
29
+ checked,
30
+ invalid,
31
+
32
+ onChange,
33
+
34
+ className = "",
35
+ }: RadioProps) {
36
+
37
+ // =========================>
38
+ // ## initial
39
+ // =========================>
40
+ const randomId = useInputRandomId()
41
+ const [invalidMessage, setInvalidMessage] = useState("");
42
+
43
+
44
+ // =========================>
45
+ // ## invalid handler
46
+ // =========================>
47
+ useEffect(() => {
48
+ setInvalidMessage(invalid || "");
49
+ }, [invalid]);
50
+
51
+
52
+ return (
53
+ <>
54
+ <div className="radio-container">
55
+ <input
56
+ type="checkbox"
57
+ className="hidden"
58
+ id={randomId}
59
+ name={name}
60
+ onChange={onChange}
61
+ defaultChecked={checked}
62
+ value={value}
63
+ disabled={disabled}
64
+ />
65
+
66
+ <label
67
+ htmlFor={randomId}
68
+ className={cn(
69
+ "radio-wrapper",
70
+ disabled && "radio-wrapper-disabled"
71
+ )}
72
+ >
73
+ <div
74
+ className={cn(
75
+ "radio-input",
76
+ checked && "radio-input-checked",
77
+ checked && pcn<CT>(className, "checked"),
78
+ pcn<CT>(className, "input"),
79
+ )}
80
+ ></div>
81
+ <div
82
+ className={cn(
83
+ "radio-label",
84
+ checked && "radio-label-checked",
85
+ pcn<CT>(className, "label"),
86
+ checked && pcn<CT>(className, "label", "checked"),
87
+ disabled && pcn<CT>(className, "label", "disabled"),
88
+ )}
89
+ >{label}</div>
90
+ </label>
91
+
92
+ {invalidMessage && (
93
+ <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
94
+ )}
95
+ </div>
96
+ </>
97
+ );
98
+ }