@launchui/launch-ui 1.0.1 → 1.0.7
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/registry/components/3d-card/3d-card-2.tsx +258 -0
- package/registry/components/3d-pin-card/3d-pin-card.tsx +172 -0
- package/registry/components/Keyboard/Keyboard.tsx +885 -0
- package/registry/components/comet-card/comet-card.tsx +122 -0
- package/registry/components/macbook-scroll/macbook-scroll.tsx +611 -0
- package/registry/components/text-flipping-board/text-flipping-board.tsx +449 -0
- /package/registry/components/3d-card/{index.tsx → 3d-card.tsx} +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useState, useContext, useRef, useEffect } from "react";
|
|
4
|
+
import { motion, useMotionValue, useSpring, useTransform } from "motion/react";
|
|
5
|
+
|
|
6
|
+
const MouseEnterContext = createContext<[boolean, React.Dispatch<React.SetStateAction<boolean>>] | undefined>(undefined);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The main provider managing the perspective grid and mouse rotation values.
|
|
10
|
+
*/
|
|
11
|
+
export const CardContainer = ({
|
|
12
|
+
children,
|
|
13
|
+
className,
|
|
14
|
+
containerClassName,
|
|
15
|
+
style,
|
|
16
|
+
}: {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
containerClassName?: string;
|
|
20
|
+
style?: React.CSSProperties;
|
|
21
|
+
}) => {
|
|
22
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
const [isMouseEntered, setIsMouseEntered] = useState(false);
|
|
24
|
+
|
|
25
|
+
const x = useMotionValue(0);
|
|
26
|
+
const y = useMotionValue(0);
|
|
27
|
+
|
|
28
|
+
const springX = useSpring(x, { stiffness: 150, damping: 15 });
|
|
29
|
+
const springY = useSpring(y, { stiffness: 150, damping: 15 });
|
|
30
|
+
|
|
31
|
+
const rotateX = useTransform(springY, [-0.5, 0.5], [15, -15]);
|
|
32
|
+
const rotateY = useTransform(springX, [-0.5, 0.5], [-15, 15]);
|
|
33
|
+
|
|
34
|
+
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
35
|
+
if (!containerRef.current) return;
|
|
36
|
+
const { left, top, width, height } = containerRef.current.getBoundingClientRect();
|
|
37
|
+
|
|
38
|
+
const currentX = (e.clientX - left) / width - 0.5;
|
|
39
|
+
const currentY = (e.clientY - top) / height - 0.5;
|
|
40
|
+
|
|
41
|
+
x.set(currentX);
|
|
42
|
+
y.set(currentY);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleMouseEnter = () => {
|
|
46
|
+
setIsMouseEntered(true);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleMouseLeave = () => {
|
|
50
|
+
setIsMouseEntered(false);
|
|
51
|
+
x.set(0);
|
|
52
|
+
y.set(0);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
|
|
57
|
+
<div
|
|
58
|
+
className={`flex items-center justify-center ${containerClassName}`}
|
|
59
|
+
style={{
|
|
60
|
+
perspective: "1000px",
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<motion.div
|
|
64
|
+
ref={containerRef}
|
|
65
|
+
onMouseEnter={handleMouseEnter}
|
|
66
|
+
onMouseMove={handleMouseMove}
|
|
67
|
+
onMouseLeave={handleMouseLeave}
|
|
68
|
+
style={{
|
|
69
|
+
transformStyle: "preserve-3d",
|
|
70
|
+
rotateX,
|
|
71
|
+
rotateY,
|
|
72
|
+
...style
|
|
73
|
+
}}
|
|
74
|
+
className={`flex items-center justify-center relative transition-all duration-200 ease-linear ${className}`}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</motion.div>
|
|
78
|
+
</div>
|
|
79
|
+
</MouseEnterContext.Provider>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The inner canvas preserving standard layout properties while inheriting perspective physics.
|
|
85
|
+
*/
|
|
86
|
+
export const CardBody = ({
|
|
87
|
+
children,
|
|
88
|
+
className,
|
|
89
|
+
style,
|
|
90
|
+
}: {
|
|
91
|
+
children: React.ReactNode;
|
|
92
|
+
className?: string;
|
|
93
|
+
style?: React.CSSProperties;
|
|
94
|
+
}) => {
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
transformStyle: "preserve-3d",
|
|
99
|
+
...style
|
|
100
|
+
}}
|
|
101
|
+
className={`[transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d] ${className}`}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* High level utility granting elements elevation along the Z axis dynamically triggered on hover.
|
|
110
|
+
*/
|
|
111
|
+
export const CardItem = ({
|
|
112
|
+
as: Tag = "div",
|
|
113
|
+
children,
|
|
114
|
+
className,
|
|
115
|
+
translateX = 0,
|
|
116
|
+
translateY = 0,
|
|
117
|
+
translateZ = 0,
|
|
118
|
+
rotateX = 0,
|
|
119
|
+
rotateY = 0,
|
|
120
|
+
rotateZ = 0,
|
|
121
|
+
...rest
|
|
122
|
+
}: {
|
|
123
|
+
as?: any;
|
|
124
|
+
children: React.ReactNode;
|
|
125
|
+
className?: string;
|
|
126
|
+
translateX?: number | string;
|
|
127
|
+
translateY?: number | string;
|
|
128
|
+
translateZ?: number | string;
|
|
129
|
+
rotateX?: number | string;
|
|
130
|
+
rotateY?: number | string;
|
|
131
|
+
rotateZ?: number | string;
|
|
132
|
+
[key: string]: any;
|
|
133
|
+
}) => {
|
|
134
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
135
|
+
const context = useContext(MouseEnterContext);
|
|
136
|
+
const isMouseEntered = context ? context[0] : false;
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
handleAnimations();
|
|
140
|
+
}, [isMouseEntered]);
|
|
141
|
+
|
|
142
|
+
const handleAnimations = () => {
|
|
143
|
+
if (!ref.current) return;
|
|
144
|
+
if (isMouseEntered) {
|
|
145
|
+
ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`;
|
|
146
|
+
} else {
|
|
147
|
+
ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Tag
|
|
153
|
+
ref={ref}
|
|
154
|
+
className={`transition duration-200 ease-out ${className}`}
|
|
155
|
+
{...rest}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</Tag>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* SECOND VARIANT: Implements complex rotation offsets on inner components.
|
|
164
|
+
*/
|
|
165
|
+
interface ThreeDCard2Props {
|
|
166
|
+
title?: string;
|
|
167
|
+
description?: string;
|
|
168
|
+
imageUrl?: string;
|
|
169
|
+
ctaText?: string;
|
|
170
|
+
ctaHref?: string;
|
|
171
|
+
actionText?: string;
|
|
172
|
+
actionHref?: string;
|
|
173
|
+
className?: string;
|
|
174
|
+
containerClassName?: string;
|
|
175
|
+
style?: React.CSSProperties;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function ThreeDCard2({
|
|
179
|
+
title = "Make things float in air",
|
|
180
|
+
description = "Hover over this card to unleash the power of CSS perspective",
|
|
181
|
+
imageUrl = "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=2560&auto=format&fit=crop",
|
|
182
|
+
ctaText = "Try now \u2192",
|
|
183
|
+
ctaHref = "#",
|
|
184
|
+
actionText = "Sign up",
|
|
185
|
+
actionHref = "#",
|
|
186
|
+
className,
|
|
187
|
+
containerClassName,
|
|
188
|
+
style
|
|
189
|
+
}: ThreeDCard2Props) {
|
|
190
|
+
return (
|
|
191
|
+
<CardContainer className={`inter-var w-full flex justify-center items-center ${containerClassName}`}>
|
|
192
|
+
<CardBody
|
|
193
|
+
style={style}
|
|
194
|
+
className={`bg-gray-50 relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-black dark:border-white/[0.2] border-black/[0.1] w-full max-w-[22rem] sm:max-w-[30rem] h-auto rounded-xl p-6 border ${className}`}
|
|
195
|
+
>
|
|
196
|
+
|
|
197
|
+
{/* Title Elevated */}
|
|
198
|
+
<CardItem
|
|
199
|
+
translateZ="50"
|
|
200
|
+
className="text-xl font-bold text-neutral-600 dark:text-white"
|
|
201
|
+
>
|
|
202
|
+
{title}
|
|
203
|
+
</CardItem>
|
|
204
|
+
|
|
205
|
+
{/* Description Elevated slightly less */}
|
|
206
|
+
<CardItem
|
|
207
|
+
as="p"
|
|
208
|
+
translateZ="60"
|
|
209
|
+
className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
|
|
210
|
+
>
|
|
211
|
+
{description}
|
|
212
|
+
</CardItem>
|
|
213
|
+
|
|
214
|
+
{/* ADVANCED VARIANT: imagery uses explicit rotateX and rotateZ defaults on top of Z depth */}
|
|
215
|
+
<CardItem
|
|
216
|
+
translateZ="100"
|
|
217
|
+
rotateX={20}
|
|
218
|
+
rotateZ={-10}
|
|
219
|
+
className="w-full mt-4"
|
|
220
|
+
>
|
|
221
|
+
<img
|
|
222
|
+
src={imageUrl}
|
|
223
|
+
height="1000"
|
|
224
|
+
width="1000"
|
|
225
|
+
className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl"
|
|
226
|
+
alt="thumbnail"
|
|
227
|
+
/>
|
|
228
|
+
</CardItem>
|
|
229
|
+
|
|
230
|
+
{/* Footer actions stack */}
|
|
231
|
+
<div className="flex justify-between items-center mt-20">
|
|
232
|
+
|
|
233
|
+
{/* ADVANCED VARIANT: uses negative horizontal translation on trigger */}
|
|
234
|
+
<CardItem
|
|
235
|
+
translateZ={20}
|
|
236
|
+
translateX={-40}
|
|
237
|
+
as="a"
|
|
238
|
+
href={ctaHref}
|
|
239
|
+
className="px-4 py-2 rounded-xl text-xs font-normal text-black dark:text-white hover:underline underline-offset-4 cursor-pointer inline-block"
|
|
240
|
+
>
|
|
241
|
+
{ctaText}
|
|
242
|
+
</CardItem>
|
|
243
|
+
|
|
244
|
+
{/* ADVANCED VARIANT: uses positive horizontal translation on trigger */}
|
|
245
|
+
<CardItem
|
|
246
|
+
translateZ={20}
|
|
247
|
+
translateX={40}
|
|
248
|
+
as="a"
|
|
249
|
+
href={actionHref}
|
|
250
|
+
className="px-4 py-2 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold shadow-md transition-all active:scale-95 cursor-pointer inline-block"
|
|
251
|
+
>
|
|
252
|
+
{actionText}
|
|
253
|
+
</CardItem>
|
|
254
|
+
</div>
|
|
255
|
+
</CardBody>
|
|
256
|
+
</CardContainer>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal utility replacing external lib requirements ensuring zero-friction installations.
|
|
8
|
+
*/
|
|
9
|
+
const cn = (...classes: any[]) => {
|
|
10
|
+
return classes.filter(Boolean).join(" ");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const PinContainer = ({
|
|
14
|
+
children,
|
|
15
|
+
title,
|
|
16
|
+
href,
|
|
17
|
+
className,
|
|
18
|
+
containerClassName,
|
|
19
|
+
}: {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
title?: string;
|
|
22
|
+
href?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
containerClassName?: string;
|
|
25
|
+
}) => {
|
|
26
|
+
const [transform, setTransform] = useState(
|
|
27
|
+
"translate(-50%,-50%) rotateX(0deg)"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const onMouseEnter = () => {
|
|
31
|
+
setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)");
|
|
32
|
+
};
|
|
33
|
+
const onMouseLeave = () => {
|
|
34
|
+
setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<a
|
|
39
|
+
className={cn(
|
|
40
|
+
"relative group/pin z-50 cursor-pointer",
|
|
41
|
+
containerClassName
|
|
42
|
+
)}
|
|
43
|
+
onMouseEnter={onMouseEnter}
|
|
44
|
+
onMouseLeave={onMouseLeave}
|
|
45
|
+
href={href || "/"}
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
perspective: "1000px",
|
|
50
|
+
transform: "rotateX(70deg) translateZ(0deg)",
|
|
51
|
+
}}
|
|
52
|
+
className="absolute left-1/2 top-1/2 ml-[0.09375rem] mt-4 -translate-x-1/2 -translate-y-1/2"
|
|
53
|
+
>
|
|
54
|
+
<div
|
|
55
|
+
style={{
|
|
56
|
+
transform: transform,
|
|
57
|
+
}}
|
|
58
|
+
className="absolute left-1/2 p-4 top-1/2 flex justify-start items-start rounded-2xl shadow-[0_8px_16px_rgb(0_0_0/0.4)] bg-black border border-white/[0.1] group-hover/pin:border-white/[0.2] transition duration-700 overflow-hidden"
|
|
59
|
+
>
|
|
60
|
+
<div className={cn(" relative z-50 ", className)}>{children}</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<PinPerspective title={title} href={href} />
|
|
64
|
+
</a>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const PinPerspective = ({
|
|
69
|
+
title,
|
|
70
|
+
href,
|
|
71
|
+
}: {
|
|
72
|
+
title?: string;
|
|
73
|
+
href?: string;
|
|
74
|
+
}) => {
|
|
75
|
+
return (
|
|
76
|
+
<motion.div className="pointer-events-none w-96 h-80 flex items-center justify-center opacity-0 group-hover/pin:opacity-100 z-[60] transition duration-500">
|
|
77
|
+
<div className=" w-full h-full -mt-7 flex-none inset-0">
|
|
78
|
+
<div className="absolute top-0 inset-x-0 flex justify-center">
|
|
79
|
+
<a
|
|
80
|
+
href={href}
|
|
81
|
+
target={"_blank"}
|
|
82
|
+
className="relative flex space-x-2 items-center z-10 rounded-full bg-zinc-950 py-0.5 px-4 ring-1 ring-white/10 "
|
|
83
|
+
>
|
|
84
|
+
<span className="relative z-20 text-white text-xs font-bold inline-block py-0.5">
|
|
85
|
+
{title}
|
|
86
|
+
</span>
|
|
87
|
+
|
|
88
|
+
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-emerald-400/90 to-emerald-400/0 transition-opacity duration-500 group-hover/btn:opacity-40"></span>
|
|
89
|
+
</a>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div
|
|
93
|
+
style={{
|
|
94
|
+
perspective: "1000px",
|
|
95
|
+
transform: "rotateX(70deg) translateZ(0)",
|
|
96
|
+
}}
|
|
97
|
+
className="absolute left-1/2 top-1/2 ml-[0.09375rem] mt-4 -translate-x-1/2 -translate-y-1/2"
|
|
98
|
+
>
|
|
99
|
+
<>
|
|
100
|
+
<motion.div
|
|
101
|
+
initial={{
|
|
102
|
+
opacity: 0,
|
|
103
|
+
scale: 0,
|
|
104
|
+
x: "-50%",
|
|
105
|
+
y: "-50%",
|
|
106
|
+
}}
|
|
107
|
+
animate={{
|
|
108
|
+
opacity: [0, 1, 0.5, 0],
|
|
109
|
+
scale: 1,
|
|
110
|
+
|
|
111
|
+
z: 0,
|
|
112
|
+
}}
|
|
113
|
+
transition={{
|
|
114
|
+
duration: 6,
|
|
115
|
+
repeat: Infinity,
|
|
116
|
+
delay: 0,
|
|
117
|
+
}}
|
|
118
|
+
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
|
|
119
|
+
></motion.div>
|
|
120
|
+
<motion.div
|
|
121
|
+
initial={{
|
|
122
|
+
opacity: 0,
|
|
123
|
+
scale: 0,
|
|
124
|
+
x: "-50%",
|
|
125
|
+
y: "-50%",
|
|
126
|
+
}}
|
|
127
|
+
animate={{
|
|
128
|
+
opacity: [0, 1, 0.5, 0],
|
|
129
|
+
scale: 1,
|
|
130
|
+
|
|
131
|
+
z: 0,
|
|
132
|
+
}}
|
|
133
|
+
transition={{
|
|
134
|
+
duration: 6,
|
|
135
|
+
repeat: Infinity,
|
|
136
|
+
delay: 2,
|
|
137
|
+
}}
|
|
138
|
+
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
|
|
139
|
+
></motion.div>
|
|
140
|
+
<motion.div
|
|
141
|
+
initial={{
|
|
142
|
+
opacity: 0,
|
|
143
|
+
scale: 0,
|
|
144
|
+
x: "-50%",
|
|
145
|
+
y: "-50%",
|
|
146
|
+
}}
|
|
147
|
+
animate={{
|
|
148
|
+
opacity: [0, 1, 0.5, 0],
|
|
149
|
+
scale: 1,
|
|
150
|
+
|
|
151
|
+
z: 0,
|
|
152
|
+
}}
|
|
153
|
+
transition={{
|
|
154
|
+
duration: 6,
|
|
155
|
+
repeat: Infinity,
|
|
156
|
+
delay: 4,
|
|
157
|
+
}}
|
|
158
|
+
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
|
|
159
|
+
></motion.div>
|
|
160
|
+
</>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<>
|
|
164
|
+
<motion.div className="absolute right-1/2 bottom-1/2 bg-gradient-to-b from-transparent to-cyan-500 translate-y-[14px] w-px h-20 group-hover/pin:h-40 blur-[2px]" />
|
|
165
|
+
<motion.div className="absolute right-1/2 bottom-1/2 bg-gradient-to-b from-transparent to-cyan-500 translate-y-[14px] w-px h-20 group-hover/pin:h-40 " />
|
|
166
|
+
<motion.div className="absolute right-1/2 translate-x-[1.5px] bottom-1/2 bg-cyan-600 translate-y-[14px] w-[4px] h-[4px] rounded-full z-40 blur-[3px]" />
|
|
167
|
+
<motion.div className="absolute right-1/2 translate-x-[0.5px] bottom-1/2 bg-cyan-300 translate-y-[14px] w-[2px] h-[2px] rounded-full z-40 " />
|
|
168
|
+
</>
|
|
169
|
+
</div>
|
|
170
|
+
</motion.div>
|
|
171
|
+
);
|
|
172
|
+
};
|