@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchui/launch-ui",
3
- "version": "1.0.1",
3
+ "version": "1.0.7",
4
4
  "description": "A shadcn/ui inspired CLI component installer for Next, Vite, React, Astro.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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
+ };