@mbao01/common 0.0.43 → 0.0.45
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.
- package/dist/types/components/Chart/Chart.d.ts +2 -0
- package/dist/types/components/Chart/ChartContext.d.ts +2 -0
- package/dist/types/components/Chart/components/ChartLegend.d.ts +4 -0
- package/dist/types/components/Chart/components/ChartStyle.d.ts +2 -0
- package/dist/types/components/Chart/components/ChartTooltip.d.ts +4 -0
- package/dist/types/components/Chart/components/index.d.ts +3 -0
- package/dist/types/components/Chart/constants.d.ts +22 -0
- package/dist/types/components/Chart/helpers.d.ts +11 -0
- package/dist/types/components/Chart/hooks/index.d.ts +1 -0
- package/dist/types/components/Chart/hooks/useChart/index.d.ts +1 -0
- package/dist/types/components/Chart/hooks/useChart/useChart.d.ts +1 -0
- package/dist/types/components/Chart/index.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/activeShapeArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/animationArgs.d.ts +12 -0
- package/dist/types/components/Chart/stories/args/areaArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/areaChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/barArgs.d.ts +5 -0
- package/dist/types/components/Chart/stories/args/barChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/cartesianChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/cartesianSharedArgs.d.ts +11 -0
- package/dist/types/components/Chart/stories/args/chartArgs.d.ts +6 -0
- package/dist/types/components/Chart/stories/args/dotArgs.d.ts +10 -0
- package/dist/types/components/Chart/stories/args/events.d.ts +804 -0
- package/dist/types/components/Chart/stories/args/labelListArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/legendArgs.d.ts +3 -0
- package/dist/types/components/Chart/stories/args/lineArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/lineChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/pieArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/pieChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/polarChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/polarSharedArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/radarArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/radarChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/radialBarArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/radialBarChartArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/rectangleArgs.d.ts +9 -0
- package/dist/types/components/Chart/stories/args/referenceSharedArgs.d.ts +4 -0
- package/dist/types/components/Chart/stories/args/sectorArgs.d.ts +7 -0
- package/dist/types/components/Chart/stories/args/sharedAxisArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/stylesArgs.d.ts +6 -0
- package/dist/types/components/Chart/stories/args/textArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/tooltipArgs.d.ts +4 -0
- package/dist/types/components/Chart/stories/args/types.d.ts +13 -0
- package/dist/types/components/Chart/stories/args/xAxisArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/args/yAxisArgs.d.ts +2 -0
- package/dist/types/components/Chart/stories/examples/AreaChart.d.ts +16 -0
- package/dist/types/components/Chart/stories/examples/BarChart.d.ts +20 -0
- package/dist/types/components/Chart/stories/examples/LineChart.d.ts +16 -0
- package/dist/types/components/Chart/stories/examples/PieChart.d.ts +19 -0
- package/dist/types/components/Chart/stories/examples/RadarChart.d.ts +24 -0
- package/dist/types/components/Chart/stories/examples/RadialChart.d.ts +17 -0
- package/dist/types/components/Chart/stories/examples/Tooltip.d.ts +15 -0
- package/dist/types/components/Chart/stories/helpers/index.d.ts +9 -0
- package/dist/types/components/Chart/types.d.ts +37 -0
- package/dist/types/components/DatetimePicker/DatetimeGrid.d.ts +26 -0
- package/dist/types/components/DatetimePicker/DatetimePicker.d.ts +13 -0
- package/dist/types/components/DatetimePicker/constants.d.ts +15 -0
- package/dist/types/components/DatetimePicker/index.d.ts +1 -0
- package/dist/types/components/DatetimePicker/types.d.ts +25 -0
- package/dist/types/components/Form/DatetimeInput/DatetimeCalendar.d.ts +5 -0
- package/dist/types/components/Form/DatetimeInput/DatetimeInput.d.ts +83 -0
- package/dist/types/components/Form/DatetimeInput/DatetimeInputContext.d.ts +2 -0
- package/dist/types/components/Form/DatetimeInput/NaturalLanguageInput.d.ts +5 -0
- package/dist/types/components/Form/DatetimeInput/TimePicker.d.ts +1 -0
- package/dist/types/components/Form/DatetimeInput/constants.d.ts +24 -0
- package/dist/types/components/Form/DatetimeInput/helpers.d.ts +27 -0
- package/dist/types/components/Form/DatetimeInput/hooks/index.d.ts +1 -0
- package/dist/types/components/Form/DatetimeInput/hooks/useDateInput/index.d.ts +1 -0
- package/dist/types/components/Form/DatetimeInput/hooks/useDateInput/useDateInput.d.ts +1 -0
- package/dist/types/components/Form/DatetimeInput/index.d.ts +1 -0
- package/dist/types/components/Form/DatetimeInput/types.d.ts +31 -0
- package/dist/types/components/Form/MultiSelect/MultiSelect.d.ts +46 -0
- package/dist/types/components/Form/MultiSelect/MultiSelectContext.d.ts +2 -0
- package/dist/types/components/Form/MultiSelect/constants.d.ts +19 -0
- package/dist/types/components/Form/MultiSelect/hooks/index.d.ts +1 -0
- package/dist/types/components/Form/MultiSelect/hooks/useMultiSelect/index.d.ts +1 -0
- package/dist/types/components/Form/MultiSelect/hooks/useMultiSelect/useMultiSelect.d.ts +1 -0
- package/dist/types/components/Form/MultiSelect/index.d.ts +1 -0
- package/dist/types/components/Form/MultiSelect/types.d.ts +31 -0
- package/dist/types/components/Form/TagsInput/TagsInput.d.ts +13 -0
- package/dist/types/components/Form/TagsInput/constants.d.ts +16 -0
- package/dist/types/components/Form/TagsInput/index.d.ts +1 -0
- package/dist/types/components/Form/TagsInput/types.d.ts +9 -0
- package/dist/types/components/Form/index.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +7 -3
- package/src/components/Chart/Chart.tsx +26 -0
- package/src/components/Chart/ChartContext.tsx +4 -0
- package/src/components/Chart/components/ChartLegend.tsx +52 -0
- package/src/components/Chart/components/ChartStyle.tsx +32 -0
- package/src/components/Chart/components/ChartTooltip.tsx +126 -0
- package/src/components/Chart/components/index.ts +3 -0
- package/src/components/Chart/constants.ts +78 -0
- package/src/components/Chart/helpers.ts +27 -0
- package/src/components/Chart/hooks/index.ts +1 -0
- package/src/components/Chart/hooks/useChart/index.ts +1 -0
- package/src/components/Chart/hooks/useChart/useChart.ts +12 -0
- package/src/components/Chart/index.ts +2 -0
- package/src/components/Chart/stories/args/activeShapeArgs.ts +34 -0
- package/src/components/Chart/stories/args/animationArgs.ts +67 -0
- package/src/components/Chart/stories/args/areaArgs.ts +16 -0
- package/src/components/Chart/stories/args/areaChartArgs.ts +19 -0
- package/src/components/Chart/stories/args/barArgs.ts +123 -0
- package/src/components/Chart/stories/args/barChartArgs.ts +65 -0
- package/src/components/Chart/stories/args/cartesianChartArgs.ts +42 -0
- package/src/components/Chart/stories/args/cartesianSharedArgs.ts +136 -0
- package/src/components/Chart/stories/args/chartArgs.ts +244 -0
- package/src/components/Chart/stories/args/dotArgs.ts +46 -0
- package/src/components/Chart/stories/args/events.ts +343 -0
- package/src/components/Chart/stories/args/index.ts +178 -0
- package/src/components/Chart/stories/args/labelListArgs.ts +124 -0
- package/src/components/Chart/stories/args/legendArgs.ts +205 -0
- package/src/components/Chart/stories/args/lineArgs.ts +87 -0
- package/src/components/Chart/stories/args/lineChartArgs.ts +17 -0
- package/src/components/Chart/stories/args/pieArgs.ts +18 -0
- package/src/components/Chart/stories/args/pieChartArgs.ts +7 -0
- package/src/components/Chart/stories/args/polarChartArgs.ts +86 -0
- package/src/components/Chart/stories/args/polarSharedArgs.ts +42 -0
- package/src/components/Chart/stories/args/radarArgs.ts +26 -0
- package/src/components/Chart/stories/args/radarChartArgs.ts +26 -0
- package/src/components/Chart/stories/args/radialBarArgs.ts +36 -0
- package/src/components/Chart/stories/args/radialBarChartArgs.ts +16 -0
- package/src/components/Chart/stories/args/rectangleArgs.ts +35 -0
- package/src/components/Chart/stories/args/referenceSharedArgs.ts +75 -0
- package/src/components/Chart/stories/args/sectorArgs.ts +106 -0
- package/src/components/Chart/stories/args/sharedAxisArgs.ts +332 -0
- package/src/components/Chart/stories/args/stylesArgs.ts +258 -0
- package/src/components/Chart/stories/args/textArgs.ts +97 -0
- package/src/components/Chart/stories/args/tooltipArgs.ts +41 -0
- package/src/components/Chart/stories/args/types.ts +23 -0
- package/src/components/Chart/stories/args/xAxisArgs.ts +109 -0
- package/src/components/Chart/stories/args/yAxisArgs.ts +73 -0
- package/src/components/Chart/stories/examples/AreaChart.tsx +156 -0
- package/src/components/Chart/stories/examples/BarChart.tsx +425 -0
- package/src/components/Chart/stories/examples/LineChart.tsx +144 -0
- package/src/components/Chart/stories/examples/PieChart.tsx +238 -0
- package/src/components/Chart/stories/examples/RadarChart.tsx +261 -0
- package/src/components/Chart/stories/examples/RadialChart.tsx +239 -0
- package/src/components/Chart/stories/examples/Tooltip.tsx +68 -0
- package/src/components/Chart/stories/helpers/index.tsx +75 -0
- package/src/components/Chart/types.ts +39 -0
- package/src/components/DatetimePicker/DatetimeGrid.tsx +59 -0
- package/src/components/DatetimePicker/DatetimePicker.tsx +59 -0
- package/src/components/DatetimePicker/constants.ts +102 -0
- package/src/components/DatetimePicker/index.ts +1 -0
- package/src/components/DatetimePicker/types.ts +36 -0
- package/src/components/Form/DatetimeInput/DatetimeCalendar.tsx +68 -0
- package/src/components/Form/DatetimeInput/DatetimeInput.tsx +90 -0
- package/src/components/Form/DatetimeInput/DatetimeInputContext.tsx +4 -0
- package/src/components/Form/DatetimeInput/NaturalLanguageInput.tsx +73 -0
- package/src/components/Form/DatetimeInput/TimePicker.tsx +202 -0
- package/src/components/Form/DatetimeInput/constants.ts +135 -0
- package/src/components/Form/DatetimeInput/helpers.ts +93 -0
- package/src/components/Form/DatetimeInput/hooks/index.ts +1 -0
- package/src/components/Form/DatetimeInput/hooks/useDateInput/index.ts +1 -0
- package/src/components/Form/DatetimeInput/hooks/useDateInput/useDateInput.ts +10 -0
- package/src/components/Form/DatetimeInput/index.ts +1 -0
- package/src/components/Form/DatetimeInput/types.ts +36 -0
- package/src/components/Form/MultiSelect/MultiSelect.tsx +348 -0
- package/src/components/Form/MultiSelect/MultiSelectContext.tsx +4 -0
- package/src/components/Form/MultiSelect/constants.ts +103 -0
- package/src/components/Form/MultiSelect/hooks/index.ts +1 -0
- package/src/components/Form/MultiSelect/hooks/useMultiSelect/index.ts +1 -0
- package/src/components/Form/MultiSelect/hooks/useMultiSelect/useMultiSelect.ts +10 -0
- package/src/components/Form/MultiSelect/index.ts +1 -0
- package/src/components/Form/MultiSelect/types.ts +46 -0
- package/src/components/Form/TagsInput/TagsInput.tsx +278 -0
- package/src/components/Form/TagsInput/constants.ts +87 -0
- package/src/components/Form/TagsInput/index.ts +1 -0
- package/src/components/Form/TagsInput/types.ts +10 -0
- package/src/components/Form/index.ts +2 -0
- package/src/index.ts +1 -0
|
@@ -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,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
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { cva } from "../../../libs";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_SIZE = 96;
|
|
4
|
+
|
|
5
|
+
export const getDatetimeInputContainerClasses = cva(
|
|
6
|
+
`h-10 flex items-center justify-between w-fit px-2 rounded-md transition-all gap-1
|
|
7
|
+
[&: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
|
|
8
|
+
[&: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
|
|
9
|
+
`,
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "border-0",
|
|
14
|
+
accent: "border border-accent",
|
|
15
|
+
error: "border border-error",
|
|
16
|
+
ghost: "border border-ghost",
|
|
17
|
+
info: "border border-info",
|
|
18
|
+
primary: "border border-primary",
|
|
19
|
+
secondary: "border border-secondary",
|
|
20
|
+
success: "border border-success",
|
|
21
|
+
warning: "border border-warning",
|
|
22
|
+
},
|
|
23
|
+
outline: {
|
|
24
|
+
true: "border",
|
|
25
|
+
},
|
|
26
|
+
disabled: {
|
|
27
|
+
true: "border-base-300",
|
|
28
|
+
},
|
|
29
|
+
wide: {
|
|
30
|
+
true: "w-full",
|
|
31
|
+
},
|
|
32
|
+
size: {
|
|
33
|
+
xs: "h-6 leading-relaxed text-xs px-1",
|
|
34
|
+
sm: "h-8 leading-8 text-sm px-1",
|
|
35
|
+
md: "h-12 leading-loose text-sm px-2",
|
|
36
|
+
lg: "h-16 leading-loose text-lg px-3",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
compoundVariants: [
|
|
40
|
+
{
|
|
41
|
+
size: undefined,
|
|
42
|
+
className: "min-h-fit h-10",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
variant: undefined,
|
|
46
|
+
outline: true,
|
|
47
|
+
className: "border-neutral-content",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
variant: "default",
|
|
51
|
+
outline: true,
|
|
52
|
+
className: "border-base-content",
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export const getDatetimeCalendarTriggerClasses = cva(
|
|
59
|
+
`size-8 rounded-md p-1 flex items-center justify-center font-normal
|
|
60
|
+
outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-base-content focus-visible:ring-inset
|
|
61
|
+
`,
|
|
62
|
+
{
|
|
63
|
+
variants: {
|
|
64
|
+
size: {
|
|
65
|
+
xs: "size-4 p-0 rounded-sm",
|
|
66
|
+
sm: "size-6",
|
|
67
|
+
md: "size-9",
|
|
68
|
+
lg: "size-10",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const getDatetimeCalendarIconClasses = cva("", {
|
|
75
|
+
variants: {
|
|
76
|
+
size: {
|
|
77
|
+
xs: "size-3",
|
|
78
|
+
sm: "size-3",
|
|
79
|
+
md: "size-5",
|
|
80
|
+
lg: "size-6",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
compoundVariants: [
|
|
84
|
+
{
|
|
85
|
+
size: undefined,
|
|
86
|
+
className: "size-4",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const getTimePickerClasses = cva(
|
|
92
|
+
"border border-neutral-content bg-base-100 shadow-sm cursor-pointer ring-0 py-2 h-8 px-3 w-full text-sm outline-0 inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-base-content disabled:pointer-events-none disabled:opacity-50 hover:bg-base-200",
|
|
93
|
+
{
|
|
94
|
+
variants: {
|
|
95
|
+
selected: {
|
|
96
|
+
true: "bg-primary border-primary text-primary-content hover:border-neutral-content hover:text-base-content",
|
|
97
|
+
},
|
|
98
|
+
suggested: {
|
|
99
|
+
true: "bg-info border-info text-info-content hover:border-neutral-content hover:text-base-content",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
export const getTimePickerListClasses = cva(
|
|
106
|
+
"flex items-center flex-col gap-1 h-full max-h-56 w-28 px-1 py-0.5"
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export const getTimePickerScrollAreaClasses = cva(
|
|
110
|
+
"h-[90%] w-full focus-visible:outline-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-0 py-0.5"
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
export const getNaturalLanguageInputClasses = cva(
|
|
114
|
+
"flex-1 border-none rounded bg-transparent outline-none ring-0 focus:outline-none focus:ring-0 focus-within:outline-none focus-within:ring-0 disabled:cursor-not-allowed disabled:opacity-50",
|
|
115
|
+
{
|
|
116
|
+
variants: {
|
|
117
|
+
size: {
|
|
118
|
+
xs: "h-4 px-1",
|
|
119
|
+
sm: "h-6 px-1",
|
|
120
|
+
md: "h-9 px-2",
|
|
121
|
+
lg: "h-11 px-3",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
compoundVariants: [
|
|
125
|
+
{
|
|
126
|
+
size: undefined,
|
|
127
|
+
className: "h-8 px-2",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
export const getDatetimeCalendarClasses = cva(
|
|
134
|
+
"peer flex justify-end bg-transparent focus:outline-none focus:ring-0 focus-within:outline-none focus-within:ring-0 sm:text-sm disabled:cursor-not-allowed disabled:opacity-50"
|
|
135
|
+
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { parseDate } from "chrono-node";
|
|
2
|
+
import { type TimeString } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility function that parses dates.
|
|
6
|
+
* Parses a given date string using the `chrono-node` library.
|
|
7
|
+
*
|
|
8
|
+
* @param str - A string representation of a date and time.
|
|
9
|
+
* @returns A `Date` object representing the parsed date and time, or `null` if the string could not be parsed.
|
|
10
|
+
*/
|
|
11
|
+
export const parseDateTime = (str: Date | string) => {
|
|
12
|
+
if (str instanceof Date) {
|
|
13
|
+
return str;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return parseDate(str) ?? undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Converts a given timestamp or the current date and time to a string representation in the local time zone.
|
|
21
|
+
* format: `HH:mm`, adjusted for the local time zone.
|
|
22
|
+
*
|
|
23
|
+
* @param timestamp {Date | string}
|
|
24
|
+
* @returns A string representation of the timestamp
|
|
25
|
+
*/
|
|
26
|
+
export const getDateTimeLocal = (timestamp?: Date): string => {
|
|
27
|
+
const d = timestamp ? new Date(timestamp) : new Date();
|
|
28
|
+
if (d.toString() === "Invalid Date") return "";
|
|
29
|
+
return new Date(d.getTime() - d.getTimezoneOffset() * 60000)
|
|
30
|
+
.toISOString()
|
|
31
|
+
.split(":")
|
|
32
|
+
.slice(0, 2)
|
|
33
|
+
.join(":");
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Formats a given date and time object or string into a human-readable string representation.
|
|
38
|
+
* "MMM D, YYYY h:mm A" (e.g. "Jan 1, 2023 12:00 PM").
|
|
39
|
+
*
|
|
40
|
+
* @param date - {Date | string}
|
|
41
|
+
* @returns A string representation of the date and time
|
|
42
|
+
*/
|
|
43
|
+
export const formatDateTime = (date: Date | string, locale?: Intl.LocalesArgument) => {
|
|
44
|
+
return new Date(date).toLocaleTimeString(locale, {
|
|
45
|
+
month: "short",
|
|
46
|
+
day: "numeric",
|
|
47
|
+
year: "numeric",
|
|
48
|
+
hour: "numeric",
|
|
49
|
+
minute: "numeric",
|
|
50
|
+
hour12: true,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const getParsedTime = (date: Date): TimeString => {
|
|
55
|
+
if (date) {
|
|
56
|
+
const PM_AM = date.getHours() >= 12 ? "PM" : "AM";
|
|
57
|
+
//fix the time format for this value
|
|
58
|
+
|
|
59
|
+
const PM_AM_hour = date.getHours();
|
|
60
|
+
const minute = date.getMinutes();
|
|
61
|
+
|
|
62
|
+
const hour =
|
|
63
|
+
PM_AM_hour > 12 ? PM_AM_hour % 12 : PM_AM_hour === 0 || PM_AM_hour === 12 ? 12 : PM_AM_hour;
|
|
64
|
+
|
|
65
|
+
return `${hour}:${minute ? minute : "00"} ${PM_AM}` as TimeString;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return "";
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const setDateTime = (date: Date, time: TimeString) => {
|
|
72
|
+
if (!time) {
|
|
73
|
+
return date;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const [HHMM, PM_AM] = time.split(" ");
|
|
77
|
+
const [HH, MM] = HHMM.split(":").map((v) => parseInt(v) || 0);
|
|
78
|
+
|
|
79
|
+
let hour = HH;
|
|
80
|
+
if (HH === 12) {
|
|
81
|
+
if (PM_AM === "AM") {
|
|
82
|
+
hour = 0;
|
|
83
|
+
}
|
|
84
|
+
} else if (HH < 12) {
|
|
85
|
+
if (PM_AM === "PM") {
|
|
86
|
+
hour += 12;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const newDate = new Date(date);
|
|
90
|
+
newDate.setHours(hour, MM);
|
|
91
|
+
|
|
92
|
+
return newDate;
|
|
93
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useDateInput } from "./useDateInput";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useDateInput } from "./useDateInput";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { DatetimeInputContext } from "../../DatetimeInputContext";
|
|
3
|
+
|
|
4
|
+
export const useDateInput = () => {
|
|
5
|
+
const context = useContext(DatetimeInputContext);
|
|
6
|
+
if (!context) {
|
|
7
|
+
throw new Error("useDateInput must be used within SmartDateInputProvider");
|
|
8
|
+
}
|
|
9
|
+
return context;
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DatetimeInput } from "./DatetimeInput";
|