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.
- package/README.md +108 -0
- package/bin/radtools.js +5 -0
- package/dist/cli/index.js +427 -0
- package/package.json +55 -0
- package/templates/api-routes/assets/optimize/route.ts +94 -0
- package/templates/api-routes/assets/route.ts +159 -0
- package/templates/api-routes/components/create-folder/route.ts +55 -0
- package/templates/api-routes/components/route.ts +156 -0
- package/templates/api-routes/fonts/route.ts +96 -0
- package/templates/api-routes/fonts/upload/route.ts +79 -0
- package/templates/api-routes/read-css/route.ts +29 -0
- package/templates/api-routes/write-css/route.ts +423 -0
- package/templates/components/Rad_os/AppWindow.tsx +423 -0
- package/templates/components/Rad_os/MobileAppModal.tsx +76 -0
- package/templates/components/Rad_os/WindowTitleBar.tsx +290 -0
- package/templates/components/icons/Icon.tsx +224 -0
- package/templates/components/icons/README.md +85 -0
- package/templates/components/icons/index.ts +20 -0
- package/templates/components/icons.tsx +164 -0
- package/templates/components/ui/Accordion.tsx +268 -0
- package/templates/components/ui/Alert.tsx +111 -0
- package/templates/components/ui/Badge.tsx +87 -0
- package/templates/components/ui/Breadcrumbs.tsx +88 -0
- package/templates/components/ui/Button.tsx +249 -0
- package/templates/components/ui/Card.tsx +137 -0
- package/templates/components/ui/Checkbox.tsx +137 -0
- package/templates/components/ui/ContextMenu.tsx +220 -0
- package/templates/components/ui/Dialog.tsx +264 -0
- package/templates/components/ui/Divider.tsx +70 -0
- package/templates/components/ui/DropdownMenu.tsx +301 -0
- package/templates/components/ui/HelpPanel.tsx +119 -0
- package/templates/components/ui/Input.tsx +176 -0
- package/templates/components/ui/Popover.tsx +211 -0
- package/templates/components/ui/Progress.tsx +158 -0
- package/templates/components/ui/Select.tsx +134 -0
- package/templates/components/ui/Sheet.tsx +316 -0
- package/templates/components/ui/Slider.tsx +223 -0
- package/templates/components/ui/Switch.tsx +155 -0
- package/templates/components/ui/Tabs.tsx +253 -0
- package/templates/components/ui/Toast.tsx +192 -0
- package/templates/components/ui/Tooltip.tsx +129 -0
- package/templates/components/ui/hooks/useModalBehavior.ts +66 -0
- package/templates/components/ui/index.ts +84 -0
- package/templates/devtools/DevToolsPanel.tsx +261 -0
- package/templates/devtools/DevToolsProvider.tsx +43 -0
- package/templates/devtools/components/BreakpointIndicator.tsx +49 -0
- package/templates/devtools/components/ColorPicker.tsx +33 -0
- package/templates/devtools/components/ComponentsSecondaryNav.tsx +44 -0
- package/templates/devtools/components/ContextualFooter.tsx +56 -0
- package/templates/devtools/components/DraggablePanel.tsx +43 -0
- package/templates/devtools/components/PrimaryNavigationFooter.tsx +254 -0
- package/templates/devtools/components/SearchableColorDropdown.tsx +253 -0
- package/templates/devtools/components/SecondaryNavigation.tsx +36 -0
- package/templates/devtools/components/TokenDropdown.tsx +47 -0
- package/templates/devtools/components/TypographyFooter.tsx +145 -0
- package/templates/devtools/hooks/useMockState.ts +16 -0
- package/templates/devtools/index.ts +17 -0
- package/templates/devtools/lib/componentScanner.ts +78 -0
- package/templates/devtools/lib/cssParser.ts +465 -0
- package/templates/devtools/lib/searchIndexes.ts +45 -0
- package/templates/devtools/lib/selectorGenerator.ts +86 -0
- package/templates/devtools/store/index.ts +66 -0
- package/templates/devtools/store/slices/assetsSlice.ts +106 -0
- package/templates/devtools/store/slices/componentsSlice.ts +59 -0
- package/templates/devtools/store/slices/mockStatesSlice.ts +77 -0
- package/templates/devtools/store/slices/panelSlice.ts +17 -0
- package/templates/devtools/store/slices/typographySlice.ts +538 -0
- package/templates/devtools/store/slices/variablesSlice.ts +167 -0
- package/templates/devtools/tabs/AssetsTab/AssetGrid.tsx +76 -0
- package/templates/devtools/tabs/AssetsTab/FolderTree.tsx +53 -0
- package/templates/devtools/tabs/AssetsTab/UploadDropzone.tsx +76 -0
- package/templates/devtools/tabs/AssetsTab/index.tsx +182 -0
- package/templates/devtools/tabs/ComponentsTab/AddTabButton.tsx +63 -0
- package/templates/devtools/tabs/ComponentsTab/ComponentList.tsx +153 -0
- package/templates/devtools/tabs/ComponentsTab/DesignSystemTab.tsx +1515 -0
- package/templates/devtools/tabs/ComponentsTab/DynamicFolderTab.tsx +113 -0
- package/templates/devtools/tabs/ComponentsTab/PropDisplay.tsx +55 -0
- package/templates/devtools/tabs/ComponentsTab/index.tsx +167 -0
- package/templates/devtools/tabs/ComponentsTab/previews/.gitkeep +4 -0
- package/templates/devtools/tabs/ComponentsTab/previews/Rad_os.tsx +262 -0
- package/templates/devtools/tabs/ComponentsTab/tabConfig.ts +53 -0
- package/templates/devtools/tabs/MockStatesTab/index.tsx +29 -0
- package/templates/devtools/tabs/TypographyTab/FontManager.tsx +421 -0
- package/templates/devtools/tabs/TypographyTab/TypographyStylesDisplay.tsx +290 -0
- package/templates/devtools/tabs/TypographyTab/index.tsx +98 -0
- package/templates/devtools/tabs/VariablesTab/BaseColorEditor.tsx +267 -0
- package/templates/devtools/tabs/VariablesTab/BorderRadiusEditor.tsx +37 -0
- package/templates/devtools/tabs/VariablesTab/ColorModeSelector.tsx +235 -0
- package/templates/devtools/tabs/VariablesTab/index.tsx +100 -0
- package/templates/devtools/types/index.ts +99 -0
- package/templates/globals.css +574 -0
- package/templates/hooks/index.ts +1 -0
- package/templates/hooks/useWindowManager.ts +212 -0
- package/templates/public/assets/icons/avatar.svg +18 -0
- package/templates/public/assets/icons/checkmark-filled.svg +14 -0
- package/templates/public/assets/icons/checkmark.svg +14 -0
- package/templates/public/assets/icons/chevron-down.svg +14 -0
- package/templates/public/assets/icons/close.svg +14 -0
- package/templates/public/assets/icons/copy.svg +14 -0
- package/templates/public/assets/icons/download.svg +14 -0
- package/templates/public/assets/icons/expand.svg +31 -0
- package/templates/public/assets/icons/file-blank.svg +17 -0
- package/templates/public/assets/icons/file-image.svg +19 -0
- package/templates/public/assets/icons/file-written.svg +17 -0
- package/templates/public/assets/icons/folder-closed.svg +17 -0
- package/templates/public/assets/icons/folder-open.svg +17 -0
- package/templates/public/assets/icons/hamburger.svg +18 -0
- package/templates/public/assets/icons/home-outline.svg +28 -0
- package/templates/public/assets/icons/home.svg +30 -0
- package/templates/public/assets/icons/hourglass.svg +25 -0
- package/templates/public/assets/icons/information-circle.svg +14 -0
- package/templates/public/assets/icons/information.svg +17 -0
- package/templates/public/assets/icons/lightning.svg +14 -0
- package/templates/public/assets/icons/locked.svg +17 -0
- package/templates/public/assets/icons/not-allowed.svg +14 -0
- package/templates/public/assets/icons/plus.svg +5 -0
- package/templates/public/assets/icons/power-thin.svg +17 -0
- package/templates/public/assets/icons/power.svg +17 -0
- package/templates/public/assets/icons/question-block.svg +14 -0
- package/templates/public/assets/icons/question.svg +17 -0
- package/templates/public/assets/icons/refresh-block.svg +14 -0
- package/templates/public/assets/icons/refresh.svg +17 -0
- package/templates/public/assets/icons/save.svg +14 -0
- package/templates/public/assets/icons/search.svg +25 -0
- package/templates/public/assets/icons/settings.svg +14 -0
- package/templates/public/assets/icons/trash-full.svg +21 -0
- package/templates/public/assets/icons/trash-open.svg +23 -0
- package/templates/public/assets/icons/trash.svg +18 -0
- package/templates/public/assets/icons/unlocked.svg +17 -0
- package/templates/public/assets/icons/waring-triangle-filled.svg +17 -0
- package/templates/public/assets/icons/warning-triangle-filled-2.svg +30 -0
- package/templates/public/assets/icons/warning-triangle-lines.svg +29 -0
- package/templates/public/assets/icons/wrench.svg +17 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Component Library
|
|
3
|
+
*
|
|
4
|
+
* Centralized exports for all design system components.
|
|
5
|
+
* Import from '@/components/ui' for convenience.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Core Components
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export { Button } from './Button';
|
|
13
|
+
export { Card, CardHeader, CardBody, CardFooter } from './Card';
|
|
14
|
+
export { Tabs, TabList, TabTrigger, TabContent } from './Tabs';
|
|
15
|
+
export { Input, TextArea, Label } from './Input';
|
|
16
|
+
export { Badge } from './Badge';
|
|
17
|
+
export { Divider } from './Divider';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Form Components
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export { Select } from './Select';
|
|
24
|
+
export { Checkbox, Radio } from './Checkbox';
|
|
25
|
+
export { Switch } from './Switch';
|
|
26
|
+
export { Slider } from './Slider';
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Feedback Components
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
export { Progress, Spinner } from './Progress';
|
|
33
|
+
export { Tooltip } from './Tooltip';
|
|
34
|
+
export { Alert } from './Alert';
|
|
35
|
+
export { ToastProvider, useToast } from './Toast';
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Navigation Components
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './Accordion';
|
|
42
|
+
export { Breadcrumbs } from './Breadcrumbs';
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Overlay Components
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
export { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from './ContextMenu';
|
|
49
|
+
export { HelpPanel } from './HelpPanel';
|
|
50
|
+
export {
|
|
51
|
+
Dialog,
|
|
52
|
+
DialogTrigger,
|
|
53
|
+
DialogContent,
|
|
54
|
+
DialogHeader,
|
|
55
|
+
DialogTitle,
|
|
56
|
+
DialogDescription,
|
|
57
|
+
DialogBody,
|
|
58
|
+
DialogFooter,
|
|
59
|
+
DialogClose,
|
|
60
|
+
} from './Dialog';
|
|
61
|
+
export {
|
|
62
|
+
Popover,
|
|
63
|
+
PopoverTrigger,
|
|
64
|
+
PopoverContent,
|
|
65
|
+
} from './Popover';
|
|
66
|
+
export {
|
|
67
|
+
Sheet,
|
|
68
|
+
SheetTrigger,
|
|
69
|
+
SheetContent,
|
|
70
|
+
SheetHeader,
|
|
71
|
+
SheetTitle,
|
|
72
|
+
SheetDescription,
|
|
73
|
+
SheetBody,
|
|
74
|
+
SheetFooter,
|
|
75
|
+
SheetClose,
|
|
76
|
+
} from './Sheet';
|
|
77
|
+
export {
|
|
78
|
+
DropdownMenu,
|
|
79
|
+
DropdownMenuTrigger,
|
|
80
|
+
DropdownMenuContent,
|
|
81
|
+
DropdownMenuItem,
|
|
82
|
+
DropdownMenuSeparator,
|
|
83
|
+
DropdownMenuLabel,
|
|
84
|
+
} from './DropdownMenu';
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Draggable from 'react-draggable';
|
|
4
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
5
|
+
import { useDevToolsStore } from './store';
|
|
6
|
+
import type { Tab } from './types';
|
|
7
|
+
|
|
8
|
+
// Import UI components
|
|
9
|
+
import { Button } from '@/components/ui/Button';
|
|
10
|
+
import { WindowTitleBar } from '@/components/Rad_os/WindowTitleBar';
|
|
11
|
+
|
|
12
|
+
// Import actual tab components
|
|
13
|
+
import { VariablesTab } from './tabs/VariablesTab';
|
|
14
|
+
import { TypographyTab } from './tabs/TypographyTab';
|
|
15
|
+
import { ComponentsTab } from './tabs/ComponentsTab';
|
|
16
|
+
import { AssetsTab } from './tabs/AssetsTab';
|
|
17
|
+
import { MockStatesTab } from './tabs/MockStatesTab';
|
|
18
|
+
import { ContextualFooter } from './components/ContextualFooter';
|
|
19
|
+
|
|
20
|
+
export function DevToolsPanel() {
|
|
21
|
+
// Store hooks must be first - all functions below depend on these values
|
|
22
|
+
const {
|
|
23
|
+
activeTab,
|
|
24
|
+
setActiveTab,
|
|
25
|
+
panelPosition,
|
|
26
|
+
setPanelPosition,
|
|
27
|
+
panelSize,
|
|
28
|
+
setPanelSize,
|
|
29
|
+
togglePanel,
|
|
30
|
+
isFullscreen,
|
|
31
|
+
toggleFullscreen,
|
|
32
|
+
} = useDevToolsStore();
|
|
33
|
+
|
|
34
|
+
const nodeRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const resizeHandleRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
|
|
37
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
38
|
+
const [resizeStart, setResizeStart] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
39
|
+
|
|
40
|
+
// Footer state
|
|
41
|
+
const [componentSubTab, setComponentSubTab] = useState<string>('design-system');
|
|
42
|
+
const [componentSearchQuery, setComponentSearchQuery] = useState<string>('');
|
|
43
|
+
const [typographySearchQuery, setTypographySearchQuery] = useState<string>('');
|
|
44
|
+
const [componentTabs, setComponentTabs] = useState<Array<{ id: string; label: string }>>([]);
|
|
45
|
+
|
|
46
|
+
const handleDragStop = (_: unknown, data: { x: number; y: number }) => {
|
|
47
|
+
setPanelPosition({ x: data.x, y: data.y });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Resize functionality
|
|
51
|
+
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
if (!nodeRef.current) return;
|
|
55
|
+
|
|
56
|
+
const rect = nodeRef.current.getBoundingClientRect();
|
|
57
|
+
setIsResizing(true);
|
|
58
|
+
setResizeStart({
|
|
59
|
+
x: e.clientX,
|
|
60
|
+
y: e.clientY,
|
|
61
|
+
width: rect.width,
|
|
62
|
+
height: rect.height,
|
|
63
|
+
});
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!isResizing) return;
|
|
68
|
+
|
|
69
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
70
|
+
if (!nodeRef.current) return;
|
|
71
|
+
|
|
72
|
+
const deltaX = e.clientX - resizeStart.x;
|
|
73
|
+
const deltaY = e.clientY - resizeStart.y;
|
|
74
|
+
|
|
75
|
+
const minWidth = 300;
|
|
76
|
+
const minHeight = 200;
|
|
77
|
+
const maxWidth = window.innerWidth * 0.9;
|
|
78
|
+
const maxHeight = window.innerHeight * 0.9;
|
|
79
|
+
|
|
80
|
+
const newWidth = Math.min(Math.max(resizeStart.width + deltaX, minWidth), maxWidth);
|
|
81
|
+
const newHeight = Math.min(Math.max(resizeStart.height + deltaY, minHeight), maxHeight);
|
|
82
|
+
|
|
83
|
+
setPanelSize({ width: newWidth, height: newHeight });
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleMouseUp = () => {
|
|
87
|
+
setIsResizing(false);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
91
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
95
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
96
|
+
};
|
|
97
|
+
}, [isResizing, resizeStart, setPanelSize]);
|
|
98
|
+
|
|
99
|
+
// Handle tab change from Tabs component
|
|
100
|
+
const handleTabChange = (value: string) => {
|
|
101
|
+
setActiveTab(value as Tab);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Fullscreen styles
|
|
105
|
+
const panelStyles = isFullscreen
|
|
106
|
+
? {
|
|
107
|
+
top: 0,
|
|
108
|
+
left: 0,
|
|
109
|
+
right: 0,
|
|
110
|
+
bottom: 0,
|
|
111
|
+
width: '100%',
|
|
112
|
+
height: '100%',
|
|
113
|
+
}
|
|
114
|
+
: {
|
|
115
|
+
top: 0,
|
|
116
|
+
left: 0,
|
|
117
|
+
width: `${panelSize.width}px`,
|
|
118
|
+
height: `${panelSize.height}px`,
|
|
119
|
+
minWidth: '300px',
|
|
120
|
+
minHeight: '200px',
|
|
121
|
+
maxWidth: '90vw',
|
|
122
|
+
maxHeight: '90vh',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Draggable
|
|
127
|
+
nodeRef={nodeRef}
|
|
128
|
+
handle="[data-drag-handle]"
|
|
129
|
+
position={isFullscreen ? { x: 0, y: 0 } : panelPosition}
|
|
130
|
+
onStop={handleDragStop}
|
|
131
|
+
bounds="parent"
|
|
132
|
+
disabled={isResizing || isFullscreen}
|
|
133
|
+
>
|
|
134
|
+
<div
|
|
135
|
+
ref={nodeRef}
|
|
136
|
+
className={`fixed z-[9999] overflow-hidden flex flex-col border ${
|
|
137
|
+
isFullscreen ? 'inset-0' : 'rounded-[8px] shadow-[4px_4px_0_0_var(--color-black)]'
|
|
138
|
+
}`}
|
|
139
|
+
style={{
|
|
140
|
+
...panelStyles,
|
|
141
|
+
background: 'linear-gradient(0deg, rgba(252, 225, 132, 1) 0%, rgba(254, 248, 226, 1) 100%)',
|
|
142
|
+
borderStyle: 'solid',
|
|
143
|
+
borderWidth: '1px',
|
|
144
|
+
borderColor: 'var(--color-black)',
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{/* Header */}
|
|
148
|
+
<WindowTitleBar
|
|
149
|
+
title="RADTOOLS"
|
|
150
|
+
windowId="radtools"
|
|
151
|
+
onClose={togglePanel}
|
|
152
|
+
iconName="lightning"
|
|
153
|
+
showTitle={true}
|
|
154
|
+
showCopyButton={false}
|
|
155
|
+
showCloseButton={true}
|
|
156
|
+
showHelpButton={true}
|
|
157
|
+
showActionButton={false}
|
|
158
|
+
showFullscreenButton={true}
|
|
159
|
+
onFullscreen={toggleFullscreen}
|
|
160
|
+
helpTitle="How to Use RadTools"
|
|
161
|
+
helpContent={
|
|
162
|
+
<div className="space-y-4">
|
|
163
|
+
<div>
|
|
164
|
+
<h3 className="font-joystix text-sm uppercase mb-2">Getting Started</h3>
|
|
165
|
+
<p className="font-mondwest text-sm text-black/80">
|
|
166
|
+
RadTools is a design system development tool that helps you manage components, typography, variables, and assets.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
<div>
|
|
170
|
+
<h3 className="font-joystix text-sm uppercase mb-2">Features</h3>
|
|
171
|
+
<ul className="font-mondwest text-sm text-black/80 space-y-1 list-disc list-inside pl-0">
|
|
172
|
+
<li>Components: Browse and inspect your UI components</li>
|
|
173
|
+
<li>Typography: Manage font families and typography styles</li>
|
|
174
|
+
<li>Variables: Edit design tokens and color variables</li>
|
|
175
|
+
<li>Assets: Upload and organize images, icons, and other assets</li>
|
|
176
|
+
<li>Mock States: Test components with different state configurations</li>
|
|
177
|
+
</ul>
|
|
178
|
+
</div>
|
|
179
|
+
<div>
|
|
180
|
+
<h3 className="font-joystix text-sm uppercase mb-2">Tips</h3>
|
|
181
|
+
<ul className="font-mondwest text-sm text-black/80 space-y-1 list-disc list-inside">
|
|
182
|
+
<li>Drag the panel by the title bar to reposition it</li>
|
|
183
|
+
<li>Use the fullscreen button for a larger workspace</li>
|
|
184
|
+
<li>Click the close button to hide the panel</li>
|
|
185
|
+
</ul>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
}
|
|
189
|
+
/>
|
|
190
|
+
|
|
191
|
+
{/* Tab Content */}
|
|
192
|
+
<div className="flex-1 overflow-y-auto">
|
|
193
|
+
{activeTab === 'variables' && (
|
|
194
|
+
<div className="h-full pr-2 pl-2 pb-2 rounded">
|
|
195
|
+
<VariablesTab />
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
{activeTab === 'typography' && (
|
|
199
|
+
<div className="h-full pr-2 pl-2 pb-2 rounded">
|
|
200
|
+
<TypographyTab searchQuery={typographySearchQuery} />
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
{activeTab === 'components' && (
|
|
204
|
+
<ComponentsTab
|
|
205
|
+
activeSubTab={componentSubTab}
|
|
206
|
+
searchQuery={componentSearchQuery}
|
|
207
|
+
onTabsChange={setComponentTabs}
|
|
208
|
+
/>
|
|
209
|
+
)}
|
|
210
|
+
{activeTab === 'assets' && (
|
|
211
|
+
<div className="h-full pr-2 pl-2 pb-2 rounded">
|
|
212
|
+
<AssetsTab />
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
{activeTab === 'mockStates' && (
|
|
216
|
+
<div className="h-full pr-2 pl-2 pb-2 rounded">
|
|
217
|
+
<MockStatesTab />
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* Contextual Footer */}
|
|
223
|
+
<ContextualFooter
|
|
224
|
+
activeTab={activeTab}
|
|
225
|
+
onTabChange={handleTabChange}
|
|
226
|
+
componentSubTab={componentSubTab}
|
|
227
|
+
componentSearchQuery={componentSearchQuery}
|
|
228
|
+
onComponentSubTabChange={setComponentSubTab}
|
|
229
|
+
onComponentSearchChange={setComponentSearchQuery}
|
|
230
|
+
componentTabs={componentTabs}
|
|
231
|
+
onAddComponentFolder={async (folderName) => {
|
|
232
|
+
// Trigger folder creation via ComponentsTab's exposed handler
|
|
233
|
+
if ((window as any).__componentsTabAddFolder) {
|
|
234
|
+
(window as any).__componentsTabAddFolder(folderName);
|
|
235
|
+
// Switch to the new tab
|
|
236
|
+
setComponentSubTab(`folder-${folderName}`);
|
|
237
|
+
}
|
|
238
|
+
}}
|
|
239
|
+
typographySearchQuery={typographySearchQuery}
|
|
240
|
+
onTypographySearchChange={setTypographySearchQuery}
|
|
241
|
+
/>
|
|
242
|
+
|
|
243
|
+
{/* Resize Handle (hidden in fullscreen) */}
|
|
244
|
+
{!isFullscreen && (
|
|
245
|
+
<div
|
|
246
|
+
ref={resizeHandleRef}
|
|
247
|
+
onMouseDown={handleResizeStart}
|
|
248
|
+
className="absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-10 flex items-center justify-center text-black"
|
|
249
|
+
style={{
|
|
250
|
+
fontFamily: '"PixelCode", monospace',
|
|
251
|
+
fontSize: '10px',
|
|
252
|
+
lineHeight: 1,
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
┘
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
</Draggable>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useEffect } from 'react';
|
|
4
|
+
import { DevToolsPanel } from './DevToolsPanel';
|
|
5
|
+
import { BreakpointIndicator } from './components/BreakpointIndicator';
|
|
6
|
+
import { useDevToolsStore } from './store';
|
|
7
|
+
import { ToastProvider } from '@/components/ui/Toast';
|
|
8
|
+
|
|
9
|
+
interface DevToolsProviderProps {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function DevToolsProvider({ children }: DevToolsProviderProps) {
|
|
14
|
+
const { isOpen, togglePanel } = useDevToolsStore();
|
|
15
|
+
|
|
16
|
+
// Keyboard shortcut: Shift+Cmd+K (Mac) / Shift+Ctrl+K (Windows)
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
19
|
+
// Toggle panel with Shift+Cmd+K or Shift+Ctrl+K
|
|
20
|
+
if (e.shiftKey && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
togglePanel();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
27
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
28
|
+
}, [togglePanel]);
|
|
29
|
+
|
|
30
|
+
// Production: render only children
|
|
31
|
+
if (process.env.NODE_ENV === 'production') {
|
|
32
|
+
return <>{children}</>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ToastProvider>
|
|
37
|
+
{children}
|
|
38
|
+
{isOpen && <DevToolsPanel />}
|
|
39
|
+
<BreakpointIndicator />
|
|
40
|
+
</ToastProvider>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
const BREAKPOINTS = {
|
|
6
|
+
sm: 640,
|
|
7
|
+
md: 768,
|
|
8
|
+
lg: 1024,
|
|
9
|
+
xl: 1280,
|
|
10
|
+
'2xl': 1536,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function BreakpointIndicator() {
|
|
14
|
+
const [width, setWidth] = useState(0);
|
|
15
|
+
const [breakpoint, setBreakpoint] = useState('');
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const updateWidth = () => {
|
|
19
|
+
const w = window.innerWidth;
|
|
20
|
+
setWidth(w);
|
|
21
|
+
|
|
22
|
+
if (w >= BREAKPOINTS['2xl']) setBreakpoint('2xl');
|
|
23
|
+
else if (w >= BREAKPOINTS.xl) setBreakpoint('xl');
|
|
24
|
+
else if (w >= BREAKPOINTS.lg) setBreakpoint('lg');
|
|
25
|
+
else if (w >= BREAKPOINTS.md) setBreakpoint('md');
|
|
26
|
+
else if (w >= BREAKPOINTS.sm) setBreakpoint('sm');
|
|
27
|
+
else setBreakpoint('xs');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
updateWidth();
|
|
31
|
+
window.addEventListener('resize', updateWidth);
|
|
32
|
+
return () => window.removeEventListener('resize', updateWidth);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
// Don't render in production
|
|
36
|
+
if (process.env.NODE_ENV === 'production') return null;
|
|
37
|
+
|
|
38
|
+
// Don't render on server
|
|
39
|
+
if (width === 0) return null;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="fixed bottom-4 left-4 z-[9999] flex items-center gap-2 rounded-full bg-black px-3 py-1.5 font-mono text-xs text-cream shadow-lg">
|
|
43
|
+
<span className="font-bold uppercase">{breakpoint}</span>
|
|
44
|
+
<span className="opacity-60">·</span>
|
|
45
|
+
<span className="opacity-60">{width}px</span>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
interface ColorPickerProps {
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ColorPicker({ value, onChange, label }: ColorPickerProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex items-center gap-2">
|
|
12
|
+
{label && (
|
|
13
|
+
<label className="font-joystix text-xs uppercase text-black/60 min-w-[60px]">{label}</label>
|
|
14
|
+
)}
|
|
15
|
+
<div className="flex items-center gap-2 flex-1">
|
|
16
|
+
<input
|
|
17
|
+
type="color"
|
|
18
|
+
value={value}
|
|
19
|
+
onChange={(e) => onChange(e.target.value)}
|
|
20
|
+
className="w-8 h-8 rounded-sm border border-black cursor-pointer"
|
|
21
|
+
/>
|
|
22
|
+
<input
|
|
23
|
+
type="text"
|
|
24
|
+
value={value}
|
|
25
|
+
onChange={(e) => onChange(e.target.value)}
|
|
26
|
+
className="flex-1 px-2 py-1 font-mondwest text-base font-mono bg-warm-cloud border border-black rounded-sm text-black focus:outline-none focus:ring-2 focus:ring-tertiary"
|
|
27
|
+
placeholder="#000000"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AddTabButton } from '../tabs/ComponentsTab/AddTabButton';
|
|
4
|
+
|
|
5
|
+
interface ComponentsSecondaryNavProps {
|
|
6
|
+
activeSubTab: string;
|
|
7
|
+
onSubTabChange: (tab: string) => void;
|
|
8
|
+
tabs: Array<{ id: string; label: string }>;
|
|
9
|
+
onAddFolder: (folderName: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ComponentsSecondaryNav({
|
|
13
|
+
activeSubTab,
|
|
14
|
+
onSubTabChange,
|
|
15
|
+
tabs,
|
|
16
|
+
onAddFolder,
|
|
17
|
+
}: ComponentsSecondaryNavProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex items-center gap-1 px-2 py-2 bg-warm-cloud border-t border-black overflow-x-auto">
|
|
20
|
+
{tabs.map((tab) => (
|
|
21
|
+
<button
|
|
22
|
+
key={tab.id}
|
|
23
|
+
type="button"
|
|
24
|
+
onClick={() => onSubTabChange(tab.id)}
|
|
25
|
+
className={`flex items-center justify-center px-4 py-2 font-joystix text-xs uppercase cursor-pointer select-none text-black transition-all duration-200 ease-out relative border border-black rounded-sm ${
|
|
26
|
+
activeSubTab === tab.id ? 'bg-sun-yellow' : 'bg-transparent hover:bg-black/5'
|
|
27
|
+
}`}
|
|
28
|
+
>
|
|
29
|
+
{tab.label}
|
|
30
|
+
</button>
|
|
31
|
+
))}
|
|
32
|
+
<AddTabButton
|
|
33
|
+
onAdd={(folderName) => {
|
|
34
|
+
onAddFolder(folderName);
|
|
35
|
+
// Also trigger via ComponentsTab's handler if available
|
|
36
|
+
if ((window as any).__componentsTabAddFolder) {
|
|
37
|
+
(window as any).__componentsTabAddFolder(folderName);
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { Tab } from '../types';
|
|
4
|
+
import { SecondaryNavigation } from './SecondaryNavigation';
|
|
5
|
+
import { PrimaryNavigationFooter } from './PrimaryNavigationFooter';
|
|
6
|
+
|
|
7
|
+
interface ContextualFooterProps {
|
|
8
|
+
activeTab: Tab;
|
|
9
|
+
onTabChange: (tab: Tab) => void;
|
|
10
|
+
// Components tab props
|
|
11
|
+
componentSubTab?: string;
|
|
12
|
+
componentSearchQuery?: string;
|
|
13
|
+
onComponentSubTabChange?: (tab: string) => void;
|
|
14
|
+
onComponentSearchChange?: (query: string) => void;
|
|
15
|
+
componentTabs?: Array<{ id: string; label: string }>;
|
|
16
|
+
onAddComponentFolder?: (folderName: string) => void;
|
|
17
|
+
// Typography tab props
|
|
18
|
+
typographySearchQuery?: string;
|
|
19
|
+
onTypographySearchChange?: (query: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ContextualFooter({
|
|
23
|
+
activeTab,
|
|
24
|
+
onTabChange,
|
|
25
|
+
componentSubTab,
|
|
26
|
+
componentSearchQuery,
|
|
27
|
+
onComponentSubTabChange,
|
|
28
|
+
onComponentSearchChange,
|
|
29
|
+
componentTabs,
|
|
30
|
+
onAddComponentFolder,
|
|
31
|
+
typographySearchQuery,
|
|
32
|
+
onTypographySearchChange,
|
|
33
|
+
}: ContextualFooterProps) {
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
{/* Secondary Navigation (above footer) */}
|
|
37
|
+
<SecondaryNavigation
|
|
38
|
+
activeTab={activeTab}
|
|
39
|
+
componentSubTab={componentSubTab}
|
|
40
|
+
onComponentSubTabChange={onComponentSubTabChange}
|
|
41
|
+
componentTabs={componentTabs}
|
|
42
|
+
onAddComponentFolder={onAddComponentFolder}
|
|
43
|
+
/>
|
|
44
|
+
{/* Primary Navigation Footer (always visible) */}
|
|
45
|
+
<PrimaryNavigationFooter
|
|
46
|
+
activeTab={activeTab}
|
|
47
|
+
onTabChange={onTabChange}
|
|
48
|
+
componentSearchQuery={componentSearchQuery}
|
|
49
|
+
onComponentSearchChange={onComponentSearchChange}
|
|
50
|
+
typographySearchQuery={typographySearchQuery}
|
|
51
|
+
onTypographySearchChange={onTypographySearchChange}
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Draggable, { DraggableEvent, DraggableData } from 'react-draggable';
|
|
4
|
+
import { useRef, ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
interface DraggablePanelProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
position: { x: number; y: number };
|
|
9
|
+
onPositionChange: (position: { x: number; y: number }) => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function DraggablePanel({
|
|
14
|
+
children,
|
|
15
|
+
position,
|
|
16
|
+
onPositionChange,
|
|
17
|
+
className = '',
|
|
18
|
+
}: DraggablePanelProps) {
|
|
19
|
+
const nodeRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
const handleDragStop = (_: DraggableEvent, data: DraggableData) => {
|
|
22
|
+
onPositionChange({ x: data.x, y: data.y });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Draggable
|
|
27
|
+
nodeRef={nodeRef}
|
|
28
|
+
handle=".drag-handle"
|
|
29
|
+
defaultPosition={position}
|
|
30
|
+
onStop={handleDragStop}
|
|
31
|
+
bounds="parent"
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
ref={nodeRef}
|
|
35
|
+
className={`fixed z-[9999] ${className}`}
|
|
36
|
+
style={{ top: 0, left: 0 }}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
</Draggable>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|