notionsoft-ui 1.0.27 → 1.0.30
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 +1 -1
- package/src/notion-ui/multi-tab-input/index.ts +3 -0
- package/src/notion-ui/multi-tab-input/multi-tab-input.tsx +196 -0
- package/src/notion-ui/multi-tab-input/multi.tab.input.stories.tsx +290 -0
- package/src/notion-ui/multi-tab-textarea/index.ts +3 -0
- package/src/notion-ui/multi-tab-textarea/multi-tab-textarea.tsx +191 -0
- package/src/notion-ui/multi-tab-textarea/multi.tab.textarea.stories.tsx +340 -0
- package/src/notion-ui/phone-input/PhoneInput.stories.tsx +112 -0
- package/src/notion-ui/phone-input/phone-input.tsx +85 -28
- package/src/notion-ui/tab/index.ts +3 -0
- package/src/notion-ui/tab/tab.tsx +46 -0
- package/src/notion-ui/textarea/Textarea.stories.tsx +77 -0
- package/src/notion-ui/textarea/index.ts +3 -0
- package/src/notion-ui/textarea/textarea.tsx +121 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Textarea } from "./textarea";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Textarea> = {
|
|
5
|
+
title: "Form/Textarea",
|
|
6
|
+
component: Textarea,
|
|
7
|
+
args: {
|
|
8
|
+
placeholder: "Write something...",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof Textarea>;
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------
|
|
15
|
+
// Default
|
|
16
|
+
// ---------------------------------------
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
placeholder: "Enter text...",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------
|
|
24
|
+
// With Label
|
|
25
|
+
// ---------------------------------------
|
|
26
|
+
export const WithLabel: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
label: "Description",
|
|
29
|
+
placeholder: "Enter description...",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------
|
|
34
|
+
// Required Hint (*)
|
|
35
|
+
// ---------------------------------------
|
|
36
|
+
export const WithRequired: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
label: "Bio",
|
|
39
|
+
requiredHint: "*",
|
|
40
|
+
placeholder: "Tell us about yourself...",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------
|
|
45
|
+
// With Error Message (AnimatedItem visible)
|
|
46
|
+
// ---------------------------------------
|
|
47
|
+
export const WithError: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
label: "Comment",
|
|
50
|
+
errorMessage: "Comment is required",
|
|
51
|
+
placeholder: "Add a comment...",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------
|
|
56
|
+
// ReadOnly Example
|
|
57
|
+
// ---------------------------------------
|
|
58
|
+
export const ReadOnly: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
label: "Readonly Field",
|
|
61
|
+
readOnly: true,
|
|
62
|
+
value: "This textarea cannot be edited",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------
|
|
67
|
+
// Custom Root Class via classNames
|
|
68
|
+
// ---------------------------------------
|
|
69
|
+
export const CustomRootClass: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
label: "Custom Styled",
|
|
72
|
+
placeholder: "Root custom style applied",
|
|
73
|
+
classNames: {
|
|
74
|
+
rootDivClassName: "bg-blue-50 p-3 rounded-lg",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
// import { cn } from "@/utils/cn";
|
|
3
|
+
import { cn } from "../../utils/cn";
|
|
4
|
+
import AnimatedItem from "../../notion-ui/animated-item";
|
|
5
|
+
// import AnimatedItem from "../animated-item";
|
|
6
|
+
|
|
7
|
+
export interface TextareaProps
|
|
8
|
+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
9
|
+
requiredHint?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
classNames?: {
|
|
13
|
+
rootDivClassName?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
className,
|
|
21
|
+
requiredHint,
|
|
22
|
+
classNames,
|
|
23
|
+
errorMessage,
|
|
24
|
+
label,
|
|
25
|
+
readOnly,
|
|
26
|
+
...rest
|
|
27
|
+
},
|
|
28
|
+
ref
|
|
29
|
+
) => {
|
|
30
|
+
const hasError = !!errorMessage;
|
|
31
|
+
const { rootDivClassName } = classNames || {};
|
|
32
|
+
const heightStyle = {
|
|
33
|
+
required: label ? "ltr:top-[4px] rtl:top-[12px]" : "top-[-19px]",
|
|
34
|
+
};
|
|
35
|
+
const readOnlyStyle = readOnly && "opacity-40";
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={cn(
|
|
40
|
+
rootDivClassName,
|
|
41
|
+
"flex w-full flex-col justify-end",
|
|
42
|
+
readOnlyStyle
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
className={cn(
|
|
47
|
+
"relative text-start select-none h-fit rtl:text-lg-rtl ltr:text-lg-ltr"
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{/* Required Hint */}
|
|
51
|
+
{requiredHint && (
|
|
52
|
+
<span
|
|
53
|
+
className={cn(
|
|
54
|
+
"absolute font-semibold text-red-600 rtl:text-[13px] ltr:text-[11px] ltr:right-2.5 rtl:left-2.5",
|
|
55
|
+
heightStyle.required
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{requiredHint}
|
|
59
|
+
</span>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
{/* Label */}
|
|
63
|
+
{label && (
|
|
64
|
+
<label
|
|
65
|
+
htmlFor={label}
|
|
66
|
+
className={cn(
|
|
67
|
+
"font-semibold rtl:text-xl-rtl ltr:text-lg-ltr inline-block pb-1"
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
{label}
|
|
71
|
+
</label>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{/* Textarea Field */}
|
|
75
|
+
|
|
76
|
+
<textarea
|
|
77
|
+
ref={ref}
|
|
78
|
+
data-slot="textarea"
|
|
79
|
+
readOnly={readOnly}
|
|
80
|
+
className={cn(
|
|
81
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring-0 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-70 md:text-sm focus-visible:border-tertiary/60 focus-visible:shadow-sm",
|
|
82
|
+
"placeholder:text-primary/60 ltr:text-sm rtl:text-sm rtl:font-semibold",
|
|
83
|
+
hasError && "border-red-400",
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
86
|
+
{...rest}
|
|
87
|
+
disabled={readOnly}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Error Message */}
|
|
92
|
+
{hasError && (
|
|
93
|
+
<AnimatedItem
|
|
94
|
+
springProps={{
|
|
95
|
+
from: {
|
|
96
|
+
opacity: 0,
|
|
97
|
+
transform: "translateY(-8px)",
|
|
98
|
+
},
|
|
99
|
+
config: {
|
|
100
|
+
mass: 1,
|
|
101
|
+
tension: 210,
|
|
102
|
+
friction: 20,
|
|
103
|
+
},
|
|
104
|
+
to: {
|
|
105
|
+
opacity: 1,
|
|
106
|
+
transform: "translateY(0px)",
|
|
107
|
+
},
|
|
108
|
+
}}
|
|
109
|
+
intersectionArgs={{ once: true, rootMargin: "-5% 0%" }}
|
|
110
|
+
>
|
|
111
|
+
<h1 className="text-red-400 text-start capitalize rtl:text-sm rtl:font-medium ltr:text-sm-ltr">
|
|
112
|
+
{errorMessage}
|
|
113
|
+
</h1>
|
|
114
|
+
</AnimatedItem>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
export default Textarea;
|