@skalfa/skalfa-component 1.0.53 → 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.
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { FC, InputHTMLAttributes, ReactNode, useEffect, useMemo, useState } from "react";
3
+ import { FC, InputHTMLAttributes, ReactNode, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { Icon } from "@skalfa/skalfa-icon";
5
5
  import { cn, pcn, useInputHandler, useInputRandomId, useResponsive, useValidation, validation, ValidationRules } from "@utils";
6
6
  import { BottomSheetComponent } from "../modal/BottomSheet.component";
@@ -12,21 +12,22 @@ import { OutsideClickComponent } from "../wrap/OutsideClick.component";
12
12
  type CT = "label" | "tip" | "error" | "input"| "icon";
13
13
 
14
14
  export interface InputTimeProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
15
- label ?: string;
16
- tip ?: string | ReactNode;
17
- leftIcon ?: any;
18
- rightIcon ?: any;
15
+ label ?: string;
16
+ tip ?: string | ReactNode;
17
+ leftIcon ?: any;
18
+ rightIcon ?: any;
19
19
 
20
20
  value ?: string;
21
21
  invalid ?: string;
22
22
  validations ?: ValidationRules;
23
+ showSeconds ?: boolean;
23
24
 
24
- onChange ?: (value: string) => any;
25
+ onChange ?: (value: string) => any;
25
26
  register ?: (name: string, validations?: ValidationRules) => void;
26
27
  unregister ?: (name: string) => void;
27
28
 
28
29
  /** Use custom class with: "label::", "tip::", "error::", "icon::". */
29
- className ?: string;
30
+ className ?: string;
30
31
  }
31
32
 
32
33
 
@@ -40,6 +41,7 @@ export function InputTimeComponent({
40
41
  value,
41
42
  invalid,
42
43
  validations,
44
+ showSeconds,
43
45
 
44
46
  register,
45
47
  unregister,
@@ -145,8 +147,11 @@ export function InputTimeComponent({
145
147
  {!isSm && inputHandler.focus && (
146
148
  <div className="input-time-picker-popover">
147
149
  <InputTimePickerComponent
150
+ value={inputHandler.value}
151
+ showSeconds={showSeconds}
148
152
  onChange={(time) => {
149
153
  inputHandler.setValue(time);
154
+ inputHandler.setIdle(false);
150
155
  onChange?.(time);
151
156
  }}
152
157
  />
@@ -178,8 +183,11 @@ export function InputTimeComponent({
178
183
  >
179
184
  <div className="p-4">
180
185
  <InputTimePickerComponent
186
+ value={inputHandler.value}
187
+ showSeconds={showSeconds}
181
188
  onChange={(time) => {
182
189
  inputHandler.setValue(time);
190
+ inputHandler.setIdle(false);
183
191
  onChange?.(time);
184
192
  }}
185
193
  />
@@ -192,7 +200,9 @@ export function InputTimeComponent({
192
200
 
193
201
 
194
202
 
195
- interface InputTimePickerProps {
203
+ export interface InputTimePickerProps {
204
+ value ?: string;
205
+ showSeconds ?: boolean;
196
206
  onChange ?: (time: string) => void;
197
207
  rightElement ?: ReactNode;
198
208
  };
@@ -201,79 +211,229 @@ interface InputTimePickerProps {
201
211
 
202
212
 
203
213
  export const InputTimePickerComponent: FC<InputTimePickerProps> = ({
214
+ value,
215
+ showSeconds = false,
204
216
  onChange,
205
217
  rightElement,
206
218
  }) => {
207
- const [hour, setHour] = useState(0);
208
- const [minute, setMinute] = useState(0);
209
- const [second, setSecond] = useState(0);
219
+ const parsed = useMemo(() => {
220
+ if (!value || typeof value !== "string") {
221
+ return { h: 0, m: 0, s: 0, hasValue: false };
222
+ }
223
+ const parts = value.split(":").map((p) => parseInt(p, 10));
224
+ return {
225
+ h: isNaN(parts[0]) ? 0 : Math.min(23, Math.max(0, parts[0])),
226
+ m: isNaN(parts[1]) ? 0 : Math.min(59, Math.max(0, parts[1])),
227
+ s: isNaN(parts[2]) ? 0 : Math.min(59, Math.max(0, parts[2])),
228
+ hasValue: true,
229
+ };
230
+ }, [value]);
231
+
232
+ const [hour, setHour] = useState(parsed.h);
233
+ const [minute, setMinute] = useState(parsed.m);
234
+ const [second, setSecond] = useState(parsed.s);
235
+
236
+ const shouldShowSeconds = showSeconds || (typeof value === "string" && value.split(":").length >= 3 && value.split(":")[2] !== "");
237
+
238
+ useEffect(() => {
239
+ if (parsed.hasValue) {
240
+ setHour(parsed.h);
241
+ setMinute(parsed.m);
242
+ setSecond(parsed.s);
243
+ }
244
+ }, [parsed]);
210
245
 
211
246
  const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
212
247
  const minutes = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
213
248
  const seconds = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
214
249
 
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));
250
+ const presetContainerRef = useRef<HTMLDivElement | null>(null);
251
+ const activePresetRef = useRef<HTMLDivElement | null>(null);
252
+
253
+ const hourContainerRef = useRef<HTMLDivElement | null>(null);
254
+ const activeHourRef = useRef<HTMLDivElement | null>(null);
255
+
256
+ const minuteContainerRef = useRef<HTMLDivElement | null>(null);
257
+ const activeMinuteRef = useRef<HTMLDivElement | null>(null);
258
+
259
+ const secondContainerRef = useRef<HTMLDivElement | null>(null);
260
+ const activeSecondRef = useRef<HTMLDivElement | null>(null);
261
+
262
+ const scrollToActive = (container: HTMLDivElement | null, active: HTMLDivElement | null) => {
263
+ if (container && active) {
264
+ container.scrollTop = active.offsetTop - container.offsetTop - (container.clientHeight / 2) + (active.clientHeight / 2);
265
+ }
219
266
  };
220
267
 
221
268
  useEffect(() => {
222
- const formatted = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}:${String(second).padStart(2, "0")}`;
269
+ scrollToActive(hourContainerRef.current, activeHourRef.current);
270
+ scrollToActive(minuteContainerRef.current, activeMinuteRef.current);
271
+ if (shouldShowSeconds) {
272
+ scrollToActive(secondContainerRef.current, activeSecondRef.current);
273
+ }
274
+ scrollToActive(presetContainerRef.current, activePresetRef.current);
275
+ }, []);
276
+
277
+ const emitChange = (newH: number, newM: number, newS: number) => {
278
+ const hh = String(newH).padStart(2, "0");
279
+ const mm = String(newM).padStart(2, "0");
280
+ const ss = String(newS).padStart(2, "0");
281
+ const formatted = shouldShowSeconds ? `${hh}:${mm}:${ss}` : `${hh}:${mm}`;
223
282
  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;
283
+ };
230
284
 
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
- );
285
+ const handleSelect = (type: "h" | "m" | "s", val: number) => {
286
+ let newH = hour;
287
+ let newM = minute;
288
+ let newS = second;
289
+
290
+ if (type === "h") {
291
+ newH = val;
292
+ setHour(val);
293
+ } else if (type === "m") {
294
+ newM = val;
295
+ setMinute(val);
296
+ } else if (type === "s") {
297
+ newS = val;
298
+ setSecond(val);
299
+ }
244
300
 
245
- const timeSlots = useMemo(() => {
246
- const newTimeSlots = [];
301
+ emitChange(newH, newM, newS);
302
+ };
247
303
 
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")}`;
304
+ const handlePresetSelect = (presetStr: string) => {
305
+ const parts = presetStr.split(":").map(Number);
306
+ const newH = parts[0] ?? 0;
307
+ const newM = parts[1] ?? 0;
308
+ const newS = 0;
309
+ setHour(newH);
310
+ setMinute(newM);
311
+ setSecond(newS);
312
+ emitChange(newH, newM, newS);
313
+ };
252
314
 
253
- newTimeSlots.push(label);
315
+ const timeSlots = useMemo(() => {
316
+ const slots: string[] = [];
317
+ for (let i = 0; i < 24 * 60; i += 30) {
318
+ const h = Math.floor(i / 60);
319
+ const m = i % 60;
320
+ slots.push(`${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`);
254
321
  }
255
-
256
- return newTimeSlots;
257
- }, [])
322
+ return slots;
323
+ }, []);
258
324
 
259
325
  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
- ))}
326
+ <div className="w-full flex gap-2 h-[260px] select-none text-xs">
327
+ {/* 1. Presets Column */}
328
+ <div className="w-20 shrink-0 h-full flex flex-col border-r border-stroke pr-1.5">
329
+ <div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
330
+ Preset
331
+ </div>
332
+ <div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={presetContainerRef}>
333
+ {timeSlots.map((time) => {
334
+ const isPresetActive = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}` === time;
335
+ return (
336
+ <div
337
+ key={time}
338
+ ref={isPresetActive ? activePresetRef : null}
339
+ className={cn(
340
+ "py-1.5 px-1 text-center font-medium rounded-md cursor-pointer transition-colors",
341
+ isPresetActive
342
+ ? "bg-primary text-white font-semibold"
343
+ : "hover:bg-light-primary text-foreground"
344
+ )}
345
+ onClick={() => handlePresetSelect(time)}
346
+ >
347
+ {time}
348
+ </div>
349
+ );
350
+ })}
351
+ </div>
269
352
  </div>
270
353
 
271
- <div className="w-2/3 flex gap-2">
272
- {renderColumn(hours, "h", hour)}
273
- {renderColumn(minutes, "m", minute)}
274
- {renderColumn(seconds, "s", second)}
354
+ {/* 2. Hour Column */}
355
+ <div className="flex-1 h-full flex flex-col">
356
+ <div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
357
+ Jam
358
+ </div>
359
+ <div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={hourContainerRef}>
360
+ {hours.map((item) => {
361
+ const active = Number(item) === hour;
362
+ return (
363
+ <div
364
+ key={item}
365
+ ref={active ? activeHourRef : null}
366
+ onClick={() => handleSelect("h", Number(item))}
367
+ className={cn(
368
+ "py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors",
369
+ active
370
+ ? "bg-primary text-white font-semibold"
371
+ : "hover:bg-light-primary text-foreground"
372
+ )}
373
+ >
374
+ {item}
375
+ </div>
376
+ );
377
+ })}
378
+ </div>
379
+ </div>
380
+
381
+ {/* 3. Minute Column */}
382
+ <div className="flex-1 h-full flex flex-col">
383
+ <div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
384
+ Menit
385
+ </div>
386
+ <div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={minuteContainerRef}>
387
+ {minutes.map((item) => {
388
+ const active = Number(item) === minute;
389
+ return (
390
+ <div
391
+ key={item}
392
+ ref={active ? activeMinuteRef : null}
393
+ onClick={() => handleSelect("m", Number(item))}
394
+ className={cn(
395
+ "py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors",
396
+ active
397
+ ? "bg-primary text-white font-semibold"
398
+ : "hover:bg-light-primary text-foreground"
399
+ )}
400
+ >
401
+ {item}
402
+ </div>
403
+ );
404
+ })}
405
+ </div>
275
406
  </div>
276
407
 
408
+ {/* 4. Second Column (Optional) */}
409
+ {shouldShowSeconds && (
410
+ <div className="flex-1 h-full flex flex-col">
411
+ <div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
412
+ Detik
413
+ </div>
414
+ <div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={secondContainerRef}>
415
+ {seconds.map((item) => {
416
+ const active = Number(item) === second;
417
+ return (
418
+ <div
419
+ key={item}
420
+ ref={active ? activeSecondRef : null}
421
+ onClick={() => handleSelect("s", Number(item))}
422
+ className={cn(
423
+ "py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors",
424
+ active
425
+ ? "bg-primary text-white font-semibold"
426
+ : "hover:bg-light-primary text-foreground"
427
+ )}
428
+ >
429
+ {item}
430
+ </div>
431
+ );
432
+ })}
433
+ </div>
434
+ </div>
435
+ )}
436
+
277
437
  {rightElement && <div>{rightElement}</div>}
278
438
  </div>
279
439
  );
@@ -15,6 +15,8 @@ export interface SelectOptionProps {
15
15
  value : string | number;
16
16
  searchable ?: string[];
17
17
  customLabel ?: ReactNode;
18
+ customOption ?: ((option: any) => ReactNode) | ReactNode;
19
+ [key: string] : any;
18
20
  };
19
21
 
20
22
  export interface SelectProps {
@@ -35,7 +37,7 @@ export interface SelectProps {
35
37
 
36
38
  options ?: SelectOptionProps[];
37
39
  searchable ?: boolean;
38
- serverOptionControl ?: ApiType & { cacheName?: string | false };
40
+ serverOptionControl ?: ApiType & { cacheName?: string | boolean; selectableOption?: string[] };
39
41
  idbOptionControl ?: { store: string, labelKey: string, valueKey: string };
40
42
  serverSearchable ?: boolean;
41
43
  includedOptions ?: SelectOptionProps[];
@@ -45,6 +47,8 @@ export interface SelectProps {
45
47
  maxShowOption ?: number;
46
48
  creatable ?: boolean | string;
47
49
  creatableLabel ?: string;
50
+ customOption ?: (option: any) => ReactNode;
51
+ selectableOption ?: string[];
48
52
 
49
53
  onChange ?: (value: any, data?: any) => any;
50
54
  register ?: (name: string, validations?: ValidationRules) => void;
@@ -83,9 +87,11 @@ export function SelectComponent({
83
87
  exceptOptions = [],
84
88
  tempOptions,
85
89
  newOption,
86
- maxShowOption = 10,
90
+ maxShowOption,
87
91
  creatable,
88
92
  creatableLabel,
93
+ customOption,
94
+ selectableOption,
89
95
 
90
96
  register,
91
97
  unregister,
@@ -184,13 +190,27 @@ export function SelectComponent({
184
190
  let newFilteredOptions: SelectOptionProps[] = [];
185
191
 
186
192
  if (searchable && !serverSearchable && dataOptions?.length) {
187
- if (e.target.value) {
188
- newFilteredOptions = dataOptions.filter((Option) => (Option.label as string)?.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1).slice(0, maxShowOption);
193
+ if (e?.target?.value) {
194
+ const val = e.target.value.toLowerCase();
195
+ const matched = dataOptions.filter((opt) => {
196
+ if (opt.searchable?.some((s) => s.toLowerCase().indexOf(val) > -1)) return true;
197
+ if (typeof opt.label === "string" && opt.label.toLowerCase().indexOf(val) > -1) return true;
198
+ if (typeof opt.code === "string" && opt.code.toLowerCase().indexOf(val) > -1) return true;
199
+ if (typeof opt.name === "string" && opt.name.toLowerCase().indexOf(val) > -1) return true;
200
+ return false;
201
+ });
202
+ newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
203
+ ? matched.slice(0, maxShowOption)
204
+ : matched;
189
205
  } else {
190
- newFilteredOptions = dataOptions.slice(0, maxShowOption);
206
+ newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
207
+ ? dataOptions.slice(0, maxShowOption)
208
+ : dataOptions;
191
209
  }
192
210
  } else {
193
- newFilteredOptions = dataOptions || [];
211
+ newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
212
+ ? (dataOptions || []).slice(0, maxShowOption)
213
+ : (dataOptions || []);
194
214
  }
195
215
 
196
216
  setActiveOption(-1);
@@ -251,40 +271,68 @@ export function SelectComponent({
251
271
  const fetchOptions = async () => {
252
272
  setLoadingOption(true);
253
273
 
274
+ const selectableOpt = selectableOption || serverOptionControl?.selectableOption || serverOptionControl?.params?.selectableOption;
275
+
254
276
  const serverControl = {
255
277
  ...serverOptionControl,
256
- params: serverSearchable ? { search: keywordSearch, ...(serverOptionControl?.params || {}) } : (serverOptionControl?.params || {}),
257
- headers: { "X-Option": 1 }
278
+ params: {
279
+ ...(serverOptionControl?.params || {}),
280
+ ...(selectableOpt ? { selectableOption: selectableOpt } : {}),
281
+ ...(serverSearchable ? { search: keywordSearch } : {}),
282
+ },
283
+ headers: { "X-Option": 1, ...(serverOptionControl?.headers || {}) }
258
284
  };
259
285
 
260
- const getCacheOptions = await cavity.get(serverOptionControl?.cacheName || `option_${serverOptionControl?.path}`)
261
- const cacheOptions = (getCacheOptions?.data || []) as SelectOptionProps[];
262
-
263
- if (cacheOptions?.length) {
264
- setDataOptions(
265
- [...cacheOptions, ...includedOptions].filter(
266
- (op: SelectOptionProps) => !exceptOptions?.includes(op.value)
267
- )
268
- );
269
- setLoadingOption(false);
270
- } else {
271
- const mutateOptions = await api(serverControl || {});
272
- setDataOptions(
273
- [...(mutateOptions?.data?.data || []), ...(includedOptions || [])].filter(
286
+ const cacheKey = typeof serverOptionControl?.cacheName === "string" && serverOptionControl.cacheName.trim() !== ""
287
+ ? serverOptionControl.cacheName
288
+ : serverOptionControl?.cacheName === true
289
+ ? `option_${serverOptionControl?.path || serverOptionControl?.url}`
290
+ : null;
291
+
292
+ if (cacheKey) {
293
+ const getCacheOptions = await cavity.get(cacheKey);
294
+ const cacheData = getCacheOptions?.data;
295
+ const cacheOptions = (Array.isArray(cacheData?.data) ? cacheData.data : Array.isArray(cacheData) ? cacheData : []) as SelectOptionProps[];
296
+
297
+ if (cacheOptions?.length) {
298
+ const cached = [...cacheOptions, ...includedOptions].filter(
274
299
  (op: SelectOptionProps) => !exceptOptions?.includes(op.value)
275
- )
276
- );
277
- setShowOption(true);
300
+ );
301
+ setDataOptions(cached);
302
+ setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? cached.slice(0, maxShowOption) : cached);
303
+ setLoadingOption(false);
304
+ return;
305
+ }
306
+ }
278
307
 
279
- if(serverOptionControl?.cacheName != false) {
280
- cavity.set({
281
- key: serverOptionControl?.cacheName || `option_${serverOptionControl?.path}`,
282
- data: mutateOptions?.data,
283
- expired: 5,
284
- });
308
+ const mutateOptions = await api(serverControl || {});
309
+ const fetchedData = mutateOptions?.data?.data || mutateOptions?.data || [];
310
+ const optionsData = Array.isArray(fetchedData) ? fetchedData.map((item: any) => {
311
+ if (item && typeof item === "object") {
312
+ return {
313
+ ...item,
314
+ name: item.name ?? item.label,
315
+ label: item.label ?? item.name ?? item.code ?? item.value,
316
+ };
285
317
  }
286
- setLoadingOption(false);
318
+ return item;
319
+ }) : [];
320
+
321
+ const finalOptions = [...optionsData, ...(includedOptions || [])].filter(
322
+ (op: SelectOptionProps) => !exceptOptions?.includes(op.value)
323
+ );
324
+ setDataOptions(finalOptions);
325
+ setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? finalOptions.slice(0, maxShowOption) : finalOptions);
326
+ setShowOption(true);
327
+
328
+ if (cacheKey) {
329
+ cavity.set({
330
+ key: cacheKey,
331
+ data: mutateOptions?.data,
332
+ expired: 5,
333
+ });
287
334
  }
335
+ setLoadingOption(false);
288
336
  };
289
337
 
290
338
  const fetchIdbOptions = async () => {
@@ -309,6 +357,7 @@ export function SelectComponent({
309
357
  })
310
358
 
311
359
  setDataOptions(rows);
360
+ setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? rows.slice(0, maxShowOption) : rows);
312
361
  setLoadingOption(false);
313
362
  }
314
363
  };
@@ -402,7 +451,8 @@ export function SelectComponent({
402
451
  placeholder={!inputHandler.value || (Array.isArray(inputHandler.value) && !inputHandler.value.length) ? placeholder : ""}
403
452
  disabled={disabled}
404
453
  className={cn(
405
- "input cursor-pointer",
454
+ "input cursor-pointer pr-10",
455
+ clearable && "pr-18",
406
456
  leftIcon && "input-with-left-icon",
407
457
  rightIcon && "input-with-right-icon",
408
458
  isCreatingNew && "pl-[4.8rem]",
@@ -493,22 +543,23 @@ export function SelectComponent({
493
543
  return (
494
544
  <div key={key} className="input-values-item">
495
545
  <span>{dataOptions?.find((option) => option.value == item)?.label}</span>
496
- <Icon
497
- icon="solid/times"
498
- className="input-values-delete"
499
- onClick={() => {
500
- const values = Array().concat(inputHandler.value);
501
- const index = values.findIndex((val: string | number) => val == item);
502
-
503
- inputHandler.setValue(values.filter((_, val) => val != index));
504
-
505
- if (!values.filter((_, val) => val != index)?.length) {
506
- setInputShowValue("");
507
- serverSearchable && setKeyword("");
508
- onChange?.("");
509
- }
510
- }}
511
- />
546
+ <div className="input-values-delete">
547
+ <Icon
548
+ icon="solid/times"
549
+ onClick={() => {
550
+ const values = Array().concat(inputHandler.value);
551
+ const index = values.findIndex((val: string | number) => val == item);
552
+
553
+ inputHandler.setValue(values.filter((_, val) => val != index));
554
+
555
+ if (!values.filter((_, val) => val != index)?.length) {
556
+ setInputShowValue("");
557
+ serverSearchable && setKeyword("");
558
+ onChange?.("");
559
+ }
560
+ }}
561
+ />
562
+ </div>
512
563
  </div>
513
564
  );
514
565
  })}
@@ -650,7 +701,7 @@ export function SelectComponent({
650
701
  return (
651
702
  <li
652
703
  className={cn(
653
- "input-suggest",
704
+ "input-suggest flex items-center",
654
705
  (key == activeOption || selected) && "input-suggest-active",
655
706
  pcn<CT>(className, "suggest-item"),
656
707
  (key == activeOption || selected) && pcn<CT>(className, "suggest-item", "active")
@@ -669,8 +720,11 @@ export function SelectComponent({
669
720
  setIsCreatingNew(false);
670
721
 
671
722
  if (!multiple) {
672
- setInputShowValue(option.label);
673
- serverSearchable && setKeyword(option.label as string);
723
+ const displayLabel = typeof option.label === "string" || typeof option.label === "number"
724
+ ? option.label
725
+ : (option.name || option.code || option.value);
726
+ setInputShowValue(displayLabel);
727
+ serverSearchable && setKeyword(typeof displayLabel === "string" ? displayLabel : "");
674
728
  inputHandler.setValue(option.value);
675
729
  onChange?.(option.value, option);
676
730
  } else {
@@ -700,7 +754,11 @@ export function SelectComponent({
700
754
  className="select-suggest-check"
701
755
  />
702
756
  )}
703
- {option.label}
757
+ {customOption
758
+ ? customOption(option)
759
+ : (option.customOption
760
+ ? (typeof option.customOption === "function" ? option.customOption(option) : option.customOption)
761
+ : (option.customLabel || option.label))}
704
762
  </li>
705
763
  );
706
764
  })}