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.
@@ -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,3 @@
1
+ import Textarea from "./textarea";
2
+
3
+ export default Textarea;
@@ -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;