torch-glare 1.1.4 → 1.1.6
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/lib/components/BadgeField.tsx +51 -44
- package/lib/components/{Calender.tsx → Calendar.tsx} +5 -5
- package/lib/components/DatePicker.tsx +140 -100
- package/lib/components/Input.tsx +2 -2
- package/lib/components/SimpleSelect.tsx +1 -1
- package/lib/components/SlideDatePicker.tsx +187 -187
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
forwardRef,
|
|
4
4
|
InputHTMLAttributes,
|
|
5
5
|
ReactNode,
|
|
6
|
+
useEffect,
|
|
6
7
|
useRef,
|
|
7
8
|
useState,
|
|
8
9
|
} from "react";
|
|
@@ -15,7 +16,6 @@ import { useClickOutside } from "../hooks/useClickOutside";
|
|
|
15
16
|
import { Badge } from "./Badge";
|
|
16
17
|
import { Tag, useTagSelection } from "../hooks/useTagSelection";
|
|
17
18
|
|
|
18
|
-
|
|
19
19
|
interface Props
|
|
20
20
|
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "variant"> {
|
|
21
21
|
size?: "XS" | "S" | "M"; // this is used to change the size style of the component
|
|
@@ -26,13 +26,12 @@ interface Props
|
|
|
26
26
|
toolTipSide?: ToolTipSide;
|
|
27
27
|
label?: string;
|
|
28
28
|
required?: boolean;
|
|
29
|
-
theme?: Themes
|
|
30
|
-
actionButton?: ReactNode
|
|
31
|
-
tags: Tag[]
|
|
32
|
-
onValueChange?: (tags: Tag[]) => void
|
|
29
|
+
theme?: Themes;
|
|
30
|
+
actionButton?: ReactNode;
|
|
31
|
+
tags: Tag[];
|
|
32
|
+
onValueChange?: (tags: Tag[]) => void;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
37
36
|
(
|
|
38
37
|
{
|
|
@@ -48,7 +47,6 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
48
47
|
actionButton,
|
|
49
48
|
theme,
|
|
50
49
|
tags,
|
|
51
|
-
onValueChange,
|
|
52
50
|
children,
|
|
53
51
|
...props
|
|
54
52
|
},
|
|
@@ -62,7 +60,8 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
62
60
|
if (
|
|
63
61
|
!inputGroupRef?.current?.contains(e?.target as Node) &&
|
|
64
62
|
!popoverContentRef?.current?.contains(e?.target as Node)
|
|
65
|
-
)
|
|
63
|
+
)
|
|
64
|
+
setIsPopoverOpen(false);
|
|
66
65
|
else setIsPopoverOpen(true);
|
|
67
66
|
});
|
|
68
67
|
|
|
@@ -79,7 +78,16 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
79
78
|
focusedPopoverIndex,
|
|
80
79
|
isPopoverOpen,
|
|
81
80
|
setIsPopoverOpen,
|
|
82
|
-
|
|
81
|
+
searchTags
|
|
82
|
+
} = useTagSelection({
|
|
83
|
+
Tags: tags,
|
|
84
|
+
onTagsChange: (e) => props.onChange?.({
|
|
85
|
+
target: {
|
|
86
|
+
value: e
|
|
87
|
+
}
|
|
88
|
+
} as any),
|
|
89
|
+
inputRef
|
|
90
|
+
});
|
|
83
91
|
|
|
84
92
|
return (
|
|
85
93
|
<Popover open={isPopoverOpen}>
|
|
@@ -88,8 +96,7 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
88
96
|
open={errorMessage !== undefined}
|
|
89
97
|
text={errorMessage}
|
|
90
98
|
>
|
|
91
|
-
<PopoverTrigger asChild
|
|
92
|
-
>
|
|
99
|
+
<PopoverTrigger asChild>
|
|
93
100
|
<Group
|
|
94
101
|
error={errorMessage !== undefined}
|
|
95
102
|
onTable={onTable}
|
|
@@ -102,44 +109,42 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
102
109
|
onFocus={(e: any) => {
|
|
103
110
|
setDropDownListWidth(e.currentTarget.offsetWidth);
|
|
104
111
|
}}
|
|
105
|
-
className={cn(
|
|
112
|
+
className={cn(
|
|
113
|
+
"flex gap-1 flex-row w-full relative p-1 flex-nowrap overflow-hidden justify-end items-center",
|
|
106
114
|
{
|
|
107
115
|
"flex-wrap justify-start": isPopoverOpen,
|
|
116
|
+
"h-fit": isPopoverOpen,
|
|
108
117
|
},
|
|
109
118
|
className
|
|
110
119
|
)}
|
|
111
120
|
>
|
|
112
|
-
{icon &&
|
|
113
|
-
<Icon >
|
|
114
|
-
{icon}
|
|
115
|
-
</Icon>
|
|
116
|
-
)}
|
|
121
|
+
{icon && <Icon>{icon}</Icon>}
|
|
117
122
|
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
123
|
+
{selectedTagsStack.map((tag, index) => (
|
|
124
|
+
<Badge
|
|
125
|
+
key={tag.id}
|
|
126
|
+
size={size}
|
|
127
|
+
variant={tag.variant as any}
|
|
128
|
+
label={tag.name}
|
|
129
|
+
isSelected={true}
|
|
130
|
+
onUnselect={() => handleUnselectTag(tag.id)}
|
|
131
|
+
className={
|
|
132
|
+
focusedTagIndex === index ? "ring-2 ring-blue-500" : ""
|
|
133
|
+
}
|
|
134
|
+
tabIndex={focusedTagIndex === index ? 0 : -1}
|
|
135
|
+
/>
|
|
136
|
+
))}
|
|
132
137
|
|
|
133
138
|
<Input
|
|
134
139
|
{...props}
|
|
140
|
+
value={searchTags}
|
|
135
141
|
onChange={(e) => {
|
|
136
|
-
|
|
137
|
-
filterTagsBySearch(e.target.value)
|
|
142
|
+
filterTagsBySearch(e.target.value);
|
|
138
143
|
}}
|
|
139
144
|
onFocus={(e) => {
|
|
140
|
-
props.onFocus?.(e)
|
|
145
|
+
props.onFocus?.(e);
|
|
141
146
|
setFocusedTagIndex(null);
|
|
142
|
-
setIsPopoverOpen(true)
|
|
147
|
+
setIsPopoverOpen(true);
|
|
143
148
|
}}
|
|
144
149
|
ref={inputRef}
|
|
145
150
|
className={cn(
|
|
@@ -151,13 +156,12 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
151
156
|
}
|
|
152
157
|
)}
|
|
153
158
|
/>
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
159
|
+
{actionButton && (
|
|
160
|
+
<Trilling className="py-0">
|
|
161
|
+
{/* Keep the ActionButton right aligned */}
|
|
162
|
+
{actionButton}
|
|
163
|
+
</Trilling>
|
|
164
|
+
)}
|
|
161
165
|
</Group>
|
|
162
166
|
</PopoverTrigger>
|
|
163
167
|
</Tooltip>
|
|
@@ -178,13 +182,16 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
178
182
|
variant={tag.variant as any}
|
|
179
183
|
label={tag.name}
|
|
180
184
|
onClick={() => handleSelectTag(tag.id)}
|
|
181
|
-
className={`outline-none ${focusedPopoverIndex === index ? "ring-2 ring-blue-500" : ""
|
|
185
|
+
className={`outline-none ${focusedPopoverIndex === index ? "ring-2 ring-blue-500" : ""
|
|
186
|
+
} ${index !== 0 ? "mt-1" : ""}`}
|
|
182
187
|
tabIndex={focusedPopoverIndex === index ? 0 : -1}
|
|
183
188
|
/>
|
|
184
189
|
))
|
|
185
190
|
) : (
|
|
186
191
|
<div className="text-sm text-gray-500 py-1 px-2">
|
|
187
|
-
{tags.length === 0
|
|
192
|
+
{tags.length === 0
|
|
193
|
+
? "All tags selected"
|
|
194
|
+
: "No matching tags found"}
|
|
188
195
|
</div>
|
|
189
196
|
)}
|
|
190
197
|
</>
|
|
@@ -9,7 +9,7 @@ import { SimpleSelectValue, SimpleSelectItem } from "./SimpleSelect";
|
|
|
9
9
|
|
|
10
10
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const Calendar = ({
|
|
13
13
|
className,
|
|
14
14
|
classNames,
|
|
15
15
|
captionLayout = "dropdown",
|
|
@@ -86,8 +86,8 @@ const Calender = ({
|
|
|
86
86
|
range_start: `rounded-l-[8px] bg-background-system-action-secondary-hover`,
|
|
87
87
|
range_end: `rounded-r-[8px] bg-background-system-action-secondary-hover`,
|
|
88
88
|
range_middle: `bg-background-system-action-secondary-hover [&_button]:!border-none [&_button]:!bg-transparent`,
|
|
89
|
-
weekdays: `text-content-presentation-global-
|
|
90
|
-
week_number: `text-content-presentation-global-
|
|
89
|
+
weekdays: `text-content-presentation-global-primary text-[12px]`,
|
|
90
|
+
week_number: `text-content-presentation-global-primary text-[12px] px-1`,
|
|
91
91
|
month_caption: `flex items-center justify-center`,
|
|
92
92
|
caption_label: `text-content-presentation-global-primary text-[14px] pt-[2px]`,
|
|
93
93
|
months: `flex items-end justify-center gap-4 flex-wrap`,
|
|
@@ -97,6 +97,6 @@ const Calender = ({
|
|
|
97
97
|
/>
|
|
98
98
|
)
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
Calendar.displayName = "Calendar"
|
|
101
101
|
|
|
102
|
-
export {
|
|
102
|
+
export { Calendar }
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
forwardRef,
|
|
4
|
+
ForwardedRef,
|
|
5
|
+
HTMLAttributes,
|
|
6
|
+
useEffect,
|
|
7
|
+
cloneElement,
|
|
8
|
+
isValidElement,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
|
|
11
|
+
import { Calendar } from "./Calendar";
|
|
12
|
+
import { Input, Trilling } from "./Input";
|
|
13
|
+
import { ActionButton } from "./ActionButton";
|
|
14
|
+
import { Group } from "./Input";
|
|
15
|
+
import { DateRange } from "react-day-picker";
|
|
16
|
+
import Picker, { PickerValue } from "torch-react-mobile-picker";
|
|
8
17
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
18
|
+
import {
|
|
19
|
+
applyTimeToDateValue,
|
|
20
|
+
formatDateValueToString,
|
|
21
|
+
TimePickerValue,
|
|
22
|
+
} from "../utils/dateFormat";
|
|
23
|
+
import { InputField } from "./InputField";
|
|
11
24
|
|
|
12
|
-
export type CalendarProps = React.ComponentProps<typeof
|
|
25
|
+
export type CalendarProps = React.ComponentProps<typeof Calendar>;
|
|
13
26
|
|
|
14
27
|
interface DatePickerProps extends HTMLAttributes<HTMLInputElement> {
|
|
15
28
|
mode?: "single" | "multiple" | "range";
|
|
@@ -22,75 +35,91 @@ interface DatePickerProps extends HTMLAttributes<HTMLInputElement> {
|
|
|
22
35
|
dateFormat?: string;
|
|
23
36
|
calendarProps?: CalendarProps;
|
|
24
37
|
timePicker?: boolean;
|
|
38
|
+
value?: any;
|
|
25
39
|
}
|
|
26
40
|
|
|
27
|
-
export const DatePicker = forwardRef(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
export const DatePicker = forwardRef(
|
|
42
|
+
(
|
|
43
|
+
{
|
|
44
|
+
size = "M",
|
|
45
|
+
min,
|
|
46
|
+
max,
|
|
47
|
+
mode = "single",
|
|
48
|
+
onChange,
|
|
49
|
+
showWeekNumber = false,
|
|
50
|
+
captionLayout = "dropdown",
|
|
51
|
+
dateFormat = "yyyy/MM/dd",
|
|
52
|
+
calendarProps,
|
|
53
|
+
timePicker = false,
|
|
54
|
+
children,
|
|
55
|
+
value = new Date(),
|
|
56
|
+
...props
|
|
57
|
+
}: DatePickerProps,
|
|
58
|
+
ref: ForwardedRef<HTMLInputElement>
|
|
59
|
+
) => {
|
|
60
|
+
const initialDate =
|
|
61
|
+
mode == "multiple"
|
|
62
|
+
? [value]
|
|
63
|
+
: mode == "range"
|
|
64
|
+
? { from: value, to: value }
|
|
65
|
+
: value;
|
|
66
|
+
const [date, setDate] = useState<Date[] | Date | DateRange | undefined>(initialDate);
|
|
67
|
+
const [pickerValue, setPickerValue] = useState<TimePickerValue>({
|
|
68
|
+
hour: (date instanceof Date ? date.getHours() : 12).toString(),
|
|
69
|
+
minute: (date instanceof Date ? date.getMinutes() : 0)
|
|
70
|
+
.toString()
|
|
71
|
+
.padStart(2, "0"),
|
|
72
|
+
time: date instanceof Date && date.getHours() < 12 ? "AM" : "PM",
|
|
73
|
+
});
|
|
74
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
// Call the onChange function when the date or picker value changes
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
onChange?.({
|
|
79
|
+
target: {
|
|
80
|
+
value: date,
|
|
81
|
+
},
|
|
82
|
+
} as any);
|
|
83
|
+
}, [date, pickerValue]);
|
|
51
84
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} as any);
|
|
59
|
-
}, [date, pickerValue]);
|
|
60
|
-
|
|
61
|
-
// Disable body scroll when popover is open
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (isOpen) {
|
|
64
|
-
// Save the current overflow style
|
|
65
|
-
const originalOverflow = document.body.style.overflow;
|
|
66
|
-
document.body.style.overflow = 'hidden';
|
|
85
|
+
// Disable body scroll when popover is open
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (isOpen) {
|
|
88
|
+
// Save the current overflow style
|
|
89
|
+
const originalOverflow = document.body.style.overflow;
|
|
90
|
+
document.body.style.overflow = "hidden";
|
|
67
91
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
// Cleanup function to restore original overflow style
|
|
93
|
+
return () => {
|
|
94
|
+
document.body.style.overflow = originalOverflow;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}, [isOpen]);
|
|
98
|
+
const formattedValue = formatDateValueToString(
|
|
99
|
+
date,
|
|
100
|
+
pickerValue,
|
|
101
|
+
dateFormat
|
|
102
|
+
);
|
|
75
103
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
isValidElement(children) ?
|
|
104
|
+
return (
|
|
105
|
+
<Popover onOpenChange={setIsOpen}>
|
|
106
|
+
<PopoverTrigger asChild>
|
|
107
|
+
{/* Clone the children element and pass the formatted value to the input element */}
|
|
108
|
+
{isValidElement(children) ? (
|
|
82
109
|
cloneElement(children as React.ReactElement<HTMLInputElement>, {
|
|
83
|
-
value:
|
|
110
|
+
value:
|
|
111
|
+
(children as React.ReactElement<HTMLInputElement>).props
|
|
112
|
+
.value ?? formattedValue,
|
|
84
113
|
type: "input",
|
|
85
|
-
readOnly: true
|
|
114
|
+
readOnly: true,
|
|
86
115
|
})
|
|
87
|
-
|
|
116
|
+
) : (
|
|
88
117
|
/* If the children is not a valid element, Show the default input */
|
|
89
118
|
<InputField
|
|
90
119
|
readOnly
|
|
91
120
|
type="input"
|
|
92
121
|
childrenSide={
|
|
93
|
-
<ActionButton type=
|
|
122
|
+
<ActionButton type="button" size={"M"}>
|
|
94
123
|
<i className="ri-calendar-event-fill"></i>
|
|
95
124
|
</ActionButton>
|
|
96
125
|
}
|
|
@@ -98,39 +127,41 @@ export const DatePicker = forwardRef(({
|
|
|
98
127
|
value={formattedValue}
|
|
99
128
|
ref={ref}
|
|
100
129
|
/>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
/>
|
|
116
|
-
{timePicker && (
|
|
117
|
-
<TimePicker
|
|
118
|
-
value={pickerValue}
|
|
119
|
-
onChange={(value: TimePickerValue) => {
|
|
120
|
-
setPickerValue(value);
|
|
121
|
-
setDate(applyTimeToDateValue(date, value));
|
|
130
|
+
)}
|
|
131
|
+
</PopoverTrigger>
|
|
132
|
+
<PopoverContent
|
|
133
|
+
data-theme="dark"
|
|
134
|
+
className="!h-fit max-h-[fit-content] p-0 border-none rounded-[12px] flex flex-col sm:flex-row "
|
|
135
|
+
>
|
|
136
|
+
<Calendar
|
|
137
|
+
{...calendarProps}
|
|
138
|
+
captionLayout={captionLayout}
|
|
139
|
+
showWeekNumber={showWeekNumber}
|
|
140
|
+
mode={mode as any}
|
|
141
|
+
selected={date as any}
|
|
142
|
+
onSelect={(e: any) => {
|
|
143
|
+
setDate(applyTimeToDateValue(e, pickerValue));
|
|
122
144
|
}}
|
|
145
|
+
min={mode != "single" ? min : undefined}
|
|
146
|
+
max={mode != "single" ? max : undefined}
|
|
123
147
|
/>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
{timePicker && (
|
|
149
|
+
<TimePicker
|
|
150
|
+
value={pickerValue}
|
|
151
|
+
onChange={(value: TimePickerValue) => {
|
|
152
|
+
setPickerValue(value);
|
|
153
|
+
setDate(applyTimeToDateValue(date, value));
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
)}
|
|
157
|
+
</PopoverContent>
|
|
158
|
+
</Popover>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
);
|
|
129
162
|
|
|
130
163
|
DatePicker.displayName = "DatePicker";
|
|
131
164
|
|
|
132
|
-
|
|
133
|
-
|
|
134
165
|
interface TimePickerProps {
|
|
135
166
|
value: TimePickerValue;
|
|
136
167
|
onChange: (value: TimePickerValue) => void;
|
|
@@ -138,7 +169,7 @@ interface TimePickerProps {
|
|
|
138
169
|
|
|
139
170
|
const TimePicker = ({ value, onChange }: TimePickerProps) => {
|
|
140
171
|
return (
|
|
141
|
-
<div className=
|
|
172
|
+
<div className="relative w-full sm:w-[189px]" data-theme="dark">
|
|
142
173
|
<Picker
|
|
143
174
|
className="flex-1"
|
|
144
175
|
selectContainerClassName="bg-background-system-body-tertiary z-[-1] rounded-[8px]"
|
|
@@ -148,11 +179,18 @@ const TimePicker = ({ value, onChange }: TimePickerProps) => {
|
|
|
148
179
|
}}
|
|
149
180
|
wheelMode="normal"
|
|
150
181
|
>
|
|
151
|
-
<span className=
|
|
182
|
+
<span className="absolute left-0 top-1/2 -translate-y-1/2 w-full h-[20px] flex justify-center items-center text-white text-[30px] pr-[55px] pb-[5px]">
|
|
183
|
+
:
|
|
184
|
+
</span>
|
|
152
185
|
<Picker.Column name="hour">
|
|
153
186
|
{Array.from({ length: 12 }, (_, i) => i + 1).map((hour) => (
|
|
154
187
|
<Picker.Item key={hour + "hour"} value={hour}>
|
|
155
|
-
<div className="typography-display-small-semibold flex gap-1 whitespace-nowrap text-content-presentation-global-primary pl-[25px]">
|
|
188
|
+
<div className="typography-display-small-semibold flex gap-1 whitespace-nowrap text-content-presentation-global-primary pl-[25px]">
|
|
189
|
+
{" "}
|
|
190
|
+
<p className="text-content-presentation-global-primary">
|
|
191
|
+
{hour}
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
156
194
|
</Picker.Item>
|
|
157
195
|
))}
|
|
158
196
|
</Picker.Column>
|
|
@@ -160,7 +198,7 @@ const TimePicker = ({ value, onChange }: TimePickerProps) => {
|
|
|
160
198
|
{Array.from({ length: 60 }, (_, i) => i).map((minute) => (
|
|
161
199
|
<Picker.Item key={minute + "minute"} value={minute}>
|
|
162
200
|
<div className="typography-display-small-semibold text-content-presentation-global-primary">
|
|
163
|
-
{minute.toString().padStart(2,
|
|
201
|
+
{minute.toString().padStart(2, "0")}
|
|
164
202
|
</div>
|
|
165
203
|
</Picker.Item>
|
|
166
204
|
))}
|
|
@@ -168,11 +206,13 @@ const TimePicker = ({ value, onChange }: TimePickerProps) => {
|
|
|
168
206
|
<Picker.Column name="time">
|
|
169
207
|
{["AM", "PM"].map((time) => (
|
|
170
208
|
<Picker.Item key={time + "time"} value={time}>
|
|
171
|
-
<div className="typography-display-small-semibold text-content-presentation-global-primary pr-[30px]">
|
|
209
|
+
<div className="typography-display-small-semibold text-content-presentation-global-primary pr-[30px]">
|
|
210
|
+
{time}
|
|
211
|
+
</div>
|
|
172
212
|
</Picker.Item>
|
|
173
213
|
))}
|
|
174
214
|
</Picker.Column>
|
|
175
215
|
</Picker>
|
|
176
216
|
</div>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
217
|
+
);
|
|
218
|
+
};
|
package/lib/components/Input.tsx
CHANGED
|
@@ -183,8 +183,8 @@ export const GroupStyles = cva(
|
|
|
183
183
|
],
|
|
184
184
|
},
|
|
185
185
|
size: {
|
|
186
|
-
S: ["h-[30px]", "rounded-[6px] [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]"],
|
|
187
|
-
M: ["h-[40px]", "rounded-[8px] [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]"],
|
|
186
|
+
S: ["h-[30px] min-h-[30px]", "rounded-[6px] [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]"],
|
|
187
|
+
M: ["h-[40px] min-h-[40px]", "rounded-[8px] [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]"],
|
|
188
188
|
},
|
|
189
189
|
error: {
|
|
190
190
|
true: [
|
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
import { cloneElement, ComponentProps, forwardRef, isValidElement, useState, useEffect } from 'react';
|
|
2
|
-
import { getDaysInMonth } from 'date-fns';
|
|
3
|
-
import Picker from 'torch-react-mobile-picker';
|
|
4
|
-
import { Popover, PopoverContent, PopoverTrigger } from './Popover';
|
|
5
|
-
import { InputField } from './InputField';
|
|
6
|
-
import { ActionButton } from './ActionButton';
|
|
7
|
-
import { formatDateValueToString } from '@/utils/dateFormat';
|
|
8
|
-
|
|
9
|
-
function getDayArray(year: number, month: number): string[] {
|
|
10
|
-
const dayCount = getDaysInMonth(new Date(year, month - 1));
|
|
11
|
-
return Array.from({ length: dayCount }, (_, i) => String(i + 1).padStart(2, '0'));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface SlideDatePickerProps extends Omit<ComponentProps<typeof InputField>, 'onChange'> {
|
|
15
|
-
onChange?: (e: any) => void;
|
|
16
|
-
theme?: "dark" | "light" | "default";
|
|
17
|
-
dateFormat?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type SlideVlaues = {
|
|
21
|
-
year: string,
|
|
22
|
-
month: string,
|
|
23
|
-
day: string,
|
|
24
|
-
hour?: string,
|
|
25
|
-
minute?: string,
|
|
26
|
-
time?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const SlideDatePicker = forwardRef<HTMLInputElement, SlideDatePickerProps>((
|
|
30
|
-
{
|
|
31
|
-
theme = "dark",
|
|
32
|
-
onChange,
|
|
33
|
-
dateFormat = "yyyy/MM/dd",
|
|
34
|
-
children,
|
|
35
|
-
...props
|
|
36
|
-
}, forwardedRef) => {
|
|
37
|
-
|
|
38
|
-
const today = new Date();
|
|
39
|
-
const defaultPickerValue = {
|
|
40
|
-
year: String(today.getFullYear()),
|
|
41
|
-
month: String(today.getMonth() + 1).padStart(2, '0'),
|
|
42
|
-
day: String(today.getDate()).padStart(2, '0'),
|
|
43
|
-
hour: "00",
|
|
44
|
-
minute: "00",
|
|
45
|
-
time: "AM"
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const [pickerValue, setPickerValue] = useState<SlideVlaues>(defaultPickerValue);
|
|
49
|
-
const [date, setDate] = useState<Date>(new Date());
|
|
50
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
51
|
-
const currentYear = new Date().getFullYear();
|
|
52
|
-
const years = Array.from({ length: 200 }, (_, i) => `${currentYear - 100 + i}`);
|
|
53
|
-
const months = Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, ''));
|
|
54
|
-
const days = getDayArray(Number(pickerValue.year), Number(pickerValue.month));
|
|
55
|
-
const monthsNames = [
|
|
56
|
-
"January", "February", "March", "April", "May", "June",
|
|
57
|
-
"July", "August", "September", "October", "November", "December",
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const handlePickerChange = (newValue: SlideVlaues, key: string) => {
|
|
61
|
-
let { year, month, day, hour, minute, time } = newValue;
|
|
62
|
-
|
|
63
|
-
if (key === 'year' || key === 'month') {
|
|
64
|
-
const newDayArray = getDayArray(Number(year), Number(month));
|
|
65
|
-
day = newDayArray.includes(day as string) ? day : newDayArray[newDayArray.length - 1];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const updatedValue = { year, month, day, hour, minute, time };
|
|
69
|
-
setPickerValue(updatedValue);
|
|
70
|
-
|
|
71
|
-
// Create a Date object from the updated value
|
|
72
|
-
const newDate = new Date(
|
|
73
|
-
Number(updatedValue.year),
|
|
74
|
-
Number(updatedValue.month) - 1, // Month is 0-indexed in JavaScript Date
|
|
75
|
-
Number(updatedValue.day)
|
|
76
|
-
);
|
|
77
|
-
setDate(newDate);
|
|
78
|
-
|
|
79
|
-
// Call the onChange callback with the Date object
|
|
80
|
-
if (onChange) {
|
|
81
|
-
onChange({
|
|
82
|
-
target: {
|
|
83
|
-
value: newDate
|
|
84
|
-
}
|
|
85
|
-
} as any);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const formattedValue = formatDateValueToString(date, {
|
|
90
|
-
hour: pickerValue.hour ?? defaultPickerValue.hour,
|
|
91
|
-
minute: pickerValue.minute ?? defaultPickerValue.minute,
|
|
92
|
-
time: pickerValue.time ?? defaultPickerValue.time
|
|
93
|
-
}, dateFormat);
|
|
94
|
-
|
|
95
|
-
// Disable body scroll when popover is open
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
if (isOpen) {
|
|
98
|
-
// Save the current overflow style
|
|
99
|
-
const originalOverflow = document.body.style.overflow;
|
|
100
|
-
document.body.style.overflow = 'hidden';
|
|
101
|
-
|
|
102
|
-
// Cleanup function to restore original overflow style
|
|
103
|
-
return () => {
|
|
104
|
-
document.body.style.overflow = originalOverflow;
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}, [isOpen]);
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
if (onChange) {
|
|
111
|
-
onChange({
|
|
112
|
-
target: {
|
|
113
|
-
value: date
|
|
114
|
-
}
|
|
115
|
-
} as any);
|
|
116
|
-
}
|
|
117
|
-
}, [formattedValue]);
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<Popover onOpenChange={setIsOpen}>
|
|
121
|
-
<PopoverTrigger asChild data-theme={theme} className='w-full flex-1' >
|
|
122
|
-
{
|
|
123
|
-
isValidElement(children) ?
|
|
124
|
-
cloneElement(children as React.ReactElement<HTMLInputElement>, {
|
|
125
|
-
value: (children as React.ReactElement<HTMLInputElement>).props.value ?? formattedValue,
|
|
126
|
-
type: "input",
|
|
127
|
-
readOnly: true
|
|
128
|
-
})
|
|
129
|
-
:
|
|
130
|
-
/* If the children is not a valid element, Show the default input */
|
|
131
|
-
<InputField
|
|
132
|
-
readOnly
|
|
133
|
-
type="input"
|
|
134
|
-
childrenSide={
|
|
135
|
-
<ActionButton type='button' size={"M"}>
|
|
136
|
-
<i className="ri-calendar-event-fill"></i>
|
|
137
|
-
</ActionButton>
|
|
138
|
-
}
|
|
139
|
-
{...props}
|
|
140
|
-
value={formattedValue}
|
|
141
|
-
ref={forwardedRef}
|
|
142
|
-
/>
|
|
143
|
-
}
|
|
144
|
-
</PopoverTrigger>
|
|
145
|
-
<PopoverContent data-theme={theme} dir="ltr" variant={props.variant} className="overflow-hidden w-[285px] flex justify-center items-center p-[6px] pt-[30px]">
|
|
146
|
-
<div className="flex justify-evenly items-center w-full absolute top-0 py-[6px]">
|
|
147
|
-
<p className="text-content-system-global-secondary typography-headers-medium-regular">Year</p>
|
|
148
|
-
<div className="flex justify-center items-center self-center">
|
|
149
|
-
<span className="h-[13px] w-[1px] bg-border-system-global-secondary rounded-[3px]"></span>
|
|
150
|
-
<p className="text-content-system-global-secondary typography-headers-medium-regular px-[18px]">Month</p>
|
|
151
|
-
<span className="h-[13px] w-[1px] bg-border-system-global-secondary rounded-[3px]"></span>
|
|
152
|
-
</div>
|
|
153
|
-
<p className="text-content-system-global-secondary typography-headers-medium-regular">Day</p>
|
|
154
|
-
</div>
|
|
155
|
-
<Picker
|
|
156
|
-
className="flex-1"
|
|
157
|
-
selectContainerClassName="bg-background-system-body-tertiary z-[-1] rounded-[8px]"
|
|
158
|
-
value={pickerValue}
|
|
159
|
-
onChange={handlePickerChange}
|
|
160
|
-
wheelMode="natural"
|
|
161
|
-
>
|
|
162
|
-
<Picker.Column name="year" >
|
|
163
|
-
{years.map((year) => (
|
|
164
|
-
<Picker.Item key={year} value={year}>
|
|
165
|
-
<div className="typography-display-small-semibold text-content-presentation-action-light-primary">{year}</div>
|
|
166
|
-
</Picker.Item>
|
|
167
|
-
))}
|
|
168
|
-
</Picker.Column>
|
|
169
|
-
<Picker.Column name="month">
|
|
170
|
-
{months.map((month, i) => (
|
|
171
|
-
<Picker.Item key={month} value={month}>
|
|
172
|
-
<div className="typography-display-small-semibold flex gap-1 whitespace-nowrap text-content-presentation-action-light-primary"> <p className='text-content-presentation-action-light-secondary'>{monthsNames[i].substring(0, 3)} - </p>{month}</div>
|
|
173
|
-
</Picker.Item>
|
|
174
|
-
))}
|
|
175
|
-
</Picker.Column>
|
|
176
|
-
<Picker.Column name="day">
|
|
177
|
-
{days.map((day) => (
|
|
178
|
-
<Picker.Item key={day} value={day}>
|
|
179
|
-
<div className="typography-display-small-semibold text-content-presentation-action-light-primary">{day}</div>
|
|
180
|
-
</Picker.Item>
|
|
181
|
-
))}
|
|
182
|
-
</Picker.Column>
|
|
183
|
-
</Picker>
|
|
184
|
-
</PopoverContent>
|
|
185
|
-
</Popover>
|
|
186
|
-
);
|
|
187
|
-
});
|
|
1
|
+
import { cloneElement, ComponentProps, forwardRef, isValidElement, useState, useEffect } from 'react';
|
|
2
|
+
import { getDaysInMonth } from 'date-fns';
|
|
3
|
+
import Picker from 'torch-react-mobile-picker';
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from './Popover';
|
|
5
|
+
import { InputField } from './InputField';
|
|
6
|
+
import { ActionButton } from './ActionButton';
|
|
7
|
+
import { formatDateValueToString } from '@/utils/dateFormat';
|
|
8
|
+
|
|
9
|
+
function getDayArray(year: number, month: number): string[] {
|
|
10
|
+
const dayCount = getDaysInMonth(new Date(year, month - 1));
|
|
11
|
+
return Array.from({ length: dayCount }, (_, i) => String(i + 1).padStart(2, '0'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SlideDatePickerProps extends Omit<ComponentProps<typeof InputField>, 'onChange'> {
|
|
15
|
+
onChange?: (e: any) => void;
|
|
16
|
+
theme?: "dark" | "light" | "default";
|
|
17
|
+
dateFormat?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type SlideVlaues = {
|
|
21
|
+
year: string,
|
|
22
|
+
month: string,
|
|
23
|
+
day: string,
|
|
24
|
+
hour?: string,
|
|
25
|
+
minute?: string,
|
|
26
|
+
time?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const SlideDatePicker = forwardRef<HTMLInputElement, SlideDatePickerProps>((
|
|
30
|
+
{
|
|
31
|
+
theme = "dark",
|
|
32
|
+
onChange,
|
|
33
|
+
dateFormat = "yyyy/MM/dd",
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}, forwardedRef) => {
|
|
37
|
+
|
|
38
|
+
const today = new Date();
|
|
39
|
+
const defaultPickerValue = {
|
|
40
|
+
year: String(today.getFullYear()),
|
|
41
|
+
month: String(today.getMonth() + 1).padStart(2, '0'),
|
|
42
|
+
day: String(today.getDate()).padStart(2, '0'),
|
|
43
|
+
hour: "00",
|
|
44
|
+
minute: "00",
|
|
45
|
+
time: "AM"
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const [pickerValue, setPickerValue] = useState<SlideVlaues>(defaultPickerValue);
|
|
49
|
+
const [date, setDate] = useState<Date>(new Date());
|
|
50
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
51
|
+
const currentYear = new Date().getFullYear();
|
|
52
|
+
const years = Array.from({ length: 200 }, (_, i) => `${currentYear - 100 + i}`);
|
|
53
|
+
const months = Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, ''));
|
|
54
|
+
const days = getDayArray(Number(pickerValue.year), Number(pickerValue.month));
|
|
55
|
+
const monthsNames = [
|
|
56
|
+
"January", "February", "March", "April", "May", "June",
|
|
57
|
+
"July", "August", "September", "October", "November", "December",
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const handlePickerChange = (newValue: SlideVlaues, key: string) => {
|
|
61
|
+
let { year, month, day, hour, minute, time } = newValue;
|
|
62
|
+
|
|
63
|
+
if (key === 'year' || key === 'month') {
|
|
64
|
+
const newDayArray = getDayArray(Number(year), Number(month));
|
|
65
|
+
day = newDayArray.includes(day as string) ? day : newDayArray[newDayArray.length - 1];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const updatedValue = { year, month, day, hour, minute, time };
|
|
69
|
+
setPickerValue(updatedValue);
|
|
70
|
+
|
|
71
|
+
// Create a Date object from the updated value
|
|
72
|
+
const newDate = new Date(
|
|
73
|
+
Number(updatedValue.year),
|
|
74
|
+
Number(updatedValue.month) - 1, // Month is 0-indexed in JavaScript Date
|
|
75
|
+
Number(updatedValue.day)
|
|
76
|
+
);
|
|
77
|
+
setDate(newDate);
|
|
78
|
+
|
|
79
|
+
// Call the onChange callback with the Date object
|
|
80
|
+
if (onChange) {
|
|
81
|
+
onChange({
|
|
82
|
+
target: {
|
|
83
|
+
value: newDate
|
|
84
|
+
}
|
|
85
|
+
} as any);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const formattedValue = formatDateValueToString(date, {
|
|
90
|
+
hour: pickerValue.hour ?? defaultPickerValue.hour,
|
|
91
|
+
minute: pickerValue.minute ?? defaultPickerValue.minute,
|
|
92
|
+
time: pickerValue.time ?? defaultPickerValue.time
|
|
93
|
+
}, dateFormat);
|
|
94
|
+
|
|
95
|
+
// Disable body scroll when popover is open
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (isOpen) {
|
|
98
|
+
// Save the current overflow style
|
|
99
|
+
const originalOverflow = document.body.style.overflow;
|
|
100
|
+
document.body.style.overflow = 'hidden';
|
|
101
|
+
|
|
102
|
+
// Cleanup function to restore original overflow style
|
|
103
|
+
return () => {
|
|
104
|
+
document.body.style.overflow = originalOverflow;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}, [isOpen]);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (onChange) {
|
|
111
|
+
onChange({
|
|
112
|
+
target: {
|
|
113
|
+
value: date
|
|
114
|
+
}
|
|
115
|
+
} as any);
|
|
116
|
+
}
|
|
117
|
+
}, [formattedValue]);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Popover onOpenChange={setIsOpen}>
|
|
121
|
+
<PopoverTrigger asChild data-theme={theme} className='w-full flex-1' >
|
|
122
|
+
{
|
|
123
|
+
isValidElement(children) ?
|
|
124
|
+
cloneElement(children as React.ReactElement<HTMLInputElement>, {
|
|
125
|
+
value: (children as React.ReactElement<HTMLInputElement>).props.value ?? formattedValue,
|
|
126
|
+
type: "input",
|
|
127
|
+
readOnly: true
|
|
128
|
+
})
|
|
129
|
+
:
|
|
130
|
+
/* If the children is not a valid element, Show the default input */
|
|
131
|
+
<InputField
|
|
132
|
+
readOnly
|
|
133
|
+
type="input"
|
|
134
|
+
childrenSide={
|
|
135
|
+
<ActionButton type='button' size={"M"}>
|
|
136
|
+
<i className="ri-calendar-event-fill"></i>
|
|
137
|
+
</ActionButton>
|
|
138
|
+
}
|
|
139
|
+
{...props}
|
|
140
|
+
value={formattedValue}
|
|
141
|
+
ref={forwardedRef}
|
|
142
|
+
/>
|
|
143
|
+
}
|
|
144
|
+
</PopoverTrigger>
|
|
145
|
+
<PopoverContent data-theme={theme} dir="ltr" variant={props.variant} className="overflow-hidden w-[285px] flex justify-center items-center p-[6px] pt-[30px]">
|
|
146
|
+
<div className="flex justify-evenly items-center w-full absolute top-0 py-[6px]">
|
|
147
|
+
<p className="text-content-system-global-secondary typography-headers-medium-regular">Year</p>
|
|
148
|
+
<div className="flex justify-center items-center self-center">
|
|
149
|
+
<span className="h-[13px] w-[1px] bg-border-system-global-secondary rounded-[3px]"></span>
|
|
150
|
+
<p className="text-content-system-global-secondary typography-headers-medium-regular px-[18px]">Month</p>
|
|
151
|
+
<span className="h-[13px] w-[1px] bg-border-system-global-secondary rounded-[3px]"></span>
|
|
152
|
+
</div>
|
|
153
|
+
<p className="text-content-system-global-secondary typography-headers-medium-regular">Day</p>
|
|
154
|
+
</div>
|
|
155
|
+
<Picker
|
|
156
|
+
className="flex-1"
|
|
157
|
+
selectContainerClassName="bg-background-system-body-tertiary z-[-1] rounded-[8px]"
|
|
158
|
+
value={pickerValue}
|
|
159
|
+
onChange={handlePickerChange}
|
|
160
|
+
wheelMode="natural"
|
|
161
|
+
>
|
|
162
|
+
<Picker.Column name="year" >
|
|
163
|
+
{years.map((year) => (
|
|
164
|
+
<Picker.Item key={year} value={year}>
|
|
165
|
+
<div className="typography-display-small-semibold text-content-presentation-action-light-primary">{year}</div>
|
|
166
|
+
</Picker.Item>
|
|
167
|
+
))}
|
|
168
|
+
</Picker.Column>
|
|
169
|
+
<Picker.Column name="month">
|
|
170
|
+
{months.map((month, i) => (
|
|
171
|
+
<Picker.Item key={month} value={month}>
|
|
172
|
+
<div className="typography-display-small-semibold flex gap-1 whitespace-nowrap text-content-presentation-action-light-primary"> <p className='text-content-presentation-action-light-secondary'>{monthsNames[i].substring(0, 3)} - </p>{month}</div>
|
|
173
|
+
</Picker.Item>
|
|
174
|
+
))}
|
|
175
|
+
</Picker.Column>
|
|
176
|
+
<Picker.Column name="day">
|
|
177
|
+
{days.map((day) => (
|
|
178
|
+
<Picker.Item key={day} value={day}>
|
|
179
|
+
<div className="typography-display-small-semibold text-content-presentation-action-light-primary">{day}</div>
|
|
180
|
+
</Picker.Item>
|
|
181
|
+
))}
|
|
182
|
+
</Picker.Column>
|
|
183
|
+
</Picker>
|
|
184
|
+
</PopoverContent>
|
|
185
|
+
</Popover>
|
|
186
|
+
);
|
|
187
|
+
});
|