@launchui/launch-ui 1.0.0

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/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import{Command as N}from"commander";import{Command as j}from"commander";import D from"prompts";import i from"picocolors";import T from"ora";import{execa as k}from"execa";import g from"fs-extra";import f from"path";async function x(r=process.cwd()){let e=process.env.npm_config_user_agent||"";if(e.startsWith("yarn"))return"yarn";if(e.startsWith("pnpm"))return"pnpm";if(e.startsWith("bun"))return"bun";try{if(await g.pathExists(f.resolve(r,"pnpm-lock.yaml")))return"pnpm";if(await g.pathExists(f.resolve(r,"yarn.lock")))return"yarn";if(await g.pathExists(f.resolve(r,"bun.lockb")))return"bun"}catch{}return"npm"}var P=new j().name("init").description("initialize your project and install dependencies").action(async()=>{try{let r=await D([{type:"select",name:"framework",message:"Which framework are you using?",choices:[{title:"Next.js",value:"nextjs"},{title:"Vite",value:"vite"},{title:"Astro",value:"astro"},{title:"React Router",value:"react-router"}],initial:0},{type:"confirm",name:"tailwind",message:"Are you using Tailwind CSS?",initial:!0}]);if(!r.framework){console.log(i.yellow("Operation cancelled"));return}let e=T("Detecting package manager...").start(),o=await x();e.text=`Installing dependencies using ${o}...`;try{let a=["motion"],n=o==="yarn"?"add":"install";await k(o,[n,...a]),r.tailwind&&(e.text="Installing Tailwind CSS...",await k(o,[n,o==="yarn"?"-D":"--save-dev",...["tailwindcss","@tailwindcss/postcss","@tailwindcss/cli"]])),e.succeed(i.green("Dependencies installed successfully!"))}catch(a){e.fail(i.red("Failed to install dependencies.")),console.error(a);return}console.log(""),console.log("Run "+i.cyan("npx @launchui/launch-ui add [component]")+" to install new UI.")}catch{console.error(i.red(`
3
+ An unexpected error occurred. Try again.`))}});import{Command as E}from"commander";import s from"picocolors";import R from"ora";import l from"fs-extra";import t from"path";import{fileURLToPath as _}from"url";var S=new E().name("add").description("add a component to your project").argument("[components...]","the components to add").action(async r=>{try{if(!r||r.length===0){console.log(s.red("Error: Please specify at least one component to add.")),console.log(`Run ${s.cyan("npx @launchui/launch-ui add --help")} for usage.`);return}let e=r,o=t.dirname(_(import.meta.url)),a=t.resolve(o,"../registry/components"),n=process.cwd(),m=await l.pathExists(t.resolve(n,"src"))?t.resolve(n,"src","components","ui"):t.resolve(n,"components","ui"),d=R("Preparing components...").start();for(let u of e){let h=u.toLowerCase(),w=t.resolve(a,h);if(d.text=`Adding ${u}...`,!await l.pathExists(w)){d.warn(s.yellow(`Component "${u}" could not be found in the registry.`));continue}let v=t.resolve(m,h);await l.ensureDir(v),await l.copy(w,v)}d.succeed(s.green("Success! Selected component(s) have been added to your project."));let I=t.relative(n,m);console.log(""),console.log(`Files were installed to: ${s.cyan(I)}`)}catch{console.error(s.red(`
4
+ An unexpected error occurred.`))}});import b from"fs-extra";import y from"path";import{fileURLToPath as M}from"url";async function A(){let r=y.dirname(M(import.meta.url)),e=y.resolve(r,"../package.json");try{return await b.readJSON(e)}catch{try{return await b.readJSON(y.resolve(r,"../../package.json"))}catch{return{version:"1.0.0"}}}}process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));async function $(){let r=await A(),e=new N().name("@launchui/launch-ui").description("Add components and dependencies to your project").version(r.version||"1.0.0","-v, --version","display the version number");e.addCommand(P),e.addCommand(S),e.parse()}$();
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/get-package-manager.ts","../src/commands/add.ts","../src/utils/get-package-info.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\"\nimport { init } from \"./commands/init.js\"\nimport { add } from \"./commands/add.js\"\nimport { getPackageInfo } from \"./utils/get-package-info.js\"\n\nprocess.on(\"SIGINT\", () => process.exit(0))\nprocess.on(\"SIGTERM\", () => process.exit(0))\n\nasync function main() {\n const packageInfo = await getPackageInfo()\n\n const program = new Command()\n .name(\"@launchui/launch-ui\")\n .description(\"Add components and dependencies to your project\")\n .version(\n packageInfo.version || \"1.0.0\",\n \"-v, --version\",\n \"display the version number\"\n )\n\n program.addCommand(init)\n program.addCommand(add)\n\n program.parse()\n}\n\nmain()\n","import { Command } from \"commander\"\nimport prompts from \"prompts\"\nimport picocolors from \"picocolors\"\nimport ora from \"ora\"\nimport { execa } from \"execa\"\nimport { getPackageManager } from \"../utils/get-package-manager.js\"\n\nexport const init = new Command()\n .name(\"init\")\n .description(\"initialize your project and install dependencies\")\n .action(async () => {\n try {\n const options = await prompts([\n {\n type: \"select\",\n name: \"framework\",\n message: \"Which framework are you using?\",\n choices: [\n { title: \"Next.js\", value: \"nextjs\" },\n { title: \"Vite\", value: \"vite\" },\n { title: \"Astro\", value: \"astro\" },\n { title: \"React Router\", value: \"react-router\" },\n ],\n initial: 0,\n },\n {\n type: \"confirm\",\n name: \"tailwind\",\n message: \"Are you using Tailwind CSS?\",\n initial: true,\n },\n ])\n\n if (!options.framework) {\n console.log(picocolors.yellow(\"Operation cancelled\"))\n return\n }\n\n const spinner = ora(`Detecting package manager...`).start()\n const packageManager = await getPackageManager()\n spinner.text = `Installing dependencies using ${packageManager}...`\n\n try {\n // 1. Core Dependencies (motion)\n const dependencies = [\"motion\"]\n const installArg = packageManager === \"yarn\" ? \"add\" : \"install\"\n \n await execa(packageManager, [installArg, ...dependencies])\n \n // 2. Tailwind Dependencies (if user needs them)\n if (options.tailwind) {\n spinner.text = `Installing Tailwind CSS...`\n const devDependencies = [\"tailwindcss\", \"@tailwindcss/postcss\", \"@tailwindcss/cli\"]\n const devArg = packageManager === \"yarn\" ? \"-D\" : \"--save-dev\"\n \n await execa(packageManager, [installArg, devArg, ...devDependencies])\n }\n\n spinner.succeed(\n picocolors.green(`Dependencies installed successfully!`)\n )\n } catch (error) {\n spinner.fail(picocolors.red(`Failed to install dependencies.`))\n console.error(error)\n return\n }\n \n console.log(\"\")\n console.log(\"Run \" + picocolors.cyan(\"npx @launchui/launch-ui add [component]\") + \" to install new UI.\")\n } catch (error) {\n console.error(picocolors.red(\"\\nAn unexpected error occurred. Try again.\"))\n }\n })\n","import fs from \"fs-extra\"\nimport path from \"path\"\n\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\" | \"bun\"\n\nexport async function getPackageManager(cwd: string = process.cwd()): Promise<PackageManager> {\n const userAgent = process.env.npm_config_user_agent || \"\"\n\n if (userAgent.startsWith(\"yarn\")) {\n return \"yarn\"\n }\n\n if (userAgent.startsWith(\"pnpm\")) {\n return \"pnpm\"\n }\n\n if (userAgent.startsWith(\"bun\")) {\n return \"bun\"\n }\n\n try {\n if (await fs.pathExists(path.resolve(cwd, \"pnpm-lock.yaml\"))) {\n return \"pnpm\"\n }\n\n if (await fs.pathExists(path.resolve(cwd, \"yarn.lock\"))) {\n return \"yarn\"\n }\n\n if (await fs.pathExists(path.resolve(cwd, \"bun.lockb\"))) {\n return \"bun\"\n }\n } catch (error) {\n // Fallback\n }\n\n return \"npm\"\n}\n","import { Command } from \"commander\"\nimport picocolors from \"picocolors\"\nimport ora from \"ora\"\nimport fs from \"fs-extra\"\nimport path from \"path\"\nimport { fileURLToPath } from \"url\"\n\nexport const add = new Command()\n .name(\"add\")\n .description(\"add a component to your project\")\n .argument(\"[components...]\", \"the components to add\")\n .action(async (components) => {\n try {\n if (!components || components.length === 0) {\n console.log(picocolors.red(\"Error: Please specify at least one component to add.\"))\n console.log(`Run ${picocolors.cyan(\"npx @launchui/launch-ui add --help\")} for usage.`)\n return\n }\n let selectedComponents = components\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url))\n // Registry is located at dist/../registry relative to the compiled file\n const registryPath = path.resolve(__dirname, \"../registry/components\")\n \n // Detect base path for user's components (support for standard src/ directory)\n const cwd = process.cwd()\n const hasSrc = await fs.pathExists(path.resolve(cwd, \"src\"))\n const targetBaseDir = hasSrc \n ? path.resolve(cwd, \"src\", \"components\", \"ui\") \n : path.resolve(cwd, \"components\", \"ui\")\n\n const spinner = ora(`Preparing components...`).start()\n\n for (const componentName of selectedComponents) {\n // Handle lowercasing or specific mapping if required\n const compDirName = componentName.toLowerCase()\n const sourcePath = path.resolve(registryPath, compDirName)\n \n spinner.text = `Adding ${componentName}...`\n\n // Check if registry directory for component exists\n if (!(await fs.pathExists(sourcePath))) {\n spinner.warn(picocolors.yellow(`Component \"${componentName}\" could not be found in the registry.`))\n continue\n }\n\n // Target directory: current_cwd/components/ui/3d-card\n const targetPath = path.resolve(targetBaseDir, compDirName)\n \n // Deep copy full directory contents (index.tsx, styles, etc)\n await fs.ensureDir(targetPath)\n await fs.copy(sourcePath, targetPath)\n }\n\n spinner.succeed(\n picocolors.green(`Success! Selected component(s) have been added to your project.`)\n )\n \n const relativeTarget = path.relative(cwd, targetBaseDir)\n console.log(\"\")\n console.log(`Files were installed to: ${picocolors.cyan(relativeTarget)}`)\n } catch (error) {\n console.error(picocolors.red(\"\\nAn unexpected error occurred.\"))\n }\n })\n","import fs from \"fs-extra\"\nimport path from \"path\"\nimport { fileURLToPath } from \"url\"\n\nexport async function getPackageInfo() {\n const __dirname = path.dirname(fileURLToPath(import.meta.url))\n // When built, package.json might be up a level\n const packageJsonPath = path.resolve(__dirname, \"../package.json\")\n \n try {\n return await fs.readJSON(packageJsonPath)\n } catch (error) {\n // Fallback during dev if execution occurs in src directory directly\n try {\n return await fs.readJSON(path.resolve(__dirname, \"../../package.json\"))\n } catch (e) {\n return { version: \"1.0.0\" }\n }\n }\n}\n"],"mappings":"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","names":["Command","Command","prompts","picocolors","ora","execa","fs","path","getPackageManager","cwd","userAgent","init","Command","options","prompts","picocolors","spinner","ora","packageManager","getPackageManager","dependencies","installArg","execa","error","Command","picocolors","ora","fs","path","fileURLToPath","add","components","selectedComponents","__dirname","registryPath","cwd","targetBaseDir","spinner","componentName","compDirName","sourcePath","targetPath","relativeTarget","fs","path","fileURLToPath","getPackageInfo","__dirname","packageJsonPath","main","packageInfo","getPackageInfo","program","Command","init","add"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@launchui/launch-ui",
3
+ "version": "1.0.0",
4
+ "description": "A shadcn/ui inspired CLI component installer for Next, Vite, React, Astro.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "launch-ui": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "registry",
15
+ "package.json"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "start": "node dist/index.js",
21
+ "test": "echo \"Error: no test specified\" && exit 1"
22
+ },
23
+ "dependencies": {
24
+ "commander": "^14.0.3",
25
+ "execa": "^9.6.1",
26
+ "fs-extra": "^11.3.5",
27
+ "ora": "^9.4.0",
28
+ "picocolors": "^1.1.1",
29
+ "prompts": "^2.4.2",
30
+ "zod": "^4.4.3"
31
+ },
32
+ "devDependencies": {
33
+ "@types/chalk": "^0.4.31",
34
+ "@types/fs-extra": "^11.0.4",
35
+ "@types/node": "^25.7.0",
36
+ "@types/prompts": "^2.4.9",
37
+ "@types/react": "^19.2.14",
38
+ "@types/react-dom": "^19.2.3",
39
+ "motion": "^12.38.0",
40
+ "react": "^19.2.6",
41
+ "react-dom": "^19.2.6",
42
+ "tsup": "^8.5.1",
43
+ "typescript": "^6.0.3"
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ # UI Registry Folder
2
+
3
+ Place all your component source files here.
4
+
5
+ ## Example Structure:
6
+ `registry/components/button.tsx`
7
+ `registry/components/card.tsx`
8
+
9
+ When building the final deployment of your CLI, the `add` command will read files from this folder and dynamically copy them directly into your user's project directories!
@@ -0,0 +1,235 @@
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
+ }: {
16
+ children: React.ReactNode;
17
+ className?: string;
18
+ containerClassName?: string;
19
+ }) => {
20
+ const containerRef = useRef<HTMLDivElement>(null);
21
+ const [isMouseEntered, setIsMouseEntered] = useState(false);
22
+
23
+ // Standard motion values tracking exact cursor coordinate state relative to container bounds
24
+ const x = useMotionValue(0);
25
+ const y = useMotionValue(0);
26
+
27
+ // Smooth spring dampening prevents jarring rotations, producing the premium heavy-glass feeling.
28
+ const springX = useSpring(x, { stiffness: 150, damping: 15 });
29
+ const springY = useSpring(y, { stiffness: 150, damping: 15 });
30
+
31
+ // Map raw normalized values from center to extreme degree rotation mappings
32
+ const rotateX = useTransform(springY, [-0.5, 0.5], [15, -15]);
33
+ const rotateY = useTransform(springX, [-0.5, 0.5], [-15, 15]);
34
+
35
+ const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
36
+ if (!containerRef.current) return;
37
+ const { left, top, width, height } = containerRef.current.getBoundingClientRect();
38
+
39
+ // Calculate normalized position (-0.5 to 0.5) relative to the element center
40
+ const currentX = (e.clientX - left) / width - 0.5;
41
+ const currentY = (e.clientY - top) / height - 0.5;
42
+
43
+ x.set(currentX);
44
+ y.set(currentY);
45
+ };
46
+
47
+ const handleMouseEnter = () => {
48
+ setIsMouseEntered(true);
49
+ };
50
+
51
+ const handleMouseLeave = () => {
52
+ setIsMouseEntered(false);
53
+ // Reset positions gently back to center
54
+ x.set(0);
55
+ y.set(0);
56
+ };
57
+
58
+ return (
59
+ <MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
60
+ <div
61
+ className={`flex items-center justify-center ${containerClassName}`}
62
+ style={{
63
+ perspective: "1000px",
64
+ }}
65
+ >
66
+ <motion.div
67
+ ref={containerRef}
68
+ onMouseEnter={handleMouseEnter}
69
+ onMouseMove={handleMouseMove}
70
+ onMouseLeave={handleMouseLeave}
71
+ style={{
72
+ transformStyle: "preserve-3d",
73
+ rotateX,
74
+ rotateY,
75
+ }}
76
+ className={`flex items-center justify-center relative transition-all duration-200 ease-linear ${className}`}
77
+ >
78
+ {children}
79
+ </motion.div>
80
+ </div>
81
+ </MouseEnterContext.Provider>
82
+ );
83
+ };
84
+
85
+ /**
86
+ * The inner canvas preserving standard layout properties while inheriting perspective physics.
87
+ */
88
+ export const CardBody = ({
89
+ children,
90
+ className,
91
+ }: {
92
+ children: React.ReactNode;
93
+ className?: string;
94
+ }) => {
95
+ return (
96
+ <div
97
+ style={{
98
+ transformStyle: "preserve-3d",
99
+ }}
100
+ className={`h-96 w-96 [transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d] ${className}`}
101
+ >
102
+ {children}
103
+ </div>
104
+ );
105
+ };
106
+
107
+ /**
108
+ * High level utility granting elements elevation along the Z axis dynamically triggered on hover.
109
+ */
110
+ export const CardItem = ({
111
+ as: Tag = "div",
112
+ children,
113
+ className,
114
+ translateX = 0,
115
+ translateY = 0,
116
+ translateZ = 0,
117
+ rotateX = 0,
118
+ rotateY = 0,
119
+ rotateZ = 0,
120
+ ...rest
121
+ }: {
122
+ as?: any;
123
+ children: React.ReactNode;
124
+ className?: string;
125
+ translateX?: number | string;
126
+ translateY?: number | string;
127
+ translateZ?: number | string;
128
+ rotateX?: number | string;
129
+ rotateY?: number | string;
130
+ rotateZ?: number | string;
131
+ [key: string]: any;
132
+ }) => {
133
+ const ref = useRef<HTMLDivElement>(null);
134
+ const context = useContext(MouseEnterContext);
135
+ const isMouseEntered = context ? context[0] : false;
136
+
137
+ useEffect(() => {
138
+ handleAnimations();
139
+ }, [isMouseEntered]);
140
+
141
+ const handleAnimations = () => {
142
+ if (!ref.current) return;
143
+ if (isMouseEntered) {
144
+ ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`;
145
+ } else {
146
+ ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`;
147
+ }
148
+ };
149
+
150
+ return (
151
+ <Tag
152
+ ref={ref}
153
+ className={`transition duration-200 ease-out ${className}`}
154
+ {...rest}
155
+ >
156
+ {children}
157
+ </Tag>
158
+ );
159
+ };
160
+
161
+ /**
162
+ * FULL COMPONENT WRAPPER matching user reference design exactly.
163
+ * Fully customizable through simple props API.
164
+ */
165
+ interface ThreeDCardProps {
166
+ title?: string;
167
+ description?: string;
168
+ imageUrl?: string;
169
+ ctaText?: string;
170
+ actionText?: string;
171
+ }
172
+
173
+ export function ThreeDCard({
174
+ title = "Make things float in air",
175
+ description = "Hover over this card to unleash the power of CSS perspective",
176
+ imageUrl = "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=2560&auto=format&fit=crop",
177
+ ctaText = "Try now \u2192",
178
+ actionText = "Sign up"
179
+ }: ThreeDCardProps) {
180
+ 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">
183
+
184
+ {/* Title Elevated */}
185
+ <CardItem
186
+ translateZ="50"
187
+ className="text-2xl font-bold text-neutral-800 dark:text-white"
188
+ >
189
+ {title}
190
+ </CardItem>
191
+
192
+ {/* Description Elevated slightly less */}
193
+ <CardItem
194
+ as="p"
195
+ translateZ="60"
196
+ className="text-neutral-500 text-base max-w-sm mt-2 dark:text-neutral-300 leading-relaxed"
197
+ >
198
+ {description}
199
+ </CardItem>
200
+
201
+ {/* Imagery container with maximum depth and radius */}
202
+ <CardItem translateZ="100" className="w-full mt-8">
203
+ <img
204
+ src={imageUrl}
205
+ height="1000"
206
+ width="1000"
207
+ className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl transition-shadow"
208
+ alt="thumbnail"
209
+ />
210
+ </CardItem>
211
+
212
+ {/* Footer actions stack */}
213
+ <div className="flex justify-between items-center mt-20">
214
+ {/* Lower left navigation prompt */}
215
+ <CardItem
216
+ 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"
219
+ >
220
+ {ctaText}
221
+ </CardItem>
222
+
223
+ {/* Right primary action capsule */}
224
+ <CardItem
225
+ 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"
228
+ >
229
+ {actionText}
230
+ </CardItem>
231
+ </div>
232
+ </CardBody>
233
+ </CardContainer>
234
+ );
235
+ }