mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 21:54:56 +00:00
Compare commits
11 Commits
copilot/re
...
b3fa462527
| Author | SHA1 | Date | |
|---|---|---|---|
| b3fa462527 | |||
| 2478948bcb | |||
| ff37033102 | |||
| e63d32eb05 | |||
| 5f92fbbf54 | |||
| bef28e8c91 | |||
| f69220e7e4 | |||
| 043eb427d3 | |||
| 3864fd247a | |||
| aa51074380 | |||
| cf74c35e0a |
@@ -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"
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -1,64 +1,153 @@
|
||||
import { PageRenderer } from '@/lib/json-ui/page-renderer'
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { FeatureToggles } from '@/types/project'
|
||||
import { useMemo } from 'react'
|
||||
import featureToggleSchema from '@/schemas/feature-toggle-settings.json'
|
||||
import type { PageSchema } from '@/types/json-ui'
|
||||
import { evaluateExpression } from '@/lib/json-ui/expression-evaluator'
|
||||
import {
|
||||
BookOpen,
|
||||
Code,
|
||||
Cube,
|
||||
Database,
|
||||
FileText,
|
||||
Flask,
|
||||
FlowArrow,
|
||||
Image,
|
||||
Lightbulb,
|
||||
PaintBrush,
|
||||
Play,
|
||||
Tree,
|
||||
Wrench,
|
||||
} from '@phosphor-icons/react'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import featureToggleSettings from '@/config/feature-toggle-settings.json'
|
||||
import type { ComponentType } from 'react'
|
||||
|
||||
interface FeatureToggleSettingsProps {
|
||||
features: FeatureToggles
|
||||
onFeaturesChange: (features: FeatureToggles) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* FeatureToggleSettings - Now JSON-driven!
|
||||
*
|
||||
* This component demonstrates how a complex React component with:
|
||||
* - Custom hooks and state management
|
||||
* - Dynamic data rendering (looping over features)
|
||||
* - Event handlers (toggle switches)
|
||||
* - Conditional styling (enabled/disabled states)
|
||||
*
|
||||
* Can be converted to a pure JSON schema with custom action handlers.
|
||||
* The JSON schema handles all UI structure, data binding, and loops,
|
||||
* while custom functions handle business logic.
|
||||
*
|
||||
* Converted from 153 lines of React/TSX to:
|
||||
* - 1 JSON schema file (195 lines, but mostly structure)
|
||||
* - 45 lines of integration code (this file)
|
||||
*
|
||||
* Benefits:
|
||||
* - UI structure is now data-driven and can be modified without code changes
|
||||
* - Feature list is in JSON and can be easily extended
|
||||
* - Styling and layout can be customized via JSON
|
||||
* - Business logic (toggle handler) stays in TypeScript for type safety
|
||||
*/
|
||||
export function FeatureToggleSettings({ features, onFeaturesChange }: FeatureToggleSettingsProps) {
|
||||
// Custom action handler - this is the "hook" that handles complex logic
|
||||
const handlers = useMemo(() => ({
|
||||
updateFeature: (action: any, eventData: any) => {
|
||||
// Evaluate the params to get the actual values
|
||||
const context = { data: { features, item: eventData.item }, event: eventData }
|
||||
|
||||
// The key param is an expression like "item.key" which needs evaluation
|
||||
const key = evaluateExpression(action.params.key, context) as keyof FeatureToggles
|
||||
const checked = eventData as boolean
|
||||
|
||||
onFeaturesChange({
|
||||
...features,
|
||||
[key]: checked,
|
||||
})
|
||||
}
|
||||
}), [features, onFeaturesChange])
|
||||
type FeatureToggleIconKey =
|
||||
| 'BookOpen'
|
||||
| 'Code'
|
||||
| 'Cube'
|
||||
| 'Database'
|
||||
| 'FileText'
|
||||
| 'Flask'
|
||||
| 'FlowArrow'
|
||||
| 'Image'
|
||||
| 'Lightbulb'
|
||||
| 'PaintBrush'
|
||||
| 'Play'
|
||||
| 'Tree'
|
||||
| 'Wrench'
|
||||
|
||||
// Pass features as external data to the JSON renderer
|
||||
const data = useMemo(() => ({ features }), [features])
|
||||
const iconMap: Record<FeatureToggleIconKey, ComponentType<{ size?: number; weight?: 'duotone' }>> = {
|
||||
BookOpen,
|
||||
Code,
|
||||
Cube,
|
||||
Database,
|
||||
FileText,
|
||||
Flask,
|
||||
FlowArrow,
|
||||
Image,
|
||||
Lightbulb,
|
||||
PaintBrush,
|
||||
Play,
|
||||
Tree,
|
||||
Wrench,
|
||||
}
|
||||
|
||||
type FeatureToggleItem = {
|
||||
key: keyof FeatureToggles
|
||||
label: string
|
||||
description: string
|
||||
icon: FeatureToggleIconKey
|
||||
}
|
||||
|
||||
const featuresList = featureToggleSettings as FeatureToggleItem[]
|
||||
|
||||
function FeatureToggleHeader({ enabledCount, totalCount }: { enabledCount: number; totalCount: number }) {
|
||||
return (
|
||||
<PageRenderer
|
||||
schema={featureToggleSchema as PageSchema}
|
||||
data={data}
|
||||
functions={handlers}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold mb-2">Feature Toggles</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Enable or disable features to customize your workspace. {enabledCount} of {totalCount} features enabled.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureToggleCard({
|
||||
item,
|
||||
enabled,
|
||||
onToggle,
|
||||
}: {
|
||||
item: FeatureToggleItem
|
||||
enabled: boolean
|
||||
onToggle: (value: boolean) => void
|
||||
}) {
|
||||
const Icon = iconMap[item.icon]
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg ${enabled ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}>
|
||||
<Icon size={20} weight="duotone" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base">{item.label}</CardTitle>
|
||||
<CardDescription className="text-xs mt-1">{item.description}</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<Switch id={item.key} checked={enabled} onCheckedChange={onToggle} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureToggleGrid({
|
||||
items,
|
||||
features,
|
||||
onToggle,
|
||||
}: {
|
||||
items: FeatureToggleItem[]
|
||||
features: FeatureToggles
|
||||
onToggle: (key: keyof FeatureToggles, value: boolean) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 pr-4">
|
||||
{items.map((item) => (
|
||||
<FeatureToggleCard
|
||||
key={item.key}
|
||||
item={item}
|
||||
enabled={features[item.key]}
|
||||
onToggle={(checked) => onToggle(item.key, checked)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FeatureToggleSettings({ features, onFeaturesChange }: FeatureToggleSettingsProps) {
|
||||
const handleToggle = (key: keyof FeatureToggles, value: boolean) => {
|
||||
onFeaturesChange({
|
||||
...features,
|
||||
[key]: value,
|
||||
})
|
||||
}
|
||||
|
||||
const enabledCount = Object.values(features).filter(Boolean).length
|
||||
const totalCount = Object.keys(features).length
|
||||
|
||||
return (
|
||||
<div className="h-full p-6 bg-background">
|
||||
<FeatureToggleHeader enabledCount={enabledCount} totalCount={totalCount} />
|
||||
|
||||
<ScrollArea className="h-[calc(100vh-200px)]">
|
||||
<FeatureToggleGrid items={featuresList} features={features} onToggle={handleToggle} />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -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,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,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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
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": {}
|
||||
}
|
||||
4
src/config/pages/atoms/icon-wrapper.json
Normal file
4
src/config/pages/atoms/icon-wrapper.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "IconWrapper",
|
||||
"props": {}
|
||||
}
|
||||
18
src/config/pages/atoms/image.json
Normal file
18
src/config/pages/atoms/image.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "Image",
|
||||
"jsonCompatible": false,
|
||||
"wrapperRequired": true,
|
||||
"load": {
|
||||
"path": "@/components/atoms/Image",
|
||||
"export": "Image"
|
||||
},
|
||||
"props": {
|
||||
"onLoad": "> void",
|
||||
"onError": "> void",
|
||||
"width": "== ",
|
||||
"height": "== "
|
||||
},
|
||||
"metadata": {
|
||||
"notes": "Contains hooks - needs wrapper"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/info-box.json
Normal file
4
src/config/pages/atoms/info-box.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "InfoBox",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/info-panel.json
Normal file
4
src/config/pages/atoms/info-panel.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "InfoPanel",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/input.json
Normal file
4
src/config/pages/atoms/input.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Input",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/kbd.json
Normal file
4
src/config/pages/atoms/kbd.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Kbd",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/key-value.json
Normal file
4
src/config/pages/atoms/key-value.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "KeyValue",
|
||||
"props": {}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"type": "Label"
|
||||
"type": "Label",
|
||||
"props": {}
|
||||
}
|
||||
|
||||
6
src/config/pages/atoms/link.json
Normal file
6
src/config/pages/atoms/link.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "Link",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/list-item.json
Normal file
6
src/config/pages/atoms/list-item.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "ListItem",
|
||||
"props": {
|
||||
"onClick": "> void"
|
||||
}
|
||||
}
|
||||
6
src/config/pages/atoms/list.json
Normal file
6
src/config/pages/atoms/list.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "List",
|
||||
"props": {
|
||||
"renderItem": "> ReactNode"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/live-indicator.json
Normal file
4
src/config/pages/atoms/live-indicator.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "LiveIndicator",
|
||||
"props": {}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"id": "loading-spinner",
|
||||
"type": "LoadingSpinner",
|
||||
"props": {
|
||||
"size": "md"
|
||||
}
|
||||
"props": {}
|
||||
}
|
||||
|
||||
4
src/config/pages/atoms/loading-state.json
Normal file
4
src/config/pages/atoms/loading-state.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "LoadingState",
|
||||
"props": {}
|
||||
}
|
||||
17
src/config/pages/atoms/menu.json
Normal file
17
src/config/pages/atoms/menu.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"type": "Menu",
|
||||
"jsonCompatible": false,
|
||||
"wrapperRequired": true,
|
||||
"load": {
|
||||
"path": "@/components/atoms/Menu",
|
||||
"export": "Menu"
|
||||
},
|
||||
"props": {
|
||||
"onClick": "> void",
|
||||
"event": "> {",
|
||||
"item": "> {"
|
||||
},
|
||||
"metadata": {
|
||||
"notes": "Contains hooks - needs wrapper"
|
||||
}
|
||||
}
|
||||
4
src/config/pages/atoms/metric-card.json
Normal file
4
src/config/pages/atoms/metric-card.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "Card, CardContent",
|
||||
"props": {}
|
||||
}
|
||||
4
src/config/pages/atoms/metric-display.json
Normal file
4
src/config/pages/atoms/metric-display.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "MetricDisplay",
|
||||
"props": {}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user