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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notionsoft-ui",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
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,273 @@
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
+ required?: boolean;
29
+ format?: string;
30
+ requiredHint?: string;
31
+ hintColor?: string;
32
+ label?: string;
33
+ errorMessage?: string;
34
+ readOnly?: boolean;
35
+ measurement?: DatePickerSize;
36
+ }
37
+
38
+ export default function DatePicker(props: DatePickerProps) {
39
+ const {
40
+ dateOnComplete,
41
+ value,
42
+ className,
43
+ classNames,
44
+ placeholder,
45
+ required,
46
+ requiredHint,
47
+ measurement,
48
+ label,
49
+ errorMessage,
50
+ readOnly,
51
+ format = "YYYY-MM-DD",
52
+ getLocalizations,
53
+ } = props;
54
+ const { rootDivClassName } = classNames || {};
55
+
56
+ const [visible, setVisible] = useState(false);
57
+ const [selectedDates, setSelectedDates] = useState<DateObject | undefined>(
58
+ typeof value === "string" ? new DateObject(new Date(value)) : value
59
+ );
60
+
61
+ const calendarWrapperRef = useRef<HTMLDivElement | null>(null);
62
+ const calenderParentRef = useRef<HTMLDivElement | null>(null);
63
+ const hasError = !!errorMessage;
64
+
65
+ const [position, setPosition] = useState<{
66
+ top: number;
67
+ left: number;
68
+ } | null>(null);
69
+
70
+ // Close calendar on outside clicks
71
+ useEffect(() => {
72
+ const handleClickOutside = (event: MouseEvent) => {
73
+ if (
74
+ calendarWrapperRef.current &&
75
+ !calendarWrapperRef.current.contains(event.target as Node) &&
76
+ calenderParentRef.current &&
77
+ !calenderParentRef.current.contains(event.target as Node)
78
+ ) {
79
+ setVisible(false);
80
+ }
81
+ };
82
+
83
+ document.addEventListener("mousedown", handleClickOutside);
84
+ return () => document.removeEventListener("mousedown", handleClickOutside);
85
+ }, []);
86
+
87
+ // Update position when calendar becomes visible
88
+ useEffect(() => {
89
+ if (visible && calenderParentRef.current) {
90
+ const rect = calenderParentRef.current.getBoundingClientRect();
91
+ setPosition({
92
+ top: rect.bottom + window.scrollY,
93
+ left: rect.left + window.scrollX,
94
+ });
95
+ }
96
+ }, [visible]);
97
+
98
+ const formatHijriDate = (date?: DateObject) => {
99
+ try {
100
+ if (date) {
101
+ return date
102
+ .convert(localizations.calendar, localizations.locale)
103
+ .format(format);
104
+ }
105
+ } catch (e: any) {
106
+ console.log(e, "DatePicker");
107
+ }
108
+ return undefined;
109
+ };
110
+
111
+ const handleDateChange = (date: DateObject) => {
112
+ setVisible(false);
113
+ const failed = dateOnComplete(date);
114
+ if (failed) return;
115
+ setSelectedDates(date);
116
+ };
117
+
118
+ const onVisibilityChange = () => {
119
+ if (!readOnly) setVisible((v) => !v);
120
+ };
121
+
122
+ const localizations = getLocalizations
123
+ ? getLocalizations()
124
+ : {
125
+ calendar: gregorian,
126
+ locale: gregorian_en,
127
+ months: [],
128
+ };
129
+ const heightStyle = useMemo(
130
+ () =>
131
+ measurement == "lg"
132
+ ? {
133
+ height: "50px",
134
+ paddingBottom: "pb-[3px]",
135
+ endContent: label
136
+ ? "ltr:top-[48px] rtl:top-[54px]-translate-y-1/2"
137
+ : "top-[26px] -translate-y-1/2",
138
+ startContent: label
139
+ ? "ltr:top-[48px] rtl:top-[54px] -translate-y-1/2"
140
+ : "top-[26px] -translate-y-1/2",
141
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
142
+ }
143
+ : measurement == "md"
144
+ ? {
145
+ height: "44px",
146
+ paddingBottom: "pb-[2px]",
147
+ endContent: label
148
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
149
+ : "top-[22px] -translate-y-1/2",
150
+ startContent: label
151
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
152
+ : "top-[22px] -translate-y-1/2",
153
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
154
+ }
155
+ : {
156
+ height: "40px",
157
+ paddingBottom: "pb-[2px]",
158
+ endContent: label
159
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
160
+ : "top-[20px] -translate-y-1/2",
161
+ startContent: label
162
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
163
+ : "top-[20px] -translate-y-1/2",
164
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
165
+ },
166
+ [measurement, label]
167
+ );
168
+ const readOnlyStyle = readOnly && "opacity-40";
169
+ return (
170
+ <div
171
+ ref={calenderParentRef}
172
+ className={cn("relative", rootDivClassName, readOnlyStyle)}
173
+ >
174
+ {/* Calendar portal */}
175
+ {visible &&
176
+ position &&
177
+ createPortal(
178
+ <div
179
+ ref={calendarWrapperRef}
180
+ style={{
181
+ position: "absolute",
182
+ top: position.top,
183
+ left: position.left,
184
+ zIndex: 9999,
185
+ backgroundColor: "white",
186
+ boxShadow: "0 4px 10px rgba(0,0,0,0.15)",
187
+ borderRadius: "6px",
188
+ }}
189
+ >
190
+ <Calendars
191
+ value={selectedDates}
192
+ onChange={handleDateChange}
193
+ months={localizations.months}
194
+ calendar={localizations.calendar}
195
+ locale={localizations.locale}
196
+ />
197
+ </div>,
198
+ document.body
199
+ )}
200
+ {/* Required Hint */}
201
+ {requiredHint && (
202
+ <span
203
+ className={cn(
204
+ "absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-2.5 rtl:left-2.5",
205
+ heightStyle.required
206
+ )}
207
+ >
208
+ {requiredHint}
209
+ </span>
210
+ )}
211
+
212
+ {/* Label */}
213
+ {label && (
214
+ <label
215
+ htmlFor={label}
216
+ className={cn(
217
+ "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
218
+ )}
219
+ >
220
+ {label}
221
+ </label>
222
+ )}
223
+ {/* Input / trigger div */}
224
+ <div
225
+ style={{
226
+ height: heightStyle.height,
227
+ }}
228
+ className={cn(
229
+ "flex items-center text-start px-3 border select-none rounded-sm rtl:text-lg-rtl ltr:text-lg-ltr",
230
+ className
231
+ )}
232
+ onClick={onVisibilityChange}
233
+ >
234
+ {selectedDates ? (
235
+ <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">
236
+ <CalendarDays className="size-4 inline-block text-tertiary rtl:ml-2 rtl:mr-2" />
237
+ {formatHijriDate(selectedDates)}
238
+ </h1>
239
+ ) : (
240
+ <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">
241
+ <CalendarDays className="size-4 inline-block text-tertiary" />
242
+ {placeholder}
243
+ </h1>
244
+ )}
245
+ </div>
246
+
247
+ {hasError && (
248
+ <AnimatedItem
249
+ springProps={{
250
+ from: {
251
+ opacity: 0,
252
+ transform: "translateY(-8px)",
253
+ },
254
+ config: {
255
+ mass: 1,
256
+ tension: 210,
257
+ friction: 20,
258
+ },
259
+ to: {
260
+ opacity: 1,
261
+ transform: "translateY(0px)",
262
+ },
263
+ }}
264
+ intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
265
+ >
266
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
267
+ {errorMessage}
268
+ </h1>
269
+ </AnimatedItem>
270
+ )}
271
+ </div>
272
+ );
273
+ }
@@ -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
+ };
@@ -0,0 +1,3 @@
1
+ import MultiDatePicker from "./multi-date-picker";
2
+
3
+ export default MultiDatePicker;