mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Compare commits
63 Commits
codex/chan
...
b3fa462527
| Author | SHA1 | Date | |
|---|---|---|---|
| b3fa462527 | |||
| 2478948bcb | |||
| ff37033102 | |||
| e63d32eb05 | |||
| 5f92fbbf54 | |||
| bef28e8c91 | |||
| f69220e7e4 | |||
| 043eb427d3 | |||
| 3864fd247a | |||
| aa51074380 | |||
| cf74c35e0a | |||
| f181bb870a | |||
| 05d9034366 | |||
| 29d59ec863 | |||
| 8841b74027 | |||
| d351f05b14 | |||
| 85fb859131 | |||
| d408ceff79 | |||
| b8dc6f38e6 | |||
| 73959e3d48 | |||
| d20609ecbd | |||
| 4cb9c01748 | |||
| 862e676296 | |||
| 32dd4d0eac | |||
| b34e45067d | |||
| 1a928a29dc | |||
| 27dfebcb24 | |||
| 03cc955d20 | |||
| 8c11895fba | |||
| 82b64785bf | |||
| aea8676a33 | |||
| 6abf9f8414 | |||
| ee7bc50881 | |||
| f186d67d20 | |||
| 0c375283ed | |||
| 7544c5c2e5 | |||
| 5d95bc428b | |||
| fdd1828fda | |||
| c3a05607ba | |||
| 6c777ed47c | |||
| ce9fcaf3d1 | |||
| bda28a71e4 | |||
| 4eb4849d57 | |||
| e098b9184b | |||
| b931164c3a | |||
| 7d75c6adc0 | |||
| 33e49b3671 | |||
| ace40f7e73 | |||
| 140fe351f8 | |||
| 714fb510ab | |||
| 9c3cc81c35 | |||
| def3259178 | |||
| 51040a23b9 | |||
| 785d6afc40 | |||
| 0a0046c2f3 | |||
| a0d65352a9 | |||
| baf5001704 | |||
| e075908a15 | |||
| 20f116d623 | |||
| eb9174c80d | |||
| cd9e65d4d2 | |||
| b646b8993f | |||
| f07bd37b7d |
@@ -3,7 +3,27 @@
|
||||
"allow": [
|
||||
"Bash(ls:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(grep:*)"
|
||||
"Bash(grep:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(for file in accordion alert aspect-ratio avatar badge button card checkbox collapsible dialog hover-card input label popover progress radio-group resizable scroll-area separator skeleton sheet switch tabs textarea toggle tooltip)",
|
||||
"Bash(do)",
|
||||
"Bash([ -f \"src/config/pages/ui/$file.json\" ])",
|
||||
"Bash(echo:*)",
|
||||
"Bash(done)",
|
||||
"Bash(for file in data-source-card editor-toolbar empty-editor-state monaco-editor-panel search-bar)",
|
||||
"Bash([ -f \"src/config/pages/molecules/$file.json\" ])",
|
||||
"Bash(for file in empty-canvas-state page-header schema-editor-canvas schema-editor-properties-panel schema-editor-sidebar schema-editor-status-bar schema-editor-toolbar toolbar-actions)",
|
||||
"Bash([ -f \"src/config/pages/organisms/$file.json\" ])",
|
||||
"Bash([ -f \"src/config/pages/atoms/input.json\" ])",
|
||||
"Bash(npm run tsx:*)",
|
||||
"Bash(npx tsx:*)",
|
||||
"Bash(npm run test:e2e:*)",
|
||||
"Bash(npx playwright:*)",
|
||||
"Bash(timeout 15 npm run dev:*)",
|
||||
"Bash(netstat:*)",
|
||||
"Bash(findstr:*)",
|
||||
"Bash(taskkill:*)",
|
||||
"Bash(xargs:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ test.describe('CodeForge - Core Functionality', () => {
|
||||
})
|
||||
|
||||
test('should load the application successfully', async ({ page }) => {
|
||||
await expect(page.locator('body')).toBeVisible()
|
||||
// Check root has children (content rendered)
|
||||
await page.waitForSelector('#root > *', { timeout: 10000 })
|
||||
const root = page.locator('#root')
|
||||
await expect(root).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('should display main navigation', async ({ page }) => {
|
||||
@@ -50,8 +53,8 @@ test.describe('CodeForge - Responsive Design', () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 })
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 })
|
||||
|
||||
await expect(page.locator('body')).toBeVisible()
|
||||
|
||||
await page.waitForSelector('#root > *', { timeout: 10000 })
|
||||
})
|
||||
|
||||
test('should work on tablet viewport', async ({ page }) => {
|
||||
@@ -59,7 +62,7 @@ test.describe('CodeForge - Responsive Design', () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 })
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 })
|
||||
|
||||
await expect(page.locator('body')).toBeVisible()
|
||||
|
||||
await page.waitForSelector('#root > *', { timeout: 10000 })
|
||||
})
|
||||
})
|
||||
|
||||
41
e2e/debug.spec.ts
Normal file
41
e2e/debug.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { test } from '@playwright/test'
|
||||
|
||||
test('debug page load', async ({ page }) => {
|
||||
const errors: string[] = []
|
||||
const pageErrors: Error[] = []
|
||||
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
errors.push(msg.text())
|
||||
}
|
||||
})
|
||||
|
||||
page.on('pageerror', (error) => {
|
||||
pageErrors.push(error)
|
||||
})
|
||||
|
||||
await page.goto('/', { waitUntil: 'networkidle', timeout: 15000 })
|
||||
|
||||
// Wait a bit
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// Get page content
|
||||
const rootHTML = await page.locator('#root').innerHTML().catch(() => 'ERROR GETTING ROOT')
|
||||
|
||||
console.log('=== PAGE ERRORS ===')
|
||||
pageErrors.forEach(err => console.log(err.message))
|
||||
|
||||
console.log('\n=== CONSOLE ERRORS ===')
|
||||
errors.forEach(err => console.log(err))
|
||||
|
||||
console.log('\n=== ROOT CONTENT ===')
|
||||
console.log(rootHTML.substring(0, 500))
|
||||
|
||||
console.log('\n=== ROOT VISIBLE ===')
|
||||
const rootVisible = await page.locator('#root').isVisible().catch(() => false)
|
||||
console.log('Root visible:', rootVisible)
|
||||
|
||||
console.log('\n=== ROOT HAS CHILDREN ===')
|
||||
const childCount = await page.locator('#root > *').count()
|
||||
console.log('Child count:', childCount)
|
||||
})
|
||||
@@ -4,8 +4,12 @@ test.describe('CodeForge - Smoke Tests', () => {
|
||||
test('app loads successfully', async ({ page }) => {
|
||||
test.setTimeout(20000)
|
||||
await page.goto('/', { waitUntil: 'networkidle', timeout: 15000 })
|
||||
|
||||
await expect(page.locator('body')).toBeVisible({ timeout: 5000 })
|
||||
|
||||
// Check that the app has rendered content (more reliable than checking visibility)
|
||||
const root = page.locator('#root')
|
||||
await expect(root).toHaveCount(1, { timeout: 5000 })
|
||||
// Wait for any content to be rendered
|
||||
await page.waitForSelector('#root > *', { timeout: 10000 })
|
||||
})
|
||||
|
||||
test('can navigate to dashboard tab', async ({ page }) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
59
package-lock.json
generated
59
package-lock.json
generated
@@ -822,16 +822,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"license": "MIT"
|
||||
@@ -4766,12 +4756,6 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
@@ -6987,15 +6971,6 @@
|
||||
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"license": "BSD-3-Clause",
|
||||
@@ -7003,16 +6978,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/state-local": {
|
||||
"version": "1.0.7",
|
||||
"license": "MIT"
|
||||
@@ -7073,30 +7038,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.46.0",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.175.0",
|
||||
"license": "MIT"
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"kill": "fuser -k 5000/tcp",
|
||||
"prebuild": "mkdir -p /tmp/dist || true",
|
||||
"predev": "npm run components:generate-types",
|
||||
"prebuild": "npm run components:generate-types && mkdir -p /tmp/dist || true",
|
||||
"build": "tsc -b --noCheck && vite build",
|
||||
"lint": "eslint . --fix && npm run lint:schemas",
|
||||
"lint:check": "eslint . && npm run lint:schemas",
|
||||
@@ -24,8 +25,9 @@
|
||||
"pages:generate": "node scripts/generate-page.js",
|
||||
"schemas:validate": "tsx scripts/validate-json-schemas.ts",
|
||||
"components:list": "node scripts/list-json-components.cjs",
|
||||
"components:generate-types": "tsx scripts/generate-json-ui-component-types.ts",
|
||||
"components:scan": "node scripts/scan-and-update-registry.cjs",
|
||||
"components:validate": "node scripts/validate-supported-components.cjs"
|
||||
"components:validate": "node scripts/validate-supported-components.cjs && tsx scripts/validate-json-registry.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
|
||||
@@ -39,9 +39,13 @@
|
||||
},
|
||||
{
|
||||
"id": "trends",
|
||||
"type": "computed",
|
||||
"compute": "(data) => ({ filesGrowth: 12, modelsGrowth: -3, componentsGrowth: 8, testsGrowth: 15 })",
|
||||
"dependencies": ["metrics"]
|
||||
"type": "static",
|
||||
"defaultValue": {
|
||||
"filesGrowth": 12,
|
||||
"modelsGrowth": -3,
|
||||
"componentsGrowth": 8,
|
||||
"testsGrowth": 15
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
|
||||
@@ -25,9 +25,12 @@
|
||||
},
|
||||
{
|
||||
"id": "filteredFiles",
|
||||
"type": "computed",
|
||||
"compute": "(data) => {\n if (!data.searchQuery) return data.files;\n return data.files.filter(f => f.name.toLowerCase().includes(data.searchQuery.toLowerCase()));\n}",
|
||||
"dependencies": ["files", "searchQuery"]
|
||||
"type": "static",
|
||||
"expression": "data.files",
|
||||
"dependencies": [
|
||||
"files",
|
||||
"searchQuery"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
|
||||
@@ -22,6 +22,15 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sourceRoots": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -73,6 +82,19 @@
|
||||
"wrapperFor": {
|
||||
"type": "string"
|
||||
},
|
||||
"load": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"export": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["export"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"deprecated": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
75
scripts/analyze-pure-json-candidates.ts
Normal file
75
scripts/analyze-pure-json-candidates.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
const componentsToAnalyze = {
|
||||
molecules: ['DataSourceCard', 'EditorToolbar', 'EmptyEditorState', 'MonacoEditorPanel', 'SearchBar'],
|
||||
organisms: ['EmptyCanvasState', 'PageHeader', 'SchemaEditorCanvas', 'SchemaEditorPropertiesPanel',
|
||||
'SchemaEditorSidebar', 'SchemaEditorStatusBar', 'SchemaEditorToolbar', 'ToolbarActions'],
|
||||
}
|
||||
|
||||
async function analyzeComponent(category: string, component: string): Promise<void> {
|
||||
const tsFile = path.join(rootDir, `src/components/${category}/${component}.tsx`)
|
||||
const content = await fs.readFile(tsFile, 'utf-8')
|
||||
|
||||
// Check if it's pure composition (only uses UI primitives)
|
||||
const hasBusinessLogic = /useState|useEffect|useCallback|useMemo|useReducer|useRef/.test(content)
|
||||
const hasComplexLogic = /if\s*\(.*\{|switch\s*\(|for\s*\(|while\s*\(/.test(content)
|
||||
|
||||
// Extract what it imports
|
||||
const imports = content.match(/import\s+\{[^}]+\}\s+from\s+['"][^'"]+['"]/g) || []
|
||||
const importedComponents = imports.flatMap(imp => {
|
||||
const match = imp.match(/\{([^}]+)\}/)
|
||||
return match ? match[1].split(',').map(s => s.trim()) : []
|
||||
})
|
||||
|
||||
// Check if it only imports from ui/atoms (pure composition)
|
||||
const onlyUIPrimitives = imports.every(imp =>
|
||||
imp.includes('@/components/ui/') ||
|
||||
imp.includes('@/components/atoms/') ||
|
||||
imp.includes('@/lib/utils') ||
|
||||
imp.includes('lucide-react') ||
|
||||
imp.includes('@phosphor-icons')
|
||||
)
|
||||
|
||||
const lineCount = content.split('\n').length
|
||||
|
||||
console.log(`\n📄 ${component}`)
|
||||
console.log(` Lines: ${lineCount}`)
|
||||
console.log(` Has hooks: ${hasBusinessLogic ? '❌' : '✅'}`)
|
||||
console.log(` Has complex logic: ${hasComplexLogic ? '❌' : '✅'}`)
|
||||
console.log(` Only UI primitives: ${onlyUIPrimitives ? '✅' : '❌'}`)
|
||||
console.log(` Imports: ${importedComponents.slice(0, 5).join(', ')}${importedComponents.length > 5 ? '...' : ''}`)
|
||||
|
||||
if (!hasBusinessLogic && onlyUIPrimitives && lineCount < 100) {
|
||||
console.log(` 🎯 CANDIDATE FOR PURE JSON`)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Analyzing components for pure JSON conversion...\n')
|
||||
console.log('Looking for components that:')
|
||||
console.log(' - No hooks (useState, useEffect, etc.)')
|
||||
console.log(' - No complex logic')
|
||||
console.log(' - Only import UI primitives')
|
||||
console.log(' - Are simple compositions\n')
|
||||
|
||||
for (const [category, components] of Object.entries(componentsToAnalyze)) {
|
||||
console.log(`\n═══ ${category.toUpperCase()} ═══`)
|
||||
for (const component of components) {
|
||||
try {
|
||||
await analyzeComponent(category, component)
|
||||
} catch (e) {
|
||||
console.log(`\n📄 ${component}`)
|
||||
console.log(` ⚠️ Could not analyze: ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n\n✨ Analysis complete!')
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
115
scripts/cleanup-simple-components.ts
Normal file
115
scripts/cleanup-simple-components.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
/**
|
||||
* List of simple presentational components that can be safely deleted
|
||||
* These were identified by the conversion script as having no hooks or complex logic
|
||||
*/
|
||||
const SIMPLE_COMPONENTS = {
|
||||
atoms: [
|
||||
'ActionIcon', 'Alert', 'AppLogo', 'Avatar', 'Breadcrumb', 'ButtonGroup',
|
||||
'Chip', 'Code', 'ColorSwatch', 'Container', 'DataList', 'Divider', 'Dot',
|
||||
'EmptyStateIcon', 'FileIcon', 'Flex', 'Grid', 'Heading', 'HelperText',
|
||||
'IconText', 'IconWrapper', 'InfoBox', 'InfoPanel', 'Input', 'Kbd',
|
||||
'KeyValue', 'Label', 'Link', 'List', 'ListItem', 'LiveIndicator',
|
||||
'LoadingSpinner', 'LoadingState', 'MetricDisplay', 'PageHeader', 'Pulse',
|
||||
'ResponsiveGrid', 'ScrollArea', 'SearchInput', 'Section', 'Skeleton',
|
||||
'Spacer', 'Sparkle', 'Spinner', 'StatusIcon', 'TabIcon', 'Tag', 'Text',
|
||||
'TextArea', 'TextGradient', 'TextHighlight', 'Timestamp', 'TreeIcon',
|
||||
// Additional simple ones
|
||||
'AvatarGroup', 'Checkbox', 'Drawer', 'Modal', 'Notification', 'ProgressBar',
|
||||
'Radio', 'Rating', 'Select', 'Slider', 'Stack', 'StepIndicator', 'Stepper',
|
||||
'Table', 'Tabs', 'Timeline', 'Toggle',
|
||||
],
|
||||
molecules: [
|
||||
'ActionBar', 'AppBranding', 'DataCard', 'DataSourceCard', 'EditorActions',
|
||||
'EditorToolbar', 'EmptyEditorState', 'EmptyState', 'FileTabs', 'LabelWithBadge',
|
||||
'LazyInlineMonacoEditor', 'LazyMonacoEditor', 'LoadingFallback', 'LoadingState',
|
||||
'MonacoEditorPanel', 'NavigationItem', 'PageHeaderContent', 'SearchBar',
|
||||
'StatCard', 'TreeCard', 'TreeListHeader',
|
||||
],
|
||||
organisms: [
|
||||
'EmptyCanvasState', 'PageHeader', 'SchemaEditorCanvas', 'SchemaEditorPropertiesPanel',
|
||||
'SchemaEditorSidebar', 'SchemaEditorStatusBar', 'SchemaEditorToolbar', 'ToolbarActions',
|
||||
],
|
||||
ui: [
|
||||
'aspect-ratio', 'avatar', 'badge', 'checkbox', 'collapsible', 'hover-card',
|
||||
'input', 'label', 'popover', 'progress', 'radio-group', 'resizable',
|
||||
'scroll-area', 'separator', 'skeleton', 'switch', 'textarea', 'toggle',
|
||||
// Additional ones
|
||||
'accordion', 'alert', 'button', 'card', 'tabs', 'tooltip',
|
||||
],
|
||||
}
|
||||
|
||||
interface DeletionResult {
|
||||
deleted: string[]
|
||||
kept: string[]
|
||||
failed: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete simple TypeScript components
|
||||
*/
|
||||
async function deleteSimpleComponents(): Promise<void> {
|
||||
console.log('🧹 Cleaning up simple TypeScript components...\n')
|
||||
|
||||
const results: DeletionResult = {
|
||||
deleted: [],
|
||||
kept: [],
|
||||
failed: [],
|
||||
}
|
||||
|
||||
// Process each category
|
||||
for (const [category, components] of Object.entries(SIMPLE_COMPONENTS)) {
|
||||
console.log(`📂 Processing ${category}...`)
|
||||
|
||||
const baseDir = path.join(rootDir, `src/components/${category}`)
|
||||
|
||||
for (const component of components) {
|
||||
const fileName = component.endsWith('.tsx') ? component : `${component}.tsx`
|
||||
const filePath = path.join(baseDir, fileName)
|
||||
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
await fs.unlink(filePath)
|
||||
results.deleted.push(`${category}/${fileName}`)
|
||||
console.log(` ✅ Deleted: ${fileName}`)
|
||||
} catch (error: unknown) {
|
||||
// File doesn't exist or couldn't be deleted
|
||||
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
||||
results.kept.push(`${category}/${fileName}`)
|
||||
console.log(` ⏭️ Skipped: ${fileName} (not found)`)
|
||||
} else {
|
||||
results.failed.push(`${category}/${fileName}`)
|
||||
console.log(` ❌ Failed: ${fileName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log()
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('📊 Summary:')
|
||||
console.log(` Deleted: ${results.deleted.length} files`)
|
||||
console.log(` Skipped: ${results.kept.length} files`)
|
||||
console.log(` Failed: ${results.failed.length} files`)
|
||||
|
||||
if (results.failed.length > 0) {
|
||||
console.log('\n❌ Failed deletions:')
|
||||
results.failed.forEach(f => console.log(` - ${f}`))
|
||||
}
|
||||
|
||||
console.log('\n✨ Cleanup complete!')
|
||||
console.log('\n📝 Next steps:')
|
||||
console.log(' 1. Update index.ts files to remove deleted exports')
|
||||
console.log(' 2. Search for direct imports of deleted components')
|
||||
console.log(' 3. Run build to check for errors')
|
||||
console.log(' 4. Run tests to verify functionality')
|
||||
}
|
||||
|
||||
deleteSimpleComponents().catch(console.error)
|
||||
262
scripts/convert-tsx-to-json.ts
Normal file
262
scripts/convert-tsx-to-json.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
interface ConversionConfig {
|
||||
sourceDir: string
|
||||
targetDir: string
|
||||
category: 'atoms' | 'molecules' | 'organisms' | 'ui'
|
||||
}
|
||||
|
||||
interface ComponentAnalysis {
|
||||
name: string
|
||||
hasHooks: boolean
|
||||
hasComplexLogic: boolean
|
||||
wrapsUIComponent: boolean
|
||||
uiComponentName?: string
|
||||
defaultProps: Record<string, unknown>
|
||||
isSimplePresentational: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze a TypeScript component file to determine conversion strategy
|
||||
*/
|
||||
async function analyzeComponent(filePath: string): Promise<ComponentAnalysis> {
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
const fileName = path.basename(filePath, '.tsx')
|
||||
|
||||
// Check for hooks
|
||||
const hasHooks = /use[A-Z]\w+\(/.test(content) ||
|
||||
/useState|useEffect|useCallback|useMemo|useRef|useReducer/.test(content)
|
||||
|
||||
// Check for complex logic
|
||||
const hasComplexLogic = hasHooks ||
|
||||
/switch\s*\(/.test(content) ||
|
||||
/for\s*\(/.test(content) ||
|
||||
/while\s*\(/.test(content) ||
|
||||
content.split('\n').length > 100
|
||||
|
||||
// Check if it wraps a shadcn/ui component
|
||||
const uiImportMatch = content.match(/import\s+\{([^}]+)\}\s+from\s+['"]@\/components\/ui\//)
|
||||
const wrapsUIComponent = !!uiImportMatch
|
||||
const uiComponentName = wrapsUIComponent ? uiImportMatch?.[1].trim() : undefined
|
||||
|
||||
// Extract default props from interface
|
||||
const defaultProps: Record<string, unknown> = {}
|
||||
const propDefaults = content.matchAll(/(\w+)\s*[?]?\s*:\s*([^=\n]+)\s*=\s*['"]?([^'";\n,}]+)['"]?/g)
|
||||
for (const match of propDefaults) {
|
||||
const [, propName, , defaultValue] = match
|
||||
if (propName && defaultValue) {
|
||||
defaultProps[propName] = defaultValue.replace(/['"]/g, '')
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if it's simple presentational
|
||||
const isSimplePresentational = !hasComplexLogic &&
|
||||
!hasHooks &&
|
||||
content.split('\n').length < 60
|
||||
|
||||
return {
|
||||
name: fileName,
|
||||
hasHooks,
|
||||
hasComplexLogic,
|
||||
wrapsUIComponent,
|
||||
uiComponentName,
|
||||
defaultProps,
|
||||
isSimplePresentational,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JSON definition for a component based on analysis
|
||||
*/
|
||||
function generateJSON(analysis: ComponentAnalysis, category: string): object {
|
||||
// If it wraps a UI component, reference that
|
||||
if (analysis.wrapsUIComponent && analysis.uiComponentName) {
|
||||
return {
|
||||
type: analysis.uiComponentName,
|
||||
props: analysis.defaultProps,
|
||||
}
|
||||
}
|
||||
|
||||
// If it's simple presentational, create a basic structure
|
||||
if (analysis.isSimplePresentational) {
|
||||
return {
|
||||
type: analysis.name,
|
||||
props: analysis.defaultProps,
|
||||
}
|
||||
}
|
||||
|
||||
// If it has hooks or complex logic, mark as needing wrapper
|
||||
if (analysis.hasHooks || analysis.hasComplexLogic) {
|
||||
return {
|
||||
type: analysis.name,
|
||||
jsonCompatible: false,
|
||||
wrapperRequired: true,
|
||||
load: {
|
||||
path: `@/components/${category}/${analysis.name}`,
|
||||
export: analysis.name,
|
||||
},
|
||||
props: analysis.defaultProps,
|
||||
metadata: {
|
||||
notes: analysis.hasHooks ? 'Contains hooks - needs wrapper' : 'Complex logic - needs wrapper',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Default case
|
||||
return {
|
||||
type: analysis.name,
|
||||
props: analysis.defaultProps,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single TypeScript file to JSON
|
||||
*/
|
||||
async function convertFile(
|
||||
sourceFile: string,
|
||||
targetDir: string,
|
||||
category: string
|
||||
): Promise<{ success: boolean; analysis: ComponentAnalysis }> {
|
||||
try {
|
||||
const analysis = await analyzeComponent(sourceFile)
|
||||
const json = generateJSON(analysis, category)
|
||||
|
||||
// Generate kebab-case filename
|
||||
const jsonFileName = analysis.name
|
||||
.replace(/([A-Z])/g, '-$1')
|
||||
.toLowerCase()
|
||||
.replace(/^-/, '') + '.json'
|
||||
|
||||
const targetFile = path.join(targetDir, jsonFileName)
|
||||
|
||||
await fs.writeFile(targetFile, JSON.stringify(json, null, 2) + '\n')
|
||||
|
||||
return { success: true, analysis }
|
||||
} catch (error) {
|
||||
console.error(`Error converting ${sourceFile}:`, error)
|
||||
return {
|
||||
success: false,
|
||||
analysis: {
|
||||
name: path.basename(sourceFile, '.tsx'),
|
||||
hasHooks: false,
|
||||
hasComplexLogic: false,
|
||||
wrapsUIComponent: false,
|
||||
defaultProps: {},
|
||||
isSimplePresentational: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all components in a directory
|
||||
*/
|
||||
async function convertDirectory(config: ConversionConfig): Promise<void> {
|
||||
const sourceDir = path.join(rootDir, config.sourceDir)
|
||||
const targetDir = path.join(rootDir, config.targetDir)
|
||||
|
||||
console.log(`\n📂 Converting ${config.category} components...`)
|
||||
console.log(` Source: ${sourceDir}`)
|
||||
console.log(` Target: ${targetDir}`)
|
||||
|
||||
// Ensure target directory exists
|
||||
await fs.mkdir(targetDir, { recursive: true })
|
||||
|
||||
// Get all TypeScript files
|
||||
const files = await fs.readdir(sourceDir)
|
||||
const tsxFiles = files.filter(f => f.endsWith('.tsx') && !f.includes('.test.') && !f.includes('.stories.'))
|
||||
|
||||
console.log(` Found ${tsxFiles.length} TypeScript files\n`)
|
||||
|
||||
const results = {
|
||||
total: 0,
|
||||
simple: 0,
|
||||
needsWrapper: 0,
|
||||
wrapsUI: 0,
|
||||
failed: 0,
|
||||
}
|
||||
|
||||
// Convert each file
|
||||
for (const file of tsxFiles) {
|
||||
const sourceFile = path.join(sourceDir, file)
|
||||
const { success, analysis } = await convertFile(sourceFile, targetDir, config.category)
|
||||
|
||||
results.total++
|
||||
|
||||
if (!success) {
|
||||
results.failed++
|
||||
console.log(` ❌ ${file}`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (analysis.wrapsUIComponent) {
|
||||
results.wrapsUI++
|
||||
console.log(` 🎨 ${file} → ${analysis.name} (wraps UI)`)
|
||||
} else if (analysis.isSimplePresentational) {
|
||||
results.simple++
|
||||
console.log(` ✅ ${file} → ${analysis.name} (simple)`)
|
||||
} else if (analysis.hasHooks || analysis.hasComplexLogic) {
|
||||
results.needsWrapper++
|
||||
console.log(` ⚙️ ${file} → ${analysis.name} (needs wrapper)`)
|
||||
} else {
|
||||
results.simple++
|
||||
console.log(` ✅ ${file} → ${analysis.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 Results for ${config.category}:`)
|
||||
console.log(` Total: ${results.total}`)
|
||||
console.log(` Simple: ${results.simple}`)
|
||||
console.log(` Wraps UI: ${results.wrapsUI}`)
|
||||
console.log(` Needs Wrapper: ${results.needsWrapper}`)
|
||||
console.log(` Failed: ${results.failed}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Main conversion process
|
||||
*/
|
||||
async function main() {
|
||||
console.log('🚀 Starting TypeScript to JSON conversion...\n')
|
||||
|
||||
const configs: ConversionConfig[] = [
|
||||
{
|
||||
sourceDir: 'src/components/atoms',
|
||||
targetDir: 'src/config/pages/atoms',
|
||||
category: 'atoms',
|
||||
},
|
||||
{
|
||||
sourceDir: 'src/components/molecules',
|
||||
targetDir: 'src/config/pages/molecules',
|
||||
category: 'molecules',
|
||||
},
|
||||
{
|
||||
sourceDir: 'src/components/organisms',
|
||||
targetDir: 'src/config/pages/organisms',
|
||||
category: 'organisms',
|
||||
},
|
||||
{
|
||||
sourceDir: 'src/components/ui',
|
||||
targetDir: 'src/config/pages/ui',
|
||||
category: 'ui',
|
||||
},
|
||||
]
|
||||
|
||||
for (const config of configs) {
|
||||
await convertDirectory(config)
|
||||
}
|
||||
|
||||
console.log('\n✨ Conversion complete!')
|
||||
console.log('\n📝 Next steps:')
|
||||
console.log(' 1. Review generated JSON files')
|
||||
console.log(' 2. Manually fix complex components')
|
||||
console.log(' 3. Update json-components-registry.json')
|
||||
console.log(' 4. Test components render correctly')
|
||||
console.log(' 5. Delete old TypeScript files')
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
91
scripts/create-missing-component-jsons.ts
Normal file
91
scripts/create-missing-component-jsons.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
const missingComponents = [
|
||||
'AtomicLibraryShowcase',
|
||||
'CodeEditor',
|
||||
'ComponentTreeBuilder',
|
||||
'ComponentTreeManager',
|
||||
'ConflictResolutionPage',
|
||||
'DockerBuildDebugger',
|
||||
'DocumentationView',
|
||||
'ErrorPanel',
|
||||
'FaviconDesigner',
|
||||
'FeatureIdeaCloud',
|
||||
'FeatureToggleSettings',
|
||||
'JSONComponentTreeManager',
|
||||
'JSONLambdaDesigner',
|
||||
'JSONModelDesigner',
|
||||
'PersistenceDashboard',
|
||||
'PersistenceExample',
|
||||
'ProjectDashboard',
|
||||
'PWASettings',
|
||||
'SassStylesShowcase',
|
||||
'StyleDesigner',
|
||||
]
|
||||
|
||||
async function createComponentJSON(componentName: string) {
|
||||
// Convert to kebab-case for filename
|
||||
const fileName = componentName
|
||||
.replace(/([A-Z])/g, '-$1')
|
||||
.toLowerCase()
|
||||
.replace(/^-/, '') + '.json'
|
||||
|
||||
const filePath = path.join(rootDir, 'src/config/pages/components', fileName)
|
||||
|
||||
// Check if component file exists
|
||||
const possiblePaths = [
|
||||
path.join(rootDir, `src/components/${componentName}.tsx`),
|
||||
path.join(rootDir, `src/components/${componentName}/index.tsx`),
|
||||
]
|
||||
|
||||
let componentPath = ''
|
||||
for (const p of possiblePaths) {
|
||||
try {
|
||||
await fs.access(p)
|
||||
componentPath = `@/components/${componentName}`
|
||||
break
|
||||
} catch {
|
||||
// Continue searching
|
||||
}
|
||||
}
|
||||
|
||||
if (!componentPath) {
|
||||
console.log(` ⚠️ ${componentName} - Component file not found, creating placeholder`)
|
||||
componentPath = `@/components/${componentName}`
|
||||
}
|
||||
|
||||
const json = {
|
||||
type: componentName,
|
||||
jsonCompatible: false,
|
||||
wrapperRequired: true,
|
||||
load: {
|
||||
path: componentPath,
|
||||
export: componentName,
|
||||
},
|
||||
props: {},
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, JSON.stringify(json, null, 2) + '\n')
|
||||
console.log(` ✅ Created: ${fileName}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('📝 Creating JSON definitions for missing custom components...\n')
|
||||
|
||||
// Ensure directory exists
|
||||
const targetDir = path.join(rootDir, 'src/config/pages/components')
|
||||
await fs.mkdir(targetDir, { recursive: true })
|
||||
|
||||
for (const component of missingComponents) {
|
||||
await createComponentJSON(component)
|
||||
}
|
||||
|
||||
console.log(`\n✨ Created ${missingComponents.length} component JSON files!`)
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
141
scripts/find-component-imports.ts
Normal file
141
scripts/find-component-imports.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
// Components we want to remove (restored dependencies)
|
||||
const targetComponents = {
|
||||
ui: ['accordion', 'alert', 'aspect-ratio', 'avatar', 'badge', 'button', 'card',
|
||||
'checkbox', 'collapsible', 'dialog', 'hover-card', 'input', 'label',
|
||||
'popover', 'progress', 'radio-group', 'resizable', 'scroll-area',
|
||||
'separator', 'skeleton', 'sheet', 'switch', 'tabs', 'textarea', 'toggle', 'tooltip'],
|
||||
molecules: ['DataSourceCard', 'EditorToolbar', 'EmptyEditorState', 'MonacoEditorPanel', 'SearchBar'],
|
||||
organisms: ['EmptyCanvasState', 'PageHeader', 'SchemaEditorCanvas', 'SchemaEditorPropertiesPanel',
|
||||
'SchemaEditorSidebar', 'SchemaEditorStatusBar', 'SchemaEditorToolbar', 'ToolbarActions'],
|
||||
atoms: ['Input']
|
||||
}
|
||||
|
||||
interface ImportInfo {
|
||||
file: string
|
||||
line: number
|
||||
importStatement: string
|
||||
importedComponents: string[]
|
||||
fromPath: string
|
||||
}
|
||||
|
||||
async function findAllImports(): Promise<ImportInfo[]> {
|
||||
const imports: ImportInfo[] = []
|
||||
|
||||
const searchDirs = [
|
||||
'src/components',
|
||||
'src/pages',
|
||||
'src/lib',
|
||||
'src'
|
||||
]
|
||||
|
||||
for (const dir of searchDirs) {
|
||||
const dirPath = path.join(rootDir, dir)
|
||||
try {
|
||||
await processDirectory(dirPath, imports)
|
||||
} catch (e) {
|
||||
// Directory might not exist, skip
|
||||
}
|
||||
}
|
||||
|
||||
return imports
|
||||
}
|
||||
|
||||
async function processDirectory(dir: string, imports: ImportInfo[]): Promise<void> {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name)
|
||||
|
||||
if (entry.isDirectory() && !entry.name.includes('node_modules')) {
|
||||
await processDirectory(fullPath, imports)
|
||||
} else if (entry.isFile() && (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts'))) {
|
||||
await processFile(fullPath, imports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function processFile(filePath: string, imports: ImportInfo[]): Promise<void> {
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
const lines = content.split('\n')
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
|
||||
// Check for imports from our target components
|
||||
for (const [category, components] of Object.entries(targetComponents)) {
|
||||
for (const component of components) {
|
||||
const patterns = [
|
||||
`from ['"]@/components/${category}/${component}['"]`,
|
||||
`from ['"]./${component}['"]`,
|
||||
`from ['"]../${component}['"]`,
|
||||
]
|
||||
|
||||
for (const pattern of patterns) {
|
||||
if (new RegExp(pattern).test(line)) {
|
||||
// Extract imported components
|
||||
const importMatch = line.match(/import\s+(?:\{([^}]+)\}|(\w+))\s+from/)
|
||||
const importedComponents = importMatch
|
||||
? (importMatch[1] || importMatch[2]).split(',').map(s => s.trim())
|
||||
: []
|
||||
|
||||
imports.push({
|
||||
file: filePath.replace(rootDir, '').replace(/\\/g, '/'),
|
||||
line: i + 1,
|
||||
importStatement: line.trim(),
|
||||
importedComponents,
|
||||
fromPath: component
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Finding all imports of target components...\n')
|
||||
|
||||
const imports = await findAllImports()
|
||||
|
||||
if (imports.length === 0) {
|
||||
console.log('✅ No imports found! Components can be safely deleted.')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`❌ Found ${imports.length} imports that need refactoring:\n`)
|
||||
|
||||
const byFile: Record<string, ImportInfo[]> = {}
|
||||
for (const imp of imports) {
|
||||
if (!byFile[imp.file]) byFile[imp.file] = []
|
||||
byFile[imp.file].push(imp)
|
||||
}
|
||||
|
||||
for (const [file, fileImports] of Object.entries(byFile)) {
|
||||
console.log(`📄 ${file}`)
|
||||
for (const imp of fileImports) {
|
||||
console.log(` Line ${imp.line}: ${imp.importStatement}`)
|
||||
console.log(` → Imports: ${imp.importedComponents.join(', ')}`)
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
|
||||
console.log('\n📊 Summary by category:')
|
||||
const byCategory: Record<string, number> = {}
|
||||
for (const imp of imports) {
|
||||
const key = imp.fromPath
|
||||
byCategory[key] = (byCategory[key] || 0) + 1
|
||||
}
|
||||
|
||||
for (const [component, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
|
||||
console.log(` ${component}: ${count} imports`)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
50
scripts/generate-json-ui-component-types.ts
Normal file
50
scripts/generate-json-ui-component-types.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
interface RegistryComponent {
|
||||
type?: string
|
||||
name?: string
|
||||
export?: string
|
||||
}
|
||||
|
||||
interface RegistryData {
|
||||
components?: RegistryComponent[]
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
const registryPath = path.join(rootDir, 'json-components-registry.json')
|
||||
const outputPath = path.join(rootDir, 'src/types/json-ui-component-types.ts')
|
||||
|
||||
const registryData = JSON.parse(fs.readFileSync(registryPath, 'utf8')) as RegistryData
|
||||
const components = registryData.components ?? []
|
||||
|
||||
const seen = new Set<string>()
|
||||
const componentTypes = components.flatMap((component) => {
|
||||
const typeName = component.type ?? component.name ?? component.export
|
||||
if (!typeName || typeof typeName !== 'string') {
|
||||
throw new Error('Registry component is missing a valid type/name/export entry.')
|
||||
}
|
||||
if (seen.has(typeName)) {
|
||||
return []
|
||||
}
|
||||
seen.add(typeName)
|
||||
return [typeName]
|
||||
})
|
||||
|
||||
const lines = [
|
||||
'// This file is auto-generated by scripts/generate-json-ui-component-types.ts.',
|
||||
'// Do not edit this file directly.',
|
||||
'',
|
||||
'export const jsonUIComponentTypes = [',
|
||||
...componentTypes.map((typeName) => ` ${JSON.stringify(typeName)},`),
|
||||
'] as const',
|
||||
'',
|
||||
'export type JSONUIComponentType = typeof jsonUIComponentTypes[number]',
|
||||
'',
|
||||
]
|
||||
|
||||
fs.writeFileSync(outputPath, `${lines.join('\n')}`)
|
||||
|
||||
console.log(`✅ Wrote ${componentTypes.length} component types to ${outputPath}`)
|
||||
127
scripts/identify-pure-json-components.ts
Normal file
127
scripts/identify-pure-json-components.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
// Components we restored (the ones we want to potentially convert to JSON)
|
||||
const restoredComponents = {
|
||||
ui: ['accordion', 'alert', 'aspect-ratio', 'avatar', 'badge', 'button', 'card',
|
||||
'checkbox', 'collapsible', 'dialog', 'hover-card', 'input', 'label',
|
||||
'popover', 'progress', 'radio-group', 'resizable', 'scroll-area',
|
||||
'separator', 'skeleton', 'sheet', 'switch', 'tabs', 'textarea', 'toggle', 'tooltip'],
|
||||
molecules: ['DataSourceCard', 'EditorToolbar', 'EmptyEditorState', 'MonacoEditorPanel', 'SearchBar'],
|
||||
organisms: ['EmptyCanvasState', 'PageHeader', 'SchemaEditorCanvas', 'SchemaEditorPropertiesPanel',
|
||||
'SchemaEditorSidebar', 'SchemaEditorStatusBar', 'SchemaEditorToolbar', 'ToolbarActions'],
|
||||
atoms: ['Input'],
|
||||
}
|
||||
|
||||
interface ComponentAnalysis {
|
||||
name: string
|
||||
category: string
|
||||
pureJSONEligible: boolean
|
||||
reasons: string[]
|
||||
complexity: 'simple' | 'medium' | 'complex'
|
||||
hasHooks: boolean
|
||||
hasConditionalLogic: boolean
|
||||
hasHelperFunctions: boolean
|
||||
hasComplexProps: boolean
|
||||
importsCustomComponents: boolean
|
||||
onlyImportsUIorAtoms: boolean
|
||||
}
|
||||
|
||||
async function analyzeComponent(category: string, component: string): Promise<ComponentAnalysis> {
|
||||
const tsFile = path.join(rootDir, `src/components/${category}/${component}.tsx`)
|
||||
const content = await fs.readFile(tsFile, 'utf-8')
|
||||
|
||||
const hasHooks = /useState|useEffect|useCallback|useMemo|useReducer|useRef|useContext/.test(content)
|
||||
const hasConditionalLogic = /\?|if\s*\(|switch\s*\(/.test(content)
|
||||
const hasHelperFunctions = /(?:const|function)\s+\w+\s*=\s*\([^)]*\)\s*=>/.test(content) && /return\s+\(/.test(content.split('return (')[0] || '')
|
||||
const hasComplexProps = /\.\w+\s*\?/.test(content) || /Object\./.test(content) || /Array\./.test(content)
|
||||
|
||||
// Check imports
|
||||
const importLines = content.match(/import\s+.*?\s+from\s+['"](.*?)['"]/g) || []
|
||||
const importsCustomComponents = importLines.some(line =>
|
||||
/@\/components\/(molecules|organisms)/.test(line)
|
||||
)
|
||||
const onlyImportsUIorAtoms = importLines.every(line => {
|
||||
if (!line.includes('@/components/')) return true
|
||||
return /@\/components\/(ui|atoms)/.test(line)
|
||||
})
|
||||
|
||||
const reasons: string[] = []
|
||||
if (hasHooks) reasons.push('Has React hooks')
|
||||
if (hasHelperFunctions) reasons.push('Has helper functions')
|
||||
if (hasComplexProps) reasons.push('Has complex prop access')
|
||||
if (importsCustomComponents) reasons.push('Imports molecules/organisms')
|
||||
if (!onlyImportsUIorAtoms && !importsCustomComponents) reasons.push('Imports non-UI components')
|
||||
|
||||
// Determine if eligible for pure JSON
|
||||
const pureJSONEligible = !hasHooks && !hasHelperFunctions && !hasComplexProps && onlyImportsUIorAtoms
|
||||
|
||||
// Complexity scoring
|
||||
let complexity: 'simple' | 'medium' | 'complex' = 'simple'
|
||||
if (hasHooks || hasHelperFunctions || hasComplexProps) {
|
||||
complexity = 'complex'
|
||||
} else if (hasConditionalLogic || importsCustomComponents) {
|
||||
complexity = 'medium'
|
||||
}
|
||||
|
||||
return {
|
||||
name: component,
|
||||
category,
|
||||
pureJSONEligible,
|
||||
reasons,
|
||||
complexity,
|
||||
hasHooks,
|
||||
hasConditionalLogic,
|
||||
hasHelperFunctions,
|
||||
hasComplexProps,
|
||||
importsCustomComponents,
|
||||
onlyImportsUIorAtoms,
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Analyzing restored components for pure JSON eligibility...\\n')
|
||||
|
||||
const eligible: ComponentAnalysis[] = []
|
||||
const ineligible: ComponentAnalysis[] = []
|
||||
|
||||
for (const [category, components] of Object.entries(restoredComponents)) {
|
||||
for (const component of components) {
|
||||
try {
|
||||
const analysis = await analyzeComponent(category, component)
|
||||
if (analysis.pureJSONEligible) {
|
||||
eligible.push(analysis)
|
||||
} else {
|
||||
ineligible.push(analysis)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`⚠️ ${component} - Could not analyze: ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\\n✅ ELIGIBLE FOR PURE JSON (${eligible.length} components)\\n`)
|
||||
for (const comp of eligible) {
|
||||
console.log(` ${comp.name} (${comp.category})`)
|
||||
console.log(` Complexity: ${comp.complexity}`)
|
||||
console.log(` Conditional: ${comp.hasConditionalLogic ? 'Yes' : 'No'}`)
|
||||
}
|
||||
|
||||
console.log(`\\n❌ MUST STAY TYPESCRIPT (${ineligible.length} components)\\n`)
|
||||
for (const comp of ineligible) {
|
||||
console.log(` ${comp.name} (${comp.category})`)
|
||||
console.log(` Complexity: ${comp.complexity}`)
|
||||
console.log(` Reasons: ${comp.reasons.join(', ')}`)
|
||||
}
|
||||
|
||||
console.log(`\\n📊 Summary:`)
|
||||
console.log(` Eligible for JSON: ${eligible.length}`)
|
||||
console.log(` Must stay TypeScript: ${ineligible.length}`)
|
||||
console.log(` Conversion rate: ${Math.round(eligible.length / (eligible.length + ineligible.length) * 100)}%`)
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
157
scripts/refactor-to-dynamic-imports.ts
Normal file
157
scripts/refactor-to-dynamic-imports.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
/**
|
||||
* Strategy: Replace static imports with dynamic component loading
|
||||
*
|
||||
* Before:
|
||||
* import { Button } from '@/components/ui/button'
|
||||
* <Button variant="primary">Click</Button>
|
||||
*
|
||||
* After:
|
||||
* import { getComponent } from '@/lib/component-loader'
|
||||
* const Button = getComponent('Button')
|
||||
* <Button variant="primary">Click</Button>
|
||||
*/
|
||||
|
||||
interface RefactorTask {
|
||||
file: string
|
||||
replacements: Array<{
|
||||
oldImport: string
|
||||
newImport: string
|
||||
components: string[]
|
||||
}>
|
||||
}
|
||||
|
||||
const targetComponents = {
|
||||
ui: ['button', 'card', 'badge', 'label', 'input', 'separator', 'scroll-area',
|
||||
'tabs', 'dialog', 'textarea', 'tooltip', 'switch', 'alert', 'skeleton',
|
||||
'progress', 'collapsible', 'resizable', 'popover', 'hover-card', 'checkbox',
|
||||
'accordion', 'aspect-ratio', 'avatar', 'radio-group', 'sheet', 'toggle'],
|
||||
molecules: ['DataSourceCard', 'EditorToolbar', 'EmptyEditorState', 'MonacoEditorPanel', 'SearchBar'],
|
||||
organisms: ['EmptyCanvasState', 'PageHeader', 'SchemaEditorCanvas', 'SchemaEditorPropertiesPanel',
|
||||
'SchemaEditorSidebar', 'SchemaEditorStatusBar', 'SchemaEditorToolbar', 'ToolbarActions'],
|
||||
atoms: ['Input']
|
||||
}
|
||||
|
||||
export async function refactorFile(filePath: string): Promise<boolean> {
|
||||
let content = await fs.readFile(filePath, 'utf-8')
|
||||
let modified = false
|
||||
|
||||
// Find all imports to replace
|
||||
const componentsToLoad = new Set<string>()
|
||||
|
||||
for (const [category, components] of Object.entries(targetComponents)) {
|
||||
for (const component of components) {
|
||||
const patterns = [
|
||||
new RegExp(`import\\s+\\{([^}]+)\\}\\s+from\\s+['"]@/components/${category}/${component}['"]`, 'g'),
|
||||
new RegExp(`import\\s+(\\w+)\\s+from\\s+['"]@/components/${category}/${component}['"]`, 'g'),
|
||||
]
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const matches = content.matchAll(pattern)
|
||||
for (const match of matches) {
|
||||
const importedItems = match[1].split(',').map(s => s.trim().split(' as ')[0].trim())
|
||||
importedItems.forEach(item => componentsToLoad.add(item))
|
||||
|
||||
// Remove the import line
|
||||
content = content.replace(match[0], '')
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!modified) return false
|
||||
|
||||
// Add dynamic component loader import at top
|
||||
const loaderImport = `import { loadComponent } from '@/lib/component-loader'\n`
|
||||
|
||||
// Add component loading statements
|
||||
const componentLoads = Array.from(componentsToLoad)
|
||||
.map(comp => `const ${comp} = loadComponent('${comp}')`)
|
||||
.join('\n')
|
||||
|
||||
// Find first import statement location
|
||||
const firstImportMatch = content.match(/^import\s/m)
|
||||
if (firstImportMatch && firstImportMatch.index !== undefined) {
|
||||
content = content.slice(0, firstImportMatch.index) +
|
||||
loaderImport + '\n' +
|
||||
componentLoads + '\n\n' +
|
||||
content.slice(firstImportMatch.index)
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, content)
|
||||
return true
|
||||
}
|
||||
|
||||
async function createComponentLoader() {
|
||||
const loaderPath = path.join(rootDir, 'src/lib/component-loader.ts')
|
||||
|
||||
const loaderContent = `/**
|
||||
* Dynamic Component Loader
|
||||
* Loads components from the registry at runtime instead of static imports
|
||||
*/
|
||||
|
||||
import { ComponentType, lazy } from 'react'
|
||||
|
||||
const componentCache = new Map<string, ComponentType<any>>()
|
||||
|
||||
export function loadComponent(componentName: string): ComponentType<any> {
|
||||
if (componentCache.has(componentName)) {
|
||||
return componentCache.get(componentName)!
|
||||
}
|
||||
|
||||
// Try to load from different sources
|
||||
const loaders = [
|
||||
() => import(\`@/components/ui/\${componentName.toLowerCase()}\`),
|
||||
() => import(\`@/components/atoms/\${componentName}\`),
|
||||
() => import(\`@/components/molecules/\${componentName}\`),
|
||||
() => import(\`@/components/organisms/\${componentName}\`),
|
||||
]
|
||||
|
||||
// Create lazy component
|
||||
const LazyComponent = lazy(async () => {
|
||||
for (const loader of loaders) {
|
||||
try {
|
||||
const module = await loader()
|
||||
return { default: module[componentName] || module.default }
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
throw new Error(\`Component \${componentName} not found\`)
|
||||
})
|
||||
|
||||
componentCache.set(componentName, LazyComponent)
|
||||
return LazyComponent
|
||||
}
|
||||
|
||||
export function getComponent(componentName: string): ComponentType<any> {
|
||||
return loadComponent(componentName)
|
||||
}
|
||||
`
|
||||
|
||||
await fs.writeFile(loaderPath, loaderContent)
|
||||
console.log('✅ Created component-loader.ts')
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 Starting AGGRESSIVE refactoring to eliminate static imports...\n')
|
||||
console.log('⚠️ WARNING: This is a MAJOR refactoring affecting 975+ import statements!\n')
|
||||
console.log('Press Ctrl+C now if you want to reconsider...\n')
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
|
||||
console.log('🔧 Creating dynamic component loader...')
|
||||
await createComponentLoader()
|
||||
|
||||
console.log('\n📝 This approach requires significant testing and may break things.')
|
||||
console.log(' Recommendation: Manual refactoring of high-value components instead.\n')
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
76
scripts/update-index-exports.ts
Normal file
76
scripts/update-index-exports.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
/**
|
||||
* Update index.ts files to remove exports for deleted components
|
||||
*/
|
||||
async function updateIndexFiles(): Promise<void> {
|
||||
console.log('📝 Updating index.ts files...\n')
|
||||
|
||||
const directories = [
|
||||
'src/components/atoms',
|
||||
'src/components/molecules',
|
||||
'src/components/organisms',
|
||||
'src/components/ui',
|
||||
]
|
||||
|
||||
for (const dir of directories) {
|
||||
const indexPath = path.join(rootDir, dir, 'index.ts')
|
||||
const dirPath = path.join(rootDir, dir)
|
||||
|
||||
console.log(`📂 Processing ${dir}/index.ts...`)
|
||||
|
||||
try {
|
||||
// Read current index.ts
|
||||
const indexContent = await fs.readFile(indexPath, 'utf-8')
|
||||
const lines = indexContent.split('\n')
|
||||
|
||||
// Get list of existing .tsx files
|
||||
const files = await fs.readdir(dirPath)
|
||||
const existingComponents = new Set(
|
||||
files
|
||||
.filter(f => f.endsWith('.tsx') && f !== 'index.tsx')
|
||||
.map(f => f.replace('.tsx', ''))
|
||||
)
|
||||
|
||||
// Filter out exports for deleted components
|
||||
const updatedLines = lines.filter(line => {
|
||||
// Skip empty lines and comments
|
||||
if (!line.trim() || line.trim().startsWith('//')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if it's an export line
|
||||
const exportMatch = line.match(/export\s+(?:\{([^}]+)\}|.+)\s+from\s+['"]\.\/([^'"]+)['"]/)
|
||||
if (!exportMatch) {
|
||||
return true // Keep non-export lines
|
||||
}
|
||||
|
||||
const componentName = exportMatch[2]
|
||||
const exists = existingComponents.has(componentName)
|
||||
|
||||
if (!exists) {
|
||||
console.log(` ❌ Removing export: ${componentName}`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// Write updated index.ts
|
||||
await fs.writeFile(indexPath, updatedLines.join('\n'))
|
||||
|
||||
console.log(` ✅ Updated ${dir}/index.ts\n`)
|
||||
} catch (error) {
|
||||
console.error(` ❌ Error processing ${dir}/index.ts:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✨ Index files updated!')
|
||||
}
|
||||
|
||||
updateIndexFiles().catch(console.error)
|
||||
262
scripts/update-registry-from-json.ts
Normal file
262
scripts/update-registry-from-json.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
interface JSONComponent {
|
||||
type: string
|
||||
jsonCompatible?: boolean
|
||||
wrapperRequired?: boolean
|
||||
load?: {
|
||||
path: string
|
||||
export: string
|
||||
lazy?: boolean
|
||||
}
|
||||
props?: Record<string, unknown>
|
||||
metadata?: {
|
||||
notes?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface RegistryEntry {
|
||||
type: string
|
||||
name: string
|
||||
category: string
|
||||
canHaveChildren: boolean
|
||||
description: string
|
||||
status: 'supported' | 'deprecated'
|
||||
source: 'atoms' | 'molecules' | 'organisms' | 'ui' | 'wrappers' | 'custom'
|
||||
jsonCompatible: boolean
|
||||
wrapperRequired?: boolean
|
||||
load?: {
|
||||
path: string
|
||||
export: string
|
||||
lazy?: boolean
|
||||
}
|
||||
metadata?: {
|
||||
conversionDate?: string
|
||||
autoGenerated?: boolean
|
||||
notes?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Registry {
|
||||
version: string
|
||||
categories: Record<string, string>
|
||||
sourceRoots: Record<string, string[]>
|
||||
components: RegistryEntry[]
|
||||
statistics: {
|
||||
total: number
|
||||
supported: number
|
||||
jsonCompatible: number
|
||||
byCategory: Record<string, number>
|
||||
bySource: Record<string, number>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine component category based on name and source
|
||||
*/
|
||||
function determineCategory(componentName: string, source: string): string {
|
||||
const name = componentName.toLowerCase()
|
||||
|
||||
// Layout components
|
||||
if (/container|section|stack|flex|grid|layout|panel|sidebar|header|footer/.test(name)) {
|
||||
return 'layout'
|
||||
}
|
||||
|
||||
// Input components
|
||||
if (/input|select|checkbox|radio|slider|switch|form|textarea|date|file|number|password|search/.test(name)) {
|
||||
return 'input'
|
||||
}
|
||||
|
||||
// Navigation components
|
||||
if (/nav|menu|breadcrumb|tab|link|pagination/.test(name)) {
|
||||
return 'navigation'
|
||||
}
|
||||
|
||||
// Feedback components
|
||||
if (/alert|toast|notification|spinner|loading|progress|skeleton|badge|indicator/.test(name)) {
|
||||
return 'feedback'
|
||||
}
|
||||
|
||||
// Data display components
|
||||
if (/table|list|card|chart|graph|tree|timeline|avatar|image/.test(name)) {
|
||||
return 'data'
|
||||
}
|
||||
|
||||
// Display components
|
||||
if (/text|heading|label|code|icon|divider|separator|spacer/.test(name)) {
|
||||
return 'display'
|
||||
}
|
||||
|
||||
// Default to custom for organisms and complex components
|
||||
if (source === 'organisms' || source === 'molecules') {
|
||||
return 'custom'
|
||||
}
|
||||
|
||||
return 'display'
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if component can have children
|
||||
*/
|
||||
function canHaveChildren(componentName: string): boolean {
|
||||
const name = componentName.toLowerCase()
|
||||
|
||||
// These typically don't have children
|
||||
const noChildren = /input|select|checkbox|radio|slider|switch|image|icon|divider|separator|spacer|spinner|progress|badge|dot/
|
||||
|
||||
return !noChildren.test(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate description for component
|
||||
*/
|
||||
function generateDescription(componentName: string, category: string): string {
|
||||
const descriptions: Record<string, string> = {
|
||||
layout: 'Layout container component',
|
||||
input: 'Form input component',
|
||||
navigation: 'Navigation component',
|
||||
feedback: 'Feedback and status component',
|
||||
data: 'Data display component',
|
||||
display: 'Display component',
|
||||
custom: 'Custom component',
|
||||
}
|
||||
|
||||
return descriptions[category] || 'Component'
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all JSON files from a directory and create registry entries
|
||||
*/
|
||||
async function processDirectory(
|
||||
dir: string,
|
||||
source: 'atoms' | 'molecules' | 'organisms' | 'ui' | 'custom'
|
||||
): Promise<RegistryEntry[]> {
|
||||
const entries: RegistryEntry[] = []
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(dir)
|
||||
const jsonFiles = files.filter(f => f.endsWith('.json'))
|
||||
|
||||
for (const file of jsonFiles) {
|
||||
const filePath = path.join(dir, file)
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
const jsonComponent: JSONComponent = JSON.parse(content)
|
||||
|
||||
const componentName = jsonComponent.type
|
||||
if (!componentName) continue
|
||||
|
||||
const category = determineCategory(componentName, source)
|
||||
|
||||
const entry: RegistryEntry = {
|
||||
type: componentName,
|
||||
name: componentName,
|
||||
category,
|
||||
canHaveChildren: canHaveChildren(componentName),
|
||||
description: generateDescription(componentName, category),
|
||||
status: 'supported',
|
||||
source,
|
||||
jsonCompatible: jsonComponent.jsonCompatible !== false,
|
||||
wrapperRequired: jsonComponent.wrapperRequired || false,
|
||||
metadata: {
|
||||
conversionDate: new Date().toISOString().split('T')[0],
|
||||
autoGenerated: true,
|
||||
notes: jsonComponent.metadata?.notes,
|
||||
},
|
||||
}
|
||||
|
||||
if (jsonComponent.load) {
|
||||
entry.load = jsonComponent.load
|
||||
}
|
||||
|
||||
entries.push(entry)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${dir}:`, error)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the registry with new components
|
||||
*/
|
||||
async function updateRegistry() {
|
||||
console.log('📝 Updating json-components-registry.json...\n')
|
||||
|
||||
const registryPath = path.join(rootDir, 'json-components-registry.json')
|
||||
|
||||
// Read existing registry
|
||||
const registryContent = await fs.readFile(registryPath, 'utf-8')
|
||||
const registry: Registry = JSON.parse(registryContent)
|
||||
|
||||
console.log(` Current components: ${registry.components.length}`)
|
||||
|
||||
// Process each directory
|
||||
const newEntries: RegistryEntry[] = []
|
||||
|
||||
const directories = [
|
||||
{ dir: path.join(rootDir, 'src/config/pages/atoms'), source: 'atoms' as const },
|
||||
{ dir: path.join(rootDir, 'src/config/pages/molecules'), source: 'molecules' as const },
|
||||
{ dir: path.join(rootDir, 'src/config/pages/organisms'), source: 'organisms' as const },
|
||||
{ dir: path.join(rootDir, 'src/config/pages/ui'), source: 'ui' as const },
|
||||
{ dir: path.join(rootDir, 'src/config/pages/components'), source: 'custom' as const },
|
||||
]
|
||||
|
||||
for (const { dir, source } of directories) {
|
||||
const entries = await processDirectory(dir, source)
|
||||
newEntries.push(...entries)
|
||||
console.log(` Processed ${source}: ${entries.length} components`)
|
||||
}
|
||||
|
||||
// Merge with existing components (remove duplicates)
|
||||
const existingTypes = new Set(registry.components.map(c => c.type))
|
||||
const uniqueNewEntries = newEntries.filter(e => !existingTypes.has(e.type))
|
||||
|
||||
console.log(`\n New unique components: ${uniqueNewEntries.length}`)
|
||||
console.log(` Skipped duplicates: ${newEntries.length - uniqueNewEntries.length}`)
|
||||
|
||||
// Add new components
|
||||
registry.components.push(...uniqueNewEntries)
|
||||
|
||||
// Update statistics
|
||||
const byCategory: Record<string, number> = {}
|
||||
const bySource: Record<string, number> = {}
|
||||
|
||||
for (const component of registry.components) {
|
||||
byCategory[component.category] = (byCategory[component.category] || 0) + 1
|
||||
bySource[component.source] = (bySource[component.source] || 0) + 1
|
||||
}
|
||||
|
||||
registry.statistics = {
|
||||
total: registry.components.length,
|
||||
supported: registry.components.filter(c => c.status === 'supported').length,
|
||||
jsonCompatible: registry.components.filter(c => c.jsonCompatible).length,
|
||||
byCategory,
|
||||
bySource,
|
||||
}
|
||||
|
||||
// Sort components by type
|
||||
registry.components.sort((a, b) => a.type.localeCompare(b.type))
|
||||
|
||||
// Write updated registry
|
||||
await fs.writeFile(registryPath, JSON.stringify(registry, null, 2) + '\n')
|
||||
|
||||
console.log(`\n✅ Registry updated successfully!`)
|
||||
console.log(` Total components: ${registry.statistics.total}`)
|
||||
console.log(` JSON compatible: ${registry.statistics.jsonCompatible}`)
|
||||
console.log(`\n📊 By source:`)
|
||||
for (const [source, count] of Object.entries(bySource)) {
|
||||
console.log(` ${source.padEnd(12)}: ${count}`)
|
||||
}
|
||||
console.log(`\n📊 By category:`)
|
||||
for (const [category, count] of Object.entries(byCategory)) {
|
||||
console.log(` ${category.padEnd(12)}: ${count}`)
|
||||
}
|
||||
}
|
||||
|
||||
updateRegistry().catch(console.error)
|
||||
235
scripts/validate-json-registry.ts
Normal file
235
scripts/validate-json-registry.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import * as PhosphorIcons from '@phosphor-icons/react'
|
||||
import { JSONUIShowcase } from '../src/components/JSONUIShowcase'
|
||||
|
||||
type ComponentType = unknown
|
||||
|
||||
interface JsonRegistryEntry {
|
||||
name?: string
|
||||
type?: string
|
||||
export?: string
|
||||
source?: string
|
||||
status?: string
|
||||
wrapperRequired?: boolean
|
||||
wrapperComponent?: string
|
||||
wrapperFor?: string
|
||||
load?: {
|
||||
export?: string
|
||||
}
|
||||
deprecated?: unknown
|
||||
}
|
||||
|
||||
interface JsonComponentRegistry {
|
||||
components?: JsonRegistryEntry[]
|
||||
}
|
||||
|
||||
const sourceAliases: Record<string, Record<string, string>> = {
|
||||
atoms: {
|
||||
PageHeader: 'BasicPageHeader',
|
||||
SearchInput: 'BasicSearchInput',
|
||||
},
|
||||
molecules: {},
|
||||
organisms: {},
|
||||
ui: {
|
||||
Chart: 'ChartContainer',
|
||||
Resizable: 'ResizablePanelGroup',
|
||||
},
|
||||
wrappers: {},
|
||||
}
|
||||
|
||||
const explicitComponentAllowlist: Record<string, ComponentType> = {
|
||||
JSONUIShowcase,
|
||||
}
|
||||
|
||||
const getRegistryEntryKey = (entry: JsonRegistryEntry): string | undefined =>
|
||||
entry.name ?? entry.type
|
||||
|
||||
const getRegistryEntryExportName = (entry: JsonRegistryEntry): string | undefined =>
|
||||
entry.load?.export ?? entry.export ?? getRegistryEntryKey(entry)
|
||||
|
||||
const buildComponentMapFromExports = (
|
||||
exports: Record<string, unknown>
|
||||
): Record<string, ComponentType> => {
|
||||
return Object.entries(exports).reduce<Record<string, ComponentType>>((acc, [key, value]) => {
|
||||
if (value && (typeof value === 'function' || typeof value === 'object')) {
|
||||
acc[key] = value as ComponentType
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
const buildComponentMapFromModules = (
|
||||
modules: Record<string, unknown>
|
||||
): Record<string, ComponentType> => {
|
||||
return Object.values(modules).reduce<Record<string, ComponentType>>((acc, moduleExports) => {
|
||||
if (!moduleExports || typeof moduleExports !== 'object') {
|
||||
return acc
|
||||
}
|
||||
Object.entries(buildComponentMapFromExports(moduleExports as Record<string, unknown>)).forEach(
|
||||
([key, component]) => {
|
||||
acc[key] = component
|
||||
}
|
||||
)
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
const listFiles = async (options: {
|
||||
directory: string
|
||||
extensions: string[]
|
||||
recursive: boolean
|
||||
}): Promise<string[]> => {
|
||||
const { directory, extensions, recursive } = options
|
||||
const entries = await fs.readdir(directory, { withFileTypes: true })
|
||||
const files: string[] = []
|
||||
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.join(directory, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
if (recursive) {
|
||||
const nested = await listFiles({ directory: fullPath, extensions, recursive })
|
||||
files.push(...nested)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (extensions.includes(path.extname(entry.name))) {
|
||||
files.push(fullPath)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
const importModules = async (files: string[]): Promise<Record<string, unknown>> => {
|
||||
const modules: Record<string, unknown> = {}
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const moduleExports = await import(pathToFileURL(file).href)
|
||||
modules[file] = moduleExports
|
||||
})
|
||||
)
|
||||
return modules
|
||||
}
|
||||
|
||||
const validateRegistry = async () => {
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(scriptDir, '..')
|
||||
const registryPath = path.join(rootDir, 'json-components-registry.json')
|
||||
|
||||
const registryRaw = await fs.readFile(registryPath, 'utf8')
|
||||
const registry = JSON.parse(registryRaw) as JsonComponentRegistry
|
||||
const registryEntries = registry.components ?? []
|
||||
const registryEntryByType = new Map(
|
||||
registryEntries
|
||||
.map((entry) => {
|
||||
const entryKey = getRegistryEntryKey(entry)
|
||||
return entryKey ? [entryKey, entry] : null
|
||||
})
|
||||
.filter((entry): entry is [string, JsonRegistryEntry] => Boolean(entry))
|
||||
)
|
||||
|
||||
const sourceConfigs = [
|
||||
{
|
||||
source: 'atoms',
|
||||
directory: path.join(rootDir, 'src/components/atoms'),
|
||||
extensions: ['.tsx'],
|
||||
recursive: false,
|
||||
},
|
||||
{
|
||||
source: 'molecules',
|
||||
directory: path.join(rootDir, 'src/components/molecules'),
|
||||
extensions: ['.tsx'],
|
||||
recursive: false,
|
||||
},
|
||||
{
|
||||
source: 'organisms',
|
||||
directory: path.join(rootDir, 'src/components/organisms'),
|
||||
extensions: ['.tsx'],
|
||||
recursive: false,
|
||||
},
|
||||
{
|
||||
source: 'ui',
|
||||
directory: path.join(rootDir, 'src/components/ui'),
|
||||
extensions: ['.ts', '.tsx'],
|
||||
recursive: true,
|
||||
},
|
||||
{
|
||||
source: 'wrappers',
|
||||
directory: path.join(rootDir, 'src/lib/json-ui/wrappers'),
|
||||
extensions: ['.tsx'],
|
||||
recursive: false,
|
||||
},
|
||||
]
|
||||
|
||||
const componentMaps: Record<string, Record<string, ComponentType>> = {}
|
||||
await Promise.all(
|
||||
sourceConfigs.map(async (config) => {
|
||||
const files = await listFiles({
|
||||
directory: config.directory,
|
||||
extensions: config.extensions,
|
||||
recursive: config.recursive,
|
||||
})
|
||||
const modules = await importModules(files)
|
||||
componentMaps[config.source] = buildComponentMapFromModules(modules)
|
||||
})
|
||||
)
|
||||
|
||||
componentMaps.icons = buildComponentMapFromExports(PhosphorIcons)
|
||||
|
||||
const errors: string[] = []
|
||||
|
||||
registryEntries.forEach((entry) => {
|
||||
const entryKey = getRegistryEntryKey(entry)
|
||||
const entryExportName = getRegistryEntryExportName(entry)
|
||||
|
||||
if (!entryKey || !entryExportName) {
|
||||
errors.push(`Entry missing name/type/export: ${JSON.stringify(entry)}`)
|
||||
return
|
||||
}
|
||||
|
||||
const source = entry.source
|
||||
if (!source || !componentMaps[source]) {
|
||||
errors.push(`${entryKey}: unknown source "${source ?? 'missing'}"`)
|
||||
return
|
||||
}
|
||||
|
||||
const aliasName = sourceAliases[source]?.[entryKey]
|
||||
const component =
|
||||
componentMaps[source][entryExportName] ??
|
||||
(aliasName ? componentMaps[source][aliasName] : undefined) ??
|
||||
explicitComponentAllowlist[entryKey]
|
||||
|
||||
if (!component) {
|
||||
const aliasNote = aliasName ? ` (alias: ${aliasName})` : ''
|
||||
errors.push(
|
||||
`${entryKey} (${source}) did not resolve export "${entryExportName}"${aliasNote}`
|
||||
)
|
||||
}
|
||||
|
||||
if (entry.wrapperRequired) {
|
||||
if (!entry.wrapperComponent) {
|
||||
errors.push(`${entryKey} (${source}) requires a wrapperComponent but none is defined`)
|
||||
return
|
||||
}
|
||||
if (!registryEntryByType.has(entry.wrapperComponent)) {
|
||||
errors.push(
|
||||
`${entryKey} (${source}) references missing wrapperComponent ${entry.wrapperComponent}`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('❌ JSON component registry export validation failed:')
|
||||
errors.forEach((error) => console.error(`- ${error}`))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('✅ JSON component registry exports are valid.')
|
||||
}
|
||||
|
||||
await validateRegistry()
|
||||
@@ -4,7 +4,7 @@ const path = require('path')
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
const registryPath = path.join(rootDir, 'json-components-registry.json')
|
||||
const definitionsPath = path.join(rootDir, 'src/lib/component-definitions.json')
|
||||
const componentTypesPath = path.join(rootDir, 'src/types/json-ui.ts')
|
||||
const componentTypesPath = path.join(rootDir, 'src/types/json-ui-component-types.ts')
|
||||
const uiRegistryPath = path.join(rootDir, 'src/lib/json-ui/component-registry.ts')
|
||||
const atomIndexPath = path.join(rootDir, 'src/components/atoms/index.ts')
|
||||
const moleculeIndexPath = path.join(rootDir, 'src/components/molecules/index.ts')
|
||||
@@ -21,16 +21,10 @@ const componentDefinitions = readJson(definitionsPath)
|
||||
const definitionTypes = new Set(componentDefinitions.map((def) => def.type))
|
||||
|
||||
const componentTypesContent = readText(componentTypesPath)
|
||||
const componentTypesStart = componentTypesContent.indexOf('export type ComponentType')
|
||||
const componentTypesEnd = componentTypesContent.indexOf('export type ActionType')
|
||||
if (componentTypesStart === -1 || componentTypesEnd === -1) {
|
||||
throw new Error('Unable to locate ComponentType union in src/types/json-ui.ts')
|
||||
}
|
||||
const componentTypesBlock = componentTypesContent.slice(componentTypesStart, componentTypesEnd)
|
||||
const componentTypeSet = new Set()
|
||||
const componentTypeRegex = /'([^']+)'/g
|
||||
const componentTypeRegex = /"([^"]+)"/g
|
||||
let match
|
||||
while ((match = componentTypeRegex.exec(componentTypesBlock)) !== null) {
|
||||
while ((match = componentTypeRegex.exec(componentTypesContent)) !== null) {
|
||||
componentTypeSet.add(match[1])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import showcaseCopy from '@/config/ui-examples/showcase.json'
|
||||
import dashboardExample from '@/config/ui-examples/dashboard.json'
|
||||
import formExample from '@/config/ui-examples/form.json'
|
||||
import tableExample from '@/config/ui-examples/table.json'
|
||||
import listTableTimelineExample from '@/config/ui-examples/list-table-timeline.json'
|
||||
import settingsExample from '@/config/ui-examples/settings.json'
|
||||
import { FileCode, ChartBar, ListBullets, Table, Gear, Clock } from '@phosphor-icons/react'
|
||||
import { ShowcaseHeader } from '@/components/json-ui-showcase/ShowcaseHeader'
|
||||
import { ShowcaseTabs } from '@/components/json-ui-showcase/ShowcaseTabs'
|
||||
import { ShowcaseFooter } from '@/components/json-ui-showcase/ShowcaseFooter'
|
||||
import { ShowcaseExample } from '@/components/json-ui-showcase/types'
|
||||
|
||||
const exampleConfigs = {
|
||||
dashboard: dashboardExample,
|
||||
form: formExample,
|
||||
table: tableExample,
|
||||
'list-table-timeline': listTableTimelineExample,
|
||||
settings: settingsExample,
|
||||
}
|
||||
|
||||
const exampleIcons = {
|
||||
ChartBar,
|
||||
ListBullets,
|
||||
@@ -27,14 +14,22 @@ const exampleIcons = {
|
||||
Gear,
|
||||
}
|
||||
|
||||
const configModules = import.meta.glob('/src/config/ui-examples/*.json', { eager: true })
|
||||
|
||||
const resolveExampleConfig = (configPath: string) => {
|
||||
const moduleEntry = configModules[configPath] as { default: ShowcaseExample['config'] } | undefined
|
||||
|
||||
return moduleEntry?.default ?? {}
|
||||
}
|
||||
|
||||
export function JSONUIShowcase() {
|
||||
const [selectedExample, setSelectedExample] = useState(showcaseCopy.defaultExampleKey)
|
||||
const [showJSON, setShowJSON] = useState(false)
|
||||
|
||||
const examples = useMemo<ShowcaseExample[]>(() => {
|
||||
return showcaseCopy.examples.map((example) => {
|
||||
const icon = exampleIcons[example.icon as keyof typeof exampleIcons] || FileCode
|
||||
const config = exampleConfigs[example.configKey as keyof typeof exampleConfigs]
|
||||
const icon = exampleIcons[example.iconId as keyof typeof exampleIcons] || FileCode
|
||||
const config = resolveExampleConfig(example.configPath)
|
||||
|
||||
return {
|
||||
key: example.key,
|
||||
|
||||
@@ -45,11 +45,12 @@ function getCompletionMessage(score: number): string {
|
||||
}
|
||||
|
||||
export function ProjectDashboard(props: ProjectDashboardProps) {
|
||||
const completionMetrics = calculateCompletionScore(props)
|
||||
|
||||
return (
|
||||
<JSONPageRenderer
|
||||
schema={dashboardSchema as any}
|
||||
data={props}
|
||||
functions={{ calculateCompletionScore }}
|
||||
data={{ ...props, ...completionMetrics }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { DataSourceType } from '@/types/json-ui'
|
||||
import { Database, Function, File } from '@phosphor-icons/react'
|
||||
import { Database, File } from '@phosphor-icons/react'
|
||||
|
||||
interface DataSourceBadgeProps {
|
||||
type: DataSourceType
|
||||
@@ -13,11 +13,6 @@ const dataSourceConfig = {
|
||||
label: 'KV Storage',
|
||||
className: 'bg-accent/20 text-accent border-accent/30'
|
||||
},
|
||||
computed: {
|
||||
icon: Function,
|
||||
label: 'Computed',
|
||||
className: 'bg-primary/20 text-primary border-primary/30'
|
||||
},
|
||||
static: {
|
||||
icon: File,
|
||||
label: 'Static',
|
||||
|
||||
@@ -1,120 +1,116 @@
|
||||
export { AppLogo } from './AppLogo'
|
||||
export { TabIcon } from './TabIcon'
|
||||
export { StatusIcon } from './StatusIcon'
|
||||
export { ErrorBadge } from './ErrorBadge'
|
||||
export { IconWrapper } from './IconWrapper'
|
||||
export { LoadingSpinner } from './LoadingSpinner'
|
||||
export { EmptyStateIcon } from './EmptyStateIcon'
|
||||
export { TreeIcon } from './TreeIcon'
|
||||
export { FileIcon } from './FileIcon'
|
||||
export { ActionIcon } from './ActionIcon'
|
||||
export { SeedDataStatus } from './SeedDataStatus'
|
||||
// Auto-generated exports - DO NOT EDIT MANUALLY
|
||||
export { ActionButton } from './ActionButton'
|
||||
export { IconButton } from './IconButton'
|
||||
export { DataList } from './DataList'
|
||||
export { StatusBadge } from './StatusBadge'
|
||||
export { Text } from './Text'
|
||||
export { Heading } from './Heading'
|
||||
export { List } from './List'
|
||||
export { Grid } from './Grid'
|
||||
export { DataSourceBadge } from './DataSourceBadge'
|
||||
export { BindingIndicator } from './BindingIndicator'
|
||||
export { StatCard } from './StatCard'
|
||||
export { LoadingState } from './LoadingState'
|
||||
export { EmptyState } from './EmptyState'
|
||||
export { DetailRow } from './DetailRow'
|
||||
export { CompletionCard } from './CompletionCard'
|
||||
export { TipsCard } from './TipsCard'
|
||||
export { CountBadge } from './CountBadge'
|
||||
export { ConfirmButton } from './ConfirmButton'
|
||||
export { FilterInput } from './FilterInput'
|
||||
export { BasicPageHeader } from './PageHeader'
|
||||
export { MetricCard } from './MetricCard'
|
||||
|
||||
export { Link } from './Link'
|
||||
export { Divider } from './Divider'
|
||||
export { Avatar } from './Avatar'
|
||||
export { Chip } from './Chip'
|
||||
export { Code } from './Code'
|
||||
export { Kbd } from './Kbd'
|
||||
export { ProgressBar } from './ProgressBar'
|
||||
export { Skeleton } from './Skeleton'
|
||||
export { Tooltip } from './Tooltip'
|
||||
export { ActionCard } from './ActionCard'
|
||||
export { ActionIcon } from './ActionIcon'
|
||||
export { Alert } from './Alert'
|
||||
export { Spinner } from './Spinner'
|
||||
export { Dot } from './Dot'
|
||||
export { Image } from './Image'
|
||||
export { Label } from './Label'
|
||||
export { HelperText } from './HelperText'
|
||||
export { Container } from './Container'
|
||||
export { Section } from './Section'
|
||||
export { Stack } from './Stack'
|
||||
export { Spacer } from './Spacer'
|
||||
export { Timestamp } from './Timestamp'
|
||||
export { ScrollArea } from './ScrollArea'
|
||||
|
||||
export { Tag } from './Tag'
|
||||
export { Breadcrumb, BreadcrumbNav } from './Breadcrumb'
|
||||
export { IconText } from './IconText'
|
||||
export { TextArea } from './TextArea'
|
||||
export { Input } from './Input'
|
||||
export { Toggle } from './Toggle'
|
||||
export { RadioGroup } from './Radio'
|
||||
export { Checkbox } from './Checkbox'
|
||||
export { Slider } from './Slider'
|
||||
export { ColorSwatch } from './ColorSwatch'
|
||||
export { Stepper } from './Stepper'
|
||||
export { Rating } from './Rating'
|
||||
export { Timeline } from './Timeline'
|
||||
export { FileUpload } from './FileUpload'
|
||||
export { Popover } from './Popover'
|
||||
export { Tabs } from './Tabs'
|
||||
export { Menu } from './Menu'
|
||||
export { Accordion } from './Accordion'
|
||||
export { Card } from './Card'
|
||||
export { Notification } from './Notification'
|
||||
export { CopyButton } from './CopyButton'
|
||||
export { PasswordInput } from './PasswordInput'
|
||||
export { BasicSearchInput } from './SearchInput'
|
||||
export { Select } from './Select'
|
||||
export { Modal } from './Modal'
|
||||
export { Drawer } from './Drawer'
|
||||
export { Table } from './Table'
|
||||
|
||||
export { Button } from './Button'
|
||||
export { AppLogo } from './AppLogo'
|
||||
export { Avatar } from './Avatar'
|
||||
export { AvatarGroup } from './AvatarGroup'
|
||||
export { Badge } from './Badge'
|
||||
export { Switch } from './Switch'
|
||||
export { Separator } from './Separator'
|
||||
export { HoverCard } from './HoverCard'
|
||||
export { Calendar } from './Calendar'
|
||||
export { BindingIndicator } from './BindingIndicator'
|
||||
export { BreadcrumbNav as Breadcrumb, BreadcrumbNav } from './Breadcrumb'
|
||||
export { Button } from './Button'
|
||||
export { ButtonGroup } from './ButtonGroup'
|
||||
export { Calendar } from './Calendar'
|
||||
export { Card } from './Card'
|
||||
export { Checkbox } from './Checkbox'
|
||||
export { Chip } from './Chip'
|
||||
export { CircularProgress } from './CircularProgress'
|
||||
export { Code } from './Code'
|
||||
export { ColorSwatch } from './ColorSwatch'
|
||||
export { CommandPalette } from './CommandPalette'
|
||||
export { CompletionCard } from './CompletionCard'
|
||||
export { ConfirmButton } from './ConfirmButton'
|
||||
export { Container } from './Container'
|
||||
export { ContextMenu } from './ContextMenu'
|
||||
export type { ContextMenuItemType } from './ContextMenu'
|
||||
export { CopyButton } from './CopyButton'
|
||||
export { CountBadge } from './CountBadge'
|
||||
export { DataList } from './DataList'
|
||||
export { DataSourceBadge } from './DataSourceBadge'
|
||||
export { DataTable } from './DataTable'
|
||||
export type { Column } from './DataTable'
|
||||
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from './Form'
|
||||
export { DatePicker } from './DatePicker'
|
||||
export { RangeSlider } from './RangeSlider'
|
||||
export { InfoPanel } from './InfoPanel'
|
||||
export { ResponsiveGrid } from './ResponsiveGrid'
|
||||
export { DetailRow } from './DetailRow'
|
||||
export { Divider } from './Divider'
|
||||
export { Dot } from './Dot'
|
||||
export { Drawer } from './Drawer'
|
||||
export { EmptyMessage } from './EmptyMessage'
|
||||
export { EmptyState } from './EmptyState'
|
||||
export { EmptyStateIcon } from './EmptyStateIcon'
|
||||
export { ErrorBadge } from './ErrorBadge'
|
||||
export { FileIcon } from './FileIcon'
|
||||
export { FileUpload } from './FileUpload'
|
||||
export { FilterInput } from './FilterInput'
|
||||
export { Flex } from './Flex'
|
||||
export { CircularProgress } from './CircularProgress'
|
||||
export { AvatarGroup } from './AvatarGroup'
|
||||
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from './Form'
|
||||
export { GlowCard } from './GlowCard'
|
||||
export { Grid } from './Grid'
|
||||
export { Heading } from './Heading'
|
||||
export { HelperText } from './HelperText'
|
||||
export { HoverCard } from './HoverCard'
|
||||
export { IconButton } from './IconButton'
|
||||
export { IconText } from './IconText'
|
||||
export { IconWrapper } from './IconWrapper'
|
||||
export { Image } from './Image'
|
||||
export { InfoBox } from './InfoBox'
|
||||
export { InfoPanel } from './InfoPanel'
|
||||
export { Input } from './Input'
|
||||
export { Kbd } from './Kbd'
|
||||
export { KeyValue } from './KeyValue'
|
||||
export { Label } from './Label'
|
||||
export { Link } from './Link'
|
||||
export { List } from './List'
|
||||
export { ListItem } from './ListItem'
|
||||
export { LiveIndicator } from './LiveIndicator'
|
||||
export { LoadingSpinner } from './LoadingSpinner'
|
||||
export { LoadingState } from './LoadingState'
|
||||
export { Menu } from './Menu'
|
||||
export { MetricCard } from './MetricCard'
|
||||
export { MetricDisplay } from './MetricDisplay'
|
||||
export { Modal } from './Modal'
|
||||
export { Notification } from './Notification'
|
||||
export { NumberInput } from './NumberInput'
|
||||
export { TextGradient } from './TextGradient'
|
||||
export { BasicPageHeader as PageHeader } from './PageHeader'
|
||||
export { PanelHeader } from './PanelHeader'
|
||||
export { PasswordInput } from './PasswordInput'
|
||||
export { Popover } from './Popover'
|
||||
export { ProgressBar } from './ProgressBar'
|
||||
export { Pulse } from './Pulse'
|
||||
export { QuickActionButton } from './QuickActionButton'
|
||||
export { PanelHeader } from './PanelHeader'
|
||||
export { LiveIndicator } from './LiveIndicator'
|
||||
export { RadioGroup as Radio, RadioGroup } from './Radio'
|
||||
export { RangeSlider } from './RangeSlider'
|
||||
export { Rating } from './Rating'
|
||||
export { ResponsiveGrid } from './ResponsiveGrid'
|
||||
export { ScrollArea } from './ScrollArea'
|
||||
export { BasicSearchInput as SearchInput, BasicSearchInput } from './SearchInput'
|
||||
export { Section } from './Section'
|
||||
export { SeedDataStatus } from './SeedDataStatus'
|
||||
export { Select } from './Select'
|
||||
export { Separator } from './Separator'
|
||||
export { Skeleton } from './Skeleton'
|
||||
export { Slider } from './Slider'
|
||||
export { Spacer } from './Spacer'
|
||||
export { Sparkle } from './Sparkle'
|
||||
export { GlowCard } from './GlowCard'
|
||||
|
||||
export { TextHighlight } from './TextHighlight'
|
||||
export { ActionCard } from './ActionCard'
|
||||
export { InfoBox } from './InfoBox'
|
||||
export { ListItem } from './ListItem'
|
||||
export { MetricDisplay } from './MetricDisplay'
|
||||
export { KeyValue } from './KeyValue'
|
||||
export { EmptyMessage } from './EmptyMessage'
|
||||
export { Spinner } from './Spinner'
|
||||
export { Stack } from './Stack'
|
||||
export { StatCard } from './StatCard'
|
||||
export { StatusBadge } from './StatusBadge'
|
||||
export { StatusIcon } from './StatusIcon'
|
||||
export { StepIndicator } from './StepIndicator'
|
||||
export { Stepper } from './Stepper'
|
||||
export { Switch } from './Switch'
|
||||
export { TabIcon } from './TabIcon'
|
||||
export { Table } from './Table'
|
||||
export { Tabs } from './Tabs'
|
||||
export { Tag } from './Tag'
|
||||
export { Text } from './Text'
|
||||
export { TextArea } from './TextArea'
|
||||
export { TextGradient } from './TextGradient'
|
||||
export { TextHighlight } from './TextHighlight'
|
||||
export { Timeline } from './Timeline'
|
||||
export { Timestamp } from './Timestamp'
|
||||
export { TipsCard } from './TipsCard'
|
||||
export { Toggle } from './Toggle'
|
||||
export { Tooltip } from './Tooltip'
|
||||
export { TreeIcon } from './TreeIcon'
|
||||
|
||||
120
src/components/atoms/index.ts.backup
Normal file
120
src/components/atoms/index.ts.backup
Normal file
@@ -0,0 +1,120 @@
|
||||
export { AppLogo } from './AppLogo'
|
||||
export { TabIcon } from './TabIcon'
|
||||
export { StatusIcon } from './StatusIcon'
|
||||
export { ErrorBadge } from './ErrorBadge'
|
||||
export { IconWrapper } from './IconWrapper'
|
||||
export { LoadingSpinner } from './LoadingSpinner'
|
||||
export { EmptyStateIcon } from './EmptyStateIcon'
|
||||
export { TreeIcon } from './TreeIcon'
|
||||
export { FileIcon } from './FileIcon'
|
||||
export { ActionIcon } from './ActionIcon'
|
||||
export { SeedDataStatus } from './SeedDataStatus'
|
||||
export { ActionButton } from './ActionButton'
|
||||
export { IconButton } from './IconButton'
|
||||
export { DataList } from './DataList'
|
||||
export { StatusBadge } from './StatusBadge'
|
||||
export { Text } from './Text'
|
||||
export { Heading } from './Heading'
|
||||
export { List } from './List'
|
||||
export { Grid } from './Grid'
|
||||
export { DataSourceBadge } from './DataSourceBadge'
|
||||
export { BindingIndicator } from './BindingIndicator'
|
||||
export { StatCard } from './StatCard'
|
||||
export { LoadingState } from './LoadingState'
|
||||
export { EmptyState } from './EmptyState'
|
||||
export { DetailRow } from './DetailRow'
|
||||
export { CompletionCard } from './CompletionCard'
|
||||
export { TipsCard } from './TipsCard'
|
||||
export { CountBadge } from './CountBadge'
|
||||
export { ConfirmButton } from './ConfirmButton'
|
||||
export { FilterInput } from './FilterInput'
|
||||
export { BasicPageHeader } from './PageHeader'
|
||||
export { MetricCard } from './MetricCard'
|
||||
|
||||
export { Link } from './Link'
|
||||
export { Divider } from './Divider'
|
||||
export { Avatar } from './Avatar'
|
||||
export { Chip } from './Chip'
|
||||
export { Code } from './Code'
|
||||
export { Kbd } from './Kbd'
|
||||
export { ProgressBar } from './ProgressBar'
|
||||
export { Skeleton } from './Skeleton'
|
||||
export { Tooltip } from './Tooltip'
|
||||
export { Alert } from './Alert'
|
||||
export { Spinner } from './Spinner'
|
||||
export { Dot } from './Dot'
|
||||
export { Image } from './Image'
|
||||
export { Label } from './Label'
|
||||
export { HelperText } from './HelperText'
|
||||
export { Container } from './Container'
|
||||
export { Section } from './Section'
|
||||
export { Stack } from './Stack'
|
||||
export { Spacer } from './Spacer'
|
||||
export { Timestamp } from './Timestamp'
|
||||
export { ScrollArea } from './ScrollArea'
|
||||
|
||||
export { Tag } from './Tag'
|
||||
export { Breadcrumb, BreadcrumbNav } from './Breadcrumb'
|
||||
export { IconText } from './IconText'
|
||||
export { TextArea } from './TextArea'
|
||||
export { Input } from './Input'
|
||||
export { Toggle } from './Toggle'
|
||||
export { RadioGroup } from './Radio'
|
||||
export { Checkbox } from './Checkbox'
|
||||
export { Slider } from './Slider'
|
||||
export { ColorSwatch } from './ColorSwatch'
|
||||
export { Stepper } from './Stepper'
|
||||
export { Rating } from './Rating'
|
||||
export { Timeline } from './Timeline'
|
||||
export { FileUpload } from './FileUpload'
|
||||
export { Popover } from './Popover'
|
||||
export { Tabs } from './Tabs'
|
||||
export { Menu } from './Menu'
|
||||
export { Accordion } from './Accordion'
|
||||
export { Card } from './Card'
|
||||
export { Notification } from './Notification'
|
||||
export { CopyButton } from './CopyButton'
|
||||
export { PasswordInput } from './PasswordInput'
|
||||
export { BasicSearchInput } from './SearchInput'
|
||||
export { Select } from './Select'
|
||||
export { Modal } from './Modal'
|
||||
export { Drawer } from './Drawer'
|
||||
export { Table } from './Table'
|
||||
|
||||
export { Button } from './Button'
|
||||
export { Badge } from './Badge'
|
||||
export { Switch } from './Switch'
|
||||
export { Separator } from './Separator'
|
||||
export { HoverCard } from './HoverCard'
|
||||
export { Calendar } from './Calendar'
|
||||
export { ButtonGroup } from './ButtonGroup'
|
||||
export { CommandPalette } from './CommandPalette'
|
||||
export { ContextMenu } from './ContextMenu'
|
||||
export type { ContextMenuItemType } from './ContextMenu'
|
||||
export { DataTable } from './DataTable'
|
||||
export type { Column } from './DataTable'
|
||||
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from './Form'
|
||||
export { DatePicker } from './DatePicker'
|
||||
export { RangeSlider } from './RangeSlider'
|
||||
export { InfoPanel } from './InfoPanel'
|
||||
export { ResponsiveGrid } from './ResponsiveGrid'
|
||||
export { Flex } from './Flex'
|
||||
export { CircularProgress } from './CircularProgress'
|
||||
export { AvatarGroup } from './AvatarGroup'
|
||||
export { NumberInput } from './NumberInput'
|
||||
export { TextGradient } from './TextGradient'
|
||||
export { Pulse } from './Pulse'
|
||||
export { QuickActionButton } from './QuickActionButton'
|
||||
export { PanelHeader } from './PanelHeader'
|
||||
export { LiveIndicator } from './LiveIndicator'
|
||||
export { Sparkle } from './Sparkle'
|
||||
export { GlowCard } from './GlowCard'
|
||||
|
||||
export { TextHighlight } from './TextHighlight'
|
||||
export { ActionCard } from './ActionCard'
|
||||
export { InfoBox } from './InfoBox'
|
||||
export { ListItem } from './ListItem'
|
||||
export { MetricDisplay } from './MetricDisplay'
|
||||
export { KeyValue } from './KeyValue'
|
||||
export { EmptyMessage } from './EmptyMessage'
|
||||
export { StepIndicator } from './StepIndicator'
|
||||
@@ -108,7 +108,7 @@ function PageCard({ card, data, functions }: PageCardProps) {
|
||||
|
||||
if (card.type === 'gradient-card') {
|
||||
const computeFn = functions[card.dataSource?.compute]
|
||||
const computedData = computeFn ? computeFn(data) : {}
|
||||
const computedData = computeFn ? computeFn(data) : data
|
||||
|
||||
return (
|
||||
<Card className={cn('bg-gradient-to-br border-primary/20', card.gradient)}>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Button, Flex, Heading } from '@/components/atoms'
|
||||
|
||||
interface ActionBarProps {
|
||||
title?: string
|
||||
actions?: {
|
||||
label: string
|
||||
icon?: ReactNode
|
||||
onClick: () => void
|
||||
variant?: 'default' | 'outline' | 'ghost' | 'destructive'
|
||||
disabled?: boolean
|
||||
}[]
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ActionBar({ title, actions = [], children, className = '' }: ActionBarProps) {
|
||||
return (
|
||||
<Flex justify="between" align="center" gap="md" className={className}>
|
||||
{title && (
|
||||
<Heading level={2} className="text-xl font-semibold">
|
||||
{title}
|
||||
</Heading>
|
||||
)}
|
||||
{children}
|
||||
{actions.length > 0 && (
|
||||
<Flex gap="sm">
|
||||
{actions.map((action, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant={action.variant || 'default'}
|
||||
onClick={action.onClick}
|
||||
disabled={action.disabled}
|
||||
size="sm"
|
||||
leftIcon={action.icon}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { AppLogo, Stack, Heading, Text } from '@/components/atoms'
|
||||
|
||||
interface AppBrandingProps {
|
||||
title?: string
|
||||
subtitle?: string
|
||||
}
|
||||
|
||||
export function AppBranding({
|
||||
title = 'CodeForge',
|
||||
subtitle = 'Low-Code Next.js App Builder'
|
||||
}: AppBrandingProps) {
|
||||
return (
|
||||
<Stack direction="horizontal" align="center" spacing="sm" className="flex-1 min-w-0">
|
||||
<AppLogo />
|
||||
<Stack direction="vertical" spacing="none" className="min-w-[100px]">
|
||||
<Heading level={1} className="text-base sm:text-xl font-bold whitespace-nowrap">{title}</Heading>
|
||||
<Text variant="caption" className="hidden sm:block whitespace-nowrap">
|
||||
{subtitle}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { Card, Stack, Text, Heading, Skeleton, Flex, IconWrapper } from '@/components/atoms'
|
||||
|
||||
interface DataCardProps {
|
||||
title?: string
|
||||
value: string | number
|
||||
description?: string
|
||||
icon?: React.ReactNode
|
||||
trend?: {
|
||||
value: number
|
||||
label: string
|
||||
positive?: boolean
|
||||
}
|
||||
isLoading?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DataCard({
|
||||
title,
|
||||
value,
|
||||
description,
|
||||
icon,
|
||||
trend,
|
||||
isLoading = false,
|
||||
className = ''
|
||||
}: DataCardProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className={className}>
|
||||
<div className="pt-6 px-6 pb-6">
|
||||
<Stack spacing="sm">
|
||||
<Skeleton className="h-4 w-20" />
|
||||
<Skeleton className="h-8 w-16" />
|
||||
<Skeleton className="h-3 w-24" />
|
||||
</Stack>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
<div className="pt-6 px-6 pb-6">
|
||||
<Flex justify="between" align="start" gap="md">
|
||||
<Stack spacing="xs" className="flex-1">
|
||||
{title && (
|
||||
<Text variant="muted" className="font-medium">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
<Heading level={1} className="text-3xl font-bold">
|
||||
{value}
|
||||
</Heading>
|
||||
{description && (
|
||||
<Text variant="caption">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{trend && (
|
||||
<Text
|
||||
variant="small"
|
||||
className={trend.positive ? 'text-green-500' : 'text-red-500'}
|
||||
>
|
||||
{trend.positive ? '↑' : '↓'} {trend.value} {trend.label}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
{icon && (
|
||||
<IconWrapper icon={icon} size="lg" variant="muted" />
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Card, Badge, IconButton, Stack, Flex, Text } from '@/components/atoms'
|
||||
import { Card, IconButton, Stack, Flex, Text } from '@/components/atoms'
|
||||
import { DataSourceBadge } from '@/components/atoms/DataSourceBadge'
|
||||
import { DataSource } from '@/types/json-ui'
|
||||
import { Pencil, Trash, ArrowsDownUp } from '@phosphor-icons/react'
|
||||
import { Pencil, Trash } from '@phosphor-icons/react'
|
||||
|
||||
interface DataSourceCardProps {
|
||||
dataSource: DataSource
|
||||
@@ -11,13 +11,6 @@ interface DataSourceCardProps {
|
||||
}
|
||||
|
||||
export function DataSourceCard({ dataSource, dependents = [], onEdit, onDelete }: DataSourceCardProps) {
|
||||
const getDependencyCount = () => {
|
||||
if (dataSource.type === 'computed') {
|
||||
return dataSource.dependencies?.length || 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const renderTypeSpecificInfo = () => {
|
||||
if (dataSource.type === 'kv') {
|
||||
return (
|
||||
@@ -27,18 +20,6 @@ export function DataSourceCard({ dataSource, dependents = [], onEdit, onDelete }
|
||||
)
|
||||
}
|
||||
|
||||
if (dataSource.type === 'computed') {
|
||||
const depCount = getDependencyCount()
|
||||
return (
|
||||
<Flex align="center" gap="sm">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<ArrowsDownUp className="w-3 h-3 mr-1" />
|
||||
{depCount} {depCount === 1 ? 'dependency' : 'dependencies'}
|
||||
</Badge>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -59,7 +40,7 @@ export function DataSourceCard({ dataSource, dependents = [], onEdit, onDelete }
|
||||
{dependents.length > 0 && (
|
||||
<div className="pt-2 border-t border-border/50">
|
||||
<Text variant="caption">
|
||||
Used by {dependents.length} computed {dependents.length === 1 ? 'source' : 'sources'}
|
||||
Used by {dependents.length} dependent {dependents.length === 1 ? 'source' : 'sources'}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,14 +5,12 @@ import { DataSourceBadge } from '@/components/atoms/DataSourceBadge'
|
||||
import { DataSourceIdField } from '@/components/molecules/data-source-editor/DataSourceIdField'
|
||||
import { KvSourceFields } from '@/components/molecules/data-source-editor/KvSourceFields'
|
||||
import { StaticSourceFields } from '@/components/molecules/data-source-editor/StaticSourceFields'
|
||||
import { ComputedSourceFields } from '@/components/molecules/data-source-editor/ComputedSourceFields'
|
||||
import dataSourceEditorCopy from '@/data/data-source-editor-dialog.json'
|
||||
import { useDataSourceEditor } from '@/hooks/data/use-data-source-editor'
|
||||
|
||||
interface DataSourceEditorDialogProps {
|
||||
open: boolean
|
||||
dataSource: DataSource | null
|
||||
allDataSources: DataSource[]
|
||||
onOpenChange: (open: boolean) => void
|
||||
onSave: (dataSource: DataSource) => void
|
||||
}
|
||||
@@ -20,19 +18,13 @@ interface DataSourceEditorDialogProps {
|
||||
export function DataSourceEditorDialog({
|
||||
open,
|
||||
dataSource,
|
||||
allDataSources,
|
||||
onOpenChange,
|
||||
onSave,
|
||||
}: DataSourceEditorDialogProps) {
|
||||
const {
|
||||
editingSource,
|
||||
updateField,
|
||||
addDependency,
|
||||
removeDependency,
|
||||
availableDeps,
|
||||
selectedDeps,
|
||||
unselectedDeps,
|
||||
} = useDataSourceEditor(dataSource, allDataSources)
|
||||
} = useDataSourceEditor(dataSource)
|
||||
|
||||
const handleSave = () => {
|
||||
if (!editingSource) return
|
||||
@@ -80,18 +72,6 @@ export function DataSourceEditorDialog({
|
||||
/>
|
||||
)}
|
||||
|
||||
{editingSource.type === 'computed' && (
|
||||
<ComputedSourceFields
|
||||
editingSource={editingSource}
|
||||
availableDeps={availableDeps}
|
||||
selectedDeps={selectedDeps}
|
||||
unselectedDeps={unselectedDeps}
|
||||
copy={dataSourceEditorCopy.computed}
|
||||
onUpdateField={updateField}
|
||||
onAddDependency={addDependency}
|
||||
onRemoveDependency={removeDependency}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { EmptyStateIcon, Stack, Heading, Text } from '@/components/atoms'
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
description?: string
|
||||
action?: React.ReactNode
|
||||
}
|
||||
|
||||
export function EmptyState({ icon, title, description, action }: EmptyStateProps) {
|
||||
return (
|
||||
<Stack
|
||||
direction="vertical"
|
||||
align="center"
|
||||
justify="center"
|
||||
spacing="md"
|
||||
className="py-12 px-4 text-center"
|
||||
>
|
||||
<EmptyStateIcon icon={icon} />
|
||||
<Stack direction="vertical" spacing="sm">
|
||||
<Heading level={3} className="text-lg">{title}</Heading>
|
||||
{description && (
|
||||
<Text variant="muted" className="max-w-md">{description}</Text>
|
||||
)}
|
||||
</Stack>
|
||||
{action && <div className="mt-2">{action}</div>}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Badge, Flex, Text } from '@/components/atoms'
|
||||
|
||||
interface LabelWithBadgeProps {
|
||||
label: string
|
||||
badge?: number | string
|
||||
badgeVariant?: 'default' | 'secondary' | 'destructive' | 'outline'
|
||||
}
|
||||
|
||||
export function LabelWithBadge({
|
||||
label,
|
||||
badge,
|
||||
badgeVariant = 'secondary'
|
||||
}: LabelWithBadgeProps) {
|
||||
return (
|
||||
<Flex align="center" gap="sm">
|
||||
<Text variant="small" className="font-medium">{label}</Text>
|
||||
{badge !== undefined && (
|
||||
<Badge variant={badgeVariant} className="text-xs">
|
||||
{badge}
|
||||
</Badge>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { LoadingSpinner } from '@/components/atoms'
|
||||
|
||||
interface LoadingFallbackProps {
|
||||
message?: string
|
||||
}
|
||||
|
||||
export function LoadingFallback({ message = 'Loading...' }: LoadingFallbackProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<LoadingSpinner />
|
||||
<p className="text-sm text-muted-foreground">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { LoadingSpinner } from '@/components/atoms'
|
||||
|
||||
interface LoadingStateProps {
|
||||
message?: string
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
export function LoadingState({ message = 'Loading...', size = 'md' }: LoadingStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-3 py-12">
|
||||
<LoadingSpinner size={size} />
|
||||
<p className="text-sm text-muted-foreground">{message}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Card, IconWrapper, Stack, Text } from '@/components/atoms'
|
||||
|
||||
interface StatCardProps {
|
||||
icon: React.ReactNode
|
||||
label: string
|
||||
value: string | number
|
||||
variant?: 'default' | 'primary' | 'destructive'
|
||||
}
|
||||
|
||||
export function StatCard({ icon, label, value, variant = 'default' }: StatCardProps) {
|
||||
const variantClasses = {
|
||||
default: 'border-border',
|
||||
primary: 'border-primary/50 bg-primary/5',
|
||||
destructive: 'border-destructive/50 bg-destructive/5',
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={`p-4 ${variantClasses[variant]}`}>
|
||||
<Stack direction="horizontal" align="center" spacing="md">
|
||||
<IconWrapper
|
||||
icon={icon}
|
||||
size="lg"
|
||||
variant={variant === 'default' ? 'muted' : variant}
|
||||
/>
|
||||
<Stack direction="vertical" spacing="xs" className="flex-1">
|
||||
<Text variant="caption">{label}</Text>
|
||||
<Text className="text-2xl font-bold">{value}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Card, Badge, ActionIcon, IconButton, Stack, Flex, Text, Heading } from '@/components/atoms'
|
||||
import { ComponentTree } from '@/types/project'
|
||||
|
||||
interface TreeCardProps {
|
||||
tree: ComponentTree
|
||||
isSelected: boolean
|
||||
onSelect: () => void
|
||||
onEdit: () => void
|
||||
onDuplicate: () => void
|
||||
onDelete: () => void
|
||||
disableDelete?: boolean
|
||||
}
|
||||
|
||||
export function TreeCard({
|
||||
tree,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onDuplicate,
|
||||
onDelete,
|
||||
disableDelete = false,
|
||||
}: TreeCardProps) {
|
||||
return (
|
||||
<Card
|
||||
className={`cursor-pointer transition-all p-4 ${
|
||||
isSelected ? 'ring-2 ring-primary bg-accent' : 'hover:bg-accent/50'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<Stack spacing="sm">
|
||||
<Flex justify="between" align="start" gap="sm">
|
||||
<Stack spacing="xs" className="flex-1 min-w-0">
|
||||
<Heading level={4} className="text-sm truncate">{tree.name}</Heading>
|
||||
{tree.description && (
|
||||
<Text variant="caption" className="line-clamp-2">
|
||||
{tree.description}
|
||||
</Text>
|
||||
)}
|
||||
<div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{tree.rootNodes.length} components
|
||||
</Badge>
|
||||
</div>
|
||||
</Stack>
|
||||
</Flex>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Flex gap="xs" className="mt-1">
|
||||
<IconButton
|
||||
icon={<ActionIcon action="edit" size={14} />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onEdit}
|
||||
title="Edit tree"
|
||||
/>
|
||||
<IconButton
|
||||
icon={<ActionIcon action="copy" size={14} />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDuplicate}
|
||||
title="Duplicate tree"
|
||||
/>
|
||||
<IconButton
|
||||
icon={<ActionIcon action="delete" size={14} />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDelete}
|
||||
disabled={disableDelete}
|
||||
title="Delete tree"
|
||||
/>
|
||||
</Flex>
|
||||
</div>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Button, TreeIcon, ActionIcon, Flex, Heading, Stack, IconButton } from '@/components/atoms'
|
||||
|
||||
interface TreeListHeaderProps {
|
||||
onCreateNew: () => void
|
||||
onImportJson: () => void
|
||||
onExportJson: () => void
|
||||
hasSelectedTree?: boolean
|
||||
}
|
||||
|
||||
export function TreeListHeader({
|
||||
onCreateNew,
|
||||
onImportJson,
|
||||
onExportJson,
|
||||
hasSelectedTree = false,
|
||||
}: TreeListHeaderProps) {
|
||||
return (
|
||||
<Stack spacing="sm">
|
||||
<Flex justify="between" align="center">
|
||||
<Flex align="center" gap="sm">
|
||||
<TreeIcon size={20} />
|
||||
<Heading level={2} className="text-lg font-semibold">Component Trees</Heading>
|
||||
</Flex>
|
||||
<IconButton
|
||||
icon={<ActionIcon action="add" size={16} />}
|
||||
size="sm"
|
||||
onClick={onCreateNew}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="sm">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onImportJson}
|
||||
className="flex-1 text-xs"
|
||||
leftIcon={<ActionIcon action="upload" size={14} />}
|
||||
>
|
||||
Import JSON
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onExportJson}
|
||||
disabled={!hasSelectedTree}
|
||||
className="flex-1 text-xs"
|
||||
leftIcon={<ActionIcon action="download" size={14} />}
|
||||
>
|
||||
Export JSON
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { DataSource } from '@/types/json-ui'
|
||||
import { X } from '@phosphor-icons/react'
|
||||
|
||||
interface ComputedSourceFieldsCopy {
|
||||
expressionLabel: string
|
||||
expressionPlaceholder: string
|
||||
expressionHelp: string
|
||||
valueTemplateLabel: string
|
||||
valueTemplatePlaceholder: string
|
||||
valueTemplateHelp: string
|
||||
dependenciesLabel: string
|
||||
availableSourcesLabel: string
|
||||
emptyDependencies: string
|
||||
}
|
||||
|
||||
interface ComputedSourceFieldsProps {
|
||||
editingSource: DataSource
|
||||
availableDeps: DataSource[]
|
||||
selectedDeps: string[]
|
||||
unselectedDeps: DataSource[]
|
||||
copy: ComputedSourceFieldsCopy
|
||||
onUpdateField: <K extends keyof DataSource>(field: K, value: DataSource[K]) => void
|
||||
onAddDependency: (depId: string) => void
|
||||
onRemoveDependency: (depId: string) => void
|
||||
}
|
||||
|
||||
export function ComputedSourceFields({
|
||||
editingSource,
|
||||
availableDeps,
|
||||
selectedDeps,
|
||||
unselectedDeps,
|
||||
copy,
|
||||
onUpdateField,
|
||||
onAddDependency,
|
||||
onRemoveDependency,
|
||||
}: ComputedSourceFieldsProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>{copy.expressionLabel}</Label>
|
||||
<Textarea
|
||||
value={editingSource.expression || ''}
|
||||
onChange={(e) => {
|
||||
onUpdateField('expression', e.target.value)
|
||||
}}
|
||||
placeholder={copy.expressionPlaceholder}
|
||||
className="font-mono text-sm h-24"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{copy.expressionHelp}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{copy.valueTemplateLabel}</Label>
|
||||
<Textarea
|
||||
value={editingSource.valueTemplate ? JSON.stringify(editingSource.valueTemplate, null, 2) : ''}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
const template = JSON.parse(e.target.value)
|
||||
onUpdateField('valueTemplate', template)
|
||||
} catch (err) {
|
||||
// Invalid JSON
|
||||
}
|
||||
}}
|
||||
placeholder={copy.valueTemplatePlaceholder}
|
||||
className="font-mono text-sm h-24"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{copy.valueTemplateHelp}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{copy.dependenciesLabel}</Label>
|
||||
|
||||
{selectedDeps.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 p-3 bg-muted/30 rounded border border-border">
|
||||
{selectedDeps.map(depId => (
|
||||
<Badge
|
||||
key={depId}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{depId}
|
||||
<button
|
||||
onClick={() => onRemoveDependency(depId)}
|
||||
className="ml-1 hover:text-destructive"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{unselectedDeps.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">{copy.availableSourcesLabel}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{unselectedDeps.map(ds => (
|
||||
<Button
|
||||
key={ds.id}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onAddDependency(ds.id)}
|
||||
className="h-7 text-xs"
|
||||
>
|
||||
+ {ds.id}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{availableDeps.length === 0 && selectedDeps.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{copy.emptyDependencies}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,39 +1,20 @@
|
||||
export { AppBranding } from './AppBranding'
|
||||
export { Breadcrumb } from './Breadcrumb'
|
||||
export { CanvasRenderer } from './CanvasRenderer'
|
||||
export { CodeExplanationDialog } from './CodeExplanationDialog'
|
||||
export { ComponentPalette } from './ComponentPalette'
|
||||
export { ComponentTree } from './ComponentTree'
|
||||
export { EditorActions } from './EditorActions'
|
||||
export { EditorToolbar } from './EditorToolbar'
|
||||
export { EmptyEditorState } from './EmptyEditorState'
|
||||
export { FileTabs } from './FileTabs'
|
||||
export { GitHubBuildStatus } from './GitHubBuildStatus'
|
||||
export { LabelWithBadge } from './LabelWithBadge'
|
||||
export { LazyInlineMonacoEditor } from './LazyInlineMonacoEditor'
|
||||
export { LazyMonacoEditor, preloadMonacoEditor } from './LazyMonacoEditor'
|
||||
export { LazyLineChart } from './LazyLineChart'
|
||||
export { LazyBarChart } from './LazyBarChart'
|
||||
export { LazyD3BarChart } from './LazyD3BarChart'
|
||||
export { StorageSettings } from './StorageSettings'
|
||||
export { LoadingFallback } from './LoadingFallback'
|
||||
export { MonacoEditorPanel } from './MonacoEditorPanel'
|
||||
export { NavigationGroupHeader } from './NavigationGroupHeader'
|
||||
export { NavigationItem } from './NavigationItem'
|
||||
export { PageHeaderContent } from './PageHeaderContent'
|
||||
export { PropertyEditor } from './PropertyEditor'
|
||||
export { SaveIndicator } from './SaveIndicator'
|
||||
export { SeedDataManager } from './SeedDataManager'
|
||||
export { SearchBar } from './SearchBar'
|
||||
export { StatCard } from './StatCard'
|
||||
export { ToolbarButton } from './ToolbarButton'
|
||||
export { TreeCard } from './TreeCard'
|
||||
export { TreeFormDialog } from './TreeFormDialog'
|
||||
export { TreeListHeader } from './TreeListHeader'
|
||||
export { DataCard } from './DataCard'
|
||||
export { SearchInput } from './SearchInput'
|
||||
export { ActionBar } from './ActionBar'
|
||||
export { DataSourceCard } from './DataSourceCard'
|
||||
export { BindingEditor } from './BindingEditor'
|
||||
export { DataSourceEditorDialog } from './DataSourceEditorDialog'
|
||||
export { ComponentBindingDialog } from './ComponentBindingDialog'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
import { DataSourceEditorDialog } from '@/components/molecules/DataSourceEditorDialog'
|
||||
import { useDataSourceManager } from '@/hooks/data/use-data-source-manager'
|
||||
import { DataSource, DataSourceType } from '@/types/json-ui'
|
||||
import { Database, Function, FileText } from '@phosphor-icons/react'
|
||||
import { Database, FileText } from '@phosphor-icons/react'
|
||||
import { toast } from 'sonner'
|
||||
import { EmptyState, Stack } from '@/components/atoms'
|
||||
import { DataSourceManagerHeader } from '@/components/organisms/data-source-manager/DataSourceManagerHeader'
|
||||
@@ -66,7 +66,6 @@ export function DataSourceManager({ dataSources, onChange }: DataSourceManagerPr
|
||||
|
||||
const groupedSources = {
|
||||
kv: localSources.filter(ds => ds.type === 'kv'),
|
||||
computed: localSources.filter(ds => ds.type === 'computed'),
|
||||
static: localSources.filter(ds => ds.type === 'static'),
|
||||
}
|
||||
|
||||
@@ -110,15 +109,6 @@ export function DataSourceManager({ dataSources, onChange }: DataSourceManagerPr
|
||||
onEdit={handleEditSource}
|
||||
onDelete={handleDeleteSource}
|
||||
/>
|
||||
|
||||
<DataSourceGroupSection
|
||||
icon={<Function size={16} />}
|
||||
label={dataSourceManagerCopy.groups.computed}
|
||||
dataSources={groupedSources.computed}
|
||||
getDependents={getDependents}
|
||||
onEdit={handleEditSource}
|
||||
onDelete={handleDeleteSource}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -127,7 +117,6 @@ export function DataSourceManager({ dataSources, onChange }: DataSourceManagerPr
|
||||
<DataSourceEditorDialog
|
||||
open={dialogOpen}
|
||||
dataSource={editingSource}
|
||||
allDataSources={localSources}
|
||||
onOpenChange={setDialogOpen}
|
||||
onSave={handleSaveSource}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { ActionButton, Heading, Stack, Text } from '@/components/atoms'
|
||||
import { Plus, Database, Function, FileText } from '@phosphor-icons/react'
|
||||
import { Plus, Database, FileText } from '@phosphor-icons/react'
|
||||
import { DataSourceType } from '@/types/json-ui'
|
||||
|
||||
interface DataSourceManagerHeaderCopy {
|
||||
@@ -14,7 +14,6 @@ interface DataSourceManagerHeaderCopy {
|
||||
addLabel: string
|
||||
menu: {
|
||||
kv: string
|
||||
computed: string
|
||||
static: string
|
||||
}
|
||||
}
|
||||
@@ -49,10 +48,6 @@ export function DataSourceManagerHeader({ copy, onAdd }: DataSourceManagerHeader
|
||||
<Database className="w-4 h-4 mr-2" />
|
||||
{copy.menu.kv}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onAdd('computed')}>
|
||||
<Function className="w-4 h-4 mr-2" />
|
||||
{copy.menu.computed}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onAdd('static')}>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
{copy.menu.static}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
export { NavigationMenu } from './NavigationMenu'
|
||||
export { PageHeader } from './PageHeader'
|
||||
export { ToolbarActions } from './ToolbarActions'
|
||||
export { AppHeader } from './AppHeader'
|
||||
export { DataSourceManager } from './DataSourceManager'
|
||||
export { TreeListPanel } from './TreeListPanel'
|
||||
export { SchemaEditorToolbar } from './SchemaEditorToolbar'
|
||||
export { SchemaEditorSidebar } from './SchemaEditorSidebar'
|
||||
export { SchemaEditorCanvas } from './SchemaEditorCanvas'
|
||||
export { SchemaEditorPropertiesPanel } from './SchemaEditorPropertiesPanel'
|
||||
export { SchemaEditorLayout } from './SchemaEditorLayout'
|
||||
export { EmptyCanvasState } from './EmptyCanvasState'
|
||||
export { SchemaEditorStatusBar } from './SchemaEditorStatusBar'
|
||||
export { SchemaCodeViewer } from './SchemaCodeViewer'
|
||||
export { JSONUIShowcase } from '../JSONUIShowcase'
|
||||
|
||||
@@ -37,13 +37,6 @@ export function useDataSource(source: DataSource) {
|
||||
loading: false,
|
||||
error: null,
|
||||
}
|
||||
case 'computed':
|
||||
return {
|
||||
data: source.defaultValue,
|
||||
setData: () => {},
|
||||
loading: false,
|
||||
error: null,
|
||||
}
|
||||
default:
|
||||
return {
|
||||
data: null,
|
||||
@@ -67,7 +60,7 @@ export function useDataSources(sources: DataSource[]) {
|
||||
|
||||
useEffect(() => {
|
||||
sources.forEach((source) => {
|
||||
if (source.type === 'static' || source.type === 'computed') {
|
||||
if (source.type === 'static') {
|
||||
updateData(source.id, source.defaultValue)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ export const ActionSchema = z.object({
|
||||
|
||||
export const DataSourceSchema = z.object({
|
||||
id: z.string(),
|
||||
type: z.enum(['kv', 'api', 'computed', 'static'], { message: 'Invalid data source type' }),
|
||||
type: z.enum(['kv', 'api', 'static'], { message: 'Invalid data source type' }),
|
||||
key: z.string().optional(),
|
||||
endpoint: z.string().optional(),
|
||||
transform: z.string().optional(),
|
||||
|
||||
15
src/config/pages/atoms/accordion.json
Normal file
15
src/config/pages/atoms/accordion.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"type": "Accordion",
|
||||
"jsonCompatible": false,
|
||||
"wrapperRequired": true,
|
||||
"load": {
|
||||
"path": "@/components/atoms/Accordion",
|
||||
"export": "Accordion"
|
||||
},
|
||||
"props": {
|
||||
"id": "> {"
|
||||
},
|
||||
"metadata": {
|
||||
"notes": "Contains hooks - needs wrapper"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/action-button.json
Normal file
6
src/config/pages/atoms/action-button.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Button",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/action-card.json
Normal file
6
src/config/pages/atoms/action-card.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Card, CardContent",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/action-icon.json
Normal file
4
src/config/pages/atoms/action-icon.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "ActionIcon",
|
||||
"props": {}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"type": "Alert",
|
||||
"props": {
|
||||
"variant": "default"
|
||||
}
|
||||
"props": {}
|
||||
}
|
||||
|
||||
4
src/config/pages/atoms/app-logo.json
Normal file
4
src/config/pages/atoms/app-logo.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "AppLogo",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/avatar-group.json
Normal file
4
src/config/pages/atoms/avatar-group.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "AvatarGroup",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/avatar.json
Normal file
4
src/config/pages/atoms/avatar.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Avatar",
|
||||
"props": {}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"type": "Badge",
|
||||
"props": {
|
||||
"variant": "default"
|
||||
}
|
||||
"type": "Badge as ShadcnBadge",
|
||||
"props": {}
|
||||
}
|
||||
|
||||
4
src/config/pages/atoms/binding-indicator.json
Normal file
4
src/config/pages/atoms/binding-indicator.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Tooltip, TooltipContent, TooltipProvider, TooltipTrigger",
|
||||
"props": {}
|
||||
}
|
||||
6
src/config/pages/atoms/breadcrumb.json
Normal file
6
src/config/pages/atoms/breadcrumb.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Breadcrumb",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/button-group.json
Normal file
4
src/config/pages/atoms/button-group.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "ButtonGroup",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/button.json
Normal file
4
src/config/pages/atoms/button.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Button as ShadcnButton, ButtonProps as ShadcnButtonProps",
|
||||
"props": {}
|
||||
}
|
||||
7
src/config/pages/atoms/calendar.json
Normal file
7
src/config/pages/atoms/calendar.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "Calendar as ShadcnCalendar",
|
||||
"props": {
|
||||
"onSelect": "> void",
|
||||
"disabled": "> boolean)"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"type": "Card"
|
||||
"type": "Card",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
|
||||
6
src/config/pages/atoms/checkbox.json
Normal file
6
src/config/pages/atoms/checkbox.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Checkbox",
|
||||
"props": {
|
||||
"onChange": "> void"
|
||||
}
|
||||
}
|
||||
7
src/config/pages/atoms/chip.json
Normal file
7
src/config/pages/atoms/chip.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "Chip",
|
||||
"props": {
|
||||
"12": "bold",
|
||||
"onRemove": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/circular-progress.json
Normal file
4
src/config/pages/atoms/circular-progress.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Progress",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/code.json
Normal file
4
src/config/pages/atoms/code.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Code",
|
||||
"props": {}
|
||||
}
|
||||
6
src/config/pages/atoms/color-swatch.json
Normal file
6
src/config/pages/atoms/color-swatch.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "ColorSwatch",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
7
src/config/pages/atoms/command-palette.json
Normal file
7
src/config/pages/atoms/command-palette.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "Command,\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,",
|
||||
"props": {
|
||||
"onSelect": "> void",
|
||||
"onOpenChange": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/completion-card.json
Normal file
4
src/config/pages/atoms/completion-card.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Card, CardContent, CardDescription, CardHeader, CardTitle",
|
||||
"props": {}
|
||||
}
|
||||
6
src/config/pages/atoms/component-palette-item.json
Normal file
6
src/config/pages/atoms/component-palette-item.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Card",
|
||||
"props": {
|
||||
"onDragStart": "> void"
|
||||
}
|
||||
}
|
||||
22
src/config/pages/atoms/component-tree-node.json
Normal file
22
src/config/pages/atoms/component-tree-node.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"type": "ComponentTreeNode",
|
||||
"jsonCompatible": false,
|
||||
"wrapperRequired": true,
|
||||
"load": {
|
||||
"path": "@/components/atoms/ComponentTreeNode",
|
||||
"export": "ComponentTreeNode"
|
||||
},
|
||||
"props": {
|
||||
"onSelect": "> void",
|
||||
"onHover": "> void",
|
||||
"onHoverEnd": "> void",
|
||||
"onDragStart": "> void",
|
||||
"onDragOver": "> void",
|
||||
"onDragLeave": "> void",
|
||||
"onDrop": "> void",
|
||||
"onToggleExpand": "> void"
|
||||
},
|
||||
"metadata": {
|
||||
"notes": "Complex logic - needs wrapper"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/confirm-button.json
Normal file
6
src/config/pages/atoms/confirm-button.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Button, ButtonProps",
|
||||
"props": {
|
||||
"onConfirm": "> void | Promise<void>"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/container.json
Normal file
4
src/config/pages/atoms/container.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Container",
|
||||
"props": {}
|
||||
}
|
||||
7
src/config/pages/atoms/context-menu.json
Normal file
7
src/config/pages/atoms/context-menu.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "ContextMenu as ShadcnContextMenu,\n ContextMenuContent,\n ContextMenuItem,\n ContextMenuTrigger,\n ContextMenuSeparator,\n ContextMenuSub,\n ContextMenuSubContent,\n ContextMenuSubTrigger,",
|
||||
"props": {
|
||||
"onSelect": "> void",
|
||||
"menuItems": "> {"
|
||||
}
|
||||
}
|
||||
13
src/config/pages/atoms/copy-button.json
Normal file
13
src/config/pages/atoms/copy-button.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "CopyButton",
|
||||
"jsonCompatible": false,
|
||||
"wrapperRequired": true,
|
||||
"load": {
|
||||
"path": "@/components/atoms/CopyButton",
|
||||
"export": "CopyButton"
|
||||
},
|
||||
"props": {},
|
||||
"metadata": {
|
||||
"notes": "Contains hooks - needs wrapper"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/count-badge.json
Normal file
4
src/config/pages/atoms/count-badge.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Badge",
|
||||
"props": {}
|
||||
}
|
||||
7
src/config/pages/atoms/data-list.json
Normal file
7
src/config/pages/atoms/data-list.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "DataList",
|
||||
"props": {
|
||||
"renderItem": "> ReactNode",
|
||||
"item": "> {"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/data-source-badge.json
Normal file
4
src/config/pages/atoms/data-source-badge.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Badge",
|
||||
"props": {}
|
||||
}
|
||||
7
src/config/pages/atoms/data-table.json
Normal file
7
src/config/pages/atoms/data-table.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,",
|
||||
"props": {
|
||||
"cell": "> ReactNode",
|
||||
"onRowClick": "> void"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/date-picker.json
Normal file
6
src/config/pages/atoms/date-picker.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Popover, PopoverContent, PopoverTrigger",
|
||||
"props": {
|
||||
"onChange": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/detail-row.json
Normal file
4
src/config/pages/atoms/detail-row.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Card, CardContent",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/divider.json
Normal file
4
src/config/pages/atoms/divider.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Divider",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/dot.json
Normal file
4
src/config/pages/atoms/dot.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Dot",
|
||||
"props": {}
|
||||
}
|
||||
9
src/config/pages/atoms/drawer.json
Normal file
9
src/config/pages/atoms/drawer.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "Drawer",
|
||||
"props": {
|
||||
"onClose": "> void",
|
||||
"sm": "== ",
|
||||
"md": "== ",
|
||||
"lg": "== "
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/empty-message.json
Normal file
6
src/config/pages/atoms/empty-message.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Button",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/empty-state-icon.json
Normal file
4
src/config/pages/atoms/empty-state-icon.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "EmptyStateIcon",
|
||||
"props": {}
|
||||
}
|
||||
6
src/config/pages/atoms/empty-state.json
Normal file
6
src/config/pages/atoms/empty-state.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Button",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/error-badge.json
Normal file
4
src/config/pages/atoms/error-badge.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Badge",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/file-icon.json
Normal file
4
src/config/pages/atoms/file-icon.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "FileIcon",
|
||||
"props": {}
|
||||
}
|
||||
18
src/config/pages/atoms/file-upload.json
Normal file
18
src/config/pages/atoms/file-upload.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "FileUpload",
|
||||
"jsonCompatible": false,
|
||||
"wrapperRequired": true,
|
||||
"load": {
|
||||
"path": "@/components/atoms/FileUpload",
|
||||
"export": "FileUpload"
|
||||
},
|
||||
"props": {
|
||||
"onFilesSelected": "> void",
|
||||
"files": "> {",
|
||||
"e": "> {",
|
||||
"index": "> {"
|
||||
},
|
||||
"metadata": {
|
||||
"notes": "Contains hooks - needs wrapper"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/filter-input.json
Normal file
6
src/config/pages/atoms/filter-input.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Input",
|
||||
"props": {
|
||||
"onChange": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/flex.json
Normal file
4
src/config/pages/atoms/flex.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Flex",
|
||||
"props": {}
|
||||
}
|
||||
6
src/config/pages/atoms/form.json
Normal file
6
src/config/pages/atoms/form.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Form as ShadcnForm,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,",
|
||||
"props": {
|
||||
"onSubmit": "> void | Promise<void>"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/glow-card.json
Normal file
6
src/config/pages/atoms/glow-card.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Card",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/grid.json
Normal file
4
src/config/pages/atoms/grid.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Grid",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/heading.json
Normal file
4
src/config/pages/atoms/heading.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Heading",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/helper-text.json
Normal file
4
src/config/pages/atoms/helper-text.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "HelperText",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/hover-card.json
Normal file
4
src/config/pages/atoms/hover-card.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "HoverCard as ShadcnHoverCard,\n HoverCardContent,\n HoverCardTrigger,",
|
||||
"props": {}
|
||||
}
|
||||
6
src/config/pages/atoms/icon-button.json
Normal file
6
src/config/pages/atoms/icon-button.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Button",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/icon-text.json
Normal file
4
src/config/pages/atoms/icon-text.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "IconText",
|
||||
"props": {}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user