notionsoft-ui 1.0.2 → 1.0.4
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/.storybook/main.ts +19 -0
- package/.storybook/preview.css +160 -0
- package/.storybook/preview.ts +21 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/cli/index.cjs +1 -1
- package/package.json +23 -2
- package/src/stories/Button.stories.ts +54 -0
- package/src/stories/Button.tsx +37 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.ts +34 -0
- package/src/stories/Header.tsx +56 -0
- package/src/stories/Page.stories.ts +33 -0
- package/src/stories/Page.tsx +73 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/stories/button.css +30 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +68 -0
- package/src/templates/button/BooleanStatusButton.stories.tsx +90 -0
- package/src/templates/button/BooleanStatusButton.tsx +29 -0
- package/src/templates/button/Button.stories.tsx +51 -0
- package/src/templates/button/button.tsx +31 -20
- package/src/templates/sheet/AnimatedSheet.stories.tsx +103 -0
- package/src/templates/sheet/AnimatedSheet.tsx +163 -0
- package/src/templates/shining-text/shining-text.stories.tsx +43 -0
- package/src/templates/shining-text/shining-text.tsx +35 -0
- package/tsconfig.json +2 -1
- package/vite.config.ts +36 -0
- package/vitest.shims.d.ts +1 -0
- package/src/templates/card/card.tsx +0 -20
- package/src/utils/cn.js +0 -15
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.storybook-header {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: space-between;
|
|
4
|
+
align-items: center;
|
|
5
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
6
|
+
padding: 15px 20px;
|
|
7
|
+
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.storybook-header svg {
|
|
11
|
+
display: inline-block;
|
|
12
|
+
vertical-align: top;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.storybook-header h1 {
|
|
16
|
+
display: inline-block;
|
|
17
|
+
vertical-align: top;
|
|
18
|
+
margin: 6px 0 6px 10px;
|
|
19
|
+
font-weight: 700;
|
|
20
|
+
font-size: 20px;
|
|
21
|
+
line-height: 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.storybook-header button + button {
|
|
25
|
+
margin-left: 10px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.storybook-header .welcome {
|
|
29
|
+
margin-right: 10px;
|
|
30
|
+
color: #333;
|
|
31
|
+
font-size: 14px;
|
|
32
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
.storybook-page {
|
|
2
|
+
margin: 0 auto;
|
|
3
|
+
padding: 48px 20px;
|
|
4
|
+
max-width: 600px;
|
|
5
|
+
color: #333;
|
|
6
|
+
font-size: 14px;
|
|
7
|
+
line-height: 24px;
|
|
8
|
+
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.storybook-page h2 {
|
|
12
|
+
display: inline-block;
|
|
13
|
+
vertical-align: top;
|
|
14
|
+
margin: 0 0 4px;
|
|
15
|
+
font-weight: 700;
|
|
16
|
+
font-size: 32px;
|
|
17
|
+
line-height: 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.storybook-page p {
|
|
21
|
+
margin: 1em 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.storybook-page a {
|
|
25
|
+
color: inherit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.storybook-page ul {
|
|
29
|
+
margin: 1em 0;
|
|
30
|
+
padding-left: 30px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.storybook-page li {
|
|
34
|
+
margin-bottom: 8px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.storybook-page .tip {
|
|
38
|
+
display: inline-block;
|
|
39
|
+
vertical-align: top;
|
|
40
|
+
margin-right: 10px;
|
|
41
|
+
border-radius: 1em;
|
|
42
|
+
background: #e7fdd8;
|
|
43
|
+
padding: 4px 12px;
|
|
44
|
+
color: #357a14;
|
|
45
|
+
font-weight: 700;
|
|
46
|
+
font-size: 11px;
|
|
47
|
+
line-height: 12px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.storybook-page .tip-wrapper {
|
|
51
|
+
margin-top: 40px;
|
|
52
|
+
margin-bottom: 40px;
|
|
53
|
+
font-size: 13px;
|
|
54
|
+
line-height: 20px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.storybook-page .tip-wrapper svg {
|
|
58
|
+
display: inline-block;
|
|
59
|
+
vertical-align: top;
|
|
60
|
+
margin-top: 3px;
|
|
61
|
+
margin-right: 4px;
|
|
62
|
+
width: 12px;
|
|
63
|
+
height: 12px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.storybook-page .tip-wrapper svg path {
|
|
67
|
+
fill: #1ea7fd;
|
|
68
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import BooleanStatusButton from "./BooleanStatusButton";
|
|
3
|
+
|
|
4
|
+
// --------------------------------------------
|
|
5
|
+
// Status options used for Storybook showcase
|
|
6
|
+
// --------------------------------------------
|
|
7
|
+
const statusOptions = {
|
|
8
|
+
success: {
|
|
9
|
+
style: "border-green-500 text-green-600",
|
|
10
|
+
value: "Active",
|
|
11
|
+
},
|
|
12
|
+
error: {
|
|
13
|
+
style: "border-red-500 text-red-600",
|
|
14
|
+
value: "Error",
|
|
15
|
+
},
|
|
16
|
+
warning: {
|
|
17
|
+
style: "border-yellow-500 text-yellow-600",
|
|
18
|
+
value: "Warning",
|
|
19
|
+
},
|
|
20
|
+
neutral: {
|
|
21
|
+
style: "border-gray-400 text-gray-600",
|
|
22
|
+
value: "Unknown",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// --------------------------------------------
|
|
27
|
+
// WRAPPER COMPONENT with real `status` prop
|
|
28
|
+
// --------------------------------------------
|
|
29
|
+
interface WrapperProps {
|
|
30
|
+
status: keyof typeof statusOptions;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Wrapper({ status, className }: WrapperProps) {
|
|
35
|
+
return (
|
|
36
|
+
<BooleanStatusButton
|
|
37
|
+
className={className}
|
|
38
|
+
getColor={() => statusOptions[status]}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --------------------------------------------
|
|
44
|
+
// Storybook meta (now uses Wrapper, not the original)
|
|
45
|
+
// --------------------------------------------
|
|
46
|
+
const meta: Meta<typeof Wrapper> = {
|
|
47
|
+
title: "Button/BooleanStatusButton",
|
|
48
|
+
component: Wrapper,
|
|
49
|
+
argTypes: {
|
|
50
|
+
status: {
|
|
51
|
+
control: "select",
|
|
52
|
+
options: Object.keys(statusOptions),
|
|
53
|
+
},
|
|
54
|
+
className: {
|
|
55
|
+
control: "text",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default meta;
|
|
61
|
+
|
|
62
|
+
type Story = StoryObj<typeof Wrapper>;
|
|
63
|
+
|
|
64
|
+
// --------------------------------------------
|
|
65
|
+
// STORIES
|
|
66
|
+
// --------------------------------------------
|
|
67
|
+
|
|
68
|
+
export const Default: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
status: "success",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const Error: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
status: "error",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Warning: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
status: "warning",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const Neutral: Story = {
|
|
87
|
+
args: {
|
|
88
|
+
status: "neutral",
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// import { cn } from "../../utils/cn";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
|
|
4
|
+
export interface BooleanStatusButtonProps {
|
|
5
|
+
getColor: () => {
|
|
6
|
+
style: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
};
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function BooleanStatusButton(props: BooleanStatusButtonProps) {
|
|
13
|
+
const { getColor, className } = props;
|
|
14
|
+
const data = getColor();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className={cn(
|
|
19
|
+
`border-[1px] mx-auto min-w-fit ltr:text-[13px] sm:ltr:text-sm rtl:text-[13px] sm:rtl:text-sm rtl:font-semibold w-fit flex items-center gap-x-2 ltr:py-1 rtl:py-[2px] px-[8px] rounded-full ${data.style}`,
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
className={`size-[12px] min-h-[12px] min-w-[12px] rounded-full border-[3px] ${data.style}`}
|
|
25
|
+
/>
|
|
26
|
+
<h1 className="text-nowrap">{data.value}</h1>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Button from "./button";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Button> = {
|
|
5
|
+
title: "Button/Button",
|
|
6
|
+
component: Button,
|
|
7
|
+
argTypes: {
|
|
8
|
+
variant: {
|
|
9
|
+
control: "select",
|
|
10
|
+
options: ["default", "primary", "secondary", "warning"],
|
|
11
|
+
},
|
|
12
|
+
disabled: {
|
|
13
|
+
control: "boolean",
|
|
14
|
+
},
|
|
15
|
+
children: {
|
|
16
|
+
control: "text",
|
|
17
|
+
},
|
|
18
|
+
onClick: { action: "clicked" },
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
type Story = StoryObj<typeof Button>;
|
|
25
|
+
|
|
26
|
+
export const Primary: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
children: "Primary Button",
|
|
29
|
+
variant: "primary",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Secondary: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
children: "Secondary Button",
|
|
36
|
+
variant: "secondary",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Warning: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
children: "Warning Button",
|
|
43
|
+
variant: "warning",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
export const Success: Story = {
|
|
47
|
+
args: {
|
|
48
|
+
children: "Success Button",
|
|
49
|
+
variant: "success",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -1,25 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
// import { cn } from "../../utils/cn";
|
|
1
3
|
import { cn } from "@/utils/cn";
|
|
2
|
-
import React from "react";
|
|
3
4
|
|
|
4
5
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
-
variant?: "
|
|
6
|
+
variant?: "primary" | "secondary" | "warning" | "success";
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
9
|
+
(props, ref: any) => {
|
|
10
|
+
const { className, children, variant, ...rest } = props;
|
|
11
|
+
const style =
|
|
12
|
+
variant == "secondary"
|
|
13
|
+
? "border hover:bg-primary hover:text-primary-foreground"
|
|
14
|
+
: variant == "warning"
|
|
15
|
+
? "bg-red-500 text-primary-foreground"
|
|
16
|
+
: variant == "success"
|
|
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";
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
{...rest}
|
|
22
|
+
ref={ref}
|
|
23
|
+
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
|
+
style,
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
25
36
|
export default Button;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import Button from "../button/button";
|
|
2
|
+
import AnimatedSheet from "./AnimatedSheet";
|
|
3
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof AnimatedSheet> = {
|
|
7
|
+
title: "Sheet/AnimatedSheet",
|
|
8
|
+
component: AnimatedSheet,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "fullscreen",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof AnimatedSheet>;
|
|
17
|
+
|
|
18
|
+
// ----------------------------------------------------
|
|
19
|
+
// WRAPPER (controls open/close since AnimatedSheet
|
|
20
|
+
// returns `button` when closed)
|
|
21
|
+
// ----------------------------------------------------
|
|
22
|
+
function SheetWrapper(args: any) {
|
|
23
|
+
const [open, setOpen] = useState(false);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<AnimatedSheet
|
|
27
|
+
{...args}
|
|
28
|
+
open={open}
|
|
29
|
+
onClose={() => setOpen(false)}
|
|
30
|
+
button={
|
|
31
|
+
<Button variant="primary" onClick={() => setOpen(true)}>
|
|
32
|
+
Open Sheet
|
|
33
|
+
</Button>
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
<div className="grid gap-x-4 gap-y-6 p-5">
|
|
37
|
+
<div className="mb-12 pb-4">
|
|
38
|
+
<h1 className="rtl:text-3xl-rtl ltr:text-2xl-ltr text-fourth">
|
|
39
|
+
Account password
|
|
40
|
+
</h1>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</AnimatedSheet>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ----------------------------------------------------
|
|
48
|
+
// STORIES
|
|
49
|
+
// ----------------------------------------------------
|
|
50
|
+
|
|
51
|
+
// 🔵 DEFAULT — also acts as alias for Right (fixes HMR)
|
|
52
|
+
export const Default: Story = {
|
|
53
|
+
render: (args) => <SheetWrapper {...args} />,
|
|
54
|
+
args: {
|
|
55
|
+
position: "right",
|
|
56
|
+
animate: "right",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// ▶️ Right
|
|
61
|
+
export const Right: Story = {
|
|
62
|
+
render: (args) => <SheetWrapper {...args} />,
|
|
63
|
+
args: {
|
|
64
|
+
position: "right",
|
|
65
|
+
animate: "right",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ◀️ Left
|
|
70
|
+
export const Left: Story = {
|
|
71
|
+
render: (args) => <SheetWrapper {...args} />,
|
|
72
|
+
args: {
|
|
73
|
+
position: "left",
|
|
74
|
+
animate: "left",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// 🔼 Top
|
|
79
|
+
export const Top: Story = {
|
|
80
|
+
render: (args) => <SheetWrapper {...args} />,
|
|
81
|
+
args: {
|
|
82
|
+
position: "top",
|
|
83
|
+
animate: "top",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// 🔽 Bottom
|
|
88
|
+
export const Bottom: Story = {
|
|
89
|
+
render: (args) => <SheetWrapper {...args} />,
|
|
90
|
+
args: {
|
|
91
|
+
position: "bottom",
|
|
92
|
+
animate: "bottom",
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// ⭕ Center
|
|
97
|
+
export const Center: Story = {
|
|
98
|
+
render: (args) => <SheetWrapper {...args} />,
|
|
99
|
+
args: {
|
|
100
|
+
position: "center",
|
|
101
|
+
animate: "bottom", // slide-up looks best for modal center
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// import { cn } from "../../utils/cn";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
|
|
4
|
+
import { useTransition, animated } from "@react-spring/web";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
|
7
|
+
import ReactDOM from "react-dom";
|
|
8
|
+
|
|
9
|
+
type PositionType = "center" | "left" | "top" | "bottom" | "right";
|
|
10
|
+
type AnimateType = "left" | "top" | "right" | "bottom";
|
|
11
|
+
type AnimatedSheetProps = {
|
|
12
|
+
open?: boolean;
|
|
13
|
+
button?: ReactNode;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
onClose?: (open: boolean) => void;
|
|
16
|
+
style?: {
|
|
17
|
+
rootContainerClassName?: string;
|
|
18
|
+
mainContainerClassName?: string;
|
|
19
|
+
iconClassName?: string;
|
|
20
|
+
};
|
|
21
|
+
position: PositionType;
|
|
22
|
+
animate: AnimateType;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default function AnimatedSheet({
|
|
26
|
+
open,
|
|
27
|
+
onClose,
|
|
28
|
+
children,
|
|
29
|
+
button,
|
|
30
|
+
style,
|
|
31
|
+
position,
|
|
32
|
+
animate,
|
|
33
|
+
}: AnimatedSheetProps) {
|
|
34
|
+
const { rootContainerClassName, mainContainerClassName, iconClassName } =
|
|
35
|
+
style || {};
|
|
36
|
+
const [status, setStatus] = useState<boolean | undefined>(false);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setStatus(open);
|
|
40
|
+
}, [open]);
|
|
41
|
+
const itemsPosition = useMemo(() => {
|
|
42
|
+
const animation =
|
|
43
|
+
animate == "left"
|
|
44
|
+
? "translateX(-100%)"
|
|
45
|
+
: animate == "right"
|
|
46
|
+
? "translateX(100%)"
|
|
47
|
+
: animate == "top"
|
|
48
|
+
? "translateY(-100%)"
|
|
49
|
+
: "translateY(100%)";
|
|
50
|
+
|
|
51
|
+
return position == "left"
|
|
52
|
+
? {
|
|
53
|
+
rootContainer: "rtl:left-0 ltr:right-0 top-0",
|
|
54
|
+
icon: "",
|
|
55
|
+
transitionFrom: animation,
|
|
56
|
+
transitionEnter: "translateX(0%)",
|
|
57
|
+
}
|
|
58
|
+
: position == "right"
|
|
59
|
+
? {
|
|
60
|
+
rootContainer: "rtl:right-0 ltr:left-0 top-0",
|
|
61
|
+
icon: "",
|
|
62
|
+
transitionFrom: animation,
|
|
63
|
+
transitionEnter: "translateX(0%)",
|
|
64
|
+
}
|
|
65
|
+
: position == "center"
|
|
66
|
+
? {
|
|
67
|
+
rootContainer:
|
|
68
|
+
"rtl:left-1/2 ltr:right-1/2 top-0 rtl:-translate-x-1/2 ltr:translate-x-1/2",
|
|
69
|
+
icon: "rtl:order-1",
|
|
70
|
+
transitionFrom: animation,
|
|
71
|
+
transitionEnter: "translateX(0%)",
|
|
72
|
+
}
|
|
73
|
+
: position == "top"
|
|
74
|
+
? {
|
|
75
|
+
rootContainer:
|
|
76
|
+
"rtl:left-1/2 ltr:right-1/2 top-0 rtl:-translate-x-1/2 ltr:translate-x-1/2",
|
|
77
|
+
icon: "order-1",
|
|
78
|
+
transitionFrom: animation,
|
|
79
|
+
transitionEnter: "translateY(0%)",
|
|
80
|
+
}
|
|
81
|
+
: {
|
|
82
|
+
rootContainer:
|
|
83
|
+
"rtl:left-1/2 ltr:right-1/2 top-0 rtl:-translate-x-1/2 ltr:translate-x-1/2",
|
|
84
|
+
icon: "order-1",
|
|
85
|
+
transitionFrom: animation,
|
|
86
|
+
transitionEnter: "translateY(0%)",
|
|
87
|
+
};
|
|
88
|
+
}, [position]);
|
|
89
|
+
|
|
90
|
+
const transitions = useTransition(status, {
|
|
91
|
+
from: {
|
|
92
|
+
opacity: 0,
|
|
93
|
+
transform: itemsPosition.transitionFrom,
|
|
94
|
+
},
|
|
95
|
+
enter: { opacity: 1, transform: itemsPosition.transitionEnter },
|
|
96
|
+
config: { tension: 250, friction: 30 },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const overlayTransitions = useTransition(status, {
|
|
100
|
+
from: { opacity: 0 },
|
|
101
|
+
enter: { opacity: 0.5 },
|
|
102
|
+
config: { tension: 250, friction: 30 },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const onCloseSheet = () => {
|
|
106
|
+
if (onClose) onClose(!status);
|
|
107
|
+
setStatus((prev) => !prev);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (!status) return button;
|
|
111
|
+
|
|
112
|
+
return ReactDOM.createPortal(
|
|
113
|
+
<>
|
|
114
|
+
{overlayTransitions(
|
|
115
|
+
(style, item) =>
|
|
116
|
+
item && (
|
|
117
|
+
<animated.div
|
|
118
|
+
style={style}
|
|
119
|
+
onClick={onCloseSheet}
|
|
120
|
+
className="fixed bg-card inset-0 z-50"
|
|
121
|
+
aria-hidden="true"
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{transitions(
|
|
127
|
+
(style, item) =>
|
|
128
|
+
item && (
|
|
129
|
+
<div
|
|
130
|
+
onClick={onCloseSheet}
|
|
131
|
+
className={cn(
|
|
132
|
+
"fixed flex w-screen xxl:min-w-[400px] xxl:w-fit z-50 h-screen justify-end",
|
|
133
|
+
itemsPosition.rootContainer,
|
|
134
|
+
rootContainerClassName
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
<X
|
|
138
|
+
onClick={onCloseSheet}
|
|
139
|
+
className={cn(
|
|
140
|
+
"fixed xxl:static z-[51] text-primary-foreground bg-primary cursor-pointer rounded-full h-6 w-7 p-1 sm:m-4 m-2 hover:bg-primary/90 xxl:hover:bg-primary/90 transition-colors",
|
|
141
|
+
itemsPosition.icon,
|
|
142
|
+
iconClassName
|
|
143
|
+
)}
|
|
144
|
+
/>
|
|
145
|
+
<animated.div
|
|
146
|
+
style={style}
|
|
147
|
+
className={cn(
|
|
148
|
+
"h-full w-full z-50 backdrop-blur-lg bg-white/10 border border-white/20 shadow-lg",
|
|
149
|
+
mainContainerClassName
|
|
150
|
+
)}
|
|
151
|
+
role="dialog"
|
|
152
|
+
aria-modal="true"
|
|
153
|
+
onClick={(e) => e.stopPropagation()}
|
|
154
|
+
>
|
|
155
|
+
{children}
|
|
156
|
+
</animated.div>
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
)}
|
|
160
|
+
</>,
|
|
161
|
+
document.body
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ShiningText } from "@/templates/shining-text/shining-text";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ShiningText> = {
|
|
5
|
+
title: "Components/ShiningText",
|
|
6
|
+
component: ShiningText,
|
|
7
|
+
args: {
|
|
8
|
+
text: "Loading...",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof ShiningText>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
text: "Loading...",
|
|
19
|
+
className: "text-4xl font-bold",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Large: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
text: "Loading...",
|
|
26
|
+
className: "text-6xl from-black via-gray-100 to-black font-extrabold",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const CustomGradient: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
text: "Custom Gradient",
|
|
33
|
+
className:
|
|
34
|
+
"text-5xl font-semibold from-black via-white to-blue-400 font-extrabold",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Small: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
text: "Loading...",
|
|
41
|
+
className: "text-md font-medium from-black via-gray-100 to-black",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useSpring, animated } from "@react-spring/web";
|
|
3
|
+
// import { cn } from "../../utils/cn";
|
|
4
|
+
import { cn } from "@/utils/cn";
|
|
5
|
+
|
|
6
|
+
interface ShiningTextProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ShiningText({ text, className, ...props }: ShiningTextProps) {
|
|
11
|
+
// Animate strictly left → right
|
|
12
|
+
const styles = useSpring({
|
|
13
|
+
from: { backgroundPosition: "-100% 0%" }, // start offscreen left
|
|
14
|
+
to: { backgroundPosition: "100% 0%" }, // end offscreen right
|
|
15
|
+
loop: true,
|
|
16
|
+
config: { duration: 2000 },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<animated.span
|
|
21
|
+
style={{
|
|
22
|
+
backgroundSize: "200% 50%",
|
|
23
|
+
...styles,
|
|
24
|
+
}}
|
|
25
|
+
className={cn(
|
|
26
|
+
"bg-gradient-to-r from-gray-300 via-white to-gray-300", // left→right gradient
|
|
27
|
+
"bg-clip-text text-transparent",
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{text}
|
|
33
|
+
</animated.span>
|
|
34
|
+
);
|
|
35
|
+
}
|