notionsoft-ui 1.0.34 → 1.0.35

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.
Files changed (33) hide show
  1. package/.storybook/main.ts +19 -13
  2. package/.storybook/preview.css +0 -16
  3. package/package.json +7 -2
  4. package/src/notion-ui/animated-item/animated-item.tsx +1 -1
  5. package/src/notion-ui/animated-item/index.ts +1 -1
  6. package/src/notion-ui/button/Button.stories.tsx +31 -8
  7. package/src/notion-ui/button/button.tsx +10 -2
  8. package/src/notion-ui/button-spinner/ButtonSpinner.stories.tsx +42 -34
  9. package/src/notion-ui/button-spinner/button-spinner.tsx +4 -5
  10. package/src/notion-ui/cache-svg/CachedSvg.stories.tsx +74 -0
  11. package/src/notion-ui/cache-svg/cached-svg.tsx +150 -0
  12. package/src/notion-ui/cache-svg/index.ts +3 -0
  13. package/src/notion-ui/cache-svg/utils.ts +7 -0
  14. package/src/notion-ui/cached-image/cached-image.stories.tsx +109 -0
  15. package/src/notion-ui/cached-image/cached-image.tsx +213 -0
  16. package/src/notion-ui/cached-image/index.ts +3 -0
  17. package/src/notion-ui/cached-image/utils.ts +7 -0
  18. package/src/notion-ui/date-picker/DatePicker.stories.tsx +0 -2
  19. package/src/notion-ui/date-picker/date-picker.tsx +5 -5
  20. package/src/notion-ui/input/Input.stories.tsx +1 -1
  21. package/src/notion-ui/input/input.tsx +5 -4
  22. package/src/notion-ui/multi-date-picker/multi-date-picker.tsx +5 -5
  23. package/src/notion-ui/page-size-select/page-size-select.tsx +29 -12
  24. package/src/notion-ui/password-input/password-input.tsx +3 -3
  25. package/src/notion-ui/phone-input/phone-input.tsx +38 -8
  26. package/src/notion-ui/shimmer/shimmer.tsx +9 -3
  27. package/src/notion-ui/shining-text/shining-text.tsx +2 -6
  28. package/src/notion-ui/sidebar/index.ts +3 -0
  29. package/src/notion-ui/sidebar/sidebar-item.tsx +198 -0
  30. package/src/notion-ui/sidebar/sidebar.stories.tsx +181 -0
  31. package/src/notion-ui/sidebar/sidebar.tsx +284 -0
  32. package/src/notion-ui/textarea/Textarea.stories.tsx +1 -1
  33. package/src/notion-ui/textarea/textarea.tsx +3 -3
@@ -0,0 +1,109 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import CachedImage, { ImageProps } from "./cached-image";
3
+
4
+ /* ---------------------------------- */
5
+ /* Helpers */
6
+ /* ---------------------------------- */
7
+
8
+ // Basic fetch wrapper
9
+ const fetchImage = async (src: string): Promise<Response> => {
10
+ return fetch(src);
11
+ };
12
+
13
+ // Delayed fetch to show shimmer/loading
14
+ const delayedFetch = async (src: string): Promise<Response> => {
15
+ await new Promise((resolve) => setTimeout(resolve, 2000));
16
+ return fetch(src);
17
+ };
18
+
19
+ /* ---------------------------------- */
20
+ /* Meta */
21
+ /* ---------------------------------- */
22
+
23
+ const meta: Meta<ImageProps> = {
24
+ title: "Media/CachedImage",
25
+ component: CachedImage,
26
+ parameters: {
27
+ layout: "centered",
28
+ },
29
+ argTypes: {
30
+ className: {
31
+ control: "text",
32
+ },
33
+ classNames: {
34
+ control: "object",
35
+ },
36
+ fetch: {
37
+ table: { disable: true },
38
+ },
39
+ apiConfig: {
40
+ table: { disable: true },
41
+ },
42
+ },
43
+ };
44
+
45
+ export default meta;
46
+ type Story = StoryObj<ImageProps>;
47
+
48
+ /* ---------------------------------- */
49
+ /* Stories */
50
+ /* ---------------------------------- */
51
+
52
+ // Default (fetch + cache)
53
+ export const Default: Story = {
54
+ args: {
55
+ src: "/images/sample.jpg",
56
+ fetch: fetchImage,
57
+ className: "w-40 h-40 rounded-md",
58
+ },
59
+ };
60
+
61
+ // API config variant
62
+ export const WithApiConfig: Story = {
63
+ args: {
64
+ apiConfig: {
65
+ src: "/images/sample.jpg",
66
+ },
67
+ className: "w-40 h-40 rounded-lg",
68
+ },
69
+ };
70
+
71
+ // Cross-origin image (bypasses cache)
72
+ export const CrossOrigin: Story = {
73
+ args: {
74
+ src: "https://picsum.photos/200",
75
+ fetch: fetchImage,
76
+ className: "w-40 h-40 rounded-full",
77
+ },
78
+ };
79
+
80
+ // Loading / shimmer state
81
+ export const LoadingState: Story = {
82
+ args: {
83
+ src: "/images/sample.jpg",
84
+ fetch: delayedFetch,
85
+ className: "w-32 h-32",
86
+ classNames: {
87
+ shimmerClassName: "bg-gray-200",
88
+ shimmerIconClassName: "stroke-gray-400",
89
+ },
90
+ },
91
+ };
92
+
93
+ // Small size
94
+ export const Small: Story = {
95
+ args: {
96
+ src: "/images/sample.jpg",
97
+ fetch: fetchImage,
98
+ className: "w-16 h-16 rounded",
99
+ },
100
+ };
101
+
102
+ // Large size
103
+ export const Large: Story = {
104
+ args: {
105
+ src: "/images/sample.jpg",
106
+ fetch: fetchImage,
107
+ className: "w-64 h-64 rounded-xl",
108
+ },
109
+ };
@@ -0,0 +1,213 @@
1
+ import Shimmer from "@/components/notion-ui/shimmer";
2
+ import { cn } from "@/utils/cn";
3
+ import React, { useEffect, useState, forwardRef, useRef } from "react";
4
+
5
+ /* ---------------------------------- */
6
+ /* Types */
7
+ /* ---------------------------------- */
8
+
9
+ interface FetchConfig {
10
+ src: string;
11
+ headers?: Record<string, string>;
12
+ params?: string;
13
+ }
14
+
15
+ interface BaseImageProps extends React.HTMLAttributes<HTMLDivElement> {
16
+ classNames?: {
17
+ shimmerClassName?: string;
18
+ shimmerIconClassName?: string;
19
+ };
20
+ }
21
+
22
+ interface FetchImageProps extends BaseImageProps {
23
+ src: string;
24
+ fetch: (src: string) => Promise<Response>;
25
+ apiConfig?: never;
26
+ }
27
+
28
+ interface ApiConfigImageProps extends BaseImageProps {
29
+ apiConfig: FetchConfig;
30
+ src?: never;
31
+ fetch?: never;
32
+ }
33
+
34
+ export type ImageProps = FetchImageProps | ApiConfigImageProps;
35
+
36
+ /* ---------------------------------- */
37
+ /* Cache helpers */
38
+ /* ---------------------------------- */
39
+
40
+ const IMAGE_CACHE = "image-cache-v1";
41
+
42
+ async function getCachedImage(url: string): Promise<string | null> {
43
+ const cache = await caches.open(IMAGE_CACHE);
44
+ const cached = await cache.match(url);
45
+
46
+ if (!cached) return null;
47
+
48
+ const blob = await cached.blob();
49
+ return URL.createObjectURL(blob);
50
+ }
51
+
52
+ async function cacheImage(url: string, response: Response) {
53
+ const cache = await caches.open(IMAGE_CACHE);
54
+ await cache.put(url, response.clone());
55
+ }
56
+
57
+ /* ---------------------------------- */
58
+ /* Utils */
59
+ /* ---------------------------------- */
60
+
61
+ function isCrossOrigin(url: string): boolean {
62
+ try {
63
+ return new URL(url, window.location.href).origin !== window.location.origin;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ /* ---------------------------------- */
70
+ /* Component */
71
+ /* ---------------------------------- */
72
+
73
+ const CachedImage = forwardRef<HTMLDivElement, ImageProps>((props, ref) => {
74
+ const { className, classNames, fetch, apiConfig, src, ...imgProps } = props;
75
+
76
+ const [imageUrl, setImageUrl] = useState<string | null>(null);
77
+ const [loading, setLoading] = useState(true);
78
+
79
+ const { shimmerClassName, shimmerIconClassName } = classNames || {};
80
+
81
+ const fetchRef = useRef(fetch);
82
+ useEffect(() => {
83
+ fetchRef.current = fetch;
84
+ }, [fetch]);
85
+
86
+ async function loadImage() {
87
+ try {
88
+ const resolvedSrc = apiConfig?.src ?? src;
89
+
90
+ if (!resolvedSrc) {
91
+ setImageUrl(null);
92
+ return;
93
+ }
94
+
95
+ /* ---------------------------------- */
96
+ /* Cross-origin → use <img src> */
97
+ /* ---------------------------------- */
98
+ if (isCrossOrigin(resolvedSrc)) {
99
+ setImageUrl(resolvedSrc);
100
+ setLoading(false);
101
+ return;
102
+ }
103
+
104
+ /* ---------------------------------- */
105
+ /* Cache Storage */
106
+ /* ---------------------------------- */
107
+ const cached = await getCachedImage(resolvedSrc);
108
+ if (cached) {
109
+ setImageUrl(cached);
110
+ setLoading(false);
111
+ return;
112
+ }
113
+
114
+ /* ---------------------------------- */
115
+ /* Fetch */
116
+ /* ---------------------------------- */
117
+ const response = fetchRef.current
118
+ ? await fetchRef.current(resolvedSrc)
119
+ : await window.fetch(resolvedSrc, {
120
+ headers: apiConfig?.headers,
121
+ });
122
+
123
+ const contentType = response.headers.get("content-type") ?? "";
124
+
125
+ if (
126
+ !response.ok ||
127
+ (!contentType.startsWith("image/") &&
128
+ contentType !== "application/octet-stream")
129
+ ) {
130
+ throw new Error(`Invalid image response: ${contentType}`);
131
+ }
132
+
133
+ await cacheImage(resolvedSrc, response.clone());
134
+
135
+ const blob = await response.blob();
136
+ setImageUrl(URL.createObjectURL(blob));
137
+ } catch (err) {
138
+ console.error(err);
139
+ } finally {
140
+ setLoading(false);
141
+ }
142
+ }
143
+
144
+ useEffect(() => {
145
+ loadImage();
146
+ }, [src, apiConfig?.src]);
147
+
148
+ /* ---------------------------------- */
149
+ /* Cleanup */
150
+ /* ---------------------------------- */
151
+
152
+ useEffect(() => {
153
+ return () => {
154
+ if (imageUrl?.startsWith("blob:")) {
155
+ URL.revokeObjectURL(imageUrl);
156
+ }
157
+ };
158
+ }, [imageUrl]);
159
+
160
+ /* ---------------------------------- */
161
+ /* UI */
162
+ /* ---------------------------------- */
163
+
164
+ if (loading || !imageUrl) {
165
+ const stop = loading ? false : !imageUrl && true;
166
+
167
+ return (
168
+ <Shimmer
169
+ className={cn(
170
+ "bg-primary/10 mx-auto flex p-2 items-center size-8 rounded border border-tertiary/10",
171
+ shimmerClassName
172
+ )}
173
+ stop={stop}
174
+ >
175
+ <svg
176
+ xmlns="http://www.w3.org/2000/svg"
177
+ width="24"
178
+ height="24"
179
+ viewBox="0 0 24 24"
180
+ fill="none"
181
+ strokeLinecap="round"
182
+ strokeLinejoin="round"
183
+ className={cn(
184
+ "stroke-primary/40 mx-auto stroke-2",
185
+ shimmerIconClassName
186
+ )}
187
+ >
188
+ <rect x="1" y="1" width="22" height="22" rx="2" ry="2" />
189
+ <polyline points="3,20 8,13 13,17 17,12 21,16" />
190
+ <circle cx="16" cy="6" r="2" />
191
+ </svg>
192
+ </Shimmer>
193
+ );
194
+ }
195
+
196
+ return (
197
+ <div
198
+ ref={ref}
199
+ // src={imageUrl}
200
+ style={{ backgroundImage: `url(${imageUrl})` }}
201
+ // alt={alt}
202
+ className={cn(
203
+ "cursor-pointer shadow-lg bg-cover bg-center mx-auto",
204
+ className
205
+ )}
206
+ {...imgProps}
207
+ />
208
+ );
209
+ });
210
+
211
+ CachedImage.displayName = "CachedImage";
212
+
213
+ export default CachedImage;
@@ -0,0 +1,3 @@
1
+ import CachedImage from "./cached-image";
2
+
3
+ export default CachedImage;
@@ -0,0 +1,7 @@
1
+ export function isCrossOrigin(url: string): boolean {
2
+ try {
3
+ return new URL(url, window.location.href).origin !== window.location.origin;
4
+ } catch {
5
+ return false;
6
+ }
7
+ }
@@ -42,7 +42,6 @@ export const Default: StoryObj<DatePickerProps> = {
42
42
  render: (args) => <Wrapper {...args} />,
43
43
  args: {
44
44
  placeholder: "Select date...",
45
- required: false,
46
45
  label: "Date",
47
46
  measurement: "md",
48
47
  },
@@ -83,7 +82,6 @@ export const WithError: StoryObj<DatePickerProps> = {
83
82
  placeholder: "Select date...",
84
83
  label: "Birthday",
85
84
  errorMessage: "This field is required",
86
- required: true,
87
85
  requiredHint: "*",
88
86
  },
89
87
  };
@@ -212,7 +212,7 @@ export default function DatePicker(props: DatePickerProps) {
212
212
  <label
213
213
  htmlFor={label}
214
214
  className={cn(
215
- "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
215
+ "font-semibold ltr:text-[13px] rtl:text-[18px] inline-block pb-1"
216
216
  )}
217
217
  >
218
218
  {label}
@@ -224,18 +224,18 @@ export default function DatePicker(props: DatePickerProps) {
224
224
  height: heightStyle.height,
225
225
  }}
226
226
  className={cn(
227
- "flex items-center text-start px-3 border select-none rounded-sm rtl:text-lg-rtl ltr:text-lg-ltr",
227
+ "flex items-center text-start px-3 border select-none rounded-sm rtl:text-[17px] ltr:text-[13px]",
228
228
  className
229
229
  )}
230
230
  onClick={onVisibilityChange}
231
231
  >
232
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">
233
+ <h1 className="flex items-center gap-x-2 text-ellipsis rtl:text-[17px] ltr:text-[13px] text-primary/80 whitespace-nowrap overflow-hidden">
234
234
  <CalendarDays className="size-4 inline-block text-tertiary rtl:ml-2 rtl:mr-2" />
235
235
  {formatHijriDate(selectedDates)}
236
236
  </h1>
237
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">
238
+ <h1 className="flex items-center gap-x-2 text-ellipsis rtl:text-[17px] ltr:text-[13px] font-semibold text-primary whitespace-nowrap overflow-hidden">
239
239
  <CalendarDays className="size-4 inline-block text-tertiary" />
240
240
  {placeholder}
241
241
  </h1>
@@ -261,7 +261,7 @@ export default function DatePicker(props: DatePickerProps) {
261
261
  }}
262
262
  intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
263
263
  >
264
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
264
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
265
265
  {errorMessage}
266
266
  </h1>
267
267
  </AnimatedItem>
@@ -1,5 +1,5 @@
1
+ import Input from "./input";
1
2
  import type { Meta, StoryObj } from "@storybook/react";
2
- import { Input } from "./input";
3
3
  import { Search, AlertTriangle } from "lucide-react";
4
4
 
5
5
  const meta: Meta<typeof Input> = {
@@ -19,7 +19,7 @@ export interface InputProps
19
19
  measurement?: NastranInputSize;
20
20
  }
21
21
 
22
- export const Input = React.forwardRef<HTMLInputElement, InputProps>(
22
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
23
23
  (
24
24
  {
25
25
  className,
@@ -86,12 +86,13 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
86
86
  className={cn(
87
87
  rootDivClassName,
88
88
  "flex w-full flex-col justify-end",
89
+ requiredHint && !label && "mt-5",
89
90
  readOnlyStyle
90
91
  )}
91
92
  >
92
93
  <div
93
94
  className={cn(
94
- "relative text-start select-none h-fit rtl:text-lg-rtl ltr:text-lg-ltr"
95
+ "relative text-start select-none h-fit ltr:text-[13px] rtl:text-[18px]"
95
96
  )}
96
97
  >
97
98
  {/* Start Content */}
@@ -135,7 +136,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
135
136
  <label
136
137
  htmlFor={label}
137
138
  className={cn(
138
- "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
139
+ "font-semibold ltr:text-[13px] rtl:text-[18px] inline-block pb-1"
139
140
  )}
140
141
  >
141
142
  {label}
@@ -189,7 +190,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
189
190
  }}
190
191
  intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
191
192
  >
192
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
193
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
193
194
  {errorMessage}
194
195
  </h1>
195
196
  </AnimatedItem>
@@ -195,7 +195,7 @@ export default function MultiDatePicker(props: MultiDatePickerProps) {
195
195
  <label
196
196
  htmlFor={label}
197
197
  className={cn(
198
- "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
198
+ "font-semibold ltr:text-[13px] rtl:text-[18px] inline-block pb-1"
199
199
  )}
200
200
  >
201
201
  {label}
@@ -206,13 +206,13 @@ export default function MultiDatePicker(props: MultiDatePickerProps) {
206
206
  height: heightStyle.height,
207
207
  }}
208
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",
209
+ "relative flex items-center text-start px-3 border select-none rounded-sm rtl:text-[17px] ltr:text-[13px]",
210
210
  className
211
211
  )}
212
212
  onClick={onVisibilityChange}
213
213
  >
214
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">
215
+ <div className="flex items-center gap-x-2 text-ellipsis rtl:text-[17px] ltr:text-[13px] text-primary/80 text-nowrap">
216
216
  <CalendarDays className="size-4 inline-block text-tertiary rtl:ml-2 rtl:mr-2" />
217
217
  {selectedDates.map((date: DateObject, index: number) => (
218
218
  <div key={index} className="flex gap-x-2">
@@ -229,7 +229,7 @@ export default function MultiDatePicker(props: MultiDatePickerProps) {
229
229
  ))}
230
230
  </div>
231
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">
232
+ <h1 className="flex items-center gap-x-2 text-ellipsis rtl:text-[17px] ltr:text-[13px] text-primary/80 text-nowrap">
233
233
  <CalendarDays className="size-4 inline-block text-tertiary" />
234
234
  {placeholder}
235
235
  </h1>
@@ -255,7 +255,7 @@ export default function MultiDatePicker(props: MultiDatePickerProps) {
255
255
  }}
256
256
  intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
257
257
  >
258
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
258
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
259
259
  {errorMessage}
260
260
  </h1>
261
261
  </AnimatedItem>
@@ -107,27 +107,44 @@ const PageSizeSelect: React.FC<SelectProps> = ({
107
107
 
108
108
  const rect = trigger.getBoundingClientRect();
109
109
  const viewportHeight = window.innerHeight;
110
+ const viewportWidth = window.innerWidth;
110
111
  const gap = 6;
111
112
 
112
113
  const dropdownHeight = Math.min(dropdown.offsetHeight || 0, 260);
114
+ const dropdownWidth = dropdown.offsetWidth || rect.width;
115
+
113
116
  const spaceBelow = viewportHeight - rect.bottom;
114
117
  const spaceAbove = rect.top;
115
118
 
119
+ const spaceRight = viewportWidth - rect.left;
120
+ const spaceLeft = rect.right;
121
+
122
+ /* ---------- Vertical (Up / Down) ---------- */
123
+ let top: number;
116
124
  if (spaceBelow < dropdownHeight && spaceAbove > spaceBelow) {
117
125
  setDropDirection("up");
118
- setPosition({
119
- top: rect.top + window.scrollY - dropdownHeight - gap,
120
- left: rect.left + window.scrollX,
121
- width: rect.width,
122
- });
126
+ top = rect.top + window.scrollY - dropdownHeight - gap;
123
127
  } else {
124
128
  setDropDirection("down");
125
- setPosition({
126
- top: rect.bottom + window.scrollY + gap,
127
- left: rect.left + window.scrollX,
128
- width: rect.width,
129
- });
129
+ top = rect.bottom + window.scrollY + gap;
130
130
  }
131
+
132
+ /* ---------- Horizontal (Left / Right) ---------- */
133
+ let left = rect.left + window.scrollX;
134
+
135
+ // If dropdown overflows right viewport → shift left
136
+ if (spaceRight < dropdownWidth && spaceLeft >= dropdownWidth) {
137
+ left = rect.right + window.scrollX - dropdownWidth;
138
+ }
139
+
140
+ // Clamp to viewport (safety)
141
+ left = Math.max(8, Math.min(left, viewportWidth - dropdownWidth - 8));
142
+
143
+ setPosition({
144
+ top,
145
+ left,
146
+ width: rect.width,
147
+ });
131
148
  };
132
149
 
133
150
  useLayoutEffect(() => {
@@ -171,7 +188,7 @@ const PageSizeSelect: React.FC<SelectProps> = ({
171
188
  <div ref={selectRef} className={cn("w-full", className)}>
172
189
  <button
173
190
  onClick={() => setSelectData((p) => ({ ...p, isOpen: !p.isOpen }))}
174
- className="w-full py-2 border rounded-md flex items-center justify-between bg-card"
191
+ className="w-full px-3 py-2 border rounded-md flex items-center justify-between bg-card"
175
192
  >
176
193
  {selectData.select.value || placeholder}
177
194
  <ChevronDown
@@ -209,7 +226,7 @@ const PageSizeSelect: React.FC<SelectProps> = ({
209
226
  ? selectData.select.value
210
227
  : ""
211
228
  }
212
- className={`bg-card dark:bg-card-secondary text-tertiary rtl:text-lg-rtl w-full [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none text-center text-sm px-4 py-2 border-b border-primary/15 rounded-t-md focus:outline-none`}
229
+ className={`bg-card dark:bg-card-secondary text-tertiary rtl:text-[17px] w-full [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none text-center text-sm px-4 py-2 border-b border-primary/15 rounded-t-md focus:outline-none`}
213
230
  />
214
231
  <Check
215
232
  className={cn(
@@ -93,7 +93,7 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
93
93
  {/* Password strength text */}
94
94
  <p
95
95
  id="password-strength"
96
- className="mb-2 text-start rtl:text-xl-rtl ltr:text-xl-ltr font-medium text-foreground"
96
+ className="mb-2 text-start rtl:text-lg ltr:text-sm font-medium text-foreground"
97
97
  >
98
98
  {`${getStrengthText(strengthScore)}. ${text.must_contain}`}
99
99
  </p>
@@ -108,9 +108,9 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
108
108
  <X size={16} className="text-muted-foreground/80" />
109
109
  )}
110
110
  <span
111
- className={`ltr:text-xs rtl:text-lg-rtl ${
111
+ className={`ltr:text-xs rtl:text-[17px] ${
112
112
  req.met
113
- ? "text-emerald-600 ltr:text-xl-ltr"
113
+ ? "text-emerald-600 ltr:text-sm"
114
114
  : "text-muted-foreground"
115
115
  }`}
116
116
  >