@launchui/launch-ui 1.0.0 → 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.0",
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
+ }
@@ -12,10 +12,12 @@ export const CardContainer = ({
12
12
  children,
13
13
  className,
14
14
  containerClassName,
15
+ style,
15
16
  }: {
16
17
  children: React.ReactNode;
17
18
  className?: string;
18
19
  containerClassName?: string;
20
+ style?: React.CSSProperties;
19
21
  }) => {
20
22
  const containerRef = useRef<HTMLDivElement>(null);
21
23
  const [isMouseEntered, setIsMouseEntered] = useState(false);
@@ -72,6 +74,7 @@ export const CardContainer = ({
72
74
  transformStyle: "preserve-3d",
73
75
  rotateX,
74
76
  rotateY,
77
+ ...style
75
78
  }}
76
79
  className={`flex items-center justify-center relative transition-all duration-200 ease-linear ${className}`}
77
80
  >
@@ -88,16 +91,19 @@ export const CardContainer = ({
88
91
  export const CardBody = ({
89
92
  children,
90
93
  className,
94
+ style,
91
95
  }: {
92
96
  children: React.ReactNode;
93
97
  className?: string;
98
+ style?: React.CSSProperties;
94
99
  }) => {
95
100
  return (
96
101
  <div
97
102
  style={{
98
103
  transformStyle: "preserve-3d",
104
+ ...style
99
105
  }}
100
- className={`h-96 w-96 [transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d] ${className}`}
106
+ className={`[transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d] ${className}`}
101
107
  >
102
108
  {children}
103
109
  </div>
@@ -167,7 +173,12 @@ interface ThreeDCardProps {
167
173
  description?: string;
168
174
  imageUrl?: string;
169
175
  ctaText?: string;
176
+ ctaHref?: string;
170
177
  actionText?: string;
178
+ actionHref?: string;
179
+ className?: string;
180
+ containerClassName?: string;
181
+ style?: React.CSSProperties;
171
182
  }
172
183
 
173
184
  export function ThreeDCard({
@@ -175,16 +186,24 @@ export function ThreeDCard({
175
186
  description = "Hover over this card to unleash the power of CSS perspective",
176
187
  imageUrl = "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=2560&auto=format&fit=crop",
177
188
  ctaText = "Try now \u2192",
178
- actionText = "Sign up"
189
+ ctaHref = "#",
190
+ actionText = "Sign up",
191
+ actionHref = "#",
192
+ className,
193
+ containerClassName,
194
+ style
179
195
  }: ThreeDCardProps) {
180
196
  return (
181
- <CardContainer className="inter-var">
182
- <CardBody className="bg-white relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-[#0a0a0a] dark:border-white/[0.2] border-neutral-200 border-opacity-50 w-auto sm:w-[30rem] h-auto rounded-2xl p-8 border shadow-sm">
197
+ <CardContainer className={`inter-var w-full flex justify-center items-center ${containerClassName}`}>
198
+ <CardBody
199
+ style={style}
200
+ 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}`}
201
+ >
183
202
 
184
203
  {/* Title Elevated */}
185
204
  <CardItem
186
205
  translateZ="50"
187
- className="text-2xl font-bold text-neutral-800 dark:text-white"
206
+ className="text-xl font-bold text-neutral-600 dark:text-white"
188
207
  >
189
208
  {title}
190
209
  </CardItem>
@@ -193,18 +212,18 @@ export function ThreeDCard({
193
212
  <CardItem
194
213
  as="p"
195
214
  translateZ="60"
196
- className="text-neutral-500 text-base max-w-sm mt-2 dark:text-neutral-300 leading-relaxed"
215
+ className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
197
216
  >
198
217
  {description}
199
218
  </CardItem>
200
219
 
201
220
  {/* Imagery container with maximum depth and radius */}
202
- <CardItem translateZ="100" className="w-full mt-8">
221
+ <CardItem translateZ="100" className="w-full mt-4">
203
222
  <img
204
223
  src={imageUrl}
205
224
  height="1000"
206
225
  width="1000"
207
- className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl transition-shadow"
226
+ className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl"
208
227
  alt="thumbnail"
209
228
  />
210
229
  </CardItem>
@@ -214,8 +233,9 @@ export function ThreeDCard({
214
233
  {/* Lower left navigation prompt */}
215
234
  <CardItem
216
235
  translateZ={20}
217
- as="button"
218
- className="px-2 py-2 text-sm font-medium text-neutral-800 dark:text-white hover:underline underline-offset-4"
236
+ as="a"
237
+ href={ctaHref}
238
+ 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"
219
239
  >
220
240
  {ctaText}
221
241
  </CardItem>
@@ -223,8 +243,9 @@ export function ThreeDCard({
223
243
  {/* Right primary action capsule */}
224
244
  <CardItem
225
245
  translateZ={20}
226
- as="button"
227
- className="px-5 py-2.5 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold shadow-md transition-all active:scale-95"
246
+ as="a"
247
+ href={actionHref}
248
+ 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"
228
249
  >
229
250
  {actionText}
230
251
  </CardItem>
@@ -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
+ };