@mbao01/common 0.0.43 → 0.0.44

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 (63) hide show
  1. package/dist/types/components/DatetimePicker/DatetimeGrid.d.ts +26 -0
  2. package/dist/types/components/DatetimePicker/DatetimePicker.d.ts +13 -0
  3. package/dist/types/components/DatetimePicker/constants.d.ts +15 -0
  4. package/dist/types/components/DatetimePicker/index.d.ts +1 -0
  5. package/dist/types/components/DatetimePicker/types.d.ts +25 -0
  6. package/dist/types/components/Form/DatetimeInput/DatetimeCalendar.d.ts +5 -0
  7. package/dist/types/components/Form/DatetimeInput/DatetimeInput.d.ts +83 -0
  8. package/dist/types/components/Form/DatetimeInput/DatetimeInputContext.d.ts +2 -0
  9. package/dist/types/components/Form/DatetimeInput/NaturalLanguageInput.d.ts +5 -0
  10. package/dist/types/components/Form/DatetimeInput/TimePicker.d.ts +1 -0
  11. package/dist/types/components/Form/DatetimeInput/constants.d.ts +24 -0
  12. package/dist/types/components/Form/DatetimeInput/helpers.d.ts +27 -0
  13. package/dist/types/components/Form/DatetimeInput/hooks/index.d.ts +1 -0
  14. package/dist/types/components/Form/DatetimeInput/hooks/useDateInput/index.d.ts +1 -0
  15. package/dist/types/components/Form/DatetimeInput/hooks/useDateInput/useDateInput.d.ts +1 -0
  16. package/dist/types/components/Form/DatetimeInput/index.d.ts +1 -0
  17. package/dist/types/components/Form/DatetimeInput/types.d.ts +31 -0
  18. package/dist/types/components/Form/MultiSelect/MultiSelect.d.ts +46 -0
  19. package/dist/types/components/Form/MultiSelect/MultiSelectContext.d.ts +2 -0
  20. package/dist/types/components/Form/MultiSelect/constants.d.ts +19 -0
  21. package/dist/types/components/Form/MultiSelect/hooks/index.d.ts +1 -0
  22. package/dist/types/components/Form/MultiSelect/hooks/useMultiSelect/index.d.ts +1 -0
  23. package/dist/types/components/Form/MultiSelect/hooks/useMultiSelect/useMultiSelect.d.ts +1 -0
  24. package/dist/types/components/Form/MultiSelect/index.d.ts +1 -0
  25. package/dist/types/components/Form/MultiSelect/types.d.ts +31 -0
  26. package/dist/types/components/Form/TagsInput/TagsInput.d.ts +13 -0
  27. package/dist/types/components/Form/TagsInput/constants.d.ts +16 -0
  28. package/dist/types/components/Form/TagsInput/index.d.ts +1 -0
  29. package/dist/types/components/Form/TagsInput/types.d.ts +9 -0
  30. package/dist/types/components/Form/index.d.ts +2 -0
  31. package/dist/types/index.d.ts +1 -0
  32. package/package.json +4 -2
  33. package/src/components/DatetimePicker/DatetimeGrid.tsx +59 -0
  34. package/src/components/DatetimePicker/DatetimePicker.tsx +59 -0
  35. package/src/components/DatetimePicker/constants.ts +102 -0
  36. package/src/components/DatetimePicker/index.ts +1 -0
  37. package/src/components/DatetimePicker/types.ts +36 -0
  38. package/src/components/Form/DatetimeInput/DatetimeCalendar.tsx +68 -0
  39. package/src/components/Form/DatetimeInput/DatetimeInput.tsx +90 -0
  40. package/src/components/Form/DatetimeInput/DatetimeInputContext.tsx +4 -0
  41. package/src/components/Form/DatetimeInput/NaturalLanguageInput.tsx +73 -0
  42. package/src/components/Form/DatetimeInput/TimePicker.tsx +202 -0
  43. package/src/components/Form/DatetimeInput/constants.ts +135 -0
  44. package/src/components/Form/DatetimeInput/helpers.ts +93 -0
  45. package/src/components/Form/DatetimeInput/hooks/index.ts +1 -0
  46. package/src/components/Form/DatetimeInput/hooks/useDateInput/index.ts +1 -0
  47. package/src/components/Form/DatetimeInput/hooks/useDateInput/useDateInput.ts +10 -0
  48. package/src/components/Form/DatetimeInput/index.ts +1 -0
  49. package/src/components/Form/DatetimeInput/types.ts +36 -0
  50. package/src/components/Form/MultiSelect/MultiSelect.tsx +348 -0
  51. package/src/components/Form/MultiSelect/MultiSelectContext.tsx +4 -0
  52. package/src/components/Form/MultiSelect/constants.ts +103 -0
  53. package/src/components/Form/MultiSelect/hooks/index.ts +1 -0
  54. package/src/components/Form/MultiSelect/hooks/useMultiSelect/index.ts +1 -0
  55. package/src/components/Form/MultiSelect/hooks/useMultiSelect/useMultiSelect.ts +10 -0
  56. package/src/components/Form/MultiSelect/index.ts +1 -0
  57. package/src/components/Form/MultiSelect/types.ts +46 -0
  58. package/src/components/Form/TagsInput/TagsInput.tsx +278 -0
  59. package/src/components/Form/TagsInput/constants.ts +87 -0
  60. package/src/components/Form/TagsInput/index.ts +1 -0
  61. package/src/components/Form/TagsInput/types.ts +10 -0
  62. package/src/components/Form/index.ts +2 -0
  63. package/src/index.ts +1 -0
@@ -0,0 +1,102 @@
1
+ import type { DateFormat, DatetimeFormatDefaults, InputPlaceholders, TimeFormat } from "./types";
2
+ import { cva } from "../../libs";
3
+
4
+ export const DEFAULTS = [
5
+ ["days", "months", "years"],
6
+ ["hours", "minutes", "am/pm"],
7
+ ] as DatetimeFormatDefaults;
8
+
9
+ export const INPUT_PLACEHOLDERS: InputPlaceholders = {
10
+ months: "MM",
11
+ days: "DD",
12
+ years: "YYYY",
13
+ hours: "HH",
14
+ minutes: "MM",
15
+ seconds: "SS",
16
+ "am/pm": "AM/PM",
17
+ };
18
+
19
+ export const getDatetimeGridClasses = cva(
20
+ `flex items-center w-fit p-1 rounded-md transition-all duration-100 gap-1 selection:bg-transparent selection:text-base-content
21
+ [&:has(input:focus)]:duration-100 [&:has(input:focus)]:outline [&:has(input:focus)]:outline-2 [&:has(input:focus)]:outline-offset-2 [&:has(input:focus)]:outline-base-content/20
22
+ [&:has(input:focus-within)]:duration-100 [&:has(input:focus-within)]:outline [&:has(input:focus-within)]:outline-2 [&:has(input:focus-within)]:outline-offset-2 [&:has(input:focus-within)]:outline-base-content/20
23
+ `,
24
+ {
25
+ variants: {
26
+ variant: {
27
+ default: "border-0",
28
+ accent: "border border-accent",
29
+ error: "border border-error",
30
+ ghost: "border border-ghost",
31
+ info: "border border-info",
32
+ primary: "border border-primary",
33
+ secondary: "border border-secondary",
34
+ success: "border border-success",
35
+ warning: "border border-warning",
36
+ },
37
+ outline: {
38
+ true: "border",
39
+ },
40
+ disabled: {
41
+ true: "border-base-300",
42
+ },
43
+ wide: {
44
+ true: "w-full",
45
+ },
46
+ size: {
47
+ xs: "h-6 leading-relaxed px-2 text-xs",
48
+ sm: "h-8 leading-8 px-3 text-sm",
49
+ md: "h-12 leading-loose px-4 text-sm",
50
+ lg: "h-16 leading-loose px-5 text-lg",
51
+ },
52
+ },
53
+ compoundVariants: [
54
+ {
55
+ size: undefined,
56
+ className: "min-h-fit h-10",
57
+ },
58
+ {
59
+ variant: undefined,
60
+ outline: true,
61
+ className: "border-neutral-content",
62
+ },
63
+ {
64
+ variant: "default",
65
+ outline: true,
66
+ className: "border-base-content",
67
+ },
68
+ ],
69
+ }
70
+ );
71
+
72
+ export const getDatetimeSeparatorClasses = cva("text-xs text-gray-400");
73
+
74
+ export const getDatetimeInputClasses = cva(
75
+ "min-w-8 p-1 inline tabular-nums h-fit border-none outline-none select-none content-box caret-transparent rounded-sm min-w-8 text-center focus:outline-none focus:bg-base-content/20 focus-visible:ring-0 focus-visible:outline-none",
76
+ {
77
+ variants: {
78
+ size: {
79
+ xs: "max-h-4",
80
+ sm: "max-h-6",
81
+ md: "",
82
+ lg: "min-w-8",
83
+ },
84
+ unit: {
85
+ years: "min-w-12",
86
+ "am/pm": "bg-base-content/15",
87
+ } as Record<DateFormat | TimeFormat, string>,
88
+ },
89
+ compoundVariants: [
90
+ {
91
+ size: "lg",
92
+ unit: "years",
93
+ className: "min-w-14",
94
+ },
95
+ {
96
+ size: "lg",
97
+ unit: "am/pm",
98
+ className: "min-w-10",
99
+ },
100
+ ],
101
+ }
102
+ );
@@ -0,0 +1 @@
1
+ export { DatetimePicker } from "./DatetimePicker";
@@ -0,0 +1,36 @@
1
+ import type { Options } from "timescape/react";
2
+ import { useTimescape } from "timescape/react";
3
+ import { type VariantProps } from "../../libs";
4
+ import { getDatetimeGridClasses } from "./constants";
5
+
6
+ export type DateFormat = "days" | "months" | "years";
7
+
8
+ export type TimeFormat = "hours" | "minutes" | "seconds" | "am/pm";
9
+
10
+ type TimescapeReturn = ReturnType<typeof useTimescape>;
11
+ type DatetimeArray<T extends DateFormat | TimeFormat> = T[];
12
+
13
+ export type DatetimeFormatDefaults =
14
+ | [DatetimeArray<DateFormat>]
15
+ | [DatetimeArray<TimeFormat>]
16
+ | [DatetimeArray<DateFormat>, DatetimeArray<TimeFormat>]
17
+ | [DatetimeArray<TimeFormat>, DatetimeArray<DateFormat>];
18
+
19
+ export type InputPlaceholders = Record<DateFormat | TimeFormat, string>;
20
+
21
+ type DatetimeGridVariantProps = VariantProps<typeof getDatetimeGridClasses>;
22
+ export type DatetimeGridProps = {
23
+ format: DatetimeFormatDefaults;
24
+ disabled?: boolean;
25
+ className?: string;
26
+ timescape: Pick<TimescapeReturn, "getRootProps" | "getInputProps">;
27
+ placeholders: InputPlaceholders;
28
+ } & DatetimeGridVariantProps;
29
+
30
+ export type DatetimePickerProps = Omit<Options, "onChangeDate"> & {
31
+ format?: DatetimeFormatDefaults;
32
+ disabled?: boolean;
33
+ placeholders?: InputPlaceholders;
34
+ onChange?: Options["onChangeDate"];
35
+ className?: string;
36
+ } & DatetimeGridVariantProps;
@@ -0,0 +1,68 @@
1
+ import { useCallback } from "react";
2
+ import { CalendarIcon } from "@radix-ui/react-icons";
3
+ import { cn } from "../../../utilities";
4
+ import { Button } from "../../Button";
5
+ import { Calendar } from "../../Calendar";
6
+ import { Popover } from "../../Popover";
7
+ import {
8
+ getDatetimeCalendarClasses,
9
+ getDatetimeCalendarIconClasses,
10
+ getDatetimeCalendarTriggerClasses,
11
+ } from "./constants";
12
+ import { parseDateTime } from "./helpers";
13
+ import { useDateInput } from "./hooks";
14
+ import { TimePicker } from "./TimePicker";
15
+ import { type DatetimeCalendarProps } from "./types";
16
+
17
+ export const DatetimeCalendar = ({
18
+ size,
19
+ disabled,
20
+ className,
21
+ ...props
22
+ }: DatetimeCalendarProps) => {
23
+ const { value, onDateChange, time } = useDateInput();
24
+
25
+ const handleSelect = useCallback(
26
+ (_: Date | undefined, triggerDate: Date) => {
27
+ const parsedDateTime = parseDateTime(triggerDate);
28
+
29
+ if (parsedDateTime) {
30
+ const [hours, minutes] = time.split(":");
31
+ parsedDateTime.setHours(parseInt(hours) || 0, parseInt(minutes) || 0);
32
+ onDateChange(parsedDateTime);
33
+ }
34
+ },
35
+ [time, onDateChange]
36
+ );
37
+
38
+ return (
39
+ <Popover>
40
+ <Popover.Trigger asChild>
41
+ <Button
42
+ variant="default"
43
+ disabled={disabled}
44
+ className={cn(getDatetimeCalendarTriggerClasses({ size }))}
45
+ >
46
+ <CalendarIcon className={getDatetimeCalendarIconClasses({ size })} />
47
+ <span className="sr-only">calendar</span>
48
+ </Button>
49
+ </Popover.Trigger>
50
+ <Popover.Content className="w-auto p-0" sideOffset={8}>
51
+ <div className="flex gap-1">
52
+ <Calendar
53
+ {...props}
54
+ autoFocus
55
+ disabled={disabled}
56
+ className={cn(getDatetimeCalendarClasses(), className)}
57
+ mode="single"
58
+ selected={value}
59
+ onSelect={handleSelect}
60
+ />
61
+ <TimePicker />
62
+ </div>
63
+ </Popover.Content>
64
+ </Popover>
65
+ );
66
+ };
67
+
68
+ DatetimeCalendar.displayName = "DatetimeCalendar";
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import { forwardRef, useCallback, useEffect, useState } from "react";
4
+ import type { DatetimeInputProps, TimeString } from "./types";
5
+ import { cn } from "../../../utilities";
6
+ import { getDatetimeInputContainerClasses } from "./constants";
7
+ import { DatetimeCalendar } from "./DatetimeCalendar";
8
+ import { DatetimeInputContext } from "./DatetimeInputContext";
9
+ import { getParsedTime, parseDateTime } from "./helpers";
10
+ import { NaturalLanguageInput } from "./NaturalLanguageInput";
11
+
12
+ export const DatetimeInput = forwardRef<HTMLInputElement, DatetimeInputProps>(
13
+ (
14
+ {
15
+ calendar,
16
+ className,
17
+ date,
18
+ defaultDate,
19
+ locale,
20
+ onDateChange,
21
+ placeholder,
22
+ outline,
23
+ disabled,
24
+ size,
25
+ variant,
26
+ wide,
27
+ ...props
28
+ },
29
+ ref
30
+ ) => {
31
+ const [_date, setDate] = useState<Date | undefined>(
32
+ defaultDate ? parseDateTime(defaultDate) : undefined
33
+ );
34
+ const [time, setTime] = useState<TimeString>(defaultDate ? getParsedTime(defaultDate) : "");
35
+ const isControlled = date !== undefined;
36
+
37
+ useEffect(() => {
38
+ // sync internal state with controlled value if provided
39
+ if (isControlled) {
40
+ setDate(date);
41
+ }
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ }, [date]);
44
+
45
+ const handleDateChange = useCallback((d: Date | undefined) => {
46
+ if (!isControlled) {
47
+ setDate(d);
48
+ }
49
+
50
+ onDateChange?.(d);
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, []);
53
+
54
+ const onTimeChange = useCallback((time: TimeString) => {
55
+ setTime(time);
56
+ }, []);
57
+
58
+ return (
59
+ <DatetimeInputContext.Provider
60
+ value={{
61
+ value: isControlled ? date : _date,
62
+ onDateChange: handleDateChange,
63
+ time,
64
+ onTimeChange,
65
+ }}
66
+ >
67
+ <div className="flex items-center justify-center flex-nowrap">
68
+ <div
69
+ className={cn(
70
+ getDatetimeInputContainerClasses({ outline, disabled, size, variant, wide }),
71
+ className
72
+ )}
73
+ >
74
+ <DatetimeCalendar size={size} disabled={disabled} defaultMonth={_date} {...calendar} />
75
+ <NaturalLanguageInput
76
+ ref={ref}
77
+ size={size}
78
+ locale={locale}
79
+ disabled={disabled}
80
+ placeholder={placeholder}
81
+ {...props}
82
+ />
83
+ </div>
84
+ </div>
85
+ </DatetimeInputContext.Provider>
86
+ );
87
+ }
88
+ );
89
+
90
+ DatetimeInput.displayName = "DatetimeInput";
@@ -0,0 +1,4 @@
1
+ import { createContext } from "react";
2
+ import { type DatetimeInputContextProps } from "./types";
3
+
4
+ export const DatetimeInputContext = createContext<DatetimeInputContextProps | null>(null);
@@ -0,0 +1,73 @@
1
+ import type { ChangeEvent, KeyboardEvent } from "react";
2
+ import { forwardRef, useCallback, useEffect, useState } from "react";
3
+ import { Input } from "../Input";
4
+ import { getNaturalLanguageInputClasses } from "./constants";
5
+ import { formatDateTime, getParsedTime, parseDateTime, setDateTime } from "./helpers";
6
+ import { useDateInput } from "./hooks";
7
+ import { type NaturalLanguageInputProps } from "./types";
8
+
9
+ export const NaturalLanguageInput = forwardRef<HTMLInputElement, NaturalLanguageInputProps>(
10
+ ({ placeholder = 'e.g. "tomorrow at 5pm" or "in 2 hours"', size, locale, ...props }, ref) => {
11
+ const { value, onDateChange, time, onTimeChange } = useDateInput();
12
+ const [inputValue, setInputValue] = useState<string>("");
13
+
14
+ useEffect(() => {
15
+ const timeVal = time || getParsedTime(new Date());
16
+ setInputValue(value ? formatDateTime(setDateTime(value, timeVal), locale) : "");
17
+ onTimeChange(timeVal);
18
+ }, [value, time, locale, onTimeChange]);
19
+
20
+ const handleParse = useCallback(
21
+ (e: ChangeEvent<HTMLInputElement>) => {
22
+ const input = e.currentTarget.value;
23
+ if (!input) {
24
+ onDateChange(undefined);
25
+ onTimeChange("");
26
+ return;
27
+ }
28
+
29
+ // parse the date string when the input field loses focus
30
+ const parsedDateTime = parseDateTime(input);
31
+ if (parsedDateTime) {
32
+ onDateChange(parsedDateTime);
33
+ setInputValue(formatDateTime(parsedDateTime, locale));
34
+ onTimeChange(getParsedTime(parsedDateTime));
35
+ }
36
+ },
37
+ [locale, onDateChange, onTimeChange]
38
+ );
39
+
40
+ const handleKeydown = useCallback(
41
+ (e: KeyboardEvent<HTMLInputElement>) => {
42
+ const parsedDateTime = parseDateTime(e.currentTarget.value);
43
+ switch (e.key) {
44
+ case "Enter":
45
+ if (parsedDateTime) {
46
+ onDateChange(parsedDateTime);
47
+ setInputValue(formatDateTime(parsedDateTime, locale));
48
+ onTimeChange(getParsedTime(parsedDateTime));
49
+ }
50
+ break;
51
+ }
52
+ },
53
+ [locale, onDateChange, onTimeChange]
54
+ );
55
+
56
+ return (
57
+ <Input
58
+ ref={ref}
59
+ type="text"
60
+ size={size}
61
+ placeholder={placeholder}
62
+ value={inputValue}
63
+ onChange={(e) => setInputValue(e.currentTarget.value)}
64
+ onKeyDown={handleKeydown}
65
+ onBlur={handleParse}
66
+ className={getNaturalLanguageInputClasses({ size })}
67
+ {...props}
68
+ />
69
+ );
70
+ }
71
+ );
72
+
73
+ NaturalLanguageInput.displayName = "NaturalLanguageInput";
@@ -0,0 +1,202 @@
1
+ import type { KeyboardEvent } from "react";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { cn } from "../../../utilities";
4
+ import { ScrollArea } from "../../ScrollArea";
5
+ import {
6
+ DEFAULT_SIZE,
7
+ getTimePickerClasses,
8
+ getTimePickerListClasses,
9
+ getTimePickerScrollAreaClasses,
10
+ } from "./constants";
11
+ import { parseDateTime, setDateTime } from "./helpers";
12
+ import { useDateInput } from "./hooks";
13
+ import { type TimeString } from "./types";
14
+
15
+ export const TimePicker = () => {
16
+ const { value, onDateChange, time, onTimeChange } = useDateInput();
17
+ const [activeIndex, setActiveIndex] = useState(-1);
18
+ const timestamp = 15;
19
+
20
+ const formateSelectedTime = useCallback(
21
+ (time: TimeString) => {
22
+ onTimeChange(time);
23
+
24
+ const newVal = parseDateTime(value ?? new Date());
25
+
26
+ if (!newVal) return;
27
+
28
+ onDateChange(setDateTime(newVal, time));
29
+ },
30
+ [value, onDateChange, onTimeChange]
31
+ );
32
+
33
+ const handleKeydown = useCallback(
34
+ (e: KeyboardEvent<HTMLDivElement>) => {
35
+ e.stopPropagation();
36
+
37
+ if (!document) return;
38
+
39
+ const moveNext = () => {
40
+ const nextIndex = activeIndex + 1 > DEFAULT_SIZE - 1 ? 0 : activeIndex + 1;
41
+
42
+ const currentElm = document.getElementById(`time-${nextIndex}`);
43
+
44
+ currentElm?.focus();
45
+
46
+ setActiveIndex(nextIndex);
47
+ };
48
+
49
+ const movePrev = () => {
50
+ const prevIndex = activeIndex - 1 < 0 ? DEFAULT_SIZE - 1 : activeIndex - 1;
51
+
52
+ const currentElm = document.getElementById(`time-${prevIndex}`);
53
+
54
+ currentElm?.focus();
55
+
56
+ setActiveIndex(prevIndex);
57
+ };
58
+
59
+ const setElement = () => {
60
+ const currentElm = document.getElementById(`time-${activeIndex}`);
61
+
62
+ if (!currentElm) return;
63
+
64
+ currentElm.focus();
65
+
66
+ formateSelectedTime((currentElm.textContent ?? "") as TimeString);
67
+ };
68
+
69
+ const reset = () => {
70
+ const currentElm = document.getElementById(`time-${activeIndex}`);
71
+ currentElm?.blur();
72
+ setActiveIndex(-1);
73
+ };
74
+
75
+ switch (e.key) {
76
+ case "ArrowUp":
77
+ movePrev();
78
+ break;
79
+
80
+ case "ArrowDown":
81
+ moveNext();
82
+ break;
83
+
84
+ case "Escape":
85
+ reset();
86
+ break;
87
+
88
+ case "Enter":
89
+ setElement();
90
+ break;
91
+ }
92
+ },
93
+ [activeIndex, formateSelectedTime]
94
+ );
95
+
96
+ const handleClick = useCallback(
97
+ (time: TimeString, currentIndex: number) => {
98
+ formateSelectedTime(time);
99
+ setActiveIndex(currentIndex);
100
+ },
101
+ [formateSelectedTime]
102
+ );
103
+
104
+ const currentTime = useMemo(() => {
105
+ const timeVal = time.split(" ")[0];
106
+ return {
107
+ hours: parseInt(timeVal.split(":")[0]),
108
+ minutes: parseInt(timeVal.split(":")[1]),
109
+ };
110
+ }, [time]);
111
+
112
+ useEffect(() => {
113
+ const getCurrentElementTime = () => {
114
+ const timeVal = time.split(" ")[0];
115
+ const hours = parseInt(timeVal.split(":")[0]);
116
+ const minutes = parseInt(timeVal.split(":")[1]);
117
+ const PM_AM = time.split(" ")[1];
118
+
119
+ const formatIndex = PM_AM === "AM" ? hours : hours === 12 ? hours : hours + 12;
120
+ const formattedHours = formatIndex;
121
+
122
+ for (let j = 0; j <= 3; j++) {
123
+ const diff = Math.abs(j * timestamp - minutes);
124
+ const selected =
125
+ PM_AM === (formattedHours >= 12 ? "PM" : "AM") &&
126
+ (minutes <= 53 ? diff < Math.ceil(timestamp / 2) : diff < timestamp);
127
+
128
+ if (selected) {
129
+ const trueIndex = activeIndex === -1 ? formattedHours * 4 + j : activeIndex;
130
+
131
+ setActiveIndex(trueIndex);
132
+
133
+ const currentElm = document.getElementById(`time-${trueIndex}`);
134
+ currentElm?.scrollIntoView({
135
+ block: "center",
136
+ behavior: "smooth",
137
+ });
138
+ }
139
+ }
140
+ };
141
+
142
+ getCurrentElementTime();
143
+ }, [time, activeIndex]);
144
+
145
+ const height = useMemo(() => {
146
+ if (!document) return;
147
+ const calendarElm = document.getElementById("calendar");
148
+ if (!calendarElm) return;
149
+ return calendarElm.style.height;
150
+ }, []);
151
+
152
+ return (
153
+ <div className="space-y-2 pr-3 py-3 relative ">
154
+ <h3 className="text-sm font-medium ">Time</h3>
155
+ <ScrollArea
156
+ onKeyDown={handleKeydown}
157
+ className={cn(getTimePickerScrollAreaClasses())}
158
+ style={{ height }}
159
+ >
160
+ <ul className={cn(getTimePickerListClasses())}>
161
+ {Array.from({ length: 24 }).map((_, i) => {
162
+ const PM_AM = i >= 12 ? "PM" : "AM";
163
+ const formatIndex = i > 12 ? i % 12 : i === 0 || i === 12 ? 12 : i;
164
+ return Array.from({ length: 4 }).map((_, part) => {
165
+ const diff = Math.abs(part * timestamp - currentTime.minutes);
166
+
167
+ const trueIndex = i * 4 + part;
168
+
169
+ // ? refactor : add the select of the default time on the current device (H:MM)
170
+ const isSelected =
171
+ (currentTime.hours === i || currentTime.hours === formatIndex) &&
172
+ time.split(" ")[1] === PM_AM &&
173
+ (currentTime.minutes <= 53 ? diff < Math.ceil(timestamp / 2) : diff < timestamp);
174
+
175
+ const isSuggested = !value && isSelected;
176
+
177
+ const currentValue = `${formatIndex}:${
178
+ part === 0 ? "00" : timestamp * part
179
+ } ${PM_AM}` as TimeString;
180
+
181
+ return (
182
+ <li
183
+ tabIndex={isSelected ? 0 : -1}
184
+ id={`time-${trueIndex}`}
185
+ key={`time-${trueIndex}`}
186
+ aria-label={currentValue}
187
+ className={cn(
188
+ getTimePickerClasses({ selected: isSelected, suggested: isSuggested })
189
+ )}
190
+ onClick={() => handleClick(currentValue, trueIndex)}
191
+ onFocus={() => isSuggested && setActiveIndex(trueIndex)}
192
+ >
193
+ {currentValue}
194
+ </li>
195
+ );
196
+ });
197
+ })}
198
+ </ul>
199
+ </ScrollArea>
200
+ </div>
201
+ );
202
+ };