notionsoft-ui 1.0.15 → 1.0.17

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.15",
3
+ "version": "1.0.17",
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"
@@ -49,3 +49,9 @@ export const Success: Story = {
49
49
  variant: "success",
50
50
  },
51
51
  };
52
+ export const Outline: Story = {
53
+ args: {
54
+ children: "Outline Button",
55
+ variant: "outline",
56
+ },
57
+ };
@@ -3,11 +3,11 @@ import { cn } from "../../utils/cn";
3
3
  // import { cn } from "@/utils/cn";
4
4
 
5
5
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
6
- variant?: "primary" | "secondary" | "warning" | "success";
6
+ variant?: "primary" | "secondary" | "warning" | "success" | "outline";
7
7
  }
8
8
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
9
9
  (props, ref: any) => {
10
- const { className, children, variant, ...rest } = props;
10
+ const { className, children, variant, disabled, ...rest } = props;
11
11
  const style =
12
12
  variant == "secondary"
13
13
  ? "border hover:bg-primary hover:text-primary-foreground"
@@ -15,16 +15,20 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
15
15
  ? "bg-red-500 text-primary-foreground"
16
16
  : variant == "success"
17
17
  ? "bg-green-500 text-primary-foreground"
18
- : "bg-primary hover:bg-primary shadow shadow-primary/50 text-primary-foreground/80 hover:opacity-90 hover:text-primary-foreground";
18
+ : variant == "outline"
19
+ ? "text-primary border border-primary/10 hover:bg-primary/5"
20
+ : "bg-primary hover:shadow hover:bg-primary shadow shadow-primary/50 text-primary-foreground/80 hover:opacity-90 hover:text-primary-foreground";
19
21
  return (
20
22
  <button
21
23
  {...rest}
24
+ disabled={disabled}
22
25
  ref={ref}
23
26
  className={cn(
24
- `rounded-sm cursor-pointer ltr:text-[13px] sm:ltr:text-sm rtl:text-[13px] sm:rtl:text-sm rtl:font-semibold
25
- hover:shadow transition w-fit
26
- sm:px-4 py-[6px] leading-normal duration-200 ease-linear`,
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
28
+ transition w-fit px-3 py-1.5 duration-200 ease-linear`,
27
29
  style,
30
+ disabled &&
31
+ "opacity-35 pointer-events-none disabled:cursor-not-allowed",
28
32
  className
29
33
  )}
30
34
  >
@@ -3,7 +3,7 @@ import ButtonSpinner from "./button-spinner";
3
3
  import type { Meta, StoryObj } from "@storybook/react";
4
4
 
5
5
  const meta: Meta<typeof ButtonSpinner> = {
6
- title: "Components/ButtonSpinner",
6
+ title: "Loader/ButtonSpinner",
7
7
  component: ButtonSpinner,
8
8
  parameters: {
9
9
  layout: "centered",
@@ -3,7 +3,7 @@ import { CircleLoaderProps } from "./circle-loader";
3
3
 
4
4
  // Meta information for Storybook
5
5
  export default {
6
- title: "Components/CircleLoader", // This will be the folder and component name in Storybook's sidebar
6
+ title: "Loader/CircleLoader", // This will be the folder and component name in Storybook's sidebar
7
7
  component: CircleLoader, // The component being showcased
8
8
  argTypes: {
9
9
  className: { control: "text" },
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from "react";
2
2
  // import { cn } from "@/utils/cn";
3
3
  import { cn } from "../../utils/cn";
4
- import AnimatedItem from "@/components/notion-ui/animated-item";
4
+ import AnimatedItem from "../../notion-ui/animated-item";
5
5
  // import AnimatedItem from "../animated-item";
6
6
 
7
7
  export type NastranInputSize = "sm" | "md" | "lg";
@@ -14,7 +14,7 @@ export interface InputProps
14
14
  endContent?: React.ReactNode;
15
15
  errorMessage?: string;
16
16
  parentClassName?: string;
17
- measurement: NastranInputSize;
17
+ measurement?: NastranInputSize;
18
18
  }
19
19
 
20
20
  export const Input = React.forwardRef<HTMLInputElement, InputProps>(
@@ -26,7 +26,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
26
26
  startContent,
27
27
  endContent,
28
28
  parentClassName = "",
29
- measurement = "md",
29
+ measurement = "sm",
30
30
  errorMessage,
31
31
  label,
32
32
  readOnly,
@@ -47,50 +47,50 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
47
47
  height: "50px",
48
48
  paddingBottom: "pb-[3px]",
49
49
  endContent: label
50
- ? "top-12 -translate-y-1/2"
50
+ ? "ltr:top-[48px] rtl:top-[54px]-translate-y-1/2"
51
51
  : "top-[26px] -translate-y-1/2",
52
52
  startContent: label
53
- ? "top-12 -translate-y-1/2"
53
+ ? "ltr:top-[48px] rtl:top-[54px] -translate-y-1/2"
54
54
  : "top-[26px] -translate-y-1/2",
55
- required: "top-[2px]",
55
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
56
56
  }
57
57
  : measurement == "md"
58
58
  ? {
59
59
  height: "44px",
60
60
  paddingBottom: "pb-[2px]",
61
61
  endContent: label
62
- ? "top-[45px] -translate-y-1/2"
62
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
63
63
  : "top-[22px] -translate-y-1/2",
64
64
  startContent: label
65
- ? "top-[45px] -translate-y-1/2"
65
+ ? "ltr:top-[45px] rtl:top-[51px] -translate-y-1/2"
66
66
  : "top-[22px] -translate-y-1/2",
67
- required: "top-[4px]",
67
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
68
68
  }
69
69
  : {
70
70
  height: "40px",
71
71
  paddingBottom: "pb-[2px]",
72
72
  endContent: label
73
- ? "top-[44px] -translate-y-1/2"
73
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
74
74
  : "top-[20px] -translate-y-1/2",
75
75
  startContent: label
76
- ? "top-[44px] -translate-y-1/2"
76
+ ? "ltr:top-[44px] rtl:top-[50px] -translate-y-1/2"
77
77
  : "top-[20px] -translate-y-1/2",
78
- required: "top-[4px]",
78
+ required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
79
79
  },
80
- []
80
+ [measurement, label]
81
81
  );
82
82
  return (
83
- <div className={cn(parentClassName, "flex flex-col justify-end")}>
83
+ <div className={cn(parentClassName, "flex w-full flex-col justify-end")}>
84
84
  <div
85
85
  className={cn(
86
- "relative select-none h-fit rtl:text-lg-rtl ltr:text-lg-ltr"
86
+ "relative text-start select-none h-fit rtl:text-lg-rtl ltr:text-lg-ltr"
87
87
  )}
88
88
  >
89
89
  {/* Start Content */}
90
90
  {startContent && (
91
91
  <span
92
92
  className={cn(
93
- "absolute flex items-center ltr:left-[12px] rtl:right-[12px]",
93
+ "absolute flex items-center ltr:left-3 rtl:right-3",
94
94
  heightStyle.startContent
95
95
  )}
96
96
  >
@@ -114,7 +114,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
114
114
  {requiredHint && (
115
115
  <span
116
116
  className={cn(
117
- "absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-[10px] rtl:left-[10px]",
117
+ "absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-2.5 rtl:left-2.5",
118
118
  heightStyle.required
119
119
  )}
120
120
  >
@@ -145,13 +145,13 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
145
145
  height: heightStyle.height,
146
146
  }}
147
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",
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",
149
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",
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",
151
+ "focus-visible:border-tertiary/60",
152
152
  "[&::-webkit-outer-spin-button]:appearance-none",
153
153
  "[&::-webkit-inner-spin-button]:appearance-none",
154
- "[-moz-appearance:textfield]",
154
+ "[-moz-appearance:textfield] ",
155
155
  inputPaddingClass,
156
156
  hasError ? "border-red-400 border" : "border-primary/25",
157
157
  readOnly && "cursor-not-allowed",
@@ -0,0 +1,3 @@
1
+ import MultiSelectInput from "./multi-select-input";
2
+
3
+ export default MultiSelectInput;
@@ -0,0 +1,166 @@
1
+ import { useState } from "react";
2
+ import { Meta, StoryFn } from "@storybook/react";
3
+ import MultiSelectInputForward, {
4
+ MultiSelectInputProps,
5
+ } from "./multi-select-input";
6
+
7
+ interface User {
8
+ uuid: string;
9
+ name: string;
10
+ email: string;
11
+ active: boolean;
12
+ admin: boolean;
13
+ }
14
+
15
+ export default {
16
+ title: "Select/MultiSelectInput",
17
+ component: MultiSelectInputForward,
18
+ } as Meta<typeof MultiSelectInputForward>;
19
+
20
+ // ------------------ Mock data ------------------
21
+ const mockUsers: User[] = [
22
+ {
23
+ uuid: "1",
24
+ name: "Alice",
25
+ email: "alice@example.com",
26
+ active: true,
27
+ admin: false,
28
+ },
29
+ {
30
+ uuid: "2",
31
+ name: "Bob",
32
+ email: "bob@example.com",
33
+ active: false,
34
+ admin: true,
35
+ },
36
+ {
37
+ uuid: "3",
38
+ name: "Charlie",
39
+ email: "charlie@example.com",
40
+ active: true,
41
+ admin: true,
42
+ },
43
+ {
44
+ uuid: "4",
45
+ name: "David",
46
+ email: "david@example.com",
47
+ active: false,
48
+ admin: false,
49
+ },
50
+ ];
51
+
52
+ // ------------------ Mock async fetch function ------------------
53
+ const fetchUsers = async (
54
+ query: string,
55
+ filters?: Record<string, boolean>,
56
+ maxFetch?: number
57
+ ) => {
58
+ let result = mockUsers;
59
+
60
+ if (filters) {
61
+ result = result.filter((user) =>
62
+ Object.entries(filters).every(([key, value]) =>
63
+ value ? (user as any)[key] : true
64
+ )
65
+ );
66
+ }
67
+
68
+ if (query) {
69
+ const q = query.toLowerCase();
70
+ result = result.filter(
71
+ (u) =>
72
+ u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
73
+ );
74
+ }
75
+
76
+ if (maxFetch) result = result.slice(0, maxFetch);
77
+
78
+ await new Promise((r) => setTimeout(r, 300));
79
+
80
+ return result;
81
+ };
82
+
83
+ // ------------------ Template ------------------
84
+ const Template: StoryFn<MultiSelectInputProps<User>> = (args) => {
85
+ const [selected, setSelected] = useState<User[]>([]);
86
+
87
+ return (
88
+ <div style={{ width: 400, padding: 20 }}>
89
+ <MultiSelectInputForward
90
+ {...args}
91
+ selected={selected}
92
+ onItemsSelect={(selectedItems) => {
93
+ if (Array.isArray(selectedItems)) setSelected(selectedItems);
94
+ else if (selectedItems) setSelected([selectedItems]);
95
+ else setSelected([]);
96
+ }}
97
+ />
98
+ <div style={{ marginTop: 20 }}>
99
+ <strong>Selected Users:</strong>
100
+ <pre>{JSON.stringify(selected, null, 2)}</pre>
101
+ </div>
102
+ </div>
103
+ );
104
+ };
105
+
106
+ // ------------------ Stories ------------------
107
+
108
+ // Multiple selection story
109
+ export const MultipleSelection = Template.bind({});
110
+ MultipleSelection.args = {
111
+ fetch: fetchUsers,
112
+ selectionMode: "multiple",
113
+ searchBy: ["name", "email"],
114
+ itemKey: "uuid",
115
+ filters: [
116
+ { key: "active", name: "Active" },
117
+ { key: "admin", name: "Admin" },
118
+ ],
119
+ text: {
120
+ fetch: "Loading users...",
121
+ notItem: "No users found",
122
+ maxRecord: "Max results",
123
+ clearFilters: "Clear Filters",
124
+ },
125
+ };
126
+
127
+ // Single selection story
128
+ export const SingleSelection = Template.bind({});
129
+ SingleSelection.args = {
130
+ fetch: fetchUsers,
131
+ selectionMode: "single",
132
+ searchBy: ["name", "email"],
133
+ itemKey: "uuid",
134
+ filters: [
135
+ { key: "active", name: "Active" },
136
+ { key: "admin", name: "Admin" },
137
+ ],
138
+ text: {
139
+ fetch: "Loading users...",
140
+ notItem: "No users found",
141
+ maxRecord: "Max results",
142
+ clearFilters: "Clear Filters",
143
+ },
144
+ };
145
+
146
+ // ------------------ API Config story ------------------
147
+ export const APIConfigExample = Template.bind({});
148
+ APIConfigExample.args = {
149
+ apiConfig: {
150
+ url: "https://jsonplaceholder.typicode.com/users",
151
+ headers: { "Content-Type": "application/json" },
152
+ },
153
+ selectionMode: "multiple",
154
+ searchBy: ["name", "email"],
155
+ filters: [
156
+ { key: "active", name: "Active" },
157
+ { key: "admin", name: "Admin" },
158
+ ],
159
+ itemKey: "id", // JSONPlaceholder uses `id` as key
160
+ text: {
161
+ fetch: "Fetching users from API...",
162
+ notItem: "No users found",
163
+ maxRecord: "Max results",
164
+ clearFilters: "Clear Filters",
165
+ },
166
+ };