notionsoft-ui 1.0.6 → 1.0.8

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 (26) hide show
  1. package/cli/index.cjs +3 -3
  2. package/package.json +1 -1
  3. package/src/notion-ui/animated-item/animated-item.tsx +54 -0
  4. package/src/notion-ui/animated-item/index.ts +3 -0
  5. package/src/{templates → notion-ui}/boolean-status-button/BooleanStatusButton.stories.tsx +1 -1
  6. package/src/{templates → notion-ui}/boolean-status-button/BooleanStatusButton.tsx +1 -1
  7. package/src/notion-ui/boolean-status-button/index.ts +3 -0
  8. package/src/notion-ui/button/index.ts +3 -0
  9. package/src/{templates → notion-ui}/button-spinner/ButtonSpinner.stories.tsx +1 -1
  10. package/src/{templates → notion-ui}/button-spinner/button-spinner.tsx +1 -1
  11. package/src/notion-ui/button-spinner/index.ts +3 -0
  12. package/src/{templates → notion-ui}/circle-loader/CircleLoader.stories.tsx +1 -1
  13. package/src/{templates → notion-ui}/circle-loader/circle-loader.tsx +1 -1
  14. package/src/notion-ui/circle-loader/index.ts +3 -0
  15. package/src/notion-ui/input/Input.stories.tsx +100 -0
  16. package/src/notion-ui/input/index.ts +3 -0
  17. package/src/notion-ui/input/input.tsx +193 -0
  18. package/src/{templates → notion-ui}/sheet/AnimatedSheet.stories.tsx +1 -1
  19. package/src/{templates → notion-ui}/sheet/AnimatedSheet.tsx +1 -1
  20. package/src/notion-ui/sheet/index.ts +3 -0
  21. package/src/notion-ui/shining-text/index.ts +3 -0
  22. package/tsconfig.json +1 -0
  23. /package/src/{templates → notion-ui}/button/Button.stories.tsx +0 -0
  24. /package/src/{templates → notion-ui}/button/button.tsx +0 -0
  25. /package/src/{templates → notion-ui}/shining-text/shining-text.stories.tsx +0 -0
  26. /package/src/{templates → notion-ui}/shining-text/shining-text.tsx +0 -0
package/cli/index.cjs CHANGED
@@ -10,10 +10,10 @@ const { execSync } = require("child_process");
10
10
  Helper: get template path
11
11
  ------------------------------- */
12
12
  function getTemplateFile(component) {
13
- // Library template: src/templates/button/button.tsx
13
+ // Library template: src/notion-ui/button/button.tsx
14
14
  return path.join(
15
15
  __dirname,
16
- "../src/templates",
16
+ "../src/notion-ui",
17
17
  component,
18
18
  component + ".tsx"
19
19
  );
@@ -153,7 +153,7 @@ program
153
153
  .command("list")
154
154
  .description("List available components")
155
155
  .action(() => {
156
- const templatesDir = path.join(__dirname, "../src/templates");
156
+ const templatesDir = path.join(__dirname, "../src/notion-ui");
157
157
  const components = fs.readdirSync(templatesDir).filter((folder) => {
158
158
  const file = path.join(templatesDir, folder, folder + ".tsx");
159
159
  return fs.existsSync(file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notionsoft-ui",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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"
@@ -0,0 +1,54 @@
1
+ import {
2
+ useInView,
3
+ animated,
4
+ type IntersectionArgs,
5
+ SpringValue,
6
+ type AnimationResult,
7
+ Controller,
8
+ } from "@react-spring/web";
9
+ import { useCallback, useState } from "react";
10
+
11
+ export interface AnimatedItemProps {
12
+ springProps: {
13
+ from?: object;
14
+ to?: object | object[];
15
+ delay?: number;
16
+ immediate?: boolean;
17
+ reset?: boolean;
18
+ reverse?: boolean;
19
+ pause?: boolean;
20
+ onStart?: (
21
+ result: AnimationResult,
22
+ spring: Controller | SpringValue,
23
+ item?: any
24
+ ) => void;
25
+ config?: {
26
+ mass: number;
27
+ friction: number;
28
+ tension: number;
29
+ };
30
+ };
31
+ intersectionArgs: IntersectionArgs;
32
+ children: React.ReactNode | ((inView: boolean) => React.ReactNode);
33
+ }
34
+
35
+ export function AnimatedItem(props: AnimatedItemProps) {
36
+ const [inView, setInView] = useState(false);
37
+ const { springProps, intersectionArgs, children } = props;
38
+ const defaultOnStart = useCallback(
39
+ () => {
40
+ setInView(true);
41
+ },
42
+ [] // no dependencies, memoized once
43
+ );
44
+ const composedOnStart = springProps.onStart ?? defaultOnStart;
45
+
46
+ const springConfig = { ...springProps, onStart: composedOnStart };
47
+ const [ref, springs] = useInView(() => springConfig, intersectionArgs);
48
+
49
+ return (
50
+ <animated.div ref={ref} style={springs}>
51
+ {typeof children === "function" ? children(inView) : children}
52
+ </animated.div>
53
+ );
54
+ }
@@ -0,0 +1,3 @@
1
+ import { AnimatedItem } from "./animated-item";
2
+
3
+ export default AnimatedItem;
@@ -1,5 +1,5 @@
1
+ import { BooleanStatusButton } from "./BooleanStatusButton";
1
2
  import type { Meta, StoryObj } from "@storybook/react";
2
- import BooleanStatusButton from "./BooleanStatusButton";
3
3
 
4
4
  // --------------------------------------------
5
5
  // Status options used for Storybook showcase
@@ -9,7 +9,7 @@ export interface BooleanStatusButtonProps {
9
9
  className?: string;
10
10
  }
11
11
 
12
- export default function BooleanStatusButton(props: BooleanStatusButtonProps) {
12
+ export function BooleanStatusButton(props: BooleanStatusButtonProps) {
13
13
  const { getColor, className } = props;
14
14
  const data = getColor();
15
15
 
@@ -0,0 +1,3 @@
1
+ import { BooleanStatusButton } from "./BooleanStatusButton";
2
+
3
+ export default BooleanStatusButton;
@@ -0,0 +1,3 @@
1
+ import Button from "./button";
2
+
3
+ export default Button;
@@ -1,5 +1,5 @@
1
1
  import Button from "../button/button";
2
- import ButtonSpinner from "./button-spinner";
2
+ import { ButtonSpinner } from "./button-spinner";
3
3
  import type { Meta, StoryObj } from "@storybook/react";
4
4
 
5
5
  const meta: Meta<typeof ButtonSpinner> = {
@@ -7,7 +7,7 @@ export interface IButtonSpinnerProps {
7
7
  className?: string;
8
8
  }
9
9
 
10
- export default function ButtonSpinner(props: IButtonSpinnerProps) {
10
+ export function ButtonSpinner(props: IButtonSpinnerProps) {
11
11
  const { loading, children, className } = props;
12
12
  return (
13
13
  <>
@@ -0,0 +1,3 @@
1
+ import { ButtonSpinner } from "./button-spinner";
2
+
3
+ export default ButtonSpinner;
@@ -1,4 +1,4 @@
1
- import CircleLoader from "../circle-loader/circle-loader"; // Adjust the import path as needed
1
+ import { CircleLoader } from "../circle-loader/circle-loader"; // Adjust the import path as needed
2
2
  import { CircleLoaderProps } from "./circle-loader";
3
3
 
4
4
  // Meta information for Storybook
@@ -8,7 +8,7 @@ export interface CircleLoaderProps {
8
8
  parentClassName?: string;
9
9
  }
10
10
 
11
- export default function CircleLoader(props: CircleLoaderProps) {
11
+ export function CircleLoader(props: CircleLoaderProps) {
12
12
  const { label, className, labelclassname, parentClassName, ...restProps } =
13
13
  props;
14
14
 
@@ -0,0 +1,3 @@
1
+ import { CircleLoader } from "./circle-loader";
2
+
3
+ export default CircleLoader;
@@ -0,0 +1,100 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Input } from "./input";
3
+ import { Search, AlertTriangle } from "lucide-react";
4
+
5
+ const meta: Meta<typeof Input> = {
6
+ title: "Form/Input",
7
+ component: Input,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ argTypes: {
12
+ label: { control: "text" },
13
+ requiredHint: { control: "text" },
14
+ errorMessage: { control: "text" },
15
+ readOnly: { control: "boolean" },
16
+ measurement: {
17
+ control: "select",
18
+ options: ["sm", "md", "lg", undefined],
19
+ },
20
+ },
21
+ };
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof Input>;
25
+
26
+ const Template = (args) => (
27
+ <div className="w-[350px]">
28
+ <Input {...args} />
29
+ </div>
30
+ );
31
+
32
+ export const Default: Story = {
33
+ render: Template,
34
+ args: {
35
+ label: "Username",
36
+ placeholder: "Enter your username",
37
+ measurement: "md",
38
+ },
39
+ };
40
+
41
+ export const WithStartIcon: Story = {
42
+ render: Template,
43
+ args: {
44
+ label: "Search",
45
+ placeholder: "Search …",
46
+ startContent: <Search size={18} />,
47
+ measurement: "md",
48
+ },
49
+ };
50
+
51
+ export const WithEndIcon: Story = {
52
+ render: Template,
53
+ args: {
54
+ label: "Email",
55
+ placeholder: "you@example.com",
56
+ endContent: <AlertTriangle size={20} color="orange" />,
57
+ measurement: "md",
58
+ },
59
+ };
60
+
61
+ export const RequiredField: Story = {
62
+ render: Template,
63
+ args: {
64
+ label: "Password",
65
+ placeholder: "Enter password",
66
+ required: true,
67
+ requiredHint: "* Required",
68
+ measurement: "md",
69
+ },
70
+ };
71
+
72
+ export const WithErrorMessage: Story = {
73
+ render: Template,
74
+ args: {
75
+ label: "Email",
76
+ placeholder: "example@mail.com",
77
+ errorMessage: "Invalid email format",
78
+ measurement: "md",
79
+ },
80
+ };
81
+
82
+ export const ReadOnly: Story = {
83
+ render: Template,
84
+ args: {
85
+ label: "Read Only Field",
86
+ value: "Can't change this",
87
+ readOnly: true,
88
+ measurement: "md",
89
+ },
90
+ };
91
+
92
+ export const SizesShowcase: Story = {
93
+ render: () => (
94
+ <div className="space-y-5 w-[350px]">
95
+ <Input label="Small" placeholder="Small size" measurement="sm" />
96
+ <Input label="Medium" placeholder="Medium size" measurement="md" />
97
+ <Input label="Large" placeholder="Large size" measurement="lg" />
98
+ </div>
99
+ ),
100
+ };
@@ -0,0 +1,3 @@
1
+ import { Input } from "./input";
2
+
3
+ export default Input;
@@ -0,0 +1,193 @@
1
+ import React, { useMemo } from "react";
2
+ // import { cn } from "@/utils/cn";
3
+ import { cn } from "../../utils/cn";
4
+ import AnimatedItem from "@/components/animated-item";
5
+ // import AnimatedItem from "../animated-item";
6
+
7
+ export type NastranInputSize = "sm" | "md" | "lg";
8
+
9
+ export interface InputProps
10
+ extends React.InputHTMLAttributes<HTMLInputElement> {
11
+ startContent?: React.ReactNode;
12
+ requiredHint?: string;
13
+ label?: string;
14
+ endContent?: React.ReactNode;
15
+ errorMessage?: string;
16
+ parentClassName?: string;
17
+ measurement: NastranInputSize;
18
+ }
19
+
20
+ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
21
+ (
22
+ {
23
+ className,
24
+ type,
25
+ requiredHint,
26
+ startContent,
27
+ endContent,
28
+ parentClassName = "",
29
+ measurement = "md",
30
+ errorMessage,
31
+ label,
32
+ readOnly,
33
+ ...rest
34
+ },
35
+ ref
36
+ ) => {
37
+ const hasError = !!errorMessage;
38
+
39
+ const inputPaddingClass = startContent
40
+ ? "rtl:pr-[42px] ltr:ps-[42px]"
41
+ : "rtl:pr-[12px] ltr:ps-[12px]";
42
+
43
+ const heightStyle = useMemo(
44
+ () =>
45
+ measurement == "lg"
46
+ ? {
47
+ height: "50px",
48
+ paddingBottom: "pb-[3px]",
49
+ endContent: label
50
+ ? "top-12 -translate-y-1/2"
51
+ : "top-[26px] -translate-y-1/2",
52
+ startContent: label
53
+ ? "top-12 -translate-y-1/2"
54
+ : "top-[26px] -translate-y-1/2",
55
+ required: "top-[2px]",
56
+ }
57
+ : measurement == "md"
58
+ ? {
59
+ height: "44px",
60
+ paddingBottom: "pb-[2px]",
61
+ endContent: label
62
+ ? "top-[45px] -translate-y-1/2"
63
+ : "top-[22px] -translate-y-1/2",
64
+ startContent: label
65
+ ? "top-[45px] -translate-y-1/2"
66
+ : "top-[22px] -translate-y-1/2",
67
+ required: "top-[4px]",
68
+ }
69
+ : {
70
+ height: "40px",
71
+ paddingBottom: "pb-[2px]",
72
+ endContent: label
73
+ ? "top-[44px] -translate-y-1/2"
74
+ : "top-[20px] -translate-y-1/2",
75
+ startContent: label
76
+ ? "top-[44px] -translate-y-1/2"
77
+ : "top-[20px] -translate-y-1/2",
78
+ required: "top-[4px]",
79
+ },
80
+ []
81
+ );
82
+ return (
83
+ <div className={cn(parentClassName, "flex flex-col justify-end")}>
84
+ <div
85
+ className={cn(
86
+ "relative select-none h-fit rtl:text-lg-rtl ltr:text-lg-ltr"
87
+ )}
88
+ >
89
+ {/* Start Content */}
90
+ {startContent && (
91
+ <span
92
+ className={cn(
93
+ "absolute flex items-center ltr:left-[12px] rtl:right-[12px]",
94
+ heightStyle.startContent
95
+ )}
96
+ >
97
+ {startContent}
98
+ </span>
99
+ )}
100
+
101
+ {/* End Content */}
102
+ {endContent && (
103
+ <span
104
+ className={cn(
105
+ "absolute flex items-center ltr:right-[5px] rtl:left-[5px]",
106
+ heightStyle.endContent
107
+ )}
108
+ >
109
+ {endContent}
110
+ </span>
111
+ )}
112
+
113
+ {/* Required Hint */}
114
+ {requiredHint && (
115
+ <span
116
+ className={cn(
117
+ "absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-[10px] rtl:left-[10px]",
118
+ heightStyle.required
119
+ )}
120
+ >
121
+ {requiredHint}
122
+ </span>
123
+ )}
124
+
125
+ {/* Label */}
126
+ {label && (
127
+ <label
128
+ htmlFor={label}
129
+ className={cn(
130
+ "font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
131
+ )}
132
+ >
133
+ {label}
134
+ </label>
135
+ )}
136
+
137
+ {/* Input Field */}
138
+
139
+ <input
140
+ ref={ref}
141
+ type={type}
142
+ data-slot="input"
143
+ readOnly={readOnly}
144
+ style={{
145
+ height: heightStyle.height,
146
+ }}
147
+ 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-md 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",
149
+ "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 rounded focus-visible:shadow-sm focus-visible:ring-offset-0 transition-[border] bg-card dark:bg-black/30",
151
+ "focus-visible:border-fourth/60",
152
+ "[&::-webkit-outer-spin-button]:appearance-none",
153
+ "[&::-webkit-inner-spin-button]:appearance-none",
154
+ "[-moz-appearance:textfield]",
155
+ inputPaddingClass,
156
+ hasError ? "border-red-400 border" : "border-primary/25",
157
+ readOnly && "cursor-not-allowed",
158
+ heightStyle.paddingBottom,
159
+ className
160
+ )}
161
+ {...rest}
162
+ />
163
+ </div>
164
+
165
+ {/* Error Message */}
166
+ {hasError && (
167
+ <AnimatedItem
168
+ springProps={{
169
+ from: {
170
+ opacity: 0,
171
+ transform: "translateY(-8px)",
172
+ },
173
+ config: {
174
+ mass: 1,
175
+ tension: 210,
176
+ friction: 20,
177
+ },
178
+ to: {
179
+ opacity: 1,
180
+ transform: "translateY(0px)",
181
+ },
182
+ }}
183
+ intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
184
+ >
185
+ <h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
186
+ {errorMessage}
187
+ </h1>
188
+ </AnimatedItem>
189
+ )}
190
+ </div>
191
+ );
192
+ }
193
+ );
@@ -1,5 +1,5 @@
1
1
  import Button from "../button/button";
2
- import AnimatedSheet from "./AnimatedSheet";
2
+ import { AnimatedSheet } from "./AnimatedSheet";
3
3
  import type { Meta, StoryObj } from "@storybook/react";
4
4
  import { useState } from "react";
5
5
 
@@ -22,7 +22,7 @@ type AnimatedSheetProps = {
22
22
  animate: AnimateType;
23
23
  };
24
24
 
25
- export default function AnimatedSheet({
25
+ export function AnimatedSheet({
26
26
  open,
27
27
  onClose,
28
28
  children,
@@ -0,0 +1,3 @@
1
+ import { AnimatedSheet } from "./AnimatedSheet";
2
+
3
+ export default AnimatedSheet;
@@ -0,0 +1,3 @@
1
+ import { ShiningText } from "./shining-text";
2
+
3
+ export default ShiningText;
package/tsconfig.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "ignoreDeprecations": "6.0",
8
8
  "paths": {
9
9
  "@/utils/*": ["utils/*"],
10
+ "@/components/*": ["notion-ui/*"],
10
11
  "@/*": ["*"]
11
12
  }
12
13
  }
File without changes