notionsoft-ui 1.0.20 → 1.0.22

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/cli/index.cjs CHANGED
@@ -137,13 +137,95 @@ program
137
137
  }
138
138
 
139
139
  // Copy as a flat file into user's project
140
- const destFile = path.join(config.componentDir, component + ".tsx");
141
- fs.ensureDirSync(config.componentDir);
142
- fs.copyFileSync(templateFile, destFile);
143
-
144
- console.log(
145
- chalk.green(`✓ Installed ${component} component as ${destFile}`)
146
- );
140
+ // const destFile = path.join(config.componentDir, component + ".tsx");
141
+ // fs.ensureDirSync(config.componentDir);
142
+ // fs.copyFileSync(templateFile, destFile);
143
+
144
+ // console.log(
145
+ // chalk.green(`✓ Installed ${component} component as ${destFile}`)
146
+ // );
147
+ try {
148
+ const templateDir = path.join(__dirname, "../src/notion-ui", component);
149
+ const destDir = path.join(config.componentDir, component);
150
+
151
+ if (!fs.existsSync(templateDir)) {
152
+ console.log(chalk.red(`❌ Component '${component}' does not exist.`));
153
+ return;
154
+ }
155
+
156
+ // --- Step 1: Merge to utils ---
157
+ const utilsDir = path.join(cwd, "src/utils");
158
+ fs.ensureDirSync(utilsDir);
159
+
160
+ let mergeSuccess = true;
161
+
162
+ fs.readdirSync(templateDir).forEach((file) => {
163
+ const filePath = path.join(templateDir, file);
164
+ const content = fs.readFileSync(filePath, "utf-8").trim();
165
+
166
+ try {
167
+ let targetPath;
168
+ if (file.endsWith("-data.ts"))
169
+ targetPath = path.join(utilsDir, "dt.ts");
170
+ else if (file === "type.ts")
171
+ targetPath = path.join(utilsDir, "type.ts");
172
+ else if (file.startsWith("use-") && file.endsWith(".ts"))
173
+ targetPath = path.join(utilsDir, "hook.ts");
174
+ else return; // skip other files
175
+
176
+ let targetContent = "";
177
+ if (fs.existsSync(targetPath))
178
+ targetContent = fs.readFileSync(targetPath, "utf-8");
179
+
180
+ // Append only if content not already in the target file
181
+ if (!targetContent.includes(content)) {
182
+ if (targetContent.length > 0) targetContent += "\n\n";
183
+ targetContent += content;
184
+ fs.writeFileSync(targetPath, targetContent);
185
+ console.log(
186
+ chalk.green(
187
+ `✓ Merged ${file} → ${path.relative(cwd, targetPath)}`
188
+ )
189
+ );
190
+ } else {
191
+ console.log(
192
+ chalk.yellow(
193
+ `⚠ ${file} already exists in ${path.relative(
194
+ cwd,
195
+ targetPath
196
+ )}, skipping`
197
+ )
198
+ );
199
+ }
200
+ } catch (err) {
201
+ mergeSuccess = false;
202
+ console.log(chalk.red(`❌ Failed merging ${file}: ${err.message}`));
203
+ }
204
+ });
205
+
206
+ if (!mergeSuccess) {
207
+ console.log(
208
+ chalk.red(
209
+ "❌ Failed to merge required files. Component installation aborted."
210
+ )
211
+ );
212
+ return;
213
+ }
214
+
215
+ // --- Step 2: Copy component files ---
216
+ fs.copySync(templateDir, destDir, {
217
+ filter: (src) => {
218
+ const filename = path.basename(src);
219
+ if (filename === "index.ts") return false;
220
+ if (filename.endsWith(".stories.tsx")) return false;
221
+ return true;
222
+ },
223
+ });
224
+
225
+ console.log(chalk.green(`✓ Installed ${component} to ${destDir}`));
226
+ } catch (err) {
227
+ console.log(chalk.red(`❌ Installation failed: ${err.message}`));
228
+ }
147
229
  });
148
230
 
149
231
  /* ------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notionsoft-ui",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "A React UI component installer (shadcn-style). Installs components directly into your project.",
5
5
  "bin": {
6
6
  "notionsoft-ui": "./cli/index.cjs"
@@ -26,6 +26,7 @@
26
26
  "lucide-react": "^0.554.0",
27
27
  "react": "^19.2.0",
28
28
  "react-dom": "^19.2.0",
29
+ "react-multi-date-picker": "^4.5.2",
29
30
  "tailwind-merge": "^3.4.0"
30
31
  },
31
32
  "devDependencies": {
@@ -24,7 +24,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
24
24
  disabled={disabled}
25
25
  ref={ref}
26
26
  className={cn(
27
- `rounded-sm flex items-center gap-x-1 cursor-pointer font-medium ltr:text-xs leading-snug li rtl:text-[13px] sm:rtl:text-sm rtl:font-semibold
27
+ `rounded-sm flex items-center justify-center gap-x-1 cursor-pointer font-medium ltr:text-xs leading-snug li rtl:text-[13px] sm:rtl:text-sm rtl:font-semibold
28
28
  transition w-fit px-3 py-1.5 duration-200 ease-linear`,
29
29
  style,
30
30
  disabled &&
@@ -0,0 +1,99 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import DateObject from "react-date-object";
4
+
5
+ import DatePicker, { DatePickerProps } from "./date-picker";
6
+
7
+ // Persian
8
+ import persian from "react-date-object/calendars/persian";
9
+ import persian_fa from "react-date-object/locales/persian_fa";
10
+
11
+ export default {
12
+ title: "Date/DatePicker",
13
+ component: DatePicker,
14
+ parameters: {
15
+ layout: "centered",
16
+ },
17
+ } as Meta<DatePickerProps>;
18
+
19
+ const Wrapper = (args: DatePickerProps) => {
20
+ const [value, setValue] = React.useState<DateObject | undefined>(
21
+ typeof args.value === "string"
22
+ ? new DateObject(new Date(args.value))
23
+ : args.value
24
+ );
25
+
26
+ return (
27
+ <div style={{ width: 320 }}>
28
+ <DatePicker
29
+ {...args}
30
+ value={value}
31
+ dateOnComplete={(date) => {
32
+ setValue(date);
33
+ return false;
34
+ }}
35
+ />
36
+ </div>
37
+ );
38
+ };
39
+
40
+ /* ------------------------ DEFAULT ------------------------ */
41
+ export const Default: StoryObj<DatePickerProps> = {
42
+ render: (args) => <Wrapper {...args} />,
43
+ args: {
44
+ placeholder: "Select date...",
45
+ required: false,
46
+ label: "Date",
47
+ measurement: "md",
48
+ },
49
+ };
50
+
51
+ /* ------------------------ PERSIAN ------------------------ */
52
+ export const Persian: StoryObj<DatePickerProps> = {
53
+ render: (args) => <Wrapper {...args} />,
54
+ args: {
55
+ placeholder: "تاریخ را انتخاب کنید",
56
+ label: "تاریخ",
57
+ measurement: "md",
58
+ getLocalizations: () => ({
59
+ calendar: persian,
60
+ locale: persian_fa,
61
+ months: [
62
+ "فروردین",
63
+ "اردیبهشت",
64
+ "خرداد",
65
+ "تیر",
66
+ "مرداد",
67
+ "شهریور",
68
+ "مهر",
69
+ "آبان",
70
+ "آذر",
71
+ "دی",
72
+ "بهمن",
73
+ "اسفند",
74
+ ],
75
+ }),
76
+ },
77
+ };
78
+
79
+ /* ------------------------ WITH ERROR ------------------------ */
80
+ export const WithError: StoryObj<DatePickerProps> = {
81
+ render: (args) => <Wrapper {...args} />,
82
+ args: {
83
+ placeholder: "Select date...",
84
+ label: "Birthday",
85
+ errorMessage: "This field is required",
86
+ required: true,
87
+ requiredHint: "*",
88
+ },
89
+ };
90
+
91
+ /* ------------------------ READONLY ------------------------ */
92
+ export const ReadOnly: StoryObj<DatePickerProps> = {
93
+ render: (args) => <Wrapper {...args} />,
94
+ args: {
95
+ placeholder: "Read-only field",
96
+ label: "Date",
97
+ readOnly: true,
98
+ },
99
+ };
@@ -0,0 +1,271 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import DateObject from "react-date-object";
3
+ import type { Calendar, Locale } from "react-date-object";
4
+ import { Calendar as Calendars } from "react-multi-date-picker";
5
+ export type DatePickerSize = "sm" | "md" | "lg";
6
+ import gregorian from "react-date-object/calendars/gregorian";
7
+ import gregorian_en from "react-date-object/locales/gregorian_en";
8
+ import { CalendarDays } from "lucide-react";
9
+ import { createPortal } from "react-dom";
10
+
11
+ import AnimatedItem from "../animated-item";
12
+ import { cn } from "../../utils/cn";
13
+
14
+ export interface DatePickerProps {
15
+ dateOnComplete: (date: DateObject) => boolean | void;
16
+ value: DateObject | undefined | string;
17
+ className?: string;
18
+ classNames?: {
19
+ rootDivClassName?: string;
20
+ };
21
+ getLocalizations?: () => {
22
+ calendar?: Calendar;
23
+ locale?: Locale;
24
+ months?: string[];
25
+ };
26
+ placeholder: string;
27
+ place?: string;
28
+ format?: string;
29
+ requiredHint?: string;
30
+ hintColor?: string;
31
+ label?: string;
32
+ errorMessage?: string;
33
+ readOnly?: boolean;
34
+ measurement?: DatePickerSize;
35
+ }
36
+
37
+ export default function DatePicker(props: DatePickerProps) {
38
+ const {
39
+ dateOnComplete,
40
+ value,
41
+ className,
42
+ classNames,
43
+ placeholder,
44
+ requiredHint,
45
+ measurement,
46
+ label,
47
+ errorMessage,
48
+ readOnly,
49
+ format = "YYYY-MM-DD",
50
+ getLocalizations,
51
+ } = props;
52
+ const { rootDivClassName } = classNames || {};
53
+
54
+ const [visible, setVisible] = useState(false);
55
+ const [selectedDates, setSelectedDates] = useState<DateObject | undefined>(
56
+ typeof value === "string" ? new DateObject(new Date(value)) : value
57
+ );
58
+
59
+ const calendarWrapperRef = useRef<HTMLDivElement | null>(null);
60
+ const calenderParentRef = useRef<HTMLDivElement | null>(null);
61
+ const hasError = !!errorMessage;
62
+
63
+ const [position, setPosition] = useState<{
64
+ top: number;
65
+ left: number;
66
+ } | null>(null);
67
+
68
+ // Close calendar on outside clicks
69
+ useEffect(() => {
70
+ const handleClickOutside = (event: MouseEvent) => {
71
+ if (
72
+ calendarWrapperRef.current &&
73
+ !calendarWrapperRef.current.contains(event.target as Node) &&
74
+ calenderParentRef.current &&
75
+ !calenderParentRef.current.contains(event.target as Node)
76
+ ) {
77
+ setVisible(false);
78
+ }
79
+ };
80
+
81
+ document.addEventListener("mousedown", handleClickOutside);
82
+ return () => document.removeEventListener("mousedown", handleClickOutside);
83
+ }, []);
84
+
85
+ // Update position when calendar becomes visible
86
+ useEffect(() => {
87
+ if (visible && calenderParentRef.current) {
88
+ const rect = calenderParentRef.current.getBoundingClientRect();
89
+ setPosition({
90
+ top: rect.bottom + window.scrollY,
91
+ left: rect.left + window.scrollX,
92
+ });
93
+ }
94
+ }, [visible]);
95
+
96
+ const formatHijriDate = (date?: DateObject) => {
97
+ try {
98
+ if (date) {
99
+ return date
100
+ .convert(localizations.calendar, localizations.locale)
101
+ .format(format);
102
+ }
103
+ } catch (e: any) {
104
+ console.log(e, "DatePicker");
105
+ }
106
+ return undefined;
107
+ };
108
+
109
+ const handleDateChange = (date: DateObject) => {
110
+ setVisible(false);
111
+ const failed = dateOnComplete(date);
112
+ if (failed) return;
113
+ setSelectedDates(date);
114
+ };
115
+
116
+ const onVisibilityChange = () => {
117
+ if (!readOnly) setVisible((v) => !v);
118
+ };
119
+
120
+ const localizations = getLocalizations
121
+ ? getLocalizations()
122
+ : {
123
+ calendar: gregorian,
124
+ locale: gregorian_en,
125
+ months: [],
126
+ };
127
+ const heightStyle = useMemo(
128
+ () =>
129
+ measurement == "lg"
130
+ ? {
131
+ height: "50px",
132
+ paddingBottom: "pb-[3px]",
133
+ endContent: label
134
+ ? "ltr:top-[48px] rtl:top-[54px]-translate-y-1/2"
135
+ : "top-[26px] -translate-y-1/2",
136
+ startContent: label
137
+ ? "ltr:top-[48px] rtl:top-[54px] -translate-y-1/2"
138
+ : "top-[26px] -translate-y-1/2",
139
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
140
+ }
141
+ : measurement == "md"
142
+ ? {
143
+ height: "44px",
144
+ paddingBottom: "pb-[2px]",
145
+ endContent: label
146
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
147
+ : "top-[22px] -translate-y-1/2",
148
+ startContent: label
149
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
150
+ : "top-[22px] -translate-y-1/2",
151
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
152
+ }
153
+ : {
154
+ height: "40px",
155
+ paddingBottom: "pb-[2px]",
156
+ endContent: label
157
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
158
+ : "top-[20px] -translate-y-1/2",
159
+ startContent: label
160
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
161
+ : "top-[20px] -translate-y-1/2",
162
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
163
+ },
164
+ [measurement, label]
165
+ );
166
+ const readOnlyStyle = readOnly && "opacity-40";
167
+ return (
168
+ <div
169
+ ref={calenderParentRef}
170
+ className={cn("relative", rootDivClassName, readOnlyStyle)}
171
+ >
172
+ {/* Calendar portal */}
173
+ {visible &&
174
+ position &&
175
+ createPortal(
176
+ <div
177
+ ref={calendarWrapperRef}
178
+ style={{
179
+ position: "absolute",
180
+ top: position.top,
181
+ left: position.left,
182
+ zIndex: 9999,
183
+ backgroundColor: "white",
184
+ boxShadow: "0 4px 10px rgba(0,0,0,0.15)",
185
+ borderRadius: "6px",
186
+ }}
187
+ >
188
+ <Calendars
189
+ value={selectedDates}
190
+ onChange={handleDateChange}
191
+ months={localizations.months}
192
+ calendar={localizations.calendar}
193
+ locale={localizations.locale}
194
+ />
195
+ </div>,
196
+ document.body
197
+ )}
198
+ {/* Required Hint */}
199
+ {requiredHint && (
200
+ <span
201
+ className={cn(
202
+ "absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-2.5 rtl:left-2.5",
203
+ heightStyle.required
204
+ )}
205
+ >
206
+ {requiredHint}
207
+ </span>
208
+ )}
209
+
210
+ {/* Label */}
211
+ {label && (
212
+ <label
213
+ htmlFor={label}
214
+ className={cn(
215
+ "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
216
+ )}
217
+ >
218
+ {label}
219
+ </label>
220
+ )}
221
+ {/* Input / trigger div */}
222
+ <div
223
+ style={{
224
+ height: heightStyle.height,
225
+ }}
226
+ className={cn(
227
+ "flex items-center text-start px-3 border select-none rounded-sm rtl:text-lg-rtl ltr:text-lg-ltr",
228
+ className
229
+ )}
230
+ onClick={onVisibilityChange}
231
+ >
232
+ {selectedDates ? (
233
+ <h1 className="flex items-center gap-x-2 text-ellipsis rtl:text-lg-rtl ltr:text-lg-ltr text-primary/80 whitespace-nowrap overflow-hidden">
234
+ <CalendarDays className="size-4 inline-block text-tertiary rtl:ml-2 rtl:mr-2" />
235
+ {formatHijriDate(selectedDates)}
236
+ </h1>
237
+ ) : (
238
+ <h1 className="flex items-center gap-x-2 text-ellipsis rtl:text-lg-rtl ltr:text-lg-ltr font-semibold text-primary whitespace-nowrap overflow-hidden">
239
+ <CalendarDays className="size-4 inline-block text-tertiary" />
240
+ {placeholder}
241
+ </h1>
242
+ )}
243
+ </div>
244
+
245
+ {hasError && (
246
+ <AnimatedItem
247
+ springProps={{
248
+ from: {
249
+ opacity: 0,
250
+ transform: "translateY(-8px)",
251
+ },
252
+ config: {
253
+ mass: 1,
254
+ tension: 210,
255
+ friction: 20,
256
+ },
257
+ to: {
258
+ opacity: 1,
259
+ transform: "translateY(0px)",
260
+ },
261
+ }}
262
+ intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
263
+ >
264
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
265
+ {errorMessage}
266
+ </h1>
267
+ </AnimatedItem>
268
+ )}
269
+ </div>
270
+ );
271
+ }
@@ -0,0 +1,3 @@
1
+ import DatePicker from "./date-picker";
2
+
3
+ export default DatePicker;
@@ -9,11 +9,13 @@ export type NastranInputSize = "sm" | "md" | "lg";
9
9
  export interface InputProps
10
10
  extends React.InputHTMLAttributes<HTMLInputElement> {
11
11
  startContent?: React.ReactNode;
12
+ endContent?: React.ReactNode;
12
13
  requiredHint?: string;
13
14
  label?: string;
14
- endContent?: React.ReactNode;
15
15
  errorMessage?: string;
16
- parentClassName?: string;
16
+ classNames?: {
17
+ rootDivClassName?: string;
18
+ };
17
19
  measurement?: NastranInputSize;
18
20
  }
19
21
 
@@ -25,7 +27,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
25
27
  requiredHint,
26
28
  startContent,
27
29
  endContent,
28
- parentClassName = "",
30
+ classNames,
29
31
  measurement = "sm",
30
32
  errorMessage,
31
33
  label,
@@ -35,6 +37,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
35
37
  ref
36
38
  ) => {
37
39
  const hasError = !!errorMessage;
40
+ const { rootDivClassName } = classNames || {};
38
41
 
39
42
  const inputPaddingClass = startContent
40
43
  ? "rtl:pr-[42px] ltr:ps-[42px]"
@@ -45,7 +48,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
45
48
  measurement == "lg"
46
49
  ? {
47
50
  height: "50px",
48
- paddingBottom: "pb-[3px]",
49
51
  endContent: label
50
52
  ? "ltr:top-[48px] rtl:top-[54px]-translate-y-1/2"
51
53
  : "top-[26px] -translate-y-1/2",
@@ -57,7 +59,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
57
59
  : measurement == "md"
58
60
  ? {
59
61
  height: "44px",
60
- paddingBottom: "pb-[2px]",
61
62
  endContent: label
62
63
  ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
63
64
  : "top-[22px] -translate-y-1/2",
@@ -68,7 +69,6 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
68
69
  }
69
70
  : {
70
71
  height: "40px",
71
- paddingBottom: "pb-[2px]",
72
72
  endContent: label
73
73
  ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
74
74
  : "top-[20px] -translate-y-1/2",
@@ -79,8 +79,16 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
79
79
  },
80
80
  [measurement, label]
81
81
  );
82
+ const readOnlyStyle = readOnly && "opacity-40";
83
+
82
84
  return (
83
- <div className={cn(parentClassName, "flex w-full flex-col justify-end")}>
85
+ <div
86
+ className={cn(
87
+ rootDivClassName,
88
+ "flex w-full flex-col justify-end",
89
+ readOnlyStyle
90
+ )}
91
+ >
84
92
  <div
85
93
  className={cn(
86
94
  "relative text-start select-none h-fit rtl:text-lg-rtl ltr:text-lg-ltr"
@@ -145,20 +153,19 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
145
153
  height: heightStyle.height,
146
154
  }}
147
155
  className={cn(
148
- "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex w-full min-w-0 rounded border bg-transparent px-3 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
156
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 flex w-full min-w-0 rounded-sm border px-3 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-70",
149
157
  "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
150
- "appearance-none placeholder:text-primary/60 ltr:text-sm rtl:text-sm rtl:font-semibold focus-visible:ring-0 focus-visible:shadow-sm focus-visible:ring-offset-0 transition-[border] bg-card dark:bg-black/30",
158
+ "appearance-none placeholder:text-primary/60 ltr:text-sm rtl:text-sm rtl:font-semibold focus-visible:ring-0 focus-visible:shadow-sm focus-visible:ring-offset-0 transition-[border] bg-card",
151
159
  "focus-visible:border-tertiary/60",
152
160
  "[&::-webkit-outer-spin-button]:appearance-none",
153
161
  "[&::-webkit-inner-spin-button]:appearance-none",
154
162
  "[-moz-appearance:textfield] ",
155
163
  inputPaddingClass,
156
- hasError ? "border-red-400 border" : "border-primary/25",
157
- readOnly && "cursor-not-allowed",
158
- heightStyle.paddingBottom,
164
+ hasError && "border-red-400",
159
165
  className
160
166
  )}
161
167
  {...rest}
168
+ disabled={readOnly}
162
169
  />
163
170
  </div>
164
171
 
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import DateObject from "react-date-object";
4
+
5
+ import MultiDatePicker, { MultiDatePickerProps } from "./multi-date-picker";
6
+
7
+ // Persian
8
+ import persian from "react-date-object/calendars/persian";
9
+ import persian_fa from "react-date-object/locales/persian_fa";
10
+
11
+ export default {
12
+ title: "Date/MultiDatePicker",
13
+ component: MultiDatePicker,
14
+ parameters: {
15
+ layout: "centered",
16
+ },
17
+ } as Meta<MultiDatePickerProps>;
18
+
19
+ /* ------------------------ WRAPPER ------------------------ */
20
+ const Wrapper = (args: MultiDatePickerProps) => {
21
+ const [dates, setDates] = React.useState<DateObject[]>(args.value);
22
+
23
+ return (
24
+ <div style={{ width: 360 }}>
25
+ <MultiDatePicker
26
+ {...args}
27
+ value={dates}
28
+ dateOnComplete={(selectedDates) => {
29
+ setDates(selectedDates);
30
+ }}
31
+ />
32
+ </div>
33
+ );
34
+ };
35
+
36
+ /* ------------------------ DEFAULT ------------------------ */
37
+ export const Default: StoryObj<MultiDatePickerProps> = {
38
+ render: (args) => <Wrapper {...args} />,
39
+ args: {
40
+ text: { label: "Select dates", placeholder: "Select dates..." },
41
+ measurement: "md",
42
+ value: [],
43
+ },
44
+ };
45
+
46
+ /* ------------------------ PERSIAN ------------------------ */
47
+ export const Persian: StoryObj<MultiDatePickerProps> = {
48
+ render: (args) => <Wrapper {...args} />,
49
+ args: {
50
+ text: { label: "تاریخ‌ها", placeholder: "تاریخ‌ها را انتخاب کنید" },
51
+ measurement: "md",
52
+ value: [],
53
+ getLocalizations: () => ({
54
+ calendar: persian,
55
+ locale: persian_fa,
56
+ months: [
57
+ "فروردین",
58
+ "اردیبهشت",
59
+ "خرداد",
60
+ "تیر",
61
+ "مرداد",
62
+ "شهریور",
63
+ "مهر",
64
+ "آبان",
65
+ "آذر",
66
+ "دی",
67
+ "بهمن",
68
+ "اسفند",
69
+ ],
70
+ }),
71
+ },
72
+ };
73
+
74
+ /* ------------------------ WITH ERROR ------------------------ */
75
+ export const WithError: StoryObj<MultiDatePickerProps> = {
76
+ render: (args) => <Wrapper {...args} />,
77
+ args: {
78
+ text: { label: "Dates", requiredHint: "*" },
79
+ value: [],
80
+ measurement: "md",
81
+ errorMessage: "Please select at least one date",
82
+ },
83
+ };
84
+
85
+ /* ------------------------ READONLY ------------------------ */
86
+ export const ReadOnly: StoryObj<MultiDatePickerProps> = {
87
+ render: (args) => <Wrapper {...args} />,
88
+ args: {
89
+ text: { label: "Dates" },
90
+ value: [new DateObject(), new DateObject()],
91
+ readOnly: true,
92
+ measurement: "md",
93
+ },
94
+ };