notionsoft-ui 1.0.20 → 1.0.21

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.
@@ -0,0 +1,265 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { CalendarDays } from "lucide-react";
3
+ import DatePanel from "react-multi-date-picker/plugins/date_panel";
4
+ import AnimatedItem from "../animated-item";
5
+ import type { Calendar, Locale } from "react-date-object";
6
+ import type DateObject from "react-date-object";
7
+ import { Calendar as Calendars } from "react-multi-date-picker";
8
+ import gregorian from "react-date-object/calendars/gregorian";
9
+ import gregorian_en from "react-date-object/locales/gregorian_en";
10
+ import { cn } from "../../utils/cn";
11
+
12
+ export type MultiDatePickerSize = "sm" | "md" | "lg";
13
+
14
+ interface MultiDatePickerText {
15
+ label?: string;
16
+ requiredHint?: string;
17
+ placeholder?: string;
18
+ to?: string;
19
+ }
20
+ export interface MultiDatePickerProps {
21
+ dateOnComplete: (selectedDates: DateObject[]) => void;
22
+ value: DateObject[];
23
+ className?: string;
24
+ measurement?: MultiDatePickerSize;
25
+ classNames?: {
26
+ rootDivClassName?: string;
27
+ };
28
+ errorMessage?: string;
29
+ readOnly?: boolean;
30
+ text?: MultiDatePickerText;
31
+ startContent?: React.ReactNode;
32
+ endContent?: React.ReactNode;
33
+ getLocalizations?: () => {
34
+ calendar?: Calendar;
35
+ locale?: Locale;
36
+ months?: string[];
37
+ };
38
+ }
39
+
40
+ export default function MultiDatePicker(props: MultiDatePickerProps) {
41
+ const {
42
+ dateOnComplete,
43
+ value,
44
+ className,
45
+ measurement = "sm",
46
+ classNames,
47
+ errorMessage,
48
+ readOnly,
49
+ startContent,
50
+ endContent,
51
+ getLocalizations,
52
+ text,
53
+ } = props;
54
+ const { requiredHint, label, placeholder, to } = text || {};
55
+ const { rootDivClassName } = classNames || {};
56
+ const [visible, setVisible] = useState(false);
57
+ const [selectedDates, setSelectedDates] = useState<DateObject[]>(value);
58
+ const calendarRef = useRef<any>(null);
59
+ const hasError = !!errorMessage;
60
+
61
+ useEffect(() => {
62
+ // Add event listener for clicks outside
63
+ document.addEventListener("mousedown", handleClickOutside);
64
+ return () => {
65
+ document.removeEventListener("mousedown", handleClickOutside);
66
+ };
67
+ }, []);
68
+ const handleClickOutside = (event: MouseEvent) => {
69
+ if (
70
+ calendarRef.current &&
71
+ !calendarRef.current.contains(event.target as Node)
72
+ ) {
73
+ setVisible(false);
74
+ }
75
+ };
76
+
77
+ const handleDateChange = (selectedDates: DateObject[]) => {
78
+ // let object = { date, format };
79
+ // const gre = new DateObject(object)
80
+ // .convert(gregorian, gregorian_en)
81
+ // .format();
82
+ dateOnComplete(selectedDates);
83
+ setSelectedDates(selectedDates);
84
+ };
85
+ const onVisibilityChange = () => {
86
+ if (!readOnly) setVisible(!visible);
87
+ };
88
+
89
+ const heightStyle = useMemo(
90
+ () =>
91
+ measurement == "lg"
92
+ ? {
93
+ height: "50px",
94
+ paddingBottom: "pb-[3px]",
95
+ endContent: label
96
+ ? "ltr:top-[48px] rtl:top-[54px]-translate-y-1/2"
97
+ : "top-[26px] -translate-y-1/2",
98
+ startContent: label
99
+ ? "ltr:top-[48px] rtl:top-[54px] -translate-y-1/2"
100
+ : "top-[26px] -translate-y-1/2",
101
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
102
+ }
103
+ : measurement == "md"
104
+ ? {
105
+ height: "44px",
106
+ paddingBottom: "pb-[2px]",
107
+ endContent: label
108
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
109
+ : "top-[22px] -translate-y-1/2",
110
+ startContent: label
111
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
112
+ : "top-[22px] -translate-y-1/2",
113
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
114
+ }
115
+ : {
116
+ height: "40px",
117
+ paddingBottom: "pb-[2px]",
118
+ endContent: label
119
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
120
+ : "top-[20px] -translate-y-1/2",
121
+ startContent: label
122
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
123
+ : "top-[20px] -translate-y-1/2",
124
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
125
+ },
126
+ [measurement, label]
127
+ );
128
+ const readOnlyStyle = readOnly && "opacity-40";
129
+ const localizations = getLocalizations
130
+ ? getLocalizations()
131
+ : {
132
+ calendar: gregorian,
133
+ locale: gregorian_en,
134
+ months: [],
135
+ };
136
+ return (
137
+ <div
138
+ className={cn(
139
+ rootDivClassName,
140
+ "relative flex w-full flex-col justify-end",
141
+ readOnlyStyle
142
+ )}
143
+ >
144
+ {visible && (
145
+ <Calendars
146
+ value={selectedDates}
147
+ ref={calendarRef}
148
+ className="absolute font-segoe top-10"
149
+ onChange={handleDateChange}
150
+ range
151
+ plugins={[<DatePanel position="top" className="h-28" />]}
152
+ months={localizations.months}
153
+ calendar={localizations.calendar}
154
+ locale={localizations.locale}
155
+ />
156
+ )}
157
+ {/* Start Content */}
158
+ {startContent && (
159
+ <span
160
+ className={cn(
161
+ "absolute flex items-center ltr:left-3 rtl:right-3",
162
+ heightStyle.startContent
163
+ )}
164
+ >
165
+ {startContent}
166
+ </span>
167
+ )}
168
+
169
+ {/* End Content */}
170
+ {endContent && (
171
+ <span
172
+ className={cn(
173
+ "absolute flex items-center ltr:right-[5px] rtl:left-[5px]",
174
+ heightStyle.endContent
175
+ )}
176
+ >
177
+ {endContent}
178
+ </span>
179
+ )}
180
+
181
+ {/* Required Hint */}
182
+ {requiredHint && (
183
+ <span
184
+ className={cn(
185
+ "absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-2.5 rtl:left-2.5",
186
+ heightStyle.required
187
+ )}
188
+ >
189
+ {requiredHint}
190
+ </span>
191
+ )}
192
+
193
+ {/* Label */}
194
+ {label && (
195
+ <label
196
+ htmlFor={label}
197
+ className={cn(
198
+ "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
199
+ )}
200
+ >
201
+ {label}
202
+ </label>
203
+ )}
204
+ <div
205
+ style={{
206
+ height: heightStyle.height,
207
+ }}
208
+ className={cn(
209
+ "relative flex items-center text-start px-3 border select-none rounded-sm rtl:text-lg-rtl ltr:text-lg-ltr",
210
+ className
211
+ )}
212
+ onClick={onVisibilityChange}
213
+ >
214
+ {selectedDates && selectedDates.length > 0 ? (
215
+ <div className="flex items-center gap-x-2 text-ellipsis rtl:text-lg-rtl ltr:text-lg-ltr text-primary/80 text-nowrap">
216
+ <CalendarDays className="size-4 inline-block text-tertiary rtl:ml-2 rtl:mr-2" />
217
+ {selectedDates.map((date: DateObject, index: number) => (
218
+ <div key={index} className="flex gap-x-2">
219
+ {index % 2 == 1 && (
220
+ <h1 className="text-tertiary font-semibold">{to}</h1>
221
+ )}
222
+ <h1>
223
+ {date
224
+ .convert(localizations.calendar, localizations.locale)
225
+ .format()}
226
+ </h1>
227
+ {/* <h1>{formatHijriDate(date)}</h1> */}
228
+ </div>
229
+ ))}
230
+ </div>
231
+ ) : (
232
+ <h1 className="flex items-center gap-x-2 text-ellipsis rtl:text-lg-rtl ltr:text-lg-ltr text-primary/80 text-nowrap">
233
+ <CalendarDays className="size-4 inline-block text-tertiary" />
234
+ {placeholder}
235
+ </h1>
236
+ )}
237
+ </div>
238
+ {/* Error Message */}
239
+ {hasError && (
240
+ <AnimatedItem
241
+ springProps={{
242
+ from: {
243
+ opacity: 0,
244
+ transform: "translateY(-8px)",
245
+ },
246
+ config: {
247
+ mass: 1,
248
+ tension: 210,
249
+ friction: 20,
250
+ },
251
+ to: {
252
+ opacity: 1,
253
+ transform: "translateY(0px)",
254
+ },
255
+ }}
256
+ intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
257
+ >
258
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
259
+ {errorMessage}
260
+ </h1>
261
+ </AnimatedItem>
262
+ )}
263
+ </div>
264
+ );
265
+ }