radtools 0.1.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.
Files changed (133) hide show
  1. package/README.md +108 -0
  2. package/bin/radtools.js +5 -0
  3. package/dist/cli/index.js +427 -0
  4. package/package.json +55 -0
  5. package/templates/api-routes/assets/optimize/route.ts +94 -0
  6. package/templates/api-routes/assets/route.ts +159 -0
  7. package/templates/api-routes/components/create-folder/route.ts +55 -0
  8. package/templates/api-routes/components/route.ts +156 -0
  9. package/templates/api-routes/fonts/route.ts +96 -0
  10. package/templates/api-routes/fonts/upload/route.ts +79 -0
  11. package/templates/api-routes/read-css/route.ts +29 -0
  12. package/templates/api-routes/write-css/route.ts +423 -0
  13. package/templates/components/Rad_os/AppWindow.tsx +423 -0
  14. package/templates/components/Rad_os/MobileAppModal.tsx +76 -0
  15. package/templates/components/Rad_os/WindowTitleBar.tsx +290 -0
  16. package/templates/components/icons/Icon.tsx +224 -0
  17. package/templates/components/icons/README.md +85 -0
  18. package/templates/components/icons/index.ts +20 -0
  19. package/templates/components/icons.tsx +164 -0
  20. package/templates/components/ui/Accordion.tsx +268 -0
  21. package/templates/components/ui/Alert.tsx +111 -0
  22. package/templates/components/ui/Badge.tsx +87 -0
  23. package/templates/components/ui/Breadcrumbs.tsx +88 -0
  24. package/templates/components/ui/Button.tsx +249 -0
  25. package/templates/components/ui/Card.tsx +137 -0
  26. package/templates/components/ui/Checkbox.tsx +137 -0
  27. package/templates/components/ui/ContextMenu.tsx +220 -0
  28. package/templates/components/ui/Dialog.tsx +264 -0
  29. package/templates/components/ui/Divider.tsx +70 -0
  30. package/templates/components/ui/DropdownMenu.tsx +301 -0
  31. package/templates/components/ui/HelpPanel.tsx +119 -0
  32. package/templates/components/ui/Input.tsx +176 -0
  33. package/templates/components/ui/Popover.tsx +211 -0
  34. package/templates/components/ui/Progress.tsx +158 -0
  35. package/templates/components/ui/Select.tsx +134 -0
  36. package/templates/components/ui/Sheet.tsx +316 -0
  37. package/templates/components/ui/Slider.tsx +223 -0
  38. package/templates/components/ui/Switch.tsx +155 -0
  39. package/templates/components/ui/Tabs.tsx +253 -0
  40. package/templates/components/ui/Toast.tsx +192 -0
  41. package/templates/components/ui/Tooltip.tsx +129 -0
  42. package/templates/components/ui/hooks/useModalBehavior.ts +66 -0
  43. package/templates/components/ui/index.ts +84 -0
  44. package/templates/devtools/DevToolsPanel.tsx +261 -0
  45. package/templates/devtools/DevToolsProvider.tsx +43 -0
  46. package/templates/devtools/components/BreakpointIndicator.tsx +49 -0
  47. package/templates/devtools/components/ColorPicker.tsx +33 -0
  48. package/templates/devtools/components/ComponentsSecondaryNav.tsx +44 -0
  49. package/templates/devtools/components/ContextualFooter.tsx +56 -0
  50. package/templates/devtools/components/DraggablePanel.tsx +43 -0
  51. package/templates/devtools/components/PrimaryNavigationFooter.tsx +254 -0
  52. package/templates/devtools/components/SearchableColorDropdown.tsx +253 -0
  53. package/templates/devtools/components/SecondaryNavigation.tsx +36 -0
  54. package/templates/devtools/components/TokenDropdown.tsx +47 -0
  55. package/templates/devtools/components/TypographyFooter.tsx +145 -0
  56. package/templates/devtools/hooks/useMockState.ts +16 -0
  57. package/templates/devtools/index.ts +17 -0
  58. package/templates/devtools/lib/componentScanner.ts +78 -0
  59. package/templates/devtools/lib/cssParser.ts +465 -0
  60. package/templates/devtools/lib/searchIndexes.ts +45 -0
  61. package/templates/devtools/lib/selectorGenerator.ts +86 -0
  62. package/templates/devtools/store/index.ts +66 -0
  63. package/templates/devtools/store/slices/assetsSlice.ts +106 -0
  64. package/templates/devtools/store/slices/componentsSlice.ts +59 -0
  65. package/templates/devtools/store/slices/mockStatesSlice.ts +77 -0
  66. package/templates/devtools/store/slices/panelSlice.ts +17 -0
  67. package/templates/devtools/store/slices/typographySlice.ts +538 -0
  68. package/templates/devtools/store/slices/variablesSlice.ts +167 -0
  69. package/templates/devtools/tabs/AssetsTab/AssetGrid.tsx +76 -0
  70. package/templates/devtools/tabs/AssetsTab/FolderTree.tsx +53 -0
  71. package/templates/devtools/tabs/AssetsTab/UploadDropzone.tsx +76 -0
  72. package/templates/devtools/tabs/AssetsTab/index.tsx +182 -0
  73. package/templates/devtools/tabs/ComponentsTab/AddTabButton.tsx +63 -0
  74. package/templates/devtools/tabs/ComponentsTab/ComponentList.tsx +153 -0
  75. package/templates/devtools/tabs/ComponentsTab/DesignSystemTab.tsx +1515 -0
  76. package/templates/devtools/tabs/ComponentsTab/DynamicFolderTab.tsx +113 -0
  77. package/templates/devtools/tabs/ComponentsTab/PropDisplay.tsx +55 -0
  78. package/templates/devtools/tabs/ComponentsTab/index.tsx +167 -0
  79. package/templates/devtools/tabs/ComponentsTab/previews/.gitkeep +4 -0
  80. package/templates/devtools/tabs/ComponentsTab/previews/Rad_os.tsx +262 -0
  81. package/templates/devtools/tabs/ComponentsTab/tabConfig.ts +53 -0
  82. package/templates/devtools/tabs/MockStatesTab/index.tsx +29 -0
  83. package/templates/devtools/tabs/TypographyTab/FontManager.tsx +421 -0
  84. package/templates/devtools/tabs/TypographyTab/TypographyStylesDisplay.tsx +290 -0
  85. package/templates/devtools/tabs/TypographyTab/index.tsx +98 -0
  86. package/templates/devtools/tabs/VariablesTab/BaseColorEditor.tsx +267 -0
  87. package/templates/devtools/tabs/VariablesTab/BorderRadiusEditor.tsx +37 -0
  88. package/templates/devtools/tabs/VariablesTab/ColorModeSelector.tsx +235 -0
  89. package/templates/devtools/tabs/VariablesTab/index.tsx +100 -0
  90. package/templates/devtools/types/index.ts +99 -0
  91. package/templates/globals.css +574 -0
  92. package/templates/hooks/index.ts +1 -0
  93. package/templates/hooks/useWindowManager.ts +212 -0
  94. package/templates/public/assets/icons/avatar.svg +18 -0
  95. package/templates/public/assets/icons/checkmark-filled.svg +14 -0
  96. package/templates/public/assets/icons/checkmark.svg +14 -0
  97. package/templates/public/assets/icons/chevron-down.svg +14 -0
  98. package/templates/public/assets/icons/close.svg +14 -0
  99. package/templates/public/assets/icons/copy.svg +14 -0
  100. package/templates/public/assets/icons/download.svg +14 -0
  101. package/templates/public/assets/icons/expand.svg +31 -0
  102. package/templates/public/assets/icons/file-blank.svg +17 -0
  103. package/templates/public/assets/icons/file-image.svg +19 -0
  104. package/templates/public/assets/icons/file-written.svg +17 -0
  105. package/templates/public/assets/icons/folder-closed.svg +17 -0
  106. package/templates/public/assets/icons/folder-open.svg +17 -0
  107. package/templates/public/assets/icons/hamburger.svg +18 -0
  108. package/templates/public/assets/icons/home-outline.svg +28 -0
  109. package/templates/public/assets/icons/home.svg +30 -0
  110. package/templates/public/assets/icons/hourglass.svg +25 -0
  111. package/templates/public/assets/icons/information-circle.svg +14 -0
  112. package/templates/public/assets/icons/information.svg +17 -0
  113. package/templates/public/assets/icons/lightning.svg +14 -0
  114. package/templates/public/assets/icons/locked.svg +17 -0
  115. package/templates/public/assets/icons/not-allowed.svg +14 -0
  116. package/templates/public/assets/icons/plus.svg +5 -0
  117. package/templates/public/assets/icons/power-thin.svg +17 -0
  118. package/templates/public/assets/icons/power.svg +17 -0
  119. package/templates/public/assets/icons/question-block.svg +14 -0
  120. package/templates/public/assets/icons/question.svg +17 -0
  121. package/templates/public/assets/icons/refresh-block.svg +14 -0
  122. package/templates/public/assets/icons/refresh.svg +17 -0
  123. package/templates/public/assets/icons/save.svg +14 -0
  124. package/templates/public/assets/icons/search.svg +25 -0
  125. package/templates/public/assets/icons/settings.svg +14 -0
  126. package/templates/public/assets/icons/trash-full.svg +21 -0
  127. package/templates/public/assets/icons/trash-open.svg +23 -0
  128. package/templates/public/assets/icons/trash.svg +18 -0
  129. package/templates/public/assets/icons/unlocked.svg +17 -0
  130. package/templates/public/assets/icons/waring-triangle-filled.svg +17 -0
  131. package/templates/public/assets/icons/warning-triangle-filled-2.svg +30 -0
  132. package/templates/public/assets/icons/warning-triangle-lines.svg +29 -0
  133. package/templates/public/assets/icons/wrench.svg +17 -0
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import Link from 'next/link';
5
+
6
+ // ============================================================================
7
+ // Types
8
+ // ============================================================================
9
+
10
+ interface BreadcrumbItem {
11
+ /** Display label */
12
+ label: string;
13
+ /** Navigation href (optional for current/last item) */
14
+ href?: string;
15
+ }
16
+
17
+ interface BreadcrumbsProps {
18
+ /** Breadcrumb items */
19
+ items: BreadcrumbItem[];
20
+ /** Separator character */
21
+ separator?: string;
22
+ /** Additional className */
23
+ className?: string;
24
+ }
25
+
26
+ // ============================================================================
27
+ // Component
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Breadcrumbs component - Navigation hierarchy
32
+ */
33
+ export function Breadcrumbs({
34
+ items,
35
+ separator = '/',
36
+ className = '',
37
+ }: BreadcrumbsProps) {
38
+ if (items.length === 0) return null;
39
+
40
+ return (
41
+ <nav
42
+ aria-label="Breadcrumb"
43
+ className={`flex items-center gap-2 ${className}`.trim()}
44
+ >
45
+ <ol className="flex items-center gap-2">
46
+ {items.map((item, index) => {
47
+ const isLast = index === items.length - 1;
48
+
49
+ return (
50
+ <li key={index} className="flex items-center gap-2">
51
+ {/* Separator */}
52
+ {index > 0 && (
53
+ <span
54
+ className="font-mondwest text-base text-black/40"
55
+ aria-hidden="true"
56
+ >
57
+ {separator}
58
+ </span>
59
+ )}
60
+
61
+ {/* Item */}
62
+ {item.href && !isLast ? (
63
+ <Link
64
+ href={item.href}
65
+ className="font-mondwest text-base text-black/60 hover:text-black hover:underline transition-colors"
66
+ >
67
+ {item.label}
68
+ </Link>
69
+ ) : (
70
+ <span
71
+ className={`
72
+ font-mondwest text-base
73
+ ${isLast ? 'text-black font-semibold' : 'text-black/60'}
74
+ `.trim()}
75
+ aria-current={isLast ? 'page' : undefined}
76
+ >
77
+ {item.label}
78
+ </span>
79
+ )}
80
+ </li>
81
+ );
82
+ })}
83
+ </ol>
84
+ </nav>
85
+ );
86
+ }
87
+
88
+ export default Breadcrumbs;
@@ -0,0 +1,249 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import Link from 'next/link';
5
+ import { Icon } from '@/components/icons';
6
+ import { Spinner } from './Progress';
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
13
+ type ButtonSize = 'sm' | 'md' | 'lg';
14
+
15
+ interface BaseButtonProps {
16
+ /** Visual variant */
17
+ variant?: ButtonVariant;
18
+ /** Size preset */
19
+ size?: ButtonSize;
20
+ /** Expand to fill container width */
21
+ fullWidth?: boolean;
22
+ /** Square button with icon only (no text) */
23
+ iconOnly?: boolean;
24
+ /** Icon name (filename without .svg extension) */
25
+ iconName?: string;
26
+ /** Show loading spinner (only applies to buttons with icons) */
27
+ loading?: boolean;
28
+ /** Button content (optional when iconOnly is true) */
29
+ children?: React.ReactNode;
30
+ /** Additional className */
31
+ className?: string;
32
+ }
33
+
34
+ interface ButtonAsButtonProps extends BaseButtonProps, Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseButtonProps> {
35
+ /** URL for navigation - when provided, button can act as a link */
36
+ href?: undefined;
37
+ }
38
+
39
+ interface ButtonAsLinkProps extends BaseButtonProps, Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseButtonProps> {
40
+ /** URL for navigation - renders as Next.js Link */
41
+ href: string;
42
+ /** Whether to render as Next.js Link (true) or use window.open (false) */
43
+ asLink?: boolean;
44
+ /** Target for link navigation (e.g., '_blank') */
45
+ target?: string;
46
+ }
47
+
48
+ type ButtonProps = ButtonAsButtonProps | ButtonAsLinkProps;
49
+
50
+ // ============================================================================
51
+ // Styles
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Base styles applied to all buttons
56
+ * - Retro lift effect with box-shadow
57
+ * - NO transitions (instant state changes)
58
+ */
59
+ const baseStyles = `
60
+ inline-flex items-center
61
+ font-joystix uppercase
62
+ whitespace-nowrap
63
+ cursor-pointer select-none
64
+ border border-black
65
+ rounded-sm
66
+ shadow-[0_1px_0_0_var(--color-black)]
67
+ hover:-translate-y-0.5
68
+ hover:shadow-[0_3px_0_0_var(--color-black)]
69
+ active:translate-y-0.5
70
+ active:shadow-none
71
+ disabled:opacity-50 disabled:cursor-not-allowed
72
+ disabled:hover:translate-y-0 disabled:hover:shadow-[0_1px_0_0_var(--color-black)]
73
+ `;
74
+
75
+ /**
76
+ * Size presets
77
+ * All buttons use h-8 (2rem) height for consistency
78
+ * Text sizes: sm=10px, md=12px, lg=14px
79
+ */
80
+ const sizeStyles: Record<ButtonSize, string> = {
81
+ sm: 'h-8 px-3 text-[10px] gap-3',
82
+ md: 'h-8 px-3 text-xs gap-3',
83
+ lg: 'h-8 px-3 text-sm gap-3',
84
+ };
85
+
86
+ /**
87
+ * Icon-only size presets (square buttons)
88
+ * All buttons use w-8 h-8 (2rem) for consistency
89
+ */
90
+ const iconOnlySizeStyles: Record<ButtonSize, string> = {
91
+ sm: 'w-8 h-8 p-0',
92
+ md: 'w-8 h-8 p-0',
93
+ lg: 'w-8 h-8 p-0',
94
+ };
95
+
96
+ /**
97
+ * Variant color schemes
98
+ * - primary: cream bg, black text, yellow on hover
99
+ * - secondary: black bg, cream text, inverts on hover
100
+ * - outline: transparent bg, black border, fills on hover
101
+ * - ghost: no border, subtle hover bg
102
+ */
103
+ const variantStyles: Record<ButtonVariant, string> = {
104
+ primary: `
105
+ bg-sun-yellow text-black
106
+ hover:bg-sun-yellow
107
+ active:bg-sun-yellow
108
+ `,
109
+ secondary: `
110
+ bg-black text-cream
111
+ hover:bg-warm-cloud hover:text-black
112
+ active:bg-sun-yellow active:text-black
113
+ `,
114
+ outline: `
115
+ bg-transparent text-black
116
+ hover:bg-warm-cloud
117
+ active:bg-sun-yellow
118
+ `,
119
+ ghost: `
120
+ bg-transparent text-black
121
+ border-transparent
122
+ shadow-none
123
+ hover:bg-transparent hover:border-black hover:text-black hover:shadow-none hover:translate-y-0
124
+ active:bg-sun-yellow active:text-black active:border-black active:translate-y-0
125
+ `,
126
+ };
127
+
128
+ // ============================================================================
129
+ // Helper Functions
130
+ // ============================================================================
131
+
132
+ function getButtonClasses(
133
+ variant: ButtonVariant,
134
+ size: ButtonSize,
135
+ iconOnly: boolean,
136
+ fullWidth: boolean,
137
+ className: string
138
+ ): string {
139
+ return [
140
+ baseStyles,
141
+ iconOnly ? iconOnlySizeStyles[size] : sizeStyles[size],
142
+ iconOnly ? 'justify-center' : 'justify-start',
143
+ variantStyles[variant],
144
+ fullWidth ? 'w-full' : '',
145
+ className,
146
+ ]
147
+ .join(' ')
148
+ .replace(/\s+/g, ' ')
149
+ .trim();
150
+ }
151
+
152
+ // ============================================================================
153
+ // Component
154
+ // ============================================================================
155
+
156
+ /**
157
+ * Button component with retro lift effect
158
+ *
159
+ * Supports both button and link behaviors:
160
+ * - Without href: renders as <button>
161
+ * - With href + asLink=true (default): renders as Next.js <Link>
162
+ * - With href + asLink=false: renders as <button> that opens URL via window.open
163
+ */
164
+ export function Button(props: ButtonProps) {
165
+ const {
166
+ variant = 'primary',
167
+ size = 'md',
168
+ fullWidth = false,
169
+ iconOnly = false,
170
+ iconName,
171
+ loading = false,
172
+ children,
173
+ className = '',
174
+ ...rest
175
+ } = props;
176
+
177
+ const classes = getButtonClasses(variant, size, iconOnly, fullWidth, className);
178
+
179
+ // Determine icon size based on button size
180
+ const iconSize = size === 'sm' ? 14 : size === 'lg' ? 18 : 16;
181
+
182
+ // Only show loading spinner for buttons with icons
183
+ const hasIcon = Boolean(iconName || iconOnly);
184
+ const showLoading: boolean = Boolean(loading && hasIcon);
185
+
186
+ // Render content with optional icon or loading spinner
187
+ const content = showLoading ? (
188
+ <>
189
+ <Spinner size={iconSize} />
190
+ {!iconOnly && children}
191
+ </>
192
+ ) : iconName ? (
193
+ <>
194
+ <Icon name={iconName} size={iconSize} />
195
+ {!iconOnly && children}
196
+ </>
197
+ ) : (
198
+ children
199
+ );
200
+
201
+ // Check if this is a link variant
202
+ if ('href' in props && props.href) {
203
+ const { href, asLink = true, target, ...linkRest } = rest as ButtonAsLinkProps;
204
+
205
+ // Use Next.js Link for navigation
206
+ if (asLink) {
207
+ return (
208
+ <Link
209
+ href={href}
210
+ target={target}
211
+ className={classes}
212
+ {...(linkRest as Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'target' | 'className'>)}
213
+ >
214
+ {content}
215
+ </Link>
216
+ );
217
+ }
218
+
219
+ // Use window.open via button click
220
+ const linkButtonDisabled: boolean = showLoading || Boolean((linkRest as React.ButtonHTMLAttributes<HTMLButtonElement>).disabled);
221
+ const { disabled: _, ...linkButtonRest } = linkRest as React.ButtonHTMLAttributes<HTMLButtonElement>;
222
+ return (
223
+ <button
224
+ type="button"
225
+ className={classes}
226
+ onClick={() => window.open(href, target || '_self')}
227
+ disabled={linkButtonDisabled}
228
+ {...linkButtonRest}
229
+ >
230
+ {content}
231
+ </button>
232
+ );
233
+ }
234
+
235
+ // Standard button
236
+ const buttonProps = rest as ButtonAsButtonProps;
237
+
238
+ // Disable button when loading
239
+ const disabled: boolean = showLoading || Boolean(buttonProps.disabled);
240
+ const { disabled: _, ...buttonPropsWithoutDisabled } = buttonProps;
241
+
242
+ return (
243
+ <button className={classes} {...buttonPropsWithoutDisabled} disabled={disabled}>
244
+ {content}
245
+ </button>
246
+ );
247
+ }
248
+
249
+ export default Button;
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ // ============================================================================
6
+ // Types
7
+ // ============================================================================
8
+
9
+ type CardVariant = 'default' | 'dark' | 'raised';
10
+
11
+ interface CardProps {
12
+ /** Visual variant */
13
+ variant?: CardVariant;
14
+ /** Card content */
15
+ children: React.ReactNode;
16
+ /** Additional classes */
17
+ className?: string;
18
+ /** Optional padding override */
19
+ noPadding?: boolean;
20
+ }
21
+
22
+ // ============================================================================
23
+ // Styles
24
+ // ============================================================================
25
+
26
+ /**
27
+ * Base styles for all cards
28
+ */
29
+ const baseStyles = `
30
+ border border-black
31
+ rounded-md
32
+ overflow-hidden
33
+ `;
34
+
35
+ /**
36
+ * Variant styles
37
+ * - default: cream bg, black text
38
+ * - dark: black bg, cream text
39
+ * - raised: cream bg with pixel shadow effect
40
+ */
41
+ const variantStyles: Record<CardVariant, string> = {
42
+ default: `
43
+ bg-warm-cloud text-black
44
+ `,
45
+ dark: `
46
+ bg-black text-cream
47
+ `,
48
+ raised: `
49
+ bg-warm-cloud text-black
50
+ shadow-[2px_2px_0_0_var(--color-black)]
51
+ `,
52
+ };
53
+
54
+ // ============================================================================
55
+ // Component
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Card container component with consistent styling
60
+ */
61
+ export function Card({
62
+ variant = 'default',
63
+ children,
64
+ className = '',
65
+ noPadding = false,
66
+ }: CardProps) {
67
+ const classes = [
68
+ baseStyles,
69
+ variantStyles[variant],
70
+ noPadding ? '' : 'p-4',
71
+ className,
72
+ ]
73
+ .join(' ')
74
+ .replace(/\s+/g, ' ')
75
+ .trim();
76
+
77
+ return (
78
+ <div className={classes}>
79
+ {children}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ // ============================================================================
85
+ // Card Sub-components
86
+ // ============================================================================
87
+
88
+ interface CardHeaderProps {
89
+ children: React.ReactNode;
90
+ className?: string;
91
+ }
92
+
93
+ /**
94
+ * Card header with bottom border
95
+ */
96
+ export function CardHeader({ children, className = '' }: CardHeaderProps) {
97
+ return (
98
+ <div className={`px-4 py-3 border-b border-black ${className}`}>
99
+ {children}
100
+ </div>
101
+ );
102
+ }
103
+
104
+ interface CardBodyProps {
105
+ children: React.ReactNode;
106
+ className?: string;
107
+ }
108
+
109
+ /**
110
+ * Card body with standard padding
111
+ */
112
+ export function CardBody({ children, className = '' }: CardBodyProps) {
113
+ return (
114
+ <div className={`p-4 ${className}`}>
115
+ {children}
116
+ </div>
117
+ );
118
+ }
119
+
120
+ interface CardFooterProps {
121
+ children: React.ReactNode;
122
+ className?: string;
123
+ }
124
+
125
+ /**
126
+ * Card footer with top border
127
+ */
128
+ export function CardFooter({ children, className = '' }: CardFooterProps) {
129
+ return (
130
+ <div className={`px-4 py-3 border-t border-black ${className}`}>
131
+ {children}
132
+ </div>
133
+ );
134
+ }
135
+
136
+ export default Card;
137
+
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import React, { forwardRef } from 'react';
4
+ import { Icon } from '@/components/icons';
5
+
6
+ // ============================================================================
7
+ // Types
8
+ // ============================================================================
9
+
10
+ interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
11
+ /** Label text */
12
+ label?: string;
13
+ /** Additional classes for container */
14
+ className?: string;
15
+ }
16
+
17
+ interface RadioProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
18
+ /** Label text */
19
+ label?: string;
20
+ /** Additional classes for container */
21
+ className?: string;
22
+ }
23
+
24
+ // ============================================================================
25
+ // Checkbox Component
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Retro-styled checkbox
30
+ */
31
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(
32
+ { label, className = '', disabled, ...props },
33
+ ref
34
+ ) {
35
+ return (
36
+ <label
37
+ className={`
38
+ inline-flex items-center gap-2 cursor-pointer
39
+ ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
40
+ ${className}
41
+ `}
42
+ >
43
+ <div className="relative">
44
+ <input
45
+ ref={ref}
46
+ type="checkbox"
47
+ disabled={disabled}
48
+ className="peer sr-only"
49
+ {...props}
50
+ />
51
+ {/* Custom checkbox visual */}
52
+ <div
53
+ className={`
54
+ w-5 h-5
55
+ bg-warm-cloud
56
+ border border-black
57
+ rounded-xs
58
+ peer-checked:bg-sun-yellow
59
+ peer-focus:ring-2 peer-focus:ring-sun-yellow peer-focus:ring-offset-1
60
+ flex items-center justify-center
61
+ `}
62
+ />
63
+ {/* Checkmark - visible when checkbox is checked */}
64
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 peer-checked:opacity-100 transition-opacity pointer-events-none">
65
+ <Icon
66
+ name="checkmark"
67
+ size={14}
68
+ className="text-black"
69
+ />
70
+ </div>
71
+ </div>
72
+ {label && (
73
+ <span className="font-mondwest text-base text-black select-none">
74
+ {label}
75
+ </span>
76
+ )}
77
+ </label>
78
+ );
79
+ });
80
+
81
+ // ============================================================================
82
+ // Radio Component
83
+ // ============================================================================
84
+
85
+ /**
86
+ * Retro-styled radio button
87
+ */
88
+ export const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(
89
+ { label, className = '', disabled, ...props },
90
+ ref
91
+ ) {
92
+ return (
93
+ <label
94
+ className={`
95
+ inline-flex items-center gap-2 cursor-pointer
96
+ ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
97
+ ${className}
98
+ `}
99
+ >
100
+ <div className="relative">
101
+ <input
102
+ ref={ref}
103
+ type="radio"
104
+ disabled={disabled}
105
+ className="peer sr-only"
106
+ {...props}
107
+ />
108
+ {/* Custom radio visual */}
109
+ <div
110
+ className={`
111
+ w-5 h-5
112
+ bg-warm-cloud
113
+ border border-black
114
+ rounded-full
115
+ peer-checked:bg-sun-yellow
116
+ peer-focus:ring-2 peer-focus:ring-sun-yellow peer-focus:ring-offset-1
117
+ flex items-center justify-center
118
+ `}
119
+ >
120
+ {/* Inner dot placeholder */}
121
+ </div>
122
+ {/* Inner dot when checked */}
123
+ <div
124
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-black rounded-full opacity-0 peer-checked:opacity-100 pointer-events-none"
125
+ />
126
+ </div>
127
+ {label && (
128
+ <span className="font-mondwest text-base text-black select-none">
129
+ {label}
130
+ </span>
131
+ )}
132
+ </label>
133
+ );
134
+ });
135
+
136
+ export default Checkbox;
137
+