notionsoft-ui 1.0.32 → 1.0.33

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.32",
3
+ "version": "1.0.33",
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"
@@ -410,7 +410,7 @@ function MultiSelectInputInner<T = any>(
410
410
  setShowSelectedOnly(true); // Show only selected items
411
411
  updatePosition(); // Recalculate dropdown position
412
412
  }}
413
- className="flex items-center hover:bg-tertiary/10 hover:text-tertiary cursor-pointer text-primary/60 rounded transition-colors"
413
+ className="flex items-center pointer-events-auto hover:bg-tertiary/10 hover:text-tertiary cursor-pointer text-primary/60 rounded transition-colors"
414
414
  >
415
415
  <List className="size-[38px] p-3" />
416
416
  <span className="text-sm px-1">{selectedItems.length}</span>
@@ -555,7 +555,7 @@ const Dropdown = <T,>(
555
555
  "focus-visible:border-tertiary/60",
556
556
  "[&::-webkit-outer-spin-button]:appearance-none",
557
557
  "[&::-webkit-inner-spin-button]:appearance-none",
558
- "[-moz-appearance:textfield] "
558
+ "[-moz-appearance:textfield]"
559
559
  )}
560
560
  placeholder={text.maxRecord}
561
561
  />
@@ -102,26 +102,55 @@ const MultiTabInput = React.forwardRef<HTMLInputElement, MultiTabInputProps>(
102
102
  tabs.map((tab, idx) => {
103
103
  const tabName = tab.props.children;
104
104
  const state: TabState = getTabState(tabName, optional);
105
+ const tabHasError = hasError(tabName);
105
106
 
106
107
  return React.cloneElement(tab, {
107
108
  key: `${optional ? "opt" : "mand"}-${idx}`,
108
109
  state,
109
110
  optional,
110
111
  onClick: () => handleTabChange(tabName, optional),
111
- className: tab.props.className,
112
+ className: cn(
113
+ tab.props.className,
114
+ tabHasError && "text-red-400 border-red-400"
115
+ ),
112
116
  });
113
117
  });
114
118
 
119
+ const hasError = (tabKey: string) => {
120
+ if (!errorData) return false;
121
+ return errorData.has(generateUniqueName(name, tabKey));
122
+ };
115
123
  const activeTabName = generateUniqueName(name, tabState.active);
116
124
  const selectTabValue = tabData[activeTabName] || "";
117
- const errorMessages = errorData?.get(activeTabName)
118
- ? [errorData.get(activeTabName)!]
119
- : [];
120
125
 
121
126
  const direction =
122
127
  activeTabName.endsWith("farsi") || activeTabName.endsWith("pashto")
123
128
  ? "rtl"
124
129
  : "ltr";
130
+
131
+ const errorMessage = useMemo(() => {
132
+ if (!errorData) return null;
133
+
134
+ return Array.from(errorData.entries())
135
+ .filter(([key]) => key.startsWith(`${name}_`))
136
+ .map(([key, value], index) => (
137
+ <AnimatedItem
138
+ key={key}
139
+ springProps={{
140
+ from: { opacity: 0, transform: "translateY(-8px)" },
141
+ to: { opacity: 1, transform: "translateY(0px)" },
142
+ delay: index * 100,
143
+ config: { mass: 1, tension: 210, friction: 20 },
144
+ }}
145
+ intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
146
+ >
147
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
148
+ {value}
149
+ </h1>
150
+ </AnimatedItem>
151
+ ));
152
+ }, [errorData, name]);
153
+
125
154
  return (
126
155
  <div className={cn("flex flex-col select-none", rootDivClassName)}>
127
156
  <div className="flex flex-col-reverse sm:flex-row sm:justify-between sm:items-end gap-4">
@@ -157,31 +186,15 @@ const MultiTabInput = React.forwardRef<HTMLInputElement, MultiTabInputProps>(
157
186
  placeholder={placeholder}
158
187
  onChange={handleInputChange}
159
188
  className={cn(
160
- `mt-2 ${errorMessages.length > 0 && "border-red-400 border-b!"}`,
189
+ `mt-2 ${
190
+ errorMessage &&
191
+ errorMessage.length > 0 &&
192
+ "border-red-400 border-b!"
193
+ }`,
161
194
  className
162
195
  )}
163
196
  />
164
-
165
- {errorMessages.map((error: string, index) => (
166
- <AnimatedItem
167
- key={index}
168
- springProps={{
169
- from: { opacity: 0, transform: "translateY(-8px)" },
170
- to: {
171
- opacity: 1,
172
- transform: "translateY(0px)",
173
- delay: index * 100,
174
- },
175
- config: { mass: 1, tension: 210, friction: 20 },
176
- delay: index * 100,
177
- }}
178
- intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
179
- >
180
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
181
- {error}
182
- </h1>
183
- </AnimatedItem>
184
- ))}
197
+ {errorMessage}
185
198
  </div>
186
199
  );
187
200
  }
@@ -101,26 +101,53 @@ const MultiTabTextarea = React.forwardRef<
101
101
  tabs.map((tab, idx) => {
102
102
  const tabName = tab.props.children;
103
103
  const state: TabState = getTabState(tabName, optional);
104
+ const tabHasError = hasError(tabName);
104
105
 
105
106
  return React.cloneElement(tab, {
106
107
  key: `${optional ? "opt" : "mand"}-${idx}`,
107
108
  state,
108
109
  optional,
109
110
  onClick: () => handleTabChange(tabName, optional),
110
- className: tab.props.className,
111
+ className: cn(
112
+ tab.props.className,
113
+ tabHasError && "text-red-400 border-red-400"
114
+ ),
111
115
  });
112
116
  });
113
-
117
+ const hasError = (tabKey: string) => {
118
+ if (!errorData) return false;
119
+ return errorData.has(generateUniqueName(name, tabKey));
120
+ };
114
121
  const activeTabName = generateUniqueName(name, tabState.active);
115
122
  const selectTabValue = tabData[activeTabName] || "";
116
- const errorMessages = errorData?.get(activeTabName)
117
- ? [errorData.get(activeTabName)!]
118
- : [];
119
123
 
120
124
  const direction =
121
125
  activeTabName.endsWith("farsi") || activeTabName.endsWith("pashto")
122
126
  ? "rtl"
123
127
  : "ltr";
128
+ const errorMessage = useMemo(() => {
129
+ if (!errorData) return null;
130
+
131
+ return Array.from(errorData.entries())
132
+ .filter(([key]) => key.startsWith(`${name}_`))
133
+ .map(([key, value], index) => (
134
+ <AnimatedItem
135
+ key={key}
136
+ springProps={{
137
+ from: { opacity: 0, transform: "translateY(-8px)" },
138
+ to: { opacity: 1, transform: "translateY(0px)" },
139
+ delay: index * 100,
140
+ config: { mass: 1, tension: 210, friction: 20 },
141
+ }}
142
+ intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
143
+ >
144
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
145
+ {value}
146
+ </h1>
147
+ </AnimatedItem>
148
+ ));
149
+ }, [errorData, name]);
150
+
124
151
  return (
125
152
  <div className={cn("flex flex-col select-none", rootDivClassName)}>
126
153
  <div className="flex flex-col-reverse sm:flex-row sm:justify-between sm:items-end gap-4">
@@ -152,31 +179,16 @@ const MultiTabTextarea = React.forwardRef<
152
179
  placeholder={placeholder}
153
180
  onChange={handleInputChange}
154
181
  className={cn(
155
- `mt-2 ${errorMessages.length > 0 ? "border-red-400 border-b!" : ""}`,
182
+ `mt-2 ${
183
+ errorMessage &&
184
+ errorMessage.length > 0 &&
185
+ "border-red-400 border-b!"
186
+ }`,
156
187
  className
157
188
  )}
158
189
  />
159
190
 
160
- {errorMessages.map((error: string, index) => (
161
- <AnimatedItem
162
- key={index}
163
- springProps={{
164
- from: { opacity: 0, transform: "translateY(-8px)" },
165
- to: {
166
- opacity: 1,
167
- transform: "translateY(0px)",
168
- delay: index * 100,
169
- },
170
- config: { mass: 1, tension: 210, friction: 20 },
171
- delay: index * 100,
172
- }}
173
- intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
174
- >
175
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
176
- {error}
177
- </h1>
178
- </AnimatedItem>
179
- ))}
191
+ {errorMessage}
180
192
  </div>
181
193
  );
182
194
  });
@@ -0,0 +1,3 @@
1
+ import Shimmer from "./shimmer";
2
+
3
+ export default Shimmer;
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { Meta, StoryObj } from "@storybook/react";
3
+ import Shimmer, { ShimmerItem, ShimmerProps } from "./shimmer";
4
+
5
+ const meta: Meta<typeof Shimmer> = {
6
+ title: "Shimmer/Shimmer",
7
+ component: Shimmer,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof Shimmer>;
14
+
15
+ export const Default: Story = {
16
+ render: (args: ShimmerProps) => (
17
+ <div className="space-y-2 p-4 w-96">
18
+ <Shimmer {...args}>
19
+ <ShimmerItem className="w-full" />
20
+ <ShimmerItem className="w-3/4" />
21
+ <ShimmerItem className="w-1/2" />
22
+ </Shimmer>
23
+ </div>
24
+ ),
25
+ };
@@ -0,0 +1,47 @@
1
+ import { cn } from "../../utils/cn";
2
+
3
+ export interface ShimmerProps extends React.HTMLAttributes<HTMLDivElement> {}
4
+
5
+ export default function Shimmer({ className, children }: ShimmerProps) {
6
+ return (
7
+ <div
8
+ className={cn("relative w-full overflow-hidden *:rounded-sm", className)}
9
+ >
10
+ {/* Scoped CSS */}
11
+ <style>{`
12
+ @keyframes shimmer {
13
+ 0% {
14
+ background-position: -1200px 0;
15
+ }
16
+ 100% {
17
+ background-position: 1200px 0;
18
+ }
19
+ }
20
+ `}</style>
21
+
22
+ {/* Shimmer overlay */}
23
+ <div
24
+ className="absolute inset-0 pointer-events-none"
25
+ style={{
26
+ backgroundImage: `linear-gradient(
27
+ to right,
28
+ var(--from-shimmer) 10%,
29
+ var(--to-shimmer) 18%,
30
+ var(--from-shimmer) 25%
31
+ )`,
32
+ backgroundSize: "1200px 100%",
33
+ animation: "shimmer 2.2s linear infinite",
34
+ }}
35
+ />
36
+
37
+ {children}
38
+ </div>
39
+ );
40
+ }
41
+ export interface ShimmerItemProps
42
+ extends React.HTMLAttributes<HTMLDivElement> {}
43
+
44
+ export function ShimmerItem(props: ShimmerItemProps) {
45
+ const { className } = props;
46
+ return <div className={cn(`h-10 bg-primary/5`, className)} />;
47
+ }