@rajdeep0510/scaffold-cli 1.0.2 → 1.3.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/.vscode/settings.json +10 -0
- package/README.md +2 -0
- package/bin/index.js +4 -4
- package/commands/addComponent.js +76 -50
- package/package.json +2 -2
- package/templates/Avatar/component.jsx +62 -0
- package/templates/Avatar/index.js +5 -0
- package/templates/Avatar/meta.json +92 -0
- package/templates/Card/Card.jsx +187 -0
- package/templates/Card/index.js +2 -0
- package/templates/Card/meta.json +100 -0
- package/templates/Input/components.jsx +33 -0
- package/templates/Input/index.js +5 -0
- package/templates/Input/meta.json +82 -0
- package/templates/Navbar/Navbar.jsx +207 -0
- package/templates/Navbar/index.js +2 -0
- package/templates/Navbar/meta.json +43 -0
- package/templates/RadioGroup/RadioGroup.jsx +148 -0
- package/templates/RadioGroup/index.js +4 -0
- package/templates/RadioGroup/meta.json +10 -0
- package/templates/button/component.jsx +73 -80
- package/templates/button/index.js +6 -2
- package/templates/button/meta.json +142 -18
package/README.md
CHANGED
package/bin/index.js
CHANGED
|
@@ -27,15 +27,15 @@ program.addHelpText('before', () => {
|
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
const importInfo = chalk.blue("How to import components:") + "\n" +
|
|
30
|
-
|
|
30
|
+
chalk.green(" import { Button } from '@/components/ui/button'");
|
|
31
31
|
|
|
32
32
|
return banner + "\n\n" + description + "\n\n" + importInfo + "\n\n";
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
program
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
.command('add <component-name>')
|
|
37
|
+
.description('Add a new component')
|
|
38
|
+
.action(addComponent)
|
|
39
39
|
|
|
40
40
|
program
|
|
41
41
|
.command('list')
|
package/commands/addComponent.js
CHANGED
|
@@ -4,73 +4,99 @@ import { fileURLToPath } from 'url'
|
|
|
4
4
|
import chalk from 'chalk'
|
|
5
5
|
import figlet from 'figlet'
|
|
6
6
|
import ora from 'ora'
|
|
7
|
+
import { execSync } from 'child_process'
|
|
7
8
|
|
|
8
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url)) // ESM-safe __dirname
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
// banner TODO: remove
|
|
12
13
|
console.log(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
chalk.cyan(
|
|
15
|
+
figlet.text('scaffold', { font: 'doom' }, function (err, data) {
|
|
16
|
+
if (err) {
|
|
17
|
+
console.log('Something went wrong...');
|
|
18
|
+
console.dir(err);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log(data);
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
)
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
export async function addComponent(name) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const useSrc = await fs.pathExists('src')
|
|
29
|
+
const hasApp = await fs.pathExists(path.join(useSrc ? 'src' : '', 'app'))
|
|
30
|
+
const hasPages = await fs.pathExists(path.join(useSrc ? 'src' : '', 'pages'))
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (!hasApp && !hasPages) {
|
|
33
|
+
console.log(chalk.red('Could not detect Next.js routing structure (no app/ or pages/ directory)'))
|
|
34
|
+
return
|
|
35
|
+
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const baseDir = useSrc ? 'src/components/ui' : 'components/ui'
|
|
38
|
+
const targetDir = path.join(baseDir, name)
|
|
39
|
+
const templateDir = path.resolve(__dirname, '../templates', name)
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
// 🔎 check if template folder exists
|
|
42
|
+
if (!(await fs.pathExists(templateDir))) {
|
|
43
|
+
console.log(chalk.red(`Template folder '${templateDir}' not found.`))
|
|
44
|
+
return
|
|
45
|
+
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
// 🔎 validate meta.json before copying
|
|
48
|
+
const metaPath = path.join(templateDir, 'meta.json')
|
|
49
|
+
if (!(await fs.pathExists(metaPath))) {
|
|
50
|
+
console.log(chalk.red(`No meta.json found in template '${name}'`))
|
|
51
|
+
return
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const meta = await fs.readJson(metaPath)
|
|
55
|
+
if (!meta.name || !meta.description || !meta.version) {
|
|
56
|
+
console.log(chalk.red(`Invalid meta.json for '${name}'. Missing required fields.`))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// 🌀 Spinner while copying
|
|
61
|
+
const spinner = ora(`Adding component '${name}'...`).start()
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
try {
|
|
64
|
+
await fs.ensureDir(targetDir)
|
|
65
|
+
await fs.copy(templateDir, targetDir)
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
// Install dependencies if specified in meta.json
|
|
68
|
+
if (meta.dependencies && (meta.dependencies.npm?.length > 0 || meta.dependencies.peer?.length > 0)) {
|
|
69
|
+
spinner.text = `Installing dependencies for '${name}'...`
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
if (meta.dependencies.npm?.length > 0) {
|
|
73
|
+
const npmDeps = meta.dependencies.npm.join(' ')
|
|
74
|
+
execSync(`npm install ${npmDeps}`, { stdio: 'inherit' })
|
|
75
|
+
console.log(chalk.green(`✅ Installed npm dependencies: ${npmDeps}`))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (meta.dependencies.peer?.length > 0) {
|
|
79
|
+
const peerDeps = meta.dependencies.peer.join(' ')
|
|
80
|
+
execSync(`npm install --save-peer ${peerDeps}`, { stdio: 'inherit' })
|
|
81
|
+
console.log(chalk.green(`✅ Installed peer dependencies: ${peerDeps}`))
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.log(chalk.yellow(`⚠️ Warning: Failed to install some dependencies. You may need to install them manually.`))
|
|
74
85
|
console.error(err)
|
|
86
|
+
}
|
|
75
87
|
}
|
|
88
|
+
|
|
89
|
+
spinner.succeed(chalk.green(`Component '${meta.name}' created at ${targetDir}`))
|
|
90
|
+
|
|
91
|
+
console.log(chalk.yellow(`ℹ️ Description:`), chalk.white(meta.description))
|
|
92
|
+
console.log(chalk.magenta(`📦 Version:`), chalk.white(meta.version))
|
|
93
|
+
console.log(chalk.magenta(`Author :`), chalk.white(meta.author))
|
|
94
|
+
console.log(chalk.cyan(`\n👉 Import in Next.js:`))
|
|
95
|
+
console.log(chalk.blueBright(` import { ${meta.name} } from "@/components/ui/${name}"\n`))
|
|
96
|
+
} catch (err) {
|
|
97
|
+
spinner.fail(chalk.red(`Failed to add component '${name}'`))
|
|
98
|
+
console.error(err)
|
|
99
|
+
}
|
|
76
100
|
}
|
|
101
|
+
|
|
102
|
+
// change made TODO:remove
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rajdeep0510/scaffold-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "This is a CLI app for a UI library which is used for creating versatile and modern UI components.",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,4 +39,4 @@
|
|
|
39
39
|
"ora": "^8.2.0",
|
|
40
40
|
"prop-types": "^15.8.1"
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// component.jsx
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
|
|
5
|
+
export default function Avatar({
|
|
6
|
+
src,
|
|
7
|
+
alt,
|
|
8
|
+
name,
|
|
9
|
+
info,
|
|
10
|
+
size = "md",
|
|
11
|
+
cardPosition = "bottom",
|
|
12
|
+
}) {
|
|
13
|
+
const [showCard, setShowCard] = useState(false);
|
|
14
|
+
|
|
15
|
+
// Avatar size styles
|
|
16
|
+
const sizeClasses = {
|
|
17
|
+
sm: "w-8 h-8",
|
|
18
|
+
md: "w-12 h-12",
|
|
19
|
+
lg: "w-16 h-16",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Hover card position
|
|
23
|
+
const positionClasses =
|
|
24
|
+
cardPosition === "top" ? "bottom-full mb-2" : "top-full mt-2";
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className="relative inline-block"
|
|
29
|
+
onMouseEnter={() => setShowCard(true)}
|
|
30
|
+
onMouseLeave={() => setShowCard(false)}
|
|
31
|
+
>
|
|
32
|
+
{/* Avatar Image */}
|
|
33
|
+
<img
|
|
34
|
+
src={src}
|
|
35
|
+
alt={alt || "Avatar"}
|
|
36
|
+
className={`${sizeClasses[size]} rounded-full object-cover border shadow-sm cursor-pointer`}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
{/* Hover Card */}
|
|
40
|
+
{showCard && (
|
|
41
|
+
<div
|
|
42
|
+
className={`absolute left-1/2 -translate-x-1/2 ${positionClasses}
|
|
43
|
+
bg-white shadow-lg rounded-2xl p-3 w-48
|
|
44
|
+
border text-sm z-10`}
|
|
45
|
+
>
|
|
46
|
+
<p className="font-extrabold">{name}</p>
|
|
47
|
+
<p className="text-gray-600">{info}</p>
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// PropTypes validation
|
|
55
|
+
Avatar.propTypes = {
|
|
56
|
+
src: PropTypes.string.isRequired,
|
|
57
|
+
alt: PropTypes.string,
|
|
58
|
+
name: PropTypes.string.isRequired,
|
|
59
|
+
info: PropTypes.string,
|
|
60
|
+
size: PropTypes.oneOf(["sm", "md", "lg"]),
|
|
61
|
+
cardPosition: PropTypes.oneOf(["top", "bottom"]),
|
|
62
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Avatar",
|
|
3
|
+
"description": "An avatar component that displays a profile picture and shows a hover card with name and info.",
|
|
4
|
+
"category": "display",
|
|
5
|
+
"tags": [
|
|
6
|
+
"avatar",
|
|
7
|
+
"profile",
|
|
8
|
+
"hover",
|
|
9
|
+
"info",
|
|
10
|
+
"card",
|
|
11
|
+
"ui",
|
|
12
|
+
"tailwind"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"author": "@rajdeep",
|
|
16
|
+
"created": "2025-08-29",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"npm": [],
|
|
19
|
+
"peer": [
|
|
20
|
+
"react",
|
|
21
|
+
"prop-types"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"props": [
|
|
25
|
+
{
|
|
26
|
+
"name": "src",
|
|
27
|
+
"type": "string",
|
|
28
|
+
"required": true,
|
|
29
|
+
"default": "undefined",
|
|
30
|
+
"description": "Image URL or path from public folder for the avatar"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "alt",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"required": false,
|
|
36
|
+
"default": "Avatar",
|
|
37
|
+
"description": "Alternative text for accessibility"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "name",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"required": true,
|
|
43
|
+
"default": "undefined",
|
|
44
|
+
"description": "Name displayed in hover card"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "info",
|
|
48
|
+
"type": "string",
|
|
49
|
+
"required": false,
|
|
50
|
+
"default": "undefined",
|
|
51
|
+
"description": "Additional info displayed in hover card"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "size",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"required": false,
|
|
57
|
+
"default": "md",
|
|
58
|
+
"description": "Avatar size",
|
|
59
|
+
"options": [
|
|
60
|
+
"sm",
|
|
61
|
+
"md",
|
|
62
|
+
"lg"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "cardPosition",
|
|
67
|
+
"type": "string",
|
|
68
|
+
"required": false,
|
|
69
|
+
"default": "bottom",
|
|
70
|
+
"description": "Position of hover card",
|
|
71
|
+
"options": [
|
|
72
|
+
"top",
|
|
73
|
+
"bottom"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"files": [
|
|
78
|
+
"component.jsx",
|
|
79
|
+
"index.js",
|
|
80
|
+
"meta.json"
|
|
81
|
+
],
|
|
82
|
+
"customization": {
|
|
83
|
+
"styling_notes": "Avatar size is controlled via the 'size' prop. Hover card colors, shadow, and border radius can be customized via Tailwind classes in the component."
|
|
84
|
+
},
|
|
85
|
+
"accessibility": {
|
|
86
|
+
"features": [
|
|
87
|
+
"Hover card provides additional info",
|
|
88
|
+
"Alt text for image for screen readers",
|
|
89
|
+
"Keyboard focus not required but can be extended"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const VARIANTS = {
|
|
4
|
+
elevated: "shadow-xl border-transparent",
|
|
5
|
+
outlined: "border border-opacity-20 shadow-sm",
|
|
6
|
+
flat: "shadow-none border-transparent",
|
|
7
|
+
glass: "backdrop-blur-xl bg-opacity-70 border border-white/10 shadow-lg",
|
|
8
|
+
gradient: "border-none shadow-2xl bg-gradient-to-br",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const THEMES = {
|
|
12
|
+
light: {
|
|
13
|
+
base: "bg-white text-slate-800",
|
|
14
|
+
glass: "bg-white/70 text-slate-900 border-slate-200/50",
|
|
15
|
+
border: "border-slate-200",
|
|
16
|
+
subtext: "text-slate-500",
|
|
17
|
+
divider: "border-slate-100",
|
|
18
|
+
},
|
|
19
|
+
dark: {
|
|
20
|
+
base: "bg-slate-900 text-slate-100",
|
|
21
|
+
glass: "bg-slate-900/60 text-white border-white/10",
|
|
22
|
+
border: "border-slate-800",
|
|
23
|
+
subtext: "text-slate-400",
|
|
24
|
+
divider: "border-slate-800",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const PADDINGS = {
|
|
29
|
+
none: "p-0",
|
|
30
|
+
sm: "p-3",
|
|
31
|
+
md: "p-6",
|
|
32
|
+
lg: "p-8",
|
|
33
|
+
xl: "p-10",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const ROUNDED = {
|
|
37
|
+
none: "rounded-none",
|
|
38
|
+
sm: "rounded-sm",
|
|
39
|
+
md: "rounded-md",
|
|
40
|
+
lg: "rounded-lg",
|
|
41
|
+
xl: "rounded-xl",
|
|
42
|
+
"2xl": "rounded-2xl",
|
|
43
|
+
"3xl": "rounded-3xl",
|
|
44
|
+
full: "rounded-full",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const HOVER_EFFECTS = {
|
|
48
|
+
none: "",
|
|
49
|
+
lift: "hover:-translate-y-1 hover:shadow-2xl",
|
|
50
|
+
scale: "hover:scale-[1.02]",
|
|
51
|
+
glow: "hover:shadow-indigo-500/20 hover:border-indigo-500/30",
|
|
52
|
+
shimmer: "hover:bg-opacity-90",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Object} props
|
|
57
|
+
* @param {React.ReactNode} [props.children]
|
|
58
|
+
* @param {string} [props.title]
|
|
59
|
+
* @param {string} [props.subtitle]
|
|
60
|
+
* @param {string} [props.image]
|
|
61
|
+
* @param {boolean} [props.imageFull]
|
|
62
|
+
* @param {React.ReactNode} [props.footer]
|
|
63
|
+
* @param {React.ReactNode} [props.action]
|
|
64
|
+
* @param {string} [props.badge]
|
|
65
|
+
* @param {string} [props.theme]
|
|
66
|
+
* @param {string} [props.variant]
|
|
67
|
+
* @param {string} [props.padding]
|
|
68
|
+
* @param {string} [props.rounded]
|
|
69
|
+
* @param {string} [props.hoverEffect]
|
|
70
|
+
* @param {string} [props.className]
|
|
71
|
+
* @param {string} [props.width]
|
|
72
|
+
* @param {function} [props.onClick]
|
|
73
|
+
*/
|
|
74
|
+
export default function Card({
|
|
75
|
+
children = undefined,
|
|
76
|
+
title = undefined,
|
|
77
|
+
subtitle = undefined,
|
|
78
|
+
image = undefined,
|
|
79
|
+
imageFull = false, // If true, image covers the background or top area completely without padding
|
|
80
|
+
footer = undefined,
|
|
81
|
+
action = undefined,
|
|
82
|
+
badge = undefined,
|
|
83
|
+
theme = "dark",
|
|
84
|
+
variant = "elevated",
|
|
85
|
+
padding = "md",
|
|
86
|
+
rounded = "2xl",
|
|
87
|
+
hoverEffect = "lift",
|
|
88
|
+
className = "",
|
|
89
|
+
width = "w-full max-w-sm", // Default width constraint
|
|
90
|
+
onClick = undefined,
|
|
91
|
+
...props
|
|
92
|
+
}) {
|
|
93
|
+
const isGlass = variant === "glass";
|
|
94
|
+
const currentTheme = THEMES[theme] || THEMES.dark;
|
|
95
|
+
const baseStyle = isGlass ? currentTheme.glass : currentTheme.base;
|
|
96
|
+
const borderColor = currentTheme.border;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
onClick={onClick}
|
|
101
|
+
className={`
|
|
102
|
+
relative flex flex-col overflow-hidden transition-all duration-300 ease-out group
|
|
103
|
+
${width}
|
|
104
|
+
${ROUNDED[rounded] || ROUNDED["2xl"]}
|
|
105
|
+
${VARIANTS[variant] || VARIANTS.elevated}
|
|
106
|
+
${baseStyle}
|
|
107
|
+
${!isGlass && variant === "outlined" ? borderColor : ""}
|
|
108
|
+
${HOVER_EFFECTS[hoverEffect] || ""}
|
|
109
|
+
${onClick ? "cursor-pointer" : ""}
|
|
110
|
+
${className}
|
|
111
|
+
`}
|
|
112
|
+
{...props}
|
|
113
|
+
>
|
|
114
|
+
{/* Background Image (Optional absolute overlay) */}
|
|
115
|
+
{image && imageFull && (
|
|
116
|
+
<div className="absolute inset-0 z-0">
|
|
117
|
+
<img
|
|
118
|
+
src={image}
|
|
119
|
+
alt={title || "Card background"}
|
|
120
|
+
className="h-full w-full object-cover opacity-20 group-hover:scale-105 transition-transform duration-700"
|
|
121
|
+
/>
|
|
122
|
+
<div
|
|
123
|
+
className={`absolute inset-0 bg-gradient-to-t ${
|
|
124
|
+
theme === "dark" ? "from-slate-900" : "from-white"
|
|
125
|
+
} via-transparent to-transparent`}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{/* Header Image (Standard top image) */}
|
|
131
|
+
{image && !imageFull && (
|
|
132
|
+
<div className="relative h-48 w-full overflow-hidden z-10">
|
|
133
|
+
<img
|
|
134
|
+
src={image}
|
|
135
|
+
alt={title}
|
|
136
|
+
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
137
|
+
/>
|
|
138
|
+
{badge && (
|
|
139
|
+
<div className="absolute top-4 left-4">
|
|
140
|
+
<span className="px-3 py-1 text-xs font-bold uppercase tracking-wider text-white bg-black/50 backdrop-blur-md rounded-full">
|
|
141
|
+
{badge}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{/* Content Container */}
|
|
149
|
+
<div className={`relative z-10 flex flex-col h-full ${PADDINGS[padding]}`}>
|
|
150
|
+
{/* Header Section */}
|
|
151
|
+
{(title || subtitle || action) && (
|
|
152
|
+
<div className="flex justify-between items-start mb-4">
|
|
153
|
+
<div className="flex-1">
|
|
154
|
+
{title && (
|
|
155
|
+
<h3 className="text-xl font-bold tracking-tight mb-1">
|
|
156
|
+
{title}
|
|
157
|
+
</h3>
|
|
158
|
+
)}
|
|
159
|
+
{subtitle && (
|
|
160
|
+
<p className={`text-sm font-medium ${currentTheme.subtext}`}>
|
|
161
|
+
{subtitle}
|
|
162
|
+
</p>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
{action && <div className="ml-4">{action}</div>}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{/* Main Body */}
|
|
170
|
+
<div className={`flex-1 ${currentTheme.subtext} leading-relaxed`}>
|
|
171
|
+
{children}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Footer Section */}
|
|
175
|
+
{footer && (
|
|
176
|
+
<div
|
|
177
|
+
className={`mt-6 pt-4 border-t ${currentTheme.divider} flex items-center justify-between`}
|
|
178
|
+
>
|
|
179
|
+
{footer}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Card.displayName = "Card";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Card",
|
|
3
|
+
"description": "A versatile card component with support for multiple themes, variants, and interactive states.",
|
|
4
|
+
"props": {
|
|
5
|
+
"title": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The main heading of the card."
|
|
8
|
+
},
|
|
9
|
+
"subtitle": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Secondary text usually placed below the title."
|
|
12
|
+
},
|
|
13
|
+
"image": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "URL of the image to display."
|
|
16
|
+
},
|
|
17
|
+
"imageFull": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false,
|
|
20
|
+
"description": "If true, the image is used as a background with an overlay."
|
|
21
|
+
},
|
|
22
|
+
"badge": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Top-left badge text overlaying the image."
|
|
25
|
+
},
|
|
26
|
+
"action": {
|
|
27
|
+
"type": "node",
|
|
28
|
+
"description": "Element to display in the top-right corner (e.g., button, icon)."
|
|
29
|
+
},
|
|
30
|
+
"footer": {
|
|
31
|
+
"type": "node",
|
|
32
|
+
"description": "Content to display at the bottom of the card with a divider."
|
|
33
|
+
},
|
|
34
|
+
"theme": {
|
|
35
|
+
"type": "enum",
|
|
36
|
+
"options": [
|
|
37
|
+
"light",
|
|
38
|
+
"dark"
|
|
39
|
+
],
|
|
40
|
+
"default": "dark",
|
|
41
|
+
"description": "Color scheme of the card."
|
|
42
|
+
},
|
|
43
|
+
"variant": {
|
|
44
|
+
"type": "enum",
|
|
45
|
+
"options": [
|
|
46
|
+
"elevated",
|
|
47
|
+
"outlined",
|
|
48
|
+
"flat",
|
|
49
|
+
"glass",
|
|
50
|
+
"gradient"
|
|
51
|
+
],
|
|
52
|
+
"default": "elevated",
|
|
53
|
+
"description": "Visual style of the card container."
|
|
54
|
+
},
|
|
55
|
+
"padding": {
|
|
56
|
+
"type": "enum",
|
|
57
|
+
"options": [
|
|
58
|
+
"none",
|
|
59
|
+
"sm",
|
|
60
|
+
"md",
|
|
61
|
+
"lg",
|
|
62
|
+
"xl"
|
|
63
|
+
],
|
|
64
|
+
"default": "md",
|
|
65
|
+
"description": "Internal padding of the content area."
|
|
66
|
+
},
|
|
67
|
+
"rounded": {
|
|
68
|
+
"type": "enum",
|
|
69
|
+
"options": [
|
|
70
|
+
"none",
|
|
71
|
+
"sm",
|
|
72
|
+
"md",
|
|
73
|
+
"lg",
|
|
74
|
+
"xl",
|
|
75
|
+
"2xl",
|
|
76
|
+
"3xl",
|
|
77
|
+
"full"
|
|
78
|
+
],
|
|
79
|
+
"default": "2xl",
|
|
80
|
+
"description": "Border radius of the card."
|
|
81
|
+
},
|
|
82
|
+
"hoverEffect": {
|
|
83
|
+
"type": "enum",
|
|
84
|
+
"options": [
|
|
85
|
+
"none",
|
|
86
|
+
"lift",
|
|
87
|
+
"scale",
|
|
88
|
+
"glow",
|
|
89
|
+
"shimmer"
|
|
90
|
+
],
|
|
91
|
+
"default": "lift",
|
|
92
|
+
"description": "Animation effect on hover."
|
|
93
|
+
},
|
|
94
|
+
"width": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"default": "w-full max-w-sm",
|
|
97
|
+
"description": "Width classes to control the card's dimensions."
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|