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,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
+