notionsoft-ui 1.0.30 → 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.30",
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
  />
@@ -3,13 +3,7 @@ import { cn } from "../../utils/cn";
3
3
  import AnimatedItem from "../animated-item";
4
4
  import type { TabState } from "../tab/tab";
5
5
  import Input, { NastranInputSize } from "../input/input";
6
- import Tab from "../tab/tab";
7
-
8
- // OptionalTabs wrapper
9
- export function OptionalTabs({ children }: { children: React.ReactNode }) {
10
- return <>{children}</>;
11
- }
12
- OptionalTabs.displayName = "OptionalTabs";
6
+ import { OptionalTabs, Tab } from "../tab/tab";
13
7
 
14
8
  export interface MultiTabInputProps
15
9
  extends React.InputHTMLAttributes<HTMLInputElement> {
@@ -108,26 +102,55 @@ const MultiTabInput = React.forwardRef<HTMLInputElement, MultiTabInputProps>(
108
102
  tabs.map((tab, idx) => {
109
103
  const tabName = tab.props.children;
110
104
  const state: TabState = getTabState(tabName, optional);
105
+ const tabHasError = hasError(tabName);
111
106
 
112
107
  return React.cloneElement(tab, {
113
108
  key: `${optional ? "opt" : "mand"}-${idx}`,
114
109
  state,
115
110
  optional,
116
111
  onClick: () => handleTabChange(tabName, optional),
117
- className: tab.props.className,
112
+ className: cn(
113
+ tab.props.className,
114
+ tabHasError && "text-red-400 border-red-400"
115
+ ),
118
116
  });
119
117
  });
120
118
 
119
+ const hasError = (tabKey: string) => {
120
+ if (!errorData) return false;
121
+ return errorData.has(generateUniqueName(name, tabKey));
122
+ };
121
123
  const activeTabName = generateUniqueName(name, tabState.active);
122
124
  const selectTabValue = tabData[activeTabName] || "";
123
- const errorMessages = errorData?.get(activeTabName)
124
- ? [errorData.get(activeTabName)!]
125
- : [];
126
125
 
127
126
  const direction =
128
127
  activeTabName.endsWith("farsi") || activeTabName.endsWith("pashto")
129
128
  ? "rtl"
130
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
+
131
154
  return (
132
155
  <div className={cn("flex flex-col select-none", rootDivClassName)}>
133
156
  <div className="flex flex-col-reverse sm:flex-row sm:justify-between sm:items-end gap-4">
@@ -163,31 +186,15 @@ const MultiTabInput = React.forwardRef<HTMLInputElement, MultiTabInputProps>(
163
186
  placeholder={placeholder}
164
187
  onChange={handleInputChange}
165
188
  className={cn(
166
- `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
+ }`,
167
194
  className
168
195
  )}
169
196
  />
170
-
171
- {errorMessages.map((error: string, index) => (
172
- <AnimatedItem
173
- key={index}
174
- springProps={{
175
- from: { opacity: 0, transform: "translateY(-8px)" },
176
- to: {
177
- opacity: 1,
178
- transform: "translateY(0px)",
179
- delay: index * 100,
180
- },
181
- config: { mass: 1, tension: 210, friction: 20 },
182
- delay: index * 100,
183
- }}
184
- intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
185
- >
186
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
187
- {error}
188
- </h1>
189
- </AnimatedItem>
190
- ))}
197
+ {errorMessage}
191
198
  </div>
192
199
  );
193
200
  }
@@ -1,8 +1,8 @@
1
1
  import { useState } from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
 
4
- import MultiTabInput, { OptionalTabs } from "./multi-tab-input";
5
- import Tab from "../tab/tab";
4
+ import { OptionalTabs, Tab } from "../tab/tab";
5
+ import MultiTabInput from "./multi-tab-input";
6
6
 
7
7
  const meta: Meta<typeof MultiTabInput> = {
8
8
  title: "Form/MultiTabInput",
@@ -2,14 +2,7 @@ import React, { type ReactElement, useState, useMemo } from "react";
2
2
  import { cn } from "../../utils/cn";
3
3
  import Textarea from "../textarea";
4
4
  import AnimatedItem from "../animated-item";
5
- import type { TabState } from "../tab/tab";
6
- import Tab from "../tab/tab";
7
-
8
- // OptionalTabs wrapper
9
- export function OptionalTabs({ children }: { children: React.ReactNode }) {
10
- return <>{children}</>;
11
- }
12
- OptionalTabs.displayName = "OptionalTabs";
5
+ import type { OptionalTabs, Tab, TabState } from "../tab/tab";
13
6
 
14
7
  export interface MultiTabTextareaProps
15
8
  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
@@ -108,26 +101,53 @@ const MultiTabTextarea = React.forwardRef<
108
101
  tabs.map((tab, idx) => {
109
102
  const tabName = tab.props.children;
110
103
  const state: TabState = getTabState(tabName, optional);
104
+ const tabHasError = hasError(tabName);
111
105
 
112
106
  return React.cloneElement(tab, {
113
107
  key: `${optional ? "opt" : "mand"}-${idx}`,
114
108
  state,
115
109
  optional,
116
110
  onClick: () => handleTabChange(tabName, optional),
117
- className: tab.props.className,
111
+ className: cn(
112
+ tab.props.className,
113
+ tabHasError && "text-red-400 border-red-400"
114
+ ),
118
115
  });
119
116
  });
120
-
117
+ const hasError = (tabKey: string) => {
118
+ if (!errorData) return false;
119
+ return errorData.has(generateUniqueName(name, tabKey));
120
+ };
121
121
  const activeTabName = generateUniqueName(name, tabState.active);
122
122
  const selectTabValue = tabData[activeTabName] || "";
123
- const errorMessages = errorData?.get(activeTabName)
124
- ? [errorData.get(activeTabName)!]
125
- : [];
126
123
 
127
124
  const direction =
128
125
  activeTabName.endsWith("farsi") || activeTabName.endsWith("pashto")
129
126
  ? "rtl"
130
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
+
131
151
  return (
132
152
  <div className={cn("flex flex-col select-none", rootDivClassName)}>
133
153
  <div className="flex flex-col-reverse sm:flex-row sm:justify-between sm:items-end gap-4">
@@ -159,31 +179,16 @@ const MultiTabTextarea = React.forwardRef<
159
179
  placeholder={placeholder}
160
180
  onChange={handleInputChange}
161
181
  className={cn(
162
- `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
+ }`,
163
187
  className
164
188
  )}
165
189
  />
166
190
 
167
- {errorMessages.map((error: string, index) => (
168
- <AnimatedItem
169
- key={index}
170
- springProps={{
171
- from: { opacity: 0, transform: "translateY(-8px)" },
172
- to: {
173
- opacity: 1,
174
- transform: "translateY(0px)",
175
- delay: index * 100,
176
- },
177
- config: { mass: 1, tension: 210, friction: 20 },
178
- delay: index * 100,
179
- }}
180
- intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
181
- >
182
- <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-[11px]">
183
- {error}
184
- </h1>
185
- </AnimatedItem>
186
- ))}
191
+ {errorMessage}
187
192
  </div>
188
193
  );
189
194
  });
@@ -1,8 +1,8 @@
1
1
  import { useState } from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
 
4
- import MultiTabTextarea, { OptionalTabs } from "./multi-tab-textarea";
5
- import Tab from "../tab/tab";
4
+ import { OptionalTabs, Tab } from "../tab/tab";
5
+ import MultiTabTextarea from "../multi-tab-textarea";
6
6
 
7
7
  const meta: Meta<typeof MultiTabTextarea> = {
8
8
  title: "Form/MultiTabTextarea",
@@ -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
+ }
@@ -12,7 +12,7 @@ interface TabProps {
12
12
  state?: TabState; // <-- now strongly typed
13
13
  optional?: boolean;
14
14
  }
15
- export default function Tab({
15
+ export function Tab({
16
16
  children,
17
17
  className,
18
18
  onClick,
@@ -44,3 +44,8 @@ export default function Tab({
44
44
  }
45
45
 
46
46
  Tab.displayName = "Tab";
47
+
48
+ export function OptionalTabs({ children }: { children: React.ReactNode }) {
49
+ return <>{children}</>;
50
+ }
51
+ OptionalTabs.displayName = "OptionalTabs";