sparesdev 0.0.5

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.
Files changed (36) hide show
  1. package/LICENSE +31 -0
  2. package/bin/index.js +89 -0
  3. package/core/add.js +172 -0
  4. package/core/doctor.js +34 -0
  5. package/core/init.js +25 -0
  6. package/core/list.js +97 -0
  7. package/package.json +23 -0
  8. package/templates/typescript/components/Accordion/Accordion.tsx +17 -0
  9. package/templates/typescript/components/Accordion/AccordionButton.tsx +52 -0
  10. package/templates/typescript/components/Accordion/AccordionContent.tsx +15 -0
  11. package/templates/typescript/components/Accordion/AccordionItem.tsx +17 -0
  12. package/templates/typescript/components/Accordion/index.ts +4 -0
  13. package/templates/typescript/components/Avatar/Avatar.tsx +34 -0
  14. package/templates/typescript/components/Avatar/AvatarFallback.tsx +35 -0
  15. package/templates/typescript/components/Avatar/AvatarImage.tsx +23 -0
  16. package/templates/typescript/components/Avatar/index.ts +3 -0
  17. package/templates/typescript/components/Backdrop/Backdrop.tsx +19 -0
  18. package/templates/typescript/components/Backdrop/index.ts +1 -0
  19. package/templates/typescript/components/Badge/Badge.tsx +63 -0
  20. package/templates/typescript/components/Badge/index.ts +1 -0
  21. package/templates/typescript/components/Breadcrumb/Breadcrumb.tsx +35 -0
  22. package/templates/typescript/components/Breadcrumb/BreadcrumbItem.tsx +18 -0
  23. package/templates/typescript/components/Breadcrumb/BreadcrumbLink.tsx +21 -0
  24. package/templates/typescript/components/Breadcrumb/BreadcrumbList.tsx +18 -0
  25. package/templates/typescript/components/Breadcrumb/BreadcrumbPage.tsx +19 -0
  26. package/templates/typescript/components/Breadcrumb/BreadcrumbSeparator.tsx +25 -0
  27. package/templates/typescript/components/Breadcrumb/index.ts +6 -0
  28. package/templates/typescript/components/Button/Button.tsx +49 -0
  29. package/templates/typescript/components/Button/index.ts +1 -0
  30. package/templates/typescript/frontend/Hero/Hero.tsx +8 -0
  31. package/templates/typescript/frontend/Hero/index.ts +1 -0
  32. package/templates/typescript/layouts/Header/Header.tsx +9 -0
  33. package/templates/typescript/layouts/Header/index.ts +1 -0
  34. package/templates/typescript/utils/cn.ts +6 -0
  35. package/utils/copy.js +19 -0
  36. package/utils/getConfig.js +10 -0
package/LICENSE ADDED
@@ -0,0 +1,31 @@
1
+ Copyright (c) 2026 Spares
2
+
3
+ All rights reserved.
4
+
5
+ This software (Spares) is provided strictly for demonstration and evaluation purposes only.
6
+
7
+ Permission is granted to access and use the Software solely to evaluate its functionality, features, and capabilities.
8
+
9
+ You may:
10
+
11
+ - Use the Software in a limited, non-production environment for evaluation
12
+ - Create internal demos for review purposes only
13
+
14
+ You may NOT, without explicit prior written permission:
15
+
16
+ - Use the Software in any production environment
17
+ - Use the Software in any commercial, business, or client project
18
+ - Build products, SaaS applications, or services using the Software
19
+ - Copy, modify for redistribution, sublicense, or resell the Software
20
+ - Repackage or distribute the Software in any form
21
+ - Create competing tools, libraries, or systems based on the Software
22
+
23
+ All evaluation usage must remain internal and must not be publicly distributed unless explicitly permitted.
24
+
25
+ The user is solely responsible for reviewing and testing the Software. Any use is at your own risk.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
28
+
29
+ TO THE FULLEST EXTENT PERMITTED BY LAW, SPARES SHALL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR RELATED TO THE USE OR INABILITY TO USE THE SOFTWARE.
30
+
31
+ You agree to indemnify and hold harmless Spares from any claims, damages, or liabilities arising from your use, misuse, or violation of these terms.
package/bin/index.js ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import { add } from "../core/add.js";
3
+ import { init } from "../core/init.js";
4
+ import { list } from "../core/list.js";
5
+ import { doctor } from "../core/doctor.js";
6
+
7
+ const args = process.argv.slice(2);
8
+
9
+ // alias support
10
+ const layerMap = {
11
+ c: "component",
12
+ l: "layout"
13
+ };
14
+
15
+ // ------------------------
16
+ // INIT / DOCTOR
17
+ // ------------------------
18
+ if (args[0] === "init") {
19
+ await init();
20
+ process.exit();
21
+ }
22
+
23
+ if (args[0] === "doctor") {
24
+ doctor();
25
+ process.exit();
26
+ }
27
+
28
+ // ------------------------
29
+ // 🔥 GLOBAL LIST (NO LAYER)
30
+ // sparesdev list
31
+ // ------------------------
32
+ if (args[0] === "list") {
33
+ list(); // show EVERYTHING
34
+ process.exit();
35
+ }
36
+
37
+ // ------------------------
38
+ // LAYER COMMANDS
39
+ // ------------------------
40
+ let [layer, command, ...rest] = args;
41
+
42
+ layer = layerMap[layer] || layer;
43
+
44
+ // fallback: sparesdev add button → global
45
+ if (!command) {
46
+ command = layer;
47
+ layer = "global";
48
+ rest = args.slice(1);
49
+ }
50
+
51
+ const validLayers = ["global", "storefront"];
52
+
53
+ if (!validLayers.includes(layer)) {
54
+ console.log(`
55
+ ❌ Invalid layer
56
+
57
+ Use:
58
+ sparesdev global add button
59
+ sparesdev storefront add product-card
60
+ `);
61
+ process.exit(1);
62
+ }
63
+
64
+ // ------------------------
65
+ // ROUTING
66
+ // ------------------------
67
+ switch (command) {
68
+ case "add":
69
+ add(layer, rest);
70
+ break;
71
+
72
+ case "list":
73
+ list(layer); // 👈 layer-specific
74
+ break;
75
+
76
+ default:
77
+ console.log(`
78
+ sparesdev CLI
79
+
80
+ Usage:
81
+ sparesdev global add button
82
+ sparesdev storefront add product-card
83
+ sparesdev list
84
+
85
+ Aliases:
86
+ sparesdev g add button
87
+ sparesdev s add product-card
88
+ `);
89
+ }
package/core/add.js ADDED
@@ -0,0 +1,172 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { copyRecursive } from "../utils/copy.js";
4
+ import { getConfig } from "../utils/getConfig.js";
5
+ import { createRequire } from "module";
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+ // 🔥 normalize helper (fixes breDCRumb issue)
10
+ function normalize(str) {
11
+ return str.toLowerCase().replace(/[^a-z0-9]/g, "");
12
+ }
13
+
14
+ export function add(layer, components) {
15
+ if (!components.length) {
16
+ console.error("❌ Provide component names");
17
+ process.exit(1);
18
+ }
19
+
20
+ const config = getConfig();
21
+ const lang = "typescript";
22
+ const packageName = "spares";
23
+
24
+ if (!fs.existsSync("src/tokens.css")) {
25
+ console.log("⚠️ Tailwind v4 tokens missing (src/tokens.css)");
26
+ }
27
+
28
+ checkDeps();
29
+
30
+ const packageRoot = path.dirname(
31
+ require.resolve(`${packageName}/package.json`)
32
+ );
33
+
34
+ const projectRoot = process.cwd();
35
+
36
+ const utilsPath = path.join(packageRoot, "templates", lang, "utils");
37
+ const targetUtils = path.join(
38
+ projectRoot,
39
+ config.utilsPath || "src/utils"
40
+ );
41
+
42
+ if (!fs.existsSync(targetUtils)) {
43
+ fs.mkdirSync(targetUtils, { recursive: true });
44
+ console.log("📁 created utils folder");
45
+ }
46
+
47
+ if (fs.existsSync(utilsPath)) {
48
+ const cnPath = path.join(targetUtils, "cn.ts");
49
+
50
+ if (!fs.existsSync(cnPath)) {
51
+ copyRecursive(utilsPath, targetUtils);
52
+ console.log("✅ utils (cn) added");
53
+ } else {
54
+ console.log("ℹ️ utils already exists, skipping");
55
+ }
56
+ }
57
+
58
+ const installed = [];
59
+
60
+ for (const nameInput of components) {
61
+ const name = nameInput;
62
+
63
+ let basePaths = [];
64
+
65
+ if (layer === "global") {
66
+ basePaths = [
67
+ {
68
+ type: "components",
69
+ path: path.join(packageRoot, "templates", lang, "global", "components"),
70
+ target: path.join(projectRoot, "src/global/components")
71
+ },
72
+ {
73
+ type: "layouts",
74
+ path: path.join(packageRoot, "templates", lang, "global", "layouts"),
75
+ target: path.join(projectRoot, "src/global/layouts")
76
+ }
77
+ ];
78
+ }
79
+
80
+ if (layer === "storefront") {
81
+ basePaths = [
82
+ {
83
+ type: "storefront",
84
+ path: path.join(packageRoot, "templates", lang, "storefront"),
85
+ target: path.join(projectRoot, "src/storefront")
86
+ }
87
+ ];
88
+ }
89
+
90
+ let found = null;
91
+
92
+ for (const bp of basePaths) {
93
+ if (!fs.existsSync(bp.path)) continue;
94
+
95
+ const available = fs.readdirSync(bp.path);
96
+
97
+ const match = available.find(
98
+ c => normalize(c) === normalize(name)
99
+ );
100
+
101
+ if (match) {
102
+ found = {
103
+ match,
104
+ src: path.join(bp.path, match),
105
+ dest: path.join(bp.target, match),
106
+ type: bp.type
107
+ };
108
+ break;
109
+ }
110
+ }
111
+
112
+ // ❌ not found → suggest
113
+ if (!found) {
114
+ let suggestions = [];
115
+
116
+ for (const bp of basePaths) {
117
+ if (!fs.existsSync(bp.path)) continue;
118
+
119
+ const available = fs.readdirSync(bp.path);
120
+
121
+ const suggestion = available.find(c =>
122
+ normalize(c).includes(normalize(name))
123
+ );
124
+
125
+ if (suggestion) suggestions.push(suggestion);
126
+ }
127
+
128
+ if (suggestions.length) {
129
+ console.log(`❌ ${name} not found. Did you mean: ${suggestions[0]}?`);
130
+ } else {
131
+ console.log(`❌ ${name} not found`);
132
+ }
133
+
134
+ continue;
135
+ }
136
+
137
+ fs.mkdirSync(path.dirname(found.dest), { recursive: true });
138
+
139
+ copyRecursive(found.src, found.dest);
140
+ installed.push(found.match);
141
+
142
+ console.log(`✅ ${found.match} → ${layer}/${found.type}`);
143
+ }
144
+
145
+ if (installed.length) {
146
+ console.log(`\n🎉 Installed: ${installed.join(", ")}\n`);
147
+ } else {
148
+ console.log("❌ No components installed");
149
+ }
150
+ }
151
+
152
+ // -------------------------------
153
+ function checkDeps() {
154
+ try {
155
+ const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
156
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
157
+
158
+ const required = ["clsx", "tailwind-merge"];
159
+ const missing = required.filter(d => !deps?.[d]);
160
+
161
+ if (missing.length) {
162
+ console.log(`
163
+ ⚠️ Missing dependencies: ${missing.join(", ")}
164
+
165
+ Run:
166
+ npm install ${missing.join(" ")}
167
+ `);
168
+ }
169
+ } catch {
170
+ console.log("⚠️ Could not read package.json");
171
+ }
172
+ }
package/core/doctor.js ADDED
@@ -0,0 +1,34 @@
1
+ import fs from "fs";
2
+ import { getConfig } from "../utils/getConfig.js";
3
+
4
+ export function doctor() {
5
+ console.log("\n🩺 Sparesdev Doctor:\n");
6
+
7
+ let config;
8
+
9
+ try {
10
+ config = getConfig();
11
+ console.log(`✅ Language: TypeScript`);
12
+ } catch {
13
+ console.log("❌ sparesdev.config.json missing");
14
+ return;
15
+ }
16
+
17
+ if (fs.existsSync("src")) console.log("✅ src folder");
18
+ else console.log("❌ src missing");
19
+
20
+ if (fs.existsSync("src/components")) console.log("✅ components folder");
21
+ else console.log("❌ components folder missing");
22
+
23
+ console.log("\n📦 Tailwind v4 check:\n");
24
+
25
+ if (fs.existsSync("src/index.css"))
26
+ console.log("✅ index.css");
27
+ else console.log("❌ index.css missing");
28
+
29
+ if (fs.existsSync("src/tokens.css"))
30
+ console.log("✅ tokens.css");
31
+ else console.log("❌ tokens.css missing");
32
+
33
+ console.log("\n💡 Fix issues based on selected system\n");
34
+ }
package/core/init.js ADDED
@@ -0,0 +1,25 @@
1
+ import fs from "fs";
2
+
3
+ export async function init() {
4
+ const config = {
5
+ language: "typescript",
6
+ version: "3.0",
7
+ style: "default",
8
+ componentsPath: "src/components",
9
+ utilsPath: "src/utils"
10
+ };
11
+
12
+ fs.writeFileSync(
13
+ "spares.config.json",
14
+ JSON.stringify(config, null, 2)
15
+ );
16
+
17
+ console.log(`\n✅ Initialized (TypeScript)\n`);
18
+
19
+ console.log(`
20
+ ⚠️ Tailwind v4 Required
21
+
22
+ - Create src/index.css
23
+ - Create src/tokens.css
24
+ `);
25
+ }
package/core/list.js ADDED
@@ -0,0 +1,97 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { createRequire } from "module";
4
+ const require = createRequire(import.meta.url);
5
+
6
+ // -------------------------------
7
+ // 🌳 Recursive tree printer
8
+ // -------------------------------
9
+ function printTree(dir, indent = " ") {
10
+ const items = fs.readdirSync(dir);
11
+
12
+ for (const item of items) {
13
+ const fullPath = path.join(dir, item);
14
+ const isDir = fs.statSync(fullPath).isDirectory();
15
+
16
+ console.log(`${indent}- ${item}${isDir ? "/" : ""}`);
17
+
18
+ if (isDir) {
19
+ printTree(fullPath, indent + " ");
20
+ }
21
+ }
22
+ }
23
+
24
+ // -------------------------------
25
+ // 📦 LIST
26
+ // -------------------------------
27
+ export function list(layer) {
28
+ const lang = "typescript";
29
+ const packageName = "spares";
30
+
31
+ let packageRoot;
32
+
33
+ try {
34
+ packageRoot = path.dirname(
35
+ require.resolve(`${packageName}/package.json`)
36
+ );
37
+ } catch {
38
+ console.log("❌ CLI package not found");
39
+ return;
40
+ }
41
+
42
+ // -------------------------------
43
+ // 📁 Paths setup
44
+ // -------------------------------
45
+ let paths = [];
46
+
47
+ if (!layer || layer === "global") {
48
+ paths.push({
49
+ label: "Components",
50
+ dir: path.join(packageRoot, "templates", lang, "global", "components")
51
+ });
52
+
53
+ paths.push({
54
+ label: "Layouts",
55
+ dir: path.join(packageRoot, "templates", lang, "global", "layouts")
56
+ });
57
+ }
58
+
59
+ if (!layer || layer === "storefront") {
60
+ paths.push({
61
+ label: "Storefront",
62
+ dir: path.join(packageRoot, "templates", lang, "storefront")
63
+ });
64
+ }
65
+
66
+ console.log("");
67
+
68
+ let hasAny = false;
69
+
70
+ for (const p of paths) {
71
+ if (!fs.existsSync(p.dir)) continue;
72
+
73
+ const items = fs.readdirSync(p.dir);
74
+ if (!items.length) continue;
75
+
76
+ hasAny = true;
77
+
78
+ console.log(`📦 ${p.label}:\n`);
79
+
80
+ for (const item of items) {
81
+ const fullPath = path.join(p.dir, item);
82
+ const isDir = fs.statSync(fullPath).isDirectory();
83
+
84
+ console.log(` - ${item}${isDir ? "/" : ""}`);
85
+
86
+ if (isDir) {
87
+ printTree(fullPath, " ");
88
+ }
89
+ }
90
+
91
+ console.log("");
92
+ }
93
+
94
+ if (!hasAny) {
95
+ console.log("❌ No components found\n");
96
+ }
97
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "sparesdev",
3
+ "version": "0.0.5",
4
+ "type": "module",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "bin": {
7
+ "sparesdev": "bin/index.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "core",
12
+ "utils",
13
+ "templates",
14
+ "README.md"
15
+ ],
16
+ "dependencies": {
17
+ "prompts": "^2.4.2"
18
+ },
19
+ "peerDependencies": {
20
+ "react": ">=18",
21
+ "react-dom": ">=18"
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from 'react'
2
+ import { cn } from '@/utils/cn'
3
+
4
+ interface AccordionProps {
5
+ children: ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ function Accordion({children, className}: AccordionProps) {
10
+ return (
11
+ <div className={cn(className)}>
12
+ {children}
13
+ </div>
14
+ )
15
+ }
16
+
17
+ export default Accordion
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import { cn } from '@/utils/cn'
3
+
4
+ interface AccordionButtonProps {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ icon?: React.ReactNode;
8
+ iconColor?: string;
9
+ isOpen?: boolean;
10
+ onClick?: () => void;
11
+ }
12
+
13
+ function AccordionButton({
14
+ children,
15
+ className,
16
+ icon,
17
+ iconColor = 'black',
18
+ isOpen = false,
19
+ onClick,
20
+ }: AccordionButtonProps) {
21
+ const defaultIcon = (
22
+ <svg
23
+ width="5.94"
24
+ height="9"
25
+ viewBox="0 0 6 9"
26
+ fill="none"
27
+ style={{
28
+ transform: isOpen ? 'rotate(270deg)' : 'rotate(90deg)',
29
+ transition: 'transform 0.3s ease',
30
+ }}
31
+ >
32
+ <path
33
+ d="M1.5 7.5L4.5 4.5L1.5 1.5"
34
+ stroke={iconColor}
35
+ strokeWidth="1"
36
+ strokeLinecap="round"
37
+ />
38
+ </svg>
39
+ );
40
+
41
+ return (
42
+ <button
43
+ onClick={onClick}
44
+ className={cn("flex flex-row items-center justify-between text-base font-normal tracking-wide", className)}
45
+ >
46
+ <span>{children}</span>
47
+ {icon || defaultIcon}
48
+ </button>
49
+ );
50
+ }
51
+
52
+ export default AccordionButton;
@@ -0,0 +1,15 @@
1
+ import React from 'react'
2
+ import { cn } from '@/utils/cn'
3
+
4
+ interface AccordionContentProps {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ function AccordionContent({children, className}: AccordionContentProps) {
10
+ return (
11
+ <p className={cn("text-base font-normal", className)}>{children}</p>
12
+ )
13
+ }
14
+
15
+ export default AccordionContent
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+ import { cn } from '@/utils/cn'
3
+
4
+ interface AccordionItemProps {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ function AccordionItem({children, className}: AccordionItemProps) {
10
+ return (
11
+ <div className={cn(className)}>
12
+ {children}
13
+ </div>
14
+ )
15
+ }
16
+
17
+ export default AccordionItem
@@ -0,0 +1,4 @@
1
+ export { default as Accordion } from './Accordion';
2
+ export { default as AccordionButton } from './AccordionButton';
3
+ export { default as AccordionContent } from './AccordionContent';
4
+ export { default as AccordionItem } from './AccordionItem';
@@ -0,0 +1,34 @@
1
+ import React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/utils/cn"
4
+
5
+ const avatarVariants = cva(
6
+ "relative flex shrink-0 overflow-hidden rounded-full items-center justify-center bg-neutral-000",
7
+ {
8
+ variants: {
9
+ size: {
10
+ sm: "w-6 h-6 text-xs font-normal",
11
+ md: "w-9 h-9 text-sm font-normal",
12
+ lg: "w-12 h-12 text-tiny font-normal",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ size: "md",
17
+ },
18
+ }
19
+ )
20
+
21
+ interface AvatarProps
22
+ extends React.HTMLAttributes<HTMLDivElement>,
23
+ VariantProps<typeof avatarVariants> {}
24
+
25
+ function Avatar({ className, size, ...props }: AvatarProps) {
26
+ return (
27
+ <div
28
+ className={cn(avatarVariants({ size }), className)}
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ export default Avatar
@@ -0,0 +1,35 @@
1
+ import React from "react"
2
+ import { cn } from "@/utils/cn"
3
+
4
+ interface AvatarFallbackProps
5
+ extends React.HTMLAttributes<HTMLSpanElement> {
6
+ initials?: string
7
+ }
8
+
9
+ function AvatarFallback({
10
+ initials,
11
+ className,
12
+ children,
13
+ ...props
14
+ }: AvatarFallbackProps) {
15
+ const fallbackIcon = (
16
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
17
+ <path d="M7 0.5C8.38071 0.5 9.5 1.61929 9.5 3C9.5 4.38071 8.38071 5.5 7 5.5C5.61929 5.5 4.5 4.38071 4.5 3C4.5 1.61929 5.61929 0.5 7 0.5Z" fill="currentColor" stroke="currentColor"/>
18
+ <path d="M7 7.5C9.48528 7.5 11.5 9.51472 11.5 12V13.5H2.5V12C2.5 9.51472 4.51472 7.5 7 7.5Z" fill="currentColor" stroke="currentColor"/>
19
+ </svg>
20
+ )
21
+
22
+ return (
23
+ <span
24
+ className={cn(
25
+ "flex h-full w-full items-center justify-center",
26
+ className
27
+ )}
28
+ {...props}
29
+ >
30
+ {children ?? initials ?? fallbackIcon}
31
+ </span>
32
+ )
33
+ }
34
+
35
+ export default AvatarFallback
@@ -0,0 +1,23 @@
1
+ import React, { useState } from "react"
2
+ import { cn } from "@/utils/cn"
3
+
4
+ interface AvatarImageProps
5
+ extends React.ImgHTMLAttributes<HTMLImageElement> {
6
+ className?: "string";
7
+ }
8
+
9
+ function AvatarImage({ className, ...props }: AvatarImageProps) {
10
+ const [error, setError] = useState(false)
11
+
12
+ if (error) return null
13
+
14
+ return (
15
+ <img
16
+ className={cn("aspect-square h-full w-full object-cover", className)}
17
+ onError={() => setError(true)}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ export default AvatarImage
@@ -0,0 +1,3 @@
1
+ export { default as Avatar } from './Avatar';
2
+ export { default as AvatarFallback } from './AvatarFallback';
3
+ export { default as AvatarImage } from './AvatarImage';
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { cn } from "@/utils/cn";
3
+
4
+ interface BackdropProps {
5
+ className?: string;
6
+ }
7
+
8
+ function Backdrop({ className }: BackdropProps) {
9
+ return (
10
+ <div
11
+ className={cn(
12
+ "fixed inset-0 bg-neutral-000/70 backdrop-blur-sm z-40",
13
+ className
14
+ )}
15
+ />
16
+ );
17
+ }
18
+
19
+ export default Backdrop;
@@ -0,0 +1 @@
1
+ export { default as Backdrop } from './Backdrop';
@@ -0,0 +1,63 @@
1
+ import { cn } from "@/utils/cn"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import React from "react"
4
+
5
+ const badgeVariants = cva(
6
+ "font-normal rounded-md inline-flex items-center justify-center gap-2",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-neutral-700 text-white",
11
+ text: "bg-transparent text-neutral-900 px-0",
12
+ capsule: "rounded-full bg-neutral-700 text-white",
13
+ outline: "border border-neutral-900",
14
+ icon: "bg-neutral-900 text-white px-0",
15
+ indicator: "bg-neutral-900 rounded-full p-0",
16
+ },
17
+
18
+ size: {
19
+ sm: "h-5 px-2 text-xs",
20
+ md: "h-6 px-3 text-sm",
21
+ lg: "h-8 px-4 text-base",
22
+ },
23
+ },
24
+
25
+ compoundVariants: [
26
+ {
27
+ variant: "indicator",
28
+ size: "sm",
29
+ className: "w-1 h-1 px-0",
30
+ },
31
+ {
32
+ variant: "indicator",
33
+ size: "md",
34
+ className: "w-2 h-2 px-0",
35
+ },
36
+ {
37
+ variant: "indicator",
38
+ size: "lg",
39
+ className: "w-3 h-3 px-0",
40
+ },
41
+ ],
42
+
43
+ defaultVariants: {
44
+ variant: "default",
45
+ size: "sm",
46
+ },
47
+ }
48
+ )
49
+
50
+ interface BadgeProps extends VariantProps<typeof badgeVariants> {
51
+ children?: React.ReactNode
52
+ className?: string
53
+ }
54
+
55
+ function Badge({ children, className, variant, size }: BadgeProps) {
56
+ return (
57
+ <span className={cn(badgeVariants({ variant, size }), className)}>
58
+ {children}
59
+ </span>
60
+ )
61
+ }
62
+
63
+ export default Badge
@@ -0,0 +1 @@
1
+ export { default as Badge } from './Badge';
@@ -0,0 +1,35 @@
1
+ import React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/utils/cn"
4
+
5
+ const breadcrumbVariants = cva(
6
+ "flex items-center text-neutral-500",
7
+ {
8
+ variants: {
9
+ size: {
10
+ sm: "text-xs gap-1",
11
+ md: "text-sm gap-2",
12
+ lg: "text-base gap-3",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ size: "md",
17
+ },
18
+ }
19
+ )
20
+
21
+ interface BreadcrumbProps
22
+ extends React.HTMLAttributes<HTMLElement>,
23
+ VariantProps<typeof breadcrumbVariants> {}
24
+
25
+ function Breadcrumb({ className, size, ...props }: BreadcrumbProps) {
26
+ return (
27
+ <nav
28
+ aria-label="breadcrumb"
29
+ className={cn(breadcrumbVariants({ size }), className)}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ export default Breadcrumb
@@ -0,0 +1,18 @@
1
+ import React from "react"
2
+ import { cn } from "@/utils/cn"
3
+
4
+ interface BreadcrumbItemProps
5
+ extends React.HTMLAttributes<HTMLLIElement> {
6
+ className?: string;
7
+ }
8
+
9
+ function BreadcrumbItem({ className, ...props }: BreadcrumbItemProps) {
10
+ return (
11
+ <li
12
+ className={cn("inline-flex items-center", className)}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export default BreadcrumbItem
@@ -0,0 +1,21 @@
1
+ import React from "react"
2
+ import { Link, type LinkProps } from "react-router-dom"
3
+ import { cn } from "@/utils/cn"
4
+
5
+ interface BreadcrumbLinkProps extends LinkProps {
6
+ className?: string
7
+ }
8
+
9
+ function BreadcrumbLink({ className, ...props }: BreadcrumbLinkProps) {
10
+ return (
11
+ <Link
12
+ className={cn(
13
+ "transition-colors hover:text-black",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ export default BreadcrumbLink
@@ -0,0 +1,18 @@
1
+ import React from "react"
2
+ import { cn } from "@/utils/cn"
3
+
4
+ interface BreadcrumbListProps
5
+ extends React.HTMLAttributes<HTMLOListElement> {
6
+ className?: string;
7
+ }
8
+
9
+ function BreadcrumbList({ className, ...props }: BreadcrumbListProps) {
10
+ return (
11
+ <ol
12
+ className={cn("flex items-center flex-wrap", className)}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export default BreadcrumbList
@@ -0,0 +1,19 @@
1
+ import React from "react"
2
+ import { cn } from "@/utils/cn"
3
+
4
+ interface BreadcrumbPageProps
5
+ extends React.HTMLAttributes<HTMLSpanElement> {
6
+ className?: string;
7
+ }
8
+
9
+ function BreadcrumbPage({ className, ...props }: BreadcrumbPageProps) {
10
+ return (
11
+ <span
12
+ aria-current="page"
13
+ className={cn("font-medium text-black", className)}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export default BreadcrumbPage
@@ -0,0 +1,25 @@
1
+ import React from "react"
2
+ import { cn } from "@/utils/cn"
3
+
4
+ interface BreadcrumbSeparatorProps
5
+ extends React.HTMLAttributes<HTMLSpanElement> {
6
+ children?: React.ReactNode
7
+ }
8
+
9
+ function BreadcrumbSeparator({
10
+ className,
11
+ children = "/",
12
+ ...props
13
+ }: BreadcrumbSeparatorProps) {
14
+ return (
15
+ <span
16
+ role="presentation"
17
+ className={cn("mx-2 text-neutral-400", className)}
18
+ {...props}
19
+ >
20
+ {children}
21
+ </span>
22
+ )
23
+ }
24
+
25
+ export default BreadcrumbSeparator
@@ -0,0 +1,6 @@
1
+ export { default as Breadcrumb } from './Breadcrumb';
2
+ export { default as BreadcrumbItem } from './BreadcrumbItem';
3
+ export { default as BreadcrumbLink } from './BreadcrumbLink';
4
+ export { default as BreadcrumbList } from './BreadcrumbList';
5
+ export { default as BreadcrumbPage } from './BreadcrumbPage';
6
+ export { default as BreadcrumbSeparator } from './BreadcrumbSeparator';
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import { cn } from '@/utils/cn'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+
6
+ const buttonVariants = cva (
7
+ "flex flex-row items-center justify-center gap-3 text-sm font-normal rounded-sm px-5",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ primary: "bg-neutral-900 text-white hover:bg-neutral-800 active:bg-neutral-700 disabled:bg-neutral-100 disabled:text-neutral-400",
12
+ secondary: "bg-neutral-100 text-neutral-700 hover:bg-neutral-200 hover:text-neutral-800 active:bg-neutral-300 active:text-neutral-900 disabled:bg-neutral-100 disabled:text-neutral-400",
13
+ capsule: "bg-neutral-900 text-white hover:bg-neutral-800 active:bg-neutral-700 disabled:bg-neutral-100 disabled:text-neutral-400 rounded-full",
14
+ outline: "bg-transparent text-neutral-900 border border-neutral-900 hover:border-neutral-500 hover:text-neutral-500 active:border-neutral-700 active:text-neutral-700 disabled:border-neutral-100 disabled:text-neutral-400",
15
+ ghost: "bg-transparent text-neutral-700 hover:text-neutral-800 hover:bg-neutral-100 active:bg-neutral-200 active:text-neutral-900 disabled:text-neutral-400",
16
+ icon: "bg-transparent text-neutral-900 hover:text-neutral-700 active:text-neutral-500 disabled:text-neutral-400 px-0 aspect-square",
17
+ iconfilled: "bg-neutral-900 text-white hover:bg-neutral-800 active:bg-neutral-700 disabled:bg-neutral-200 rounded-sm px-0 aspect-square"
18
+ },
19
+ size: {
20
+ xs: "h-6",
21
+ sm: "h-9",
22
+ md: "h-12",
23
+ lg: "h-14"
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "primary",
28
+ size: "sm",
29
+ },
30
+ }
31
+ )
32
+
33
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>,
34
+ VariantProps<typeof buttonVariants> {
35
+ className?: string;
36
+ children?: React.ReactNode;
37
+ }
38
+
39
+ function Button({className, variant, size, children, ...props}: ButtonProps) {
40
+ return (
41
+ <button className={cn(buttonVariants({variant, size}), className)}
42
+ {...props}
43
+ >
44
+ {children}
45
+ </button>
46
+ )
47
+ }
48
+
49
+ export default Button
@@ -0,0 +1 @@
1
+ export { default as Button } from './Button';
@@ -0,0 +1,8 @@
1
+
2
+ function Hero() {
3
+ return (
4
+ <div>Hero</div>
5
+ )
6
+ }
7
+
8
+ export default Hero
@@ -0,0 +1 @@
1
+ export { default as Hero } from './Hero';
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+
3
+ function Header() {
4
+ return (
5
+ <div>Header</div>
6
+ )
7
+ }
8
+
9
+ export default Header
@@ -0,0 +1 @@
1
+ export { default as Header } from './Header';
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(...inputs));
6
+ }
package/utils/copy.js ADDED
@@ -0,0 +1,19 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export function copyRecursive(src, dest) {
5
+ fs.mkdirSync(dest, { recursive: true });
6
+
7
+ for (const item of fs.readdirSync(src)) {
8
+ const srcItem = path.join(src, item);
9
+ const destItem = path.join(dest, item);
10
+
11
+ if (fs.statSync(srcItem).isDirectory()) {
12
+ copyRecursive(srcItem, destItem);
13
+ } else {
14
+ if (!fs.existsSync(destItem)) {
15
+ fs.copyFileSync(srcItem, destItem);
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ import fs from "fs";
2
+
3
+ export function getConfig() {
4
+ if (!fs.existsSync("sparesdev.config.json")) {
5
+ console.error("❌ Run 'sparesdev init' first");
6
+ process.exit(1);
7
+ }
8
+
9
+ return JSON.parse(fs.readFileSync("sparesdev.config.json", "utf-8"));
10
+ }