@rajdeep0510/scaffold-cli 1.2.0 → 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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "files.watcherExclude": {
3
+ "**/.git/objects/**": true,
4
+ "**/.git/subtree-cache/**": true,
5
+ "**/.hg/store/**": true,
6
+ "**/.git/**": true,
7
+ "**/node_modules/**": true,
8
+ "**/.vscode/**": true
9
+ }
10
+ }
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
- chalk.green(" import { Button } from '@/components/ui/button'");
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
- .command('add <component-name>')
37
- .description('Add a new component')
38
- .action(addComponent)
36
+ .command('add <component-name>')
37
+ .description('Add a new component')
38
+ .action(addComponent)
39
39
 
40
40
  program
41
41
  .command('list')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rajdeep0510/scaffold-cli",
3
- "version": "1.2.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,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,2 @@
1
+ export { default as Card } from "./Card";
2
+ export { default } from "./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
+ }
@@ -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,2 @@
1
+ export { default as Navbar } from "./Navbar";
2
+ export { default } from "./Navbar";
@@ -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,4 @@
1
+ import RadioGroup from "./RadioGroup";
2
+ export { RadioButton } from "./RadioGroup";
3
+
4
+ export default 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
+ }