@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} LinkItem
|
|
5
|
+
* @property {string} label - The text to display.
|
|
6
|
+
* @property {string} href - The URL link.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {React.ReactNode | string} [props.logo] - Brand logo or text.
|
|
12
|
+
* @param {LinkItem[]} [props.links] - Array of navigation links.
|
|
13
|
+
* @param {actionItem[]} [props.actions] - Right-side actions (e.g. Login buttons).
|
|
14
|
+
* @param {string} [props.theme] - "light" or "dark".
|
|
15
|
+
* @param {boolean} [props.sticky] - Whether the navbar is sticky.
|
|
16
|
+
* @param {string} [props.className] - Additional classes.
|
|
17
|
+
* @param {function} [props.onLinkClick] - Callback when a link is clicked.
|
|
18
|
+
*/
|
|
19
|
+
export default function Navbar({
|
|
20
|
+
logo = "Brand",
|
|
21
|
+
links = [
|
|
22
|
+
{ label: "Home", href: "#" },
|
|
23
|
+
{ label: "Features", href: "#" },
|
|
24
|
+
{ label: "Pricing", href: "#" },
|
|
25
|
+
{ label: "About", href: "#" },
|
|
26
|
+
],
|
|
27
|
+
actions = [
|
|
28
|
+
{ label: "Login", href: "#" },
|
|
29
|
+
{ label: "Sign Up", href: "#" },
|
|
30
|
+
],
|
|
31
|
+
theme = "light",
|
|
32
|
+
sticky = true,
|
|
33
|
+
className = "",
|
|
34
|
+
onLinkClick = undefined,
|
|
35
|
+
...props
|
|
36
|
+
}) {
|
|
37
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
38
|
+
const [scrolled, setScrolled] = useState(false);
|
|
39
|
+
|
|
40
|
+
// Handle scroll for glass effect or shadow
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const handleScroll = () => {
|
|
43
|
+
setScrolled(window.scrollY > 20);
|
|
44
|
+
};
|
|
45
|
+
window.addEventListener("scroll", handleScroll);
|
|
46
|
+
return () => window.removeEventListener("scroll", handleScroll);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const isDark = theme === "dark";
|
|
50
|
+
|
|
51
|
+
// Theme Styles
|
|
52
|
+
const baseStyles = isDark ? "bg-slate-900 text-white" : "bg-white text-slate-900";
|
|
53
|
+
const scrolledStyles = isDark
|
|
54
|
+
? "bg-slate-900/80 backdrop-blur-md border-b border-white/5"
|
|
55
|
+
: "bg-white/80 backdrop-blur-md border-b border-slate-200/50";
|
|
56
|
+
|
|
57
|
+
const navClasses = `
|
|
58
|
+
${sticky ? "fixed top-0 left-0 right-0 z-50" : "relative"}
|
|
59
|
+
${scrolled ? scrolledStyles : baseStyles}
|
|
60
|
+
${scrolled ? "shadow-sm" : ""}
|
|
61
|
+
transition-all duration-300 ease-in-out
|
|
62
|
+
${className}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
// Link Styles
|
|
66
|
+
const linkBase = "text-sm font-medium transition-colors duration-200 cursor-pointer";
|
|
67
|
+
const linkColor = isDark
|
|
68
|
+
? "text-slate-300 hover:text-white"
|
|
69
|
+
: "text-slate-600 hover:text-slate-900";
|
|
70
|
+
|
|
71
|
+
// Mobile Menu Styles
|
|
72
|
+
const mobileMenuClasses = `
|
|
73
|
+
absolute top-full left-0 right-0 p-4 border-b
|
|
74
|
+
${isDark ? "bg-slate-900 border-white/5" : "bg-white border-slate-100"}
|
|
75
|
+
${isOpen ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-4 pointer-events-none"}
|
|
76
|
+
transition-all duration-300 ease-out origin-top shadow-xl
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<nav className={navClasses} {...props}>
|
|
81
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
82
|
+
<div className="flex justify-between items-center h-16">
|
|
83
|
+
|
|
84
|
+
{/* Logo Section */}
|
|
85
|
+
<div className="flex-shrink-0 flex items-center gap-2">
|
|
86
|
+
<span className={`text-xl font-bold tracking-tight ${isDark ? "text-white" : "text-slate-900"}`}>
|
|
87
|
+
{logo}
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Desktop Links */}
|
|
92
|
+
<div className="hidden md:flex items-center space-x-8">
|
|
93
|
+
{links && links.map((link, index) => (
|
|
94
|
+
<a
|
|
95
|
+
key={index}
|
|
96
|
+
href={link.href}
|
|
97
|
+
onClick={(e) => {
|
|
98
|
+
if (onLinkClick) onLinkClick(e, link);
|
|
99
|
+
}}
|
|
100
|
+
className={`${linkBase} ${linkColor}`}
|
|
101
|
+
>
|
|
102
|
+
{link.label}
|
|
103
|
+
</a>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Actions (Desktop) */}
|
|
108
|
+
<div className="hidden md:flex items-center gap-4">
|
|
109
|
+
{Array.isArray(actions) ? (
|
|
110
|
+
actions.map((action, index) => (
|
|
111
|
+
<a
|
|
112
|
+
key={index}
|
|
113
|
+
href={action.href}
|
|
114
|
+
className={`px-4 py-2 text-sm font-medium rounded-lg transition-all ${
|
|
115
|
+
index === actions.length - 1
|
|
116
|
+
? isDark
|
|
117
|
+
? "bg-indigo-600 text-white hover:bg-indigo-500 shadow-lg shadow-indigo-500/20"
|
|
118
|
+
: "bg-slate-900 text-white hover:bg-slate-800 shadow-lg"
|
|
119
|
+
: isDark
|
|
120
|
+
? "text-slate-300 hover:text-white"
|
|
121
|
+
: "text-slate-600 hover:text-slate-900"
|
|
122
|
+
}`}
|
|
123
|
+
>
|
|
124
|
+
{action.label}
|
|
125
|
+
</a>
|
|
126
|
+
))
|
|
127
|
+
) : (
|
|
128
|
+
actions
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Mobile Toggle Button */}
|
|
133
|
+
<div className="flex md:hidden">
|
|
134
|
+
<button
|
|
135
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
136
|
+
className={`p-2 rounded-md ${
|
|
137
|
+
isDark ? "text-slate-300 hover:bg-slate-800" : "text-slate-600 hover:bg-slate-100"
|
|
138
|
+
} focus:outline-none`}
|
|
139
|
+
>
|
|
140
|
+
<span className="sr-only">Open main menu</span>
|
|
141
|
+
{/* Hamburger / Close Icon */}
|
|
142
|
+
<svg
|
|
143
|
+
className="h-6 w-6"
|
|
144
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
145
|
+
fill="none"
|
|
146
|
+
viewBox="0 0 24 24"
|
|
147
|
+
stroke="currentColor"
|
|
148
|
+
>
|
|
149
|
+
{isOpen ? (
|
|
150
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
151
|
+
) : (
|
|
152
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
153
|
+
)}
|
|
154
|
+
</svg>
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{/* Mobile Menu */}
|
|
161
|
+
<div className={mobileMenuClasses}>
|
|
162
|
+
<div className="flex flex-col space-y-4">
|
|
163
|
+
{links && links.map((link, index) => (
|
|
164
|
+
<a
|
|
165
|
+
key={index}
|
|
166
|
+
href={link.href}
|
|
167
|
+
onClick={(e) => {
|
|
168
|
+
setIsOpen(false);
|
|
169
|
+
if (onLinkClick) onLinkClick(e, link);
|
|
170
|
+
}}
|
|
171
|
+
className={`block px-3 py-2 rounded-md text-base font-medium ${
|
|
172
|
+
isDark
|
|
173
|
+
? "text-slate-300 hover:text-white hover:bg-slate-800"
|
|
174
|
+
: "text-slate-600 hover:text-slate-900 hover:bg-slate-50"
|
|
175
|
+
}`}
|
|
176
|
+
>
|
|
177
|
+
{link.label}
|
|
178
|
+
</a>
|
|
179
|
+
))}
|
|
180
|
+
<div className="pt-4 border-t border-gray-200/10 flex flex-col gap-3">
|
|
181
|
+
{Array.isArray(actions) ? (
|
|
182
|
+
actions.map((action, index) => (
|
|
183
|
+
<a
|
|
184
|
+
key={index}
|
|
185
|
+
href={action.href}
|
|
186
|
+
className={`w-full px-4 py-2 text-sm font-medium rounded-lg text-center transition-all ${
|
|
187
|
+
index === actions.length - 1
|
|
188
|
+
? isDark
|
|
189
|
+
? "bg-indigo-600 text-white hover:bg-indigo-500 shadow-lg"
|
|
190
|
+
: "bg-slate-900 text-white hover:bg-slate-800 shadow-lg"
|
|
191
|
+
: isDark
|
|
192
|
+
? "text-slate-300 hover:text-white border border-slate-700 hover:bg-slate-800"
|
|
193
|
+
: "text-slate-600 hover:text-slate-900 border border-slate-200 hover:bg-slate-50"
|
|
194
|
+
}`}
|
|
195
|
+
>
|
|
196
|
+
{action.label}
|
|
197
|
+
</a>
|
|
198
|
+
))
|
|
199
|
+
) : (
|
|
200
|
+
actions
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</nav>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Navbar",
|
|
3
|
+
"description": "A responsive navigation bar with logo, links, and action buttons. Supports desktop and mobile layouts with a hamburger menu.",
|
|
4
|
+
"props": {
|
|
5
|
+
"logo": {
|
|
6
|
+
"type": "node",
|
|
7
|
+
"default": "Brand",
|
|
8
|
+
"description": "The logo or brand name displayed on the left."
|
|
9
|
+
},
|
|
10
|
+
"links": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"default": "Home, Features, Pricing, About",
|
|
13
|
+
"description": "List of navigation items: { label, href }."
|
|
14
|
+
},
|
|
15
|
+
"actions": {
|
|
16
|
+
"type": "node",
|
|
17
|
+
"description": "Custom action buttons (e.g., Login/Signup) for the right side."
|
|
18
|
+
},
|
|
19
|
+
"theme": {
|
|
20
|
+
"type": "enum",
|
|
21
|
+
"options": [
|
|
22
|
+
"light",
|
|
23
|
+
"dark"
|
|
24
|
+
],
|
|
25
|
+
"default": "light",
|
|
26
|
+
"description": "Visual theme of the navbar."
|
|
27
|
+
},
|
|
28
|
+
"sticky": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"default": true,
|
|
31
|
+
"description": "If true, the navbar remains fixed at the top of the viewport."
|
|
32
|
+
},
|
|
33
|
+
"className": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"default": "",
|
|
36
|
+
"description": "Additional custom classes."
|
|
37
|
+
},
|
|
38
|
+
"onLinkClick": {
|
|
39
|
+
"type": "function",
|
|
40
|
+
"description": "Callback function triggered when a navigation link is clicked."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
const SIZES = {
|
|
4
|
+
sm: { circle: "h-4 w-4", dot: "h-1.5 w-1.5", text: "text-xs" },
|
|
5
|
+
md: { circle: "h-5 w-5", dot: "h-2 w-2", text: "text-sm" },
|
|
6
|
+
lg: { circle: "h-6 w-6", dot: "h-2.5 w-2.5", text: "text-base" },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const THEMES = {
|
|
10
|
+
light: {
|
|
11
|
+
labelRaw: "text-slate-500 group-hover:text-slate-800",
|
|
12
|
+
labelChecked: "text-slate-900",
|
|
13
|
+
borderRaw: "border-slate-300",
|
|
14
|
+
borderHover: "group-hover:border-slate-400",
|
|
15
|
+
dot: "bg-current",
|
|
16
|
+
},
|
|
17
|
+
dark: {
|
|
18
|
+
labelRaw: "text-slate-400 group-hover:text-slate-200",
|
|
19
|
+
labelChecked: "text-white",
|
|
20
|
+
borderRaw: "border-slate-700",
|
|
21
|
+
borderHover: "group-hover:border-slate-500",
|
|
22
|
+
dot: "bg-white",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const RadioButton = forwardRef(
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
label,
|
|
30
|
+
value,
|
|
31
|
+
selectedValue,
|
|
32
|
+
onChange,
|
|
33
|
+
name,
|
|
34
|
+
disabled,
|
|
35
|
+
size = "md",
|
|
36
|
+
theme = "dark",
|
|
37
|
+
activeColor = "text-blue-500",
|
|
38
|
+
...props
|
|
39
|
+
},
|
|
40
|
+
ref,
|
|
41
|
+
) => {
|
|
42
|
+
const isSelected = value === selectedValue;
|
|
43
|
+
const currentSize = SIZES[size] || SIZES.md;
|
|
44
|
+
const currentTheme = THEMES[theme] || THEMES.dark;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<label
|
|
48
|
+
className={`group flex items-center gap-3 select-none transition-all ${
|
|
49
|
+
disabled ? "opacity-30 cursor-not-allowed" : "cursor-pointer"
|
|
50
|
+
}`}
|
|
51
|
+
>
|
|
52
|
+
<div className="relative flex items-center justify-center">
|
|
53
|
+
<input
|
|
54
|
+
{...props}
|
|
55
|
+
ref={ref}
|
|
56
|
+
type="radio"
|
|
57
|
+
name={name}
|
|
58
|
+
checked={isSelected}
|
|
59
|
+
disabled={disabled}
|
|
60
|
+
onChange={() => onChange(value)}
|
|
61
|
+
className="peer sr-only"
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
{/* Outer Circle */}
|
|
65
|
+
<div
|
|
66
|
+
className={`
|
|
67
|
+
rounded-full border-2 transition-all duration-200 bg-transparent
|
|
68
|
+
${currentSize.circle}
|
|
69
|
+
${currentTheme.borderRaw}
|
|
70
|
+
${!disabled && currentTheme.borderHover}
|
|
71
|
+
peer-checked:border-current ${activeColor}
|
|
72
|
+
`}
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
{/* Inner Dot */}
|
|
76
|
+
<div
|
|
77
|
+
className={`
|
|
78
|
+
absolute rounded-full transition-transform duration-200
|
|
79
|
+
${currentSize.dot}
|
|
80
|
+
${currentTheme.dot}
|
|
81
|
+
${activeColor}
|
|
82
|
+
${isSelected ? "scale-100 opacity-100" : "scale-0 opacity-0"}
|
|
83
|
+
`}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{label && (
|
|
88
|
+
<span
|
|
89
|
+
className={`
|
|
90
|
+
font-medium transition-colors
|
|
91
|
+
${currentSize.text}
|
|
92
|
+
${isSelected ? currentTheme.labelChecked : currentTheme.labelRaw}
|
|
93
|
+
`}
|
|
94
|
+
>
|
|
95
|
+
{label}
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
</label>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
RadioButton.displayName = "RadioButton";
|
|
104
|
+
|
|
105
|
+
export default function RadioGroup({
|
|
106
|
+
options,
|
|
107
|
+
value,
|
|
108
|
+
onChange,
|
|
109
|
+
name,
|
|
110
|
+
label,
|
|
111
|
+
orientation = "vertical",
|
|
112
|
+
size = "md",
|
|
113
|
+
theme = "dark",
|
|
114
|
+
activeColor = "text-blue-500",
|
|
115
|
+
className = "",
|
|
116
|
+
}) {
|
|
117
|
+
return (
|
|
118
|
+
<fieldset className={`flex flex-col gap-4 ${className}`}>
|
|
119
|
+
{label && (
|
|
120
|
+
<legend className="text-[10px] font-bold tracking-[0.2em] text-slate-500 uppercase">
|
|
121
|
+
{label}
|
|
122
|
+
</legend>
|
|
123
|
+
)}
|
|
124
|
+
<div
|
|
125
|
+
className={`flex ${
|
|
126
|
+
orientation === "vertical" ? "flex-col gap-3" : "flex-row gap-6"
|
|
127
|
+
}`}
|
|
128
|
+
>
|
|
129
|
+
{options.map((opt) => (
|
|
130
|
+
<RadioButton
|
|
131
|
+
key={opt.value}
|
|
132
|
+
name={name}
|
|
133
|
+
label={opt.label}
|
|
134
|
+
value={opt.value}
|
|
135
|
+
selectedValue={value}
|
|
136
|
+
onChange={onChange}
|
|
137
|
+
disabled={opt.disabled}
|
|
138
|
+
size={size}
|
|
139
|
+
theme={theme}
|
|
140
|
+
activeColor={activeColor}
|
|
141
|
+
/>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
</fieldset>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
RadioGroup.displayName = "RadioGroup";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "RadioGroup",
|
|
3
|
+
"description": "Minimalist dark-mode radio group with full prop-based customizability.",
|
|
4
|
+
"props": {
|
|
5
|
+
"activeColor": "Tailwind text color class (default: text-blue-500)",
|
|
6
|
+
"size": ["sm", "md", "lg"],
|
|
7
|
+
"orientation": ["vertical", "horizontal"],
|
|
8
|
+
"options": "Array<{label, value, disabled}>"
|
|
9
|
+
}
|
|
10
|
+
}
|