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,113 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { useDevToolsStore } from '../../store';
5
+ import { ComponentList } from './ComponentList';
6
+ import { Button } from '@/components/ui/Button';
7
+ import type { DiscoveredComponent } from '../../types';
8
+
9
+ interface DynamicFolderTabProps {
10
+ folderName: string;
11
+ }
12
+
13
+ export function DynamicFolderTab({ folderName }: DynamicFolderTabProps) {
14
+ const { components, isLoading, scanComponents } = useDevToolsStore();
15
+ const [folderComponents, setFolderComponents] = useState<DiscoveredComponent[]>([]);
16
+
17
+ // Scan components from this specific folder
18
+ useEffect(() => {
19
+ const scanFolder = async () => {
20
+ try {
21
+ const response = await fetch(`/api/devtools/components?folder=${encodeURIComponent(folderName)}`);
22
+ const data = await response.json();
23
+ setFolderComponents(data.components || []);
24
+ } catch (error) {
25
+ setFolderComponents([]);
26
+ }
27
+ };
28
+
29
+ scanFolder();
30
+ }, [folderName]);
31
+
32
+ // Try to import preview file if it exists
33
+ const [PreviewContent, setPreviewContent] = useState<React.ComponentType | null>(null);
34
+ const [previewError, setPreviewError] = useState<string | null>(null);
35
+
36
+ useEffect(() => {
37
+ const loadPreview = async () => {
38
+ try {
39
+ // Dynamic import of preview file
40
+ const previewModule = await import(`./previews/${folderName}.tsx`);
41
+ setPreviewContent(() => previewModule.default || previewModule.Preview);
42
+ setPreviewError(null);
43
+ } catch (error) {
44
+ // Preview file doesn't exist yet - that's fine
45
+ setPreviewContent(null);
46
+ setPreviewError(null);
47
+ }
48
+ };
49
+
50
+ loadPreview();
51
+ }, [folderName]);
52
+
53
+ return (
54
+ <div className="flex flex-col h-full overflow-auto pt-4 pb-4 pl-4 pr-2 bg-[var(--color-white)] border border-black rounded space-y-4">
55
+ {/* Header */}
56
+ <div className="flex items-center justify-between">
57
+ <h2 className="font-joystix text-sm uppercase text-black">{folderName}</h2>
58
+ <Button
59
+ variant="outline"
60
+ size="sm"
61
+ iconName="refresh"
62
+ onClick={async () => {
63
+ try {
64
+ const response = await fetch(`/api/devtools/components?folder=${encodeURIComponent(folderName)}`);
65
+ const data = await response.json();
66
+ setFolderComponents(data.components || []);
67
+ } catch (error) {
68
+ // Failed to refresh
69
+ }
70
+ }}
71
+ disabled={isLoading}
72
+ >
73
+ {isLoading ? 'Scanning...' : 'Refresh'}
74
+ </Button>
75
+ </div>
76
+
77
+ {/* Stats */}
78
+ <div className="flex gap-4 font-mondwest text-base text-black/60">
79
+ <span>{folderComponents.length} components found</span>
80
+ <span>in /components/{folderName}/</span>
81
+ </div>
82
+
83
+ {/* Loading State */}
84
+ {isLoading && folderComponents.length === 0 && (
85
+ <div className="text-center py-8 text-black/60 font-mondwest text-base">
86
+ Scanning components...
87
+ </div>
88
+ )}
89
+
90
+ {/* Preview Content (if exists) */}
91
+ {PreviewContent && (
92
+ <div className="mb-6">
93
+ <PreviewContent />
94
+ </div>
95
+ )}
96
+
97
+ {/* Component List */}
98
+ {!isLoading && folderComponents.length === 0 && (
99
+ <div className="text-center py-8">
100
+ <p className="text-black/60 font-mondwest text-base mb-2">No components found</p>
101
+ <p className="font-mondwest text-sm text-black/60">
102
+ Create components with default exports in /components/{folderName}/
103
+ </p>
104
+ </div>
105
+ )}
106
+
107
+ {folderComponents.length > 0 && (
108
+ <ComponentList components={folderComponents} folderName={folderName} />
109
+ )}
110
+ </div>
111
+ );
112
+ }
113
+
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import { Icon } from '@/components/icons';
4
+ import type { DiscoveredComponent } from '../../types';
5
+
6
+ interface PropDisplayProps {
7
+ component: DiscoveredComponent;
8
+ }
9
+
10
+ export function PropDisplay({ component }: PropDisplayProps) {
11
+ return (
12
+ <div className="space-y-3">
13
+ <div className="flex items-center justify-between">
14
+ <h3 className="font-joystix text-sm uppercase text-black">{component.name}</h3>
15
+ <span className="font-mondwest text-base text-black/60 font-mono">{component.path}</span>
16
+ </div>
17
+
18
+ {component.props.length > 0 ? (
19
+ <div className="border border-black rounded-md overflow-hidden">
20
+ <table className="w-full font-mondwest text-base">
21
+ <thead className="bg-black/10">
22
+ <tr>
23
+ <th className="text-left px-3 py-2 text-black/60 font-joystix text-xs uppercase">Prop</th>
24
+ <th className="text-left px-3 py-2 text-black/60 font-joystix text-xs uppercase">Type</th>
25
+ <th className="text-left px-3 py-2 text-black/60 font-joystix text-xs uppercase">Required</th>
26
+ <th className="text-left px-3 py-2 text-black/60 font-joystix text-xs uppercase">Default</th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ {component.props.map((prop) => (
31
+ <tr key={prop.name} className="border-t border-black" style={{ borderTopColor: 'var(--border-black-20)' }}>
32
+ <td className="px-3 py-2 font-mono text-black">{prop.name}</td>
33
+ <td className="px-3 py-2 font-mono text-black/60">{prop.type}</td>
34
+ <td className="px-3 py-2">
35
+ {prop.required ? (
36
+ <Icon name="checkmark-filled" size={14} className="text-error-red" />
37
+ ) : (
38
+ <span className="text-black/60">—</span>
39
+ )}
40
+ </td>
41
+ <td className="px-3 py-2 font-mono text-black/60">
42
+ {prop.defaultValue || '—'}
43
+ </td>
44
+ </tr>
45
+ ))}
46
+ </tbody>
47
+ </table>
48
+ </div>
49
+ ) : (
50
+ <p className="font-mondwest text-base text-black/60 italic">No props defined for this component</p>
51
+ )}
52
+ </div>
53
+ );
54
+ }
55
+
@@ -0,0 +1,167 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useMemo } from 'react';
4
+ import { Tabs, TabList, TabTrigger, TabContent, useToast } from '@/components/ui';
5
+ import { DesignSystemTab } from './DesignSystemTab';
6
+ import { DynamicFolderTab } from './DynamicFolderTab';
7
+ import { AddTabButton } from './AddTabButton';
8
+ import { COMPONENT_TABS, type ComponentTabConfig } from './tabConfig';
9
+
10
+ const STORAGE_KEY = 'devtools-dynamic-component-tabs';
11
+
12
+ /**
13
+ * Load dynamic tabs from localStorage
14
+ */
15
+ function loadDynamicTabs(): ComponentTabConfig[] {
16
+ if (typeof window === 'undefined') return [];
17
+ try {
18
+ const stored = localStorage.getItem(STORAGE_KEY);
19
+ return stored ? JSON.parse(stored) : [];
20
+ } catch {
21
+ return [];
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Save dynamic tabs to localStorage
27
+ */
28
+ function saveDynamicTabs(tabs: ComponentTabConfig[]) {
29
+ if (typeof window === 'undefined') return;
30
+ try {
31
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(tabs));
32
+ } catch {
33
+ // Ignore storage errors
34
+ }
35
+ }
36
+
37
+ interface ComponentsTabProps {
38
+ activeSubTab?: string;
39
+ searchQuery?: string;
40
+ onTabsChange?: (tabs: Array<{ id: string; label: string }>) => void;
41
+ onAddFolder?: (folderName: string) => void;
42
+ }
43
+
44
+ export function ComponentsTab({
45
+ activeSubTab = 'design-system',
46
+ searchQuery = '',
47
+ onTabsChange,
48
+ onAddFolder,
49
+ }: ComponentsTabProps) {
50
+ const [dynamicTabs, setDynamicTabs] = useState<ComponentTabConfig[]>([]);
51
+ const { addToast } = useToast();
52
+
53
+ // Load dynamic tabs on mount
54
+ useEffect(() => {
55
+ setDynamicTabs(loadDynamicTabs());
56
+ }, []);
57
+
58
+ // Memoize allTabs to prevent unnecessary re-renders
59
+ const allTabs = useMemo(() => [...COMPONENT_TABS, ...dynamicTabs], [dynamicTabs]);
60
+
61
+ // Memoize the mapped tabs array to prevent unnecessary effect runs
62
+ const tabsForParent = useMemo(
63
+ () => allTabs.map((tab) => ({ id: tab.id, label: tab.label })),
64
+ [allTabs]
65
+ );
66
+
67
+ // Notify parent of tabs change
68
+ useEffect(() => {
69
+ if (onTabsChange) {
70
+ onTabsChange(tabsForParent);
71
+ }
72
+ }, [tabsForParent, onTabsChange]);
73
+
74
+ const handleAddFolder = async (folderName: string) => {
75
+ // Validate folder name
76
+ if (!folderName || !/^[a-zA-Z0-9_-]+$/.test(folderName)) {
77
+ addToast({
78
+ title: 'Invalid folder name',
79
+ description: 'Folder name must contain only letters, numbers, underscores, or hyphens',
80
+ variant: 'warning',
81
+ });
82
+ return;
83
+ }
84
+
85
+ // Check if folder already exists
86
+ if (dynamicTabs.some((tab) => tab.id === `folder-${folderName}`)) {
87
+ addToast({
88
+ title: 'Folder exists',
89
+ description: 'A tab for this folder already exists',
90
+ variant: 'warning',
91
+ });
92
+ return;
93
+ }
94
+
95
+ try {
96
+ // Create folder via API
97
+ const response = await fetch('/api/devtools/components/create-folder', {
98
+ method: 'POST',
99
+ headers: { 'Content-Type': 'application/json' },
100
+ body: JSON.stringify({ folderName }),
101
+ });
102
+
103
+ if (!response.ok) {
104
+ const error = await response.json();
105
+ throw new Error(error.error || 'Failed to create folder');
106
+ }
107
+
108
+ // Add new tab
109
+ const newTab: ComponentTabConfig = {
110
+ id: `folder-${folderName}`,
111
+ label: folderName,
112
+ description: `Components from /components/${folderName}/`,
113
+ };
114
+
115
+ const updatedTabs = [...dynamicTabs, newTab];
116
+ setDynamicTabs(updatedTabs);
117
+ saveDynamicTabs(updatedTabs);
118
+
119
+ // Tabs will be updated via useEffect that calls onTabsChange
120
+ } catch (error) {
121
+ console.error('Failed to create folder:', error);
122
+ addToast({
123
+ title: 'Failed to create folder',
124
+ description: error instanceof Error ? error.message : 'Unknown error',
125
+ variant: 'error',
126
+ });
127
+ }
128
+ };
129
+
130
+ // Expose handleAddFolder for footer access
131
+ useEffect(() => {
132
+ (window as any).__componentsTabAddFolder = handleAddFolder;
133
+ return () => {
134
+ delete (window as any).__componentsTabAddFolder;
135
+ };
136
+ }, [dynamicTabs]);
137
+
138
+ return (
139
+ <div className="flex flex-col h-full">
140
+ {/* Tab Content - no internal navigation */}
141
+ <div className="flex-1 overflow-hidden">
142
+ {allTabs.map((tab: ComponentTabConfig) => {
143
+ // Design system tab
144
+ if (tab.id === 'design-system' && activeSubTab === 'design-system') {
145
+ return (
146
+ <div key={tab.id} className="h-full pr-2 pl-2 pb-2 rounded">
147
+ <DesignSystemTab searchQuery={searchQuery} />
148
+ </div>
149
+ );
150
+ }
151
+
152
+ // Dynamic folder tabs
153
+ if (tab.id.startsWith('folder-') && activeSubTab === tab.id) {
154
+ const folderName = tab.id.replace('folder-', '');
155
+ return (
156
+ <div key={tab.id} className="h-full pr-2 pl-2 pb-2 rounded">
157
+ <DynamicFolderTab folderName={folderName} />
158
+ </div>
159
+ );
160
+ }
161
+
162
+ return null;
163
+ })}
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,4 @@
1
+ # Preview files for dynamic component folders
2
+ # Each folder gets its own preview file (e.g., my-widgets.tsx)
3
+ # These files follow the Section/Row/PropsDisplay pattern from DesignSystemTab.tsx
4
+
@@ -0,0 +1,262 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { WindowTitleBar } from '@/components/Rad_os/WindowTitleBar';
5
+
6
+ // ============================================================================
7
+ // Section Component
8
+ // ============================================================================
9
+
10
+ function Section({ title, children }: { title: string; children: React.ReactNode }) {
11
+ return (
12
+ <div className="mb-6">
13
+ <h3
14
+ className="mb-3 border-b border-black/20 pb-2"
15
+ >
16
+ {title}
17
+ </h3>
18
+ <div className="space-y-3">
19
+ {children}
20
+ </div>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ function PropsDisplay({ props }: { props: string }) {
26
+ return (
27
+ <code className="bg-black/5 px-2 py-1 rounded-sm block mt-2">
28
+ {props}
29
+ </code>
30
+ );
31
+ }
32
+
33
+ function Row({ children, props }: {
34
+ children: React.ReactNode;
35
+ props?: string;
36
+ }) {
37
+ return (
38
+ <div>
39
+ <div className="flex flex-wrap items-center gap-2">
40
+ {children}
41
+ </div>
42
+ {props && <PropsDisplay props={props} />}
43
+ </div>
44
+ );
45
+ }
46
+
47
+
48
+ // ============================================================================
49
+ // Preview Content
50
+ // ============================================================================
51
+
52
+ function WindowTitleBarContent() {
53
+ const [isOpen, setIsOpen] = useState(true);
54
+
55
+ return (
56
+ <div className="space-y-6">
57
+ <div className="p-4 border border-black bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4">
58
+ <h3>
59
+ Basic Usage
60
+ </h3>
61
+ <div className="flex flex-col gap-4">
62
+ <WindowTitleBar
63
+ title="My Application"
64
+ windowId="my-app"
65
+ onClose={() => setIsOpen(false)}
66
+ data-edit-scope="component-definition"
67
+ data-component="WindowTitleBar"
68
+ />
69
+ <PropsDisplay props='title: string, windowId: string, onClose: () => void' />
70
+ <WindowTitleBar
71
+ title="With Icon"
72
+ windowId="my-app-icon"
73
+ onClose={() => setIsOpen(false)}
74
+ iconName="home"
75
+ />
76
+ <PropsDisplay props='iconName?: string' />
77
+ </div>
78
+ </div>
79
+
80
+ <div className="p-4 border border-black bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4">
81
+ <h3>
82
+ Visibility Controls
83
+ </h3>
84
+ <div className="flex flex-col gap-4">
85
+ <WindowTitleBar
86
+ title="Hidden Title"
87
+ windowId="no-title"
88
+ onClose={() => {}}
89
+ showTitle={false}
90
+ />
91
+ <PropsDisplay props='showTitle={false}' />
92
+ <WindowTitleBar
93
+ title="No Copy Button"
94
+ windowId="no-copy"
95
+ onClose={() => {}}
96
+ showCopyButton={false}
97
+ />
98
+ <PropsDisplay props='showCopyButton={false}' />
99
+ <WindowTitleBar
100
+ title="No Close Button"
101
+ windowId="no-close"
102
+ onClose={() => {}}
103
+ showCloseButton={false}
104
+ />
105
+ <PropsDisplay props='showCloseButton={false}' />
106
+ <WindowTitleBar
107
+ title="Minimal"
108
+ windowId="minimal"
109
+ onClose={() => {}}
110
+ showTitle={false}
111
+ showCopyButton={false}
112
+ showCloseButton={false}
113
+ />
114
+ <PropsDisplay props='showTitle={false}, showCopyButton={false}, showCloseButton={false}' />
115
+ <WindowTitleBar
116
+ title="With Fullscreen"
117
+ windowId="with-fullscreen"
118
+ onClose={() => {}}
119
+ showFullscreenButton={true}
120
+ onFullscreen={() => alert('Fullscreen clicked!')}
121
+ />
122
+ <PropsDisplay props='showFullscreenButton={true}, onFullscreen: () => void' />
123
+ </div>
124
+ </div>
125
+
126
+ <div className="p-4 border border-black bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4">
127
+ <h3>
128
+ Help Button
129
+ </h3>
130
+ <div className="flex flex-col gap-4">
131
+ <WindowTitleBar
132
+ title="With Help"
133
+ windowId="with-help"
134
+ onClose={() => {}}
135
+ showHelpButton={true}
136
+ helpContent={
137
+ <div>
138
+ <p className="mb-2">This is the help content.</p>
139
+ <p>
140
+ You can add any React content here to help users understand how to use this window.
141
+ </p>
142
+ </div>
143
+ }
144
+ helpTitle="Window Help"
145
+ />
146
+ <PropsDisplay props='showHelpButton={true}, helpContent: React.ReactNode, helpTitle?: string' />
147
+ <WindowTitleBar
148
+ title="Default Help"
149
+ windowId="default-help"
150
+ onClose={() => {}}
151
+ showHelpButton={true}
152
+ />
153
+ <PropsDisplay props='showHelpButton={true} (default help content)' />
154
+ </div>
155
+ </div>
156
+
157
+ <div className="p-4 border border-black bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4">
158
+ <h3>
159
+ Action Button
160
+ </h3>
161
+ <div className="flex flex-col gap-4">
162
+ <WindowTitleBar
163
+ title="With Action Button"
164
+ windowId="with-action"
165
+ onClose={() => {}}
166
+ showActionButton={true}
167
+ actionButton={{
168
+ text: "Connect Wallet",
169
+ onClick: () => alert('Wallet connect clicked!'),
170
+ }}
171
+ />
172
+ <PropsDisplay props='showActionButton={true}, actionButton: ActionButtonConfig' />
173
+ <WindowTitleBar
174
+ title="External Link"
175
+ windowId="external-link"
176
+ onClose={() => {}}
177
+ showActionButton={true}
178
+ actionButton={{
179
+ text: "Visit Site",
180
+ href: "https://example.com",
181
+ target: "_blank",
182
+ }}
183
+ />
184
+ <PropsDisplay props='actionButton with href' />
185
+ <WindowTitleBar
186
+ title="With Icon"
187
+ windowId="with-icon"
188
+ onClose={() => {}}
189
+ showActionButton={true}
190
+ actionButton={{
191
+ text: "Download",
192
+ iconName: "download",
193
+ onClick: () => alert('Download clicked!'),
194
+ }}
195
+ />
196
+ <PropsDisplay props='actionButton with icon' />
197
+ </div>
198
+ </div>
199
+
200
+ <div className="p-4 border border-black bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4">
201
+ <h3>
202
+ All Features Combined
203
+ </h3>
204
+ <div className="flex flex-col gap-4">
205
+ <WindowTitleBar
206
+ title="Full Featured"
207
+ windowId="full-featured"
208
+ onClose={() => {}}
209
+ showHelpButton={true}
210
+ showActionButton={true}
211
+ helpContent={
212
+ <div>
213
+ <p className="mb-2">Complete Help Guide</p>
214
+ <ul className="list-disc list-inside space-y-1">
215
+ <li>This window has all features enabled</li>
216
+ <li>Help button opens contextual help</li>
217
+ <li>Action button performs custom actions</li>
218
+ <li>Copy button shares the window link</li>
219
+ </ul>
220
+ </div>
221
+ }
222
+ helpTitle="Complete Guide"
223
+ actionButton={{
224
+ text: "Get Started",
225
+ iconName: "lightning",
226
+ onClick: () => alert('Get started clicked!'),
227
+ }}
228
+ />
229
+ <PropsDisplay props='All optional props enabled' />
230
+ </div>
231
+ </div>
232
+
233
+ <div className="p-4 border border-black bg-[var(--color-cream)] rounded flex flex-col gap-4 mb-4">
234
+ <h3>
235
+ Custom Styling
236
+ </h3>
237
+ <div className="flex flex-col gap-4">
238
+ <WindowTitleBar
239
+ title="Custom Styled"
240
+ windowId="custom-styled"
241
+ onClose={() => {}}
242
+ className="bg-sun-yellow/20"
243
+ />
244
+ <PropsDisplay props='className: string' />
245
+ </div>
246
+ </div>
247
+ </div>
248
+ );
249
+ }
250
+
251
+ // ============================================================================
252
+ // Main Export
253
+ // ============================================================================
254
+
255
+ export default function Preview() {
256
+ return (
257
+ <div className="space-y-6">
258
+ <WindowTitleBarContent />
259
+ </div>
260
+ );
261
+ }
262
+
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Tab Configuration for Components Tab
3
+ *
4
+ * This config allows adding new component tabs dynamically.
5
+ * Each tab can have its own content component.
6
+ */
7
+
8
+ export interface ComponentTabConfig {
9
+ /** Unique identifier for the tab */
10
+ id: string;
11
+ /** Display label for the tab */
12
+ label: string;
13
+ /** Optional description */
14
+ description?: string;
15
+ }
16
+
17
+ /**
18
+ * Default component tabs configuration
19
+ *
20
+ * To add a new tab:
21
+ * 1. Add an entry here with a unique id and label
22
+ * 2. Create a corresponding component in the ComponentsTab folder
23
+ * 3. Update the ComponentsTab index.tsx to render the new component
24
+ */
25
+ export const COMPONENT_TABS: ComponentTabConfig[] = [
26
+ {
27
+ id: 'folder-Rad_os',
28
+ label: 'RadOS',
29
+ description: 'RadOS window components (AppWindow, WindowTitleBar, etc.)',
30
+ },
31
+ {
32
+ id: 'design-system',
33
+ label: 'Design System',
34
+ description: 'Core UI components from the design system',
35
+ },
36
+ // Dynamic folder tabs are added via localStorage and the Add button
37
+ ];
38
+
39
+ /**
40
+ * Get a tab config by ID
41
+ */
42
+ export function getTabById(id: string): ComponentTabConfig | undefined {
43
+ return COMPONENT_TABS.find((tab) => tab.id === id);
44
+ }
45
+
46
+ /**
47
+ * Check if a tab ID is valid
48
+ */
49
+ export function isValidTabId(id: string): boolean {
50
+ return COMPONENT_TABS.some((tab) => tab.id === id);
51
+ }
52
+
53
+ export default COMPONENT_TABS;
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { useDevToolsStore } from '../../store';
4
+ import { Button } from '@/components/ui/Button';
5
+
6
+ export function MockStatesTab() {
7
+ const { mockStates, toggleMockState } = useDevToolsStore();
8
+
9
+ return (
10
+ <div className="flex flex-col h-full overflow-auto pt-4 pb-4 pl-4 pr-2 bg-[var(--color-white)] border border-black rounded space-y-4">
11
+ <h2 className="font-joystix text-sm uppercase text-black">Mock States</h2>
12
+ <p className="font-mondwest text-base text-black/60">
13
+ Use <code className="px-1 bg-black/10 rounded-xs font-mono">useMockState('wallet')</code> in your components.
14
+ </p>
15
+ <div className="grid grid-cols-1 gap-2">
16
+ {mockStates.map((state) => (
17
+ <Button
18
+ key={state.id}
19
+ variant={state.active ? 'primary' : 'outline'}
20
+ fullWidth
21
+ onClick={() => toggleMockState(state.id)}
22
+ >
23
+ {state.name}
24
+ </Button>
25
+ ))}
26
+ </div>
27
+ </div>
28
+ );
29
+ }