@rajdeep0510/scaffold-cli 1.0.1 → 1.2.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/.github/workflows/npm-publish.yml +28 -0
- package/README.md +2 -0
- package/commands/addComponent.js +76 -50
- package/package.json +1 -1
- package/templates/Avatar/component.jsx +62 -0
- package/templates/Avatar/index.js +5 -0
- package/templates/Avatar/meta.json +92 -0
- package/templates/Input/components.jsx +33 -0
- package/templates/Input/index.js +5 -0
- package/templates/Input/meta.json +82 -0
- package/templates/button/component.jsx +73 -80
- package/templates/button/index.js +6 -2
- package/templates/button/meta.json +142 -18
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish npm Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main # Trigger when pushing to main branch
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout repo
|
|
14
|
+
uses: actions/checkout@v3
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v3
|
|
18
|
+
with:
|
|
19
|
+
node-version: "20"
|
|
20
|
+
registry-url: "https://registry.npmjs.org/"
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Publish to npm
|
|
26
|
+
run: npm publish
|
|
27
|
+
env:
|
|
28
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
CHANGED
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
|
@@ -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,33 @@
|
|
|
1
|
+
// component.jsx
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
|
|
4
|
+
export default function Input({
|
|
5
|
+
type = "text",
|
|
6
|
+
placeholder = "",
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
className = "",
|
|
10
|
+
}) {
|
|
11
|
+
return (
|
|
12
|
+
<input
|
|
13
|
+
type={type}
|
|
14
|
+
placeholder={placeholder}
|
|
15
|
+
value={value}
|
|
16
|
+
onChange={onChange}
|
|
17
|
+
className={`px-3 py-2 rounded-md border outline-none
|
|
18
|
+
transition-all duration-200
|
|
19
|
+
border-gray-300 focus:border-gray-500
|
|
20
|
+
focus:ring-2 focus:ring-gray-300 focus:ring
|
|
21
|
+
${className}`}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// PropTypes validation
|
|
27
|
+
Input.propTypes = {
|
|
28
|
+
type: PropTypes.string,
|
|
29
|
+
placeholder: PropTypes.string,
|
|
30
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
31
|
+
onChange: PropTypes.func,
|
|
32
|
+
className: PropTypes.string,
|
|
33
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Input",
|
|
3
|
+
"description": "A customizable input field component with glow effect on focus.",
|
|
4
|
+
"category": "form",
|
|
5
|
+
"tags": [
|
|
6
|
+
"input",
|
|
7
|
+
"form",
|
|
8
|
+
"field",
|
|
9
|
+
"text",
|
|
10
|
+
"tailwind",
|
|
11
|
+
"ui"
|
|
12
|
+
],
|
|
13
|
+
"version": "1.0.0",
|
|
14
|
+
"author": "@rajdeep",
|
|
15
|
+
"created": "2025-08-30",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"npm": [],
|
|
18
|
+
"peer": [
|
|
19
|
+
"react",
|
|
20
|
+
"prop-types"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"props": [
|
|
24
|
+
{
|
|
25
|
+
"name": "type",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"required": false,
|
|
28
|
+
"default": "text",
|
|
29
|
+
"description": "The type of the input field",
|
|
30
|
+
"options": [
|
|
31
|
+
"text",
|
|
32
|
+
"email",
|
|
33
|
+
"password",
|
|
34
|
+
"number",
|
|
35
|
+
"url"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "placeholder",
|
|
40
|
+
"type": "string",
|
|
41
|
+
"required": false,
|
|
42
|
+
"default": "''",
|
|
43
|
+
"description": "Placeholder text shown when input is empty"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "value",
|
|
47
|
+
"type": "string | number",
|
|
48
|
+
"required": false,
|
|
49
|
+
"default": "undefined",
|
|
50
|
+
"description": "The value of the input"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "onChange",
|
|
54
|
+
"type": "function",
|
|
55
|
+
"required": false,
|
|
56
|
+
"default": "undefined",
|
|
57
|
+
"description": "Callback function triggered when input value changes"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "className",
|
|
61
|
+
"type": "string",
|
|
62
|
+
"required": false,
|
|
63
|
+
"default": "''",
|
|
64
|
+
"description": "Custom Tailwind or CSS classes to style the input"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"files": [
|
|
68
|
+
"component.jsx",
|
|
69
|
+
"index.js",
|
|
70
|
+
"meta.json"
|
|
71
|
+
],
|
|
72
|
+
"customization": {
|
|
73
|
+
"styling_notes": "Input uses Tailwind classes for border, padding, and glow effect. Customize glow using focus:ring utilities."
|
|
74
|
+
},
|
|
75
|
+
"accessibility": {
|
|
76
|
+
"features": [
|
|
77
|
+
"Semantic <input> element",
|
|
78
|
+
"Supports keyboard navigation",
|
|
79
|
+
"Screen reader friendly with placeholder"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -1,92 +1,85 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import PropTypes from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import clsx from "clsx";
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* A customizable button component with multiple variants and sizes + click animation
|
|
6
|
-
*/
|
|
7
5
|
const Button = ({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
children,
|
|
7
|
+
size = "sm",
|
|
8
|
+
disabled = false,
|
|
9
|
+
loading = false,
|
|
10
|
+
className = "",
|
|
11
|
+
onClick,
|
|
12
|
+
type = "button",
|
|
13
|
+
borderColor = "white", // "white" or "black"
|
|
14
|
+
borderWidth = 1,
|
|
15
|
+
borderStyle = "solid",
|
|
16
|
+
borderRadius = "md", // sm, md, lg, xl, full
|
|
17
|
+
...rest
|
|
18
|
+
}) => {
|
|
19
|
+
const sizeClasses = {
|
|
20
|
+
sm: "h-8 px-3 text-sm",
|
|
21
|
+
md: "h-10 px-4 text-sm",
|
|
22
|
+
lg: "h-12 px-5 text-base",
|
|
23
|
+
xl: "h-14 px-6 text-lg",
|
|
24
|
+
};
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
fontWeight: '500',
|
|
28
|
-
cursor: disabled || loading ? 'not-allowed' : 'pointer',
|
|
29
|
-
transition: 'all 0.15s ease-in-out',
|
|
30
|
-
opacity: disabled || loading ? 0.6 : 1,
|
|
31
|
-
transform: isActive ? 'scale(0.95)' : 'scale(1)', // 👈 shrink effect
|
|
32
|
-
};
|
|
26
|
+
const radiusClasses = {
|
|
27
|
+
sm: "rounded-sm",
|
|
28
|
+
md: "rounded-md",
|
|
29
|
+
lg: "rounded-lg",
|
|
30
|
+
xl: "rounded-xl",
|
|
31
|
+
full: "rounded-full",
|
|
32
|
+
};
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ghost: { backgroundColor: 'transparent', color: '#374151' },
|
|
40
|
-
};
|
|
34
|
+
const borderClasses = clsx(
|
|
35
|
+
`border-${borderWidth}`,
|
|
36
|
+
borderStyle !== "solid" && `border-${borderStyle}`,
|
|
37
|
+
borderColor === "white" ? "border-white" : "border-black"
|
|
38
|
+
);
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
const buttonClasses = clsx(
|
|
41
|
+
"inline-flex items-center justify-center font-medium",
|
|
42
|
+
"transition-transform duration-150 ease-in-out", // smooth animation
|
|
43
|
+
"active:scale-95", // 👈 shrink when clicked
|
|
44
|
+
borderClasses,
|
|
45
|
+
radiusClasses[borderRadius],
|
|
46
|
+
sizeClasses[size],
|
|
47
|
+
disabled || loading ? "opacity-60 cursor-not-allowed" : "cursor-pointer",
|
|
48
|
+
className
|
|
49
|
+
);
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
type={type}
|
|
68
|
-
disabled={disabled || loading}
|
|
69
|
-
onClick={handleClick}
|
|
70
|
-
className={className}
|
|
71
|
-
style={buttonStyles}
|
|
72
|
-
aria-disabled={disabled || loading}
|
|
73
|
-
aria-busy={loading}
|
|
74
|
-
{...rest}
|
|
75
|
-
>
|
|
76
|
-
{loading ? 'Loading...' : children}
|
|
77
|
-
</button>
|
|
78
|
-
);
|
|
51
|
+
return (
|
|
52
|
+
<button
|
|
53
|
+
type={type}
|
|
54
|
+
disabled={disabled || loading}
|
|
55
|
+
onClick={onClick}
|
|
56
|
+
className={buttonClasses}
|
|
57
|
+
{...rest}
|
|
58
|
+
>
|
|
59
|
+
{loading ? (
|
|
60
|
+
<span className="flex items-center gap-2">
|
|
61
|
+
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
62
|
+
Loading...
|
|
63
|
+
</span>
|
|
64
|
+
) : (
|
|
65
|
+
children
|
|
66
|
+
)}
|
|
67
|
+
</button>
|
|
68
|
+
);
|
|
79
69
|
};
|
|
80
70
|
|
|
81
71
|
Button.propTypes = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
72
|
+
children: PropTypes.node,
|
|
73
|
+
size: PropTypes.oneOf(["sm", "md", "lg", "xl"]),
|
|
74
|
+
disabled: PropTypes.bool,
|
|
75
|
+
loading: PropTypes.bool,
|
|
76
|
+
className: PropTypes.string,
|
|
77
|
+
onClick: PropTypes.func,
|
|
78
|
+
type: PropTypes.oneOf(["button", "submit", "reset"]),
|
|
79
|
+
borderColor: PropTypes.oneOf(["black", "white"]),
|
|
80
|
+
borderWidth: PropTypes.number,
|
|
81
|
+
borderStyle: PropTypes.oneOf(["solid", "dashed", "dotted", "double"]),
|
|
82
|
+
borderRadius: PropTypes.oneOf(["sm", "md", "lg", "xl", "full"]),
|
|
90
83
|
};
|
|
91
84
|
|
|
92
85
|
export default Button;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Button component barrel export
|
|
3
|
+
* This file allows importing the Button component cleanly
|
|
4
|
+
*/
|
|
5
|
+
export { default } from "./component";
|
|
6
|
+
export { default as Button } from "./component";
|
|
@@ -1,28 +1,152 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Button",
|
|
3
|
-
"description": "A customizable button component with
|
|
3
|
+
"description": "A customizable button component with size, border, radius, and click animation. Fully compatible with Tailwind CSS for background and text colors.",
|
|
4
4
|
"category": "form",
|
|
5
|
-
"tags": [
|
|
5
|
+
"tags": [
|
|
6
|
+
"button",
|
|
7
|
+
"interactive",
|
|
8
|
+
"form",
|
|
9
|
+
"cta",
|
|
10
|
+
"action",
|
|
11
|
+
"tailwind"
|
|
12
|
+
],
|
|
6
13
|
"version": "1.0.0",
|
|
14
|
+
"author": "@rajdeep",
|
|
15
|
+
"created": "2025-08-28",
|
|
7
16
|
"dependencies": {
|
|
8
|
-
"npm": [
|
|
9
|
-
|
|
17
|
+
"npm": [
|
|
18
|
+
"clsx"
|
|
19
|
+
],
|
|
20
|
+
"peer": [
|
|
21
|
+
"react",
|
|
22
|
+
"prop-types"
|
|
23
|
+
]
|
|
10
24
|
},
|
|
11
25
|
"props": [
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
26
|
+
{
|
|
27
|
+
"name": "children",
|
|
28
|
+
"type": "React.ReactNode",
|
|
29
|
+
"required": false,
|
|
30
|
+
"default": "undefined",
|
|
31
|
+
"description": "The content to display inside the button"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "size",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"required": false,
|
|
37
|
+
"default": "sm",
|
|
38
|
+
"description": "The size of the button",
|
|
39
|
+
"options": [
|
|
40
|
+
"sm",
|
|
41
|
+
"md",
|
|
42
|
+
"lg",
|
|
43
|
+
"xl"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "disabled",
|
|
48
|
+
"type": "boolean",
|
|
49
|
+
"required": false,
|
|
50
|
+
"default": "false",
|
|
51
|
+
"description": "Whether the button is disabled and non-interactive"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "loading",
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"required": false,
|
|
57
|
+
"default": "false",
|
|
58
|
+
"description": "Whether the button is in a loading state with spinner"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "className",
|
|
62
|
+
"type": "string",
|
|
63
|
+
"required": false,
|
|
64
|
+
"default": "''",
|
|
65
|
+
"description": "Tailwind or custom classes to style background, text, hover, etc."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "onClick",
|
|
69
|
+
"type": "function",
|
|
70
|
+
"required": false,
|
|
71
|
+
"default": "undefined",
|
|
72
|
+
"description": "Function to call when the button is clicked"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "type",
|
|
76
|
+
"type": "string",
|
|
77
|
+
"required": false,
|
|
78
|
+
"default": "button",
|
|
79
|
+
"description": "The HTML button type attribute",
|
|
80
|
+
"options": [
|
|
81
|
+
"button",
|
|
82
|
+
"submit",
|
|
83
|
+
"reset"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "borderColor",
|
|
88
|
+
"type": "string",
|
|
89
|
+
"required": false,
|
|
90
|
+
"default": "white",
|
|
91
|
+
"description": "Border color of the button",
|
|
92
|
+
"options": [
|
|
93
|
+
"black",
|
|
94
|
+
"white"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "borderWidth",
|
|
99
|
+
"type": "number",
|
|
100
|
+
"required": false,
|
|
101
|
+
"default": "1",
|
|
102
|
+
"description": "Border thickness of the button"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "borderStyle",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"required": false,
|
|
108
|
+
"default": "solid",
|
|
109
|
+
"description": "Border style of the button",
|
|
110
|
+
"options": [
|
|
111
|
+
"solid",
|
|
112
|
+
"dashed",
|
|
113
|
+
"dotted",
|
|
114
|
+
"double"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "borderRadius",
|
|
119
|
+
"type": "string",
|
|
120
|
+
"required": false,
|
|
121
|
+
"default": "md",
|
|
122
|
+
"description": "Border radius for rounded corners",
|
|
123
|
+
"options": [
|
|
124
|
+
"sm",
|
|
125
|
+
"md",
|
|
126
|
+
"lg",
|
|
127
|
+
"xl",
|
|
128
|
+
"full"
|
|
129
|
+
]
|
|
130
|
+
}
|
|
20
131
|
],
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ "name": "Loading", "code": "<Button loading>Loading...</Button>" }
|
|
132
|
+
"files": [
|
|
133
|
+
"component.jsx",
|
|
134
|
+
"index.js",
|
|
135
|
+
"meta.json"
|
|
26
136
|
],
|
|
27
|
-
"
|
|
137
|
+
"customization": {
|
|
138
|
+
"css_variables": [
|
|
139
|
+
"--button-border-radius",
|
|
140
|
+
"--button-transition"
|
|
141
|
+
],
|
|
142
|
+
"styling_notes": "Background and text colors are meant to be customized using Tailwind classes via className. Borders and radius are controlled via props."
|
|
143
|
+
},
|
|
144
|
+
"accessibility": {
|
|
145
|
+
"features": [
|
|
146
|
+
"Keyboard navigation support",
|
|
147
|
+
"Screen reader friendly with aria-disabled and aria-busy",
|
|
148
|
+
"Focus visible outline",
|
|
149
|
+
"Semantic <button> element"
|
|
150
|
+
]
|
|
151
|
+
}
|
|
28
152
|
}
|