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,145 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { Input } from '@/components/ui/Input';
|
|
5
|
+
import { TYPOGRAPHY_SEARCH_INDEX, type TypographySearchableItem } from '../lib/searchIndexes';
|
|
6
|
+
|
|
7
|
+
interface TypographyFooterProps {
|
|
8
|
+
searchQuery: string;
|
|
9
|
+
onSearchChange: (query: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function TypographyFooter({
|
|
13
|
+
searchQuery,
|
|
14
|
+
onSearchChange,
|
|
15
|
+
}: TypographyFooterProps) {
|
|
16
|
+
const [showAutocomplete, setShowAutocomplete] = useState(false);
|
|
17
|
+
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(0);
|
|
18
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
19
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
// Filter suggestions based on search query
|
|
22
|
+
const suggestions = searchQuery
|
|
23
|
+
? TYPOGRAPHY_SEARCH_INDEX.filter((item) => {
|
|
24
|
+
const queryLower = searchQuery.toLowerCase();
|
|
25
|
+
return (
|
|
26
|
+
item.text.toLowerCase().includes(queryLower) ||
|
|
27
|
+
item.aliases.some((alias) => alias.toLowerCase().includes(queryLower)) ||
|
|
28
|
+
item.element.toLowerCase().includes(queryLower)
|
|
29
|
+
);
|
|
30
|
+
}).slice(0, 10)
|
|
31
|
+
: [];
|
|
32
|
+
|
|
33
|
+
// Close autocomplete when clicking outside
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
36
|
+
if (
|
|
37
|
+
containerRef.current &&
|
|
38
|
+
!containerRef.current.contains(event.target as Node)
|
|
39
|
+
) {
|
|
40
|
+
setShowAutocomplete(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
45
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
49
|
+
onSearchChange(e.target.value);
|
|
50
|
+
setShowAutocomplete(true);
|
|
51
|
+
setSelectedSuggestionIndex(0);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleSelectSuggestion = (item: TypographySearchableItem) => {
|
|
55
|
+
onSearchChange(item.text);
|
|
56
|
+
setShowAutocomplete(false);
|
|
57
|
+
// Scroll to the section
|
|
58
|
+
const sectionElement = document.getElementById(`typography-${item.sectionId}`);
|
|
59
|
+
if (sectionElement) {
|
|
60
|
+
sectionElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
65
|
+
if (e.key === 'Tab' && suggestions.length > 0 && showAutocomplete) {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
if (suggestions[selectedSuggestionIndex]) {
|
|
68
|
+
handleSelectSuggestion(suggestions[selectedSuggestionIndex]);
|
|
69
|
+
}
|
|
70
|
+
} else if (e.key === 'ArrowDown') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
setShowAutocomplete(true);
|
|
73
|
+
setSelectedSuggestionIndex((prev) =>
|
|
74
|
+
prev < suggestions.length - 1 ? prev + 1 : prev
|
|
75
|
+
);
|
|
76
|
+
} else if (e.key === 'ArrowUp') {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
setSelectedSuggestionIndex((prev) => (prev > 0 ? prev - 1 : 0));
|
|
79
|
+
} else if (e.key === 'Enter' && suggestions.length > 0 && showAutocomplete) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
if (suggestions[selectedSuggestionIndex]) {
|
|
82
|
+
handleSelectSuggestion(suggestions[selectedSuggestionIndex]);
|
|
83
|
+
}
|
|
84
|
+
} else if (e.key === 'Escape') {
|
|
85
|
+
setShowAutocomplete(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const highlightText = (text: string, query: string) => {
|
|
90
|
+
const index = text.toLowerCase().indexOf(query.toLowerCase());
|
|
91
|
+
if (index === -1) return text;
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
{text.substring(0, index)}
|
|
95
|
+
<span className="bg-sun-yellow">{text.substring(index, index + query.length)}</span>
|
|
96
|
+
{text.substring(index + query.length)}
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex items-center justify-end px-4 py-2 bg-warm-cloud border-t border-black">
|
|
103
|
+
<div className="relative w-80" ref={containerRef}>
|
|
104
|
+
<Input
|
|
105
|
+
ref={inputRef}
|
|
106
|
+
type="text"
|
|
107
|
+
placeholder="Search typography (H1, Heading 1, P, Paragraph...)"
|
|
108
|
+
value={searchQuery}
|
|
109
|
+
onChange={handleInputChange}
|
|
110
|
+
onKeyDown={handleKeyDown}
|
|
111
|
+
onFocus={() => setShowAutocomplete(true)}
|
|
112
|
+
/>
|
|
113
|
+
{showAutocomplete && suggestions.length > 0 && (
|
|
114
|
+
<div className="absolute z-50 w-full mt-1 bg-warm-cloud border border-black rounded-sm shadow-[4px_4px_0_0_var(--color-black)] max-h-64 overflow-y-auto bottom-full mb-1">
|
|
115
|
+
{suggestions.map((item, index) => (
|
|
116
|
+
<button
|
|
117
|
+
key={`${item.sectionId}-${item.text}-${index}`}
|
|
118
|
+
type="button"
|
|
119
|
+
onClick={() => handleSelectSuggestion(item)}
|
|
120
|
+
className={`w-full text-left px-3 py-2 font-mondwest text-sm transition-colors ${
|
|
121
|
+
index === selectedSuggestionIndex
|
|
122
|
+
? 'bg-sun-yellow text-black'
|
|
123
|
+
: 'bg-warm-cloud text-black hover:bg-black/5'
|
|
124
|
+
}`}
|
|
125
|
+
>
|
|
126
|
+
<div className="flex items-center justify-between">
|
|
127
|
+
<div className="flex flex-col gap-0.5">
|
|
128
|
+
<span className="font-joystix text-xs font-bold text-black/60 uppercase">
|
|
129
|
+
{item.sectionId}
|
|
130
|
+
</span>
|
|
131
|
+
<span>{highlightText(item.text, searchQuery)}</span>
|
|
132
|
+
</div>
|
|
133
|
+
<span className="text-xs text-black/40 uppercase font-mono">
|
|
134
|
+
{`<${item.element}>`}
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
137
|
+
</button>
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useDevToolsStore } from '../store';
|
|
2
|
+
|
|
3
|
+
export function useMockState<T = unknown>(category: string): T | undefined {
|
|
4
|
+
// In production, always return undefined
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const mockStates = useDevToolsStore((state) => state.mockStates);
|
|
10
|
+
const activeState = mockStates.find(
|
|
11
|
+
(s) => s.category === category && s.active
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return activeState?.values as T | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Main exports
|
|
2
|
+
export { DevToolsProvider } from './DevToolsProvider';
|
|
3
|
+
export { useDevToolsStore } from './store';
|
|
4
|
+
export { useMockState } from './hooks/useMockState';
|
|
5
|
+
|
|
6
|
+
// Re-export types
|
|
7
|
+
export type {
|
|
8
|
+
BrandColor,
|
|
9
|
+
ColorMode,
|
|
10
|
+
DiscoveredComponent,
|
|
11
|
+
PropDefinition,
|
|
12
|
+
AssetFile,
|
|
13
|
+
AssetFolder,
|
|
14
|
+
MockState,
|
|
15
|
+
Tab,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { DiscoveredComponent, PropDefinition } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a component file and extract metadata
|
|
5
|
+
* This runs on the server side via API route
|
|
6
|
+
*/
|
|
7
|
+
export function parseComponent(content: string, filePath: string): DiscoveredComponent | null {
|
|
8
|
+
// Check for default export
|
|
9
|
+
const hasDefaultExport = /export\s+default\s+function\s+(\w+)/.test(content) ||
|
|
10
|
+
/export\s+default\s+(\w+)/.test(content);
|
|
11
|
+
|
|
12
|
+
if (!hasDefaultExport) return null;
|
|
13
|
+
|
|
14
|
+
// Extract component name
|
|
15
|
+
const nameMatch = content.match(/export\s+default\s+function\s+(\w+)/);
|
|
16
|
+
const name = nameMatch?.[1] || 'Unknown';
|
|
17
|
+
|
|
18
|
+
// Extract props interface
|
|
19
|
+
const propsMatch = content.match(/interface\s+(\w+Props)\s*\{([^}]+)\}/);
|
|
20
|
+
const props: PropDefinition[] = [];
|
|
21
|
+
|
|
22
|
+
if (propsMatch) {
|
|
23
|
+
const propsBody = propsMatch[2];
|
|
24
|
+
const propLines = propsBody.split('\n').filter((l) => l.trim());
|
|
25
|
+
|
|
26
|
+
for (const line of propLines) {
|
|
27
|
+
const propMatch = line.match(/(\w+)(\?)?:\s*([^;]+)/);
|
|
28
|
+
if (propMatch) {
|
|
29
|
+
props.push({
|
|
30
|
+
name: propMatch[1],
|
|
31
|
+
type: propMatch[3].trim(),
|
|
32
|
+
required: !propMatch[2],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Try inline type annotation if no interface found
|
|
39
|
+
if (props.length === 0) {
|
|
40
|
+
const inlineMatch = content.match(/\{\s*([^}]+)\s*\}\s*:\s*\{([^}]+)\}/);
|
|
41
|
+
if (inlineMatch) {
|
|
42
|
+
const propsBody = inlineMatch[2];
|
|
43
|
+
const propLines = propsBody.split(/[,;]/).filter((l) => l.trim());
|
|
44
|
+
|
|
45
|
+
for (const line of propLines) {
|
|
46
|
+
const propMatch = line.trim().match(/(\w+)(\?)?:\s*(.+)/);
|
|
47
|
+
if (propMatch) {
|
|
48
|
+
props.push({
|
|
49
|
+
name: propMatch[1],
|
|
50
|
+
type: propMatch[3].trim(),
|
|
51
|
+
required: !propMatch[2],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extract default values from destructuring
|
|
59
|
+
const destructureMatch = content.match(/\{\s*([^}]+)\s*\}\s*:\s*(?:\w+Props|\{[^}]+\})/);
|
|
60
|
+
if (destructureMatch) {
|
|
61
|
+
const destructureBody = destructureMatch[1];
|
|
62
|
+
for (const prop of props) {
|
|
63
|
+
const defaultMatch = destructureBody.match(
|
|
64
|
+
new RegExp(`${prop.name}\\s*=\\s*(['"\`]?[^,}]+['"\`]?)`)
|
|
65
|
+
);
|
|
66
|
+
if (defaultMatch) {
|
|
67
|
+
prop.defaultValue = defaultMatch[1].trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
path: filePath,
|
|
75
|
+
props,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|