diff --git a/frontends/nextjs/src/components/rendering/PropertyInspector.tsx b/frontends/nextjs/src/components/rendering/PropertyInspector.tsx
index 801ac2f4c..baeced0a5 100644
--- a/frontends/nextjs/src/components/rendering/PropertyInspector.tsx
+++ b/frontends/nextjs/src/components/rendering/PropertyInspector.tsx
@@ -1,16 +1,11 @@
import { useState, useEffect } from 'react'
-import { ScrollArea } from '@/components/ui'
-import { Input } from '@/components/ui'
-import { Label } from '@/components/ui'
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui'
-import { Button } from '@/components/ui'
-import { Separator } from '@/components/ui'
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui'
-import type { ComponentInstance } from '@/lib/builder-types'
+import { Separator, Button } from '@/components/ui'
+import type { ComponentInstance } from '@/lib/types/builder-types'
import { componentCatalog } from '@/lib/component-catalog'
-import { Code, PaintBrush, Trash, Palette } from '@phosphor-icons/react'
+import { Trash } from '@phosphor-icons/react'
import { CssClassBuilder } from '@/components/CssClassBuilder'
import { Database, DropdownConfig } from '@/lib/database'
+import { PropertyPanels } from './components/PropertyPanels'
interface PropertyInspectorProps {
component: ComponentInstance | null
@@ -67,131 +62,14 @@ export function PropertyInspector({ component, onUpdate, onDelete, onCodeEdit }:
Component Properties
-
-
-
-
- Props
-
-
-
- Code
-
-
-
-
-
-
- {componentDef?.propSchema.map(propDef => {
- const dynamicDropdown = propDef.type === 'dynamic-select'
- ? dynamicDropdowns.find(d => d.name === propDef.dynamicSource)
- : null
-
- return (
-
-
-
- {propDef.name === 'className' ? (
-
-
handlePropChange(propDef.name, e.target.value)}
- className="flex-1 font-mono text-xs"
- />
-
-
- ) : propDef.type === 'string' ? (
-
handlePropChange(propDef.name, e.target.value)}
- />
- ) : propDef.type === 'number' ? (
-
handlePropChange(propDef.name, Number(e.target.value))}
- />
- ) : propDef.type === 'boolean' ? (
-
- ) : propDef.type === 'select' && propDef.options ? (
-
- ) : propDef.type === 'dynamic-select' && dynamicDropdown ? (
-
- ) : null}
-
- {propDef.description && (
-
{propDef.description}
- )}
-
- )
- })}
-
- {(!componentDef?.propSchema || componentDef.propSchema.length === 0) && (
-
This component has no configurable properties.
- )}
-
-
-
-
-
-
-
-
-
- Add custom JavaScript code for this component
-
-
-
-
-
-
+
diff --git a/frontends/nextjs/src/components/rendering/RenderComponent.tsx b/frontends/nextjs/src/components/rendering/RenderComponent.tsx
index 2cf420926..8b23fa0aa 100644
--- a/frontends/nextjs/src/components/rendering/RenderComponent.tsx
+++ b/frontends/nextjs/src/components/rendering/RenderComponent.tsx
@@ -1,32 +1,16 @@
-import type { ComponentInstance } from '@/lib/builder-types'
-import { Button } from '@/components/ui'
-import { Input } from '@/components/ui'
-import { Textarea } from '@/components/ui'
-import { Label } from '@/components/ui'
-import { Badge } from '@/components/ui'
-import { Card } from '@/components/ui'
-import { Switch } from '@/components/ui'
-import { Checkbox } from '@/components/ui'
-import { Separator } from '@/components/ui'
-import { Alert } from '@/components/ui'
-import { Progress } from '@/components/ui'
-import { Slider } from '@/components/ui'
-import { Avatar, AvatarFallback } from '@/components/ui'
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui'
-import { IRCWebchatDeclarative } from '@/components/IRCWebchatDeclarative'
-import { NotificationSummaryCard } from '@/components/NotificationSummaryCard'
+import type React from 'react'
+import type { ComponentInstance } from '@/lib/types/builder-types'
import type { User } from '@/lib/level-types'
-import { getDeclarativeRenderer } from '@/lib/declarative-component-renderer'
+import { RenderNode } from './components/RenderNode'
interface RenderComponentProps {
component: ComponentInstance
isSelected: boolean
onSelect: (id: string) => void
user?: User
- contextData?: Record
}
-export function RenderComponent({ component, isSelected, onSelect, user, contextData }: RenderComponentProps) {
+export function RenderComponent({ component, isSelected, onSelect, user }: RenderComponentProps) {
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
onSelect(component.id)
@@ -41,191 +25,19 @@ export function RenderComponent({ component, isSelected, onSelect, user, context
isSelected={isSelected}
onSelect={onSelect}
user={user}
- contextData={contextData}
/>
))
}
const wrapperClass = `relative ${isSelected ? 'ring-2 ring-accent ring-offset-2' : 'hover:ring-1 hover:ring-accent/50'} transition-all cursor-pointer`
- const renderComponentByType = () => {
- const { type, props } = component
- const renderer = getDeclarativeRenderer()
-
- if (renderer.hasComponentConfig(type)) {
- if (type === 'IRCWebchat' && user) {
- return (
-
- )
- }
-
- return (
-
- Declarative Component: {type}
-
- This is a package-defined component
-
-
- )
- }
-
- switch (type) {
- case 'Container':
- return (
-
- {renderChildren()}
-
- )
-
- case 'Flex':
- return (
-
- {renderChildren()}
-
- )
-
- case 'Grid':
- return (
-
- {renderChildren()}
-
- )
-
- case 'Stack':
- return (
-
- {renderChildren()}
-
- )
-
- case 'Card':
- return (
-
- {renderChildren()}
-
- )
-
- case 'NotificationSummary':
- return (
-
- )
-
- case 'Button':
- return (
-
- )
-
- case 'Input':
- return (
-
- )
-
- case 'Textarea':
- return (
-
- )
-
- case 'Label':
- return
-
- case 'Heading': {
- const level = props.level || '1'
- const className = props.className
- const text = props.children || 'Heading'
-
- if (level === '1') return {text}
- if (level === '2') return {text}
- if (level === '3') return {text}
- if (level === '4') return {text}
- return {text}
- }
-
- case 'Text':
- return (
-
- {props.children || 'Text'}
-
- )
-
- case 'Badge':
- return (
-
- {props.children || 'Badge'}
-
- )
-
- case 'Switch':
- return
-
- case 'Checkbox':
- return
-
- case 'Separator':
- return
-
- case 'Alert':
- return (
-
- {renderChildren()}
-
- )
-
- case 'Progress':
- return
-
- case 'Slider':
- return
-
- case 'Avatar':
- return (
-
- U
-
- )
-
- case 'Table':
- return (
-
-
-
- Column 1
- Column 2
-
-
-
-
- Data 1
- Data 2
-
-
-
- )
-
- default:
- return Unknown Component: {type}
- }
- }
-
return (
- {renderComponentByType()}
+
)
}
diff --git a/frontends/nextjs/src/components/rendering/components/FieldTypes.tsx b/frontends/nextjs/src/components/rendering/components/FieldTypes.tsx
new file mode 100644
index 000000000..2e53c0431
--- /dev/null
+++ b/frontends/nextjs/src/components/rendering/components/FieldTypes.tsx
@@ -0,0 +1,114 @@
+import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui'
+import type { PropDefinition } from '@/lib/types/builder-types'
+import type { DropdownConfig } from '@/lib/database'
+import { Palette } from '@phosphor-icons/react'
+
+interface FieldTypesProps {
+ propDef: PropDefinition
+ value: any
+ onChange: (value: any) => void
+ dynamicDropdown?: DropdownConfig | null
+ onOpenCssBuilder?: () => void
+}
+
+export function FieldTypes({ propDef, value, onChange, dynamicDropdown, onOpenCssBuilder }: FieldTypesProps) {
+ const renderInputByType = () => {
+ if (propDef.name === 'className') {
+ return (
+
+
onChange(e.target.value)}
+ className="flex-1 font-mono text-xs"
+ />
+ {onOpenCssBuilder && (
+
+ )}
+
+ )
+ }
+
+ switch (propDef.type) {
+ case 'string':
+ return (
+ onChange(e.target.value)}
+ />
+ )
+ case 'number':
+ return (
+ onChange(Number(e.target.value))}
+ />
+ )
+ case 'boolean':
+ return (
+
+ )
+ case 'select':
+ return (
+
+ )
+ case 'dynamic-select':
+ return (
+
+ )
+ default:
+ return null
+ }
+ }
+
+ return (
+
+
+ {renderInputByType()}
+ {propDef.description && (
+
{propDef.description}
+ )}
+
+ )
+}
diff --git a/frontends/nextjs/src/components/rendering/components/PropertyPanels.tsx b/frontends/nextjs/src/components/rendering/components/PropertyPanels.tsx
new file mode 100644
index 000000000..f7452d992
--- /dev/null
+++ b/frontends/nextjs/src/components/rendering/components/PropertyPanels.tsx
@@ -0,0 +1,80 @@
+import { ScrollArea, Tabs, TabsContent, TabsList, TabsTrigger, Button } from '@/components/ui'
+import type { ComponentDefinition, ComponentInstance } from '@/lib/types/builder-types'
+import type { DropdownConfig } from '@/lib/database'
+import { Code, PaintBrush } from '@phosphor-icons/react'
+import { FieldTypes } from './FieldTypes'
+
+interface PropertyPanelsProps {
+ component: ComponentInstance
+ componentDef?: ComponentDefinition
+ dynamicDropdowns: DropdownConfig[]
+ onPropChange: (propName: string, value: any) => void
+ onCodeEdit: () => void
+ onOpenCssBuilder: (propName: string) => void
+}
+
+export function PropertyPanels({
+ component,
+ componentDef,
+ dynamicDropdowns,
+ onPropChange,
+ onCodeEdit,
+ onOpenCssBuilder,
+}: PropertyPanelsProps) {
+ return (
+
+
+
+
+ Props
+
+
+
+ Code
+
+
+
+
+
+
+ {componentDef?.propSchema?.length ? (
+ componentDef.propSchema.map(propDef => {
+ const dynamicDropdown =
+ propDef.type === 'dynamic-select'
+ ? dynamicDropdowns.find(d => d.name === propDef.dynamicSource)
+ : null
+
+ return (
+
onPropChange(propDef.name, value)}
+ dynamicDropdown={dynamicDropdown}
+ onOpenCssBuilder={() => onOpenCssBuilder(propDef.name)}
+ />
+ )
+ })
+ ) : (
+ This component has no configurable properties.
+ )}
+
+
+
+
+
+
+
+
+
+ Add custom JavaScript code for this component
+
+
+
+
+
+
+ )
+}
diff --git a/frontends/nextjs/src/components/rendering/components/RenderNode.tsx b/frontends/nextjs/src/components/rendering/components/RenderNode.tsx
new file mode 100644
index 000000000..a2ea3c18a
--- /dev/null
+++ b/frontends/nextjs/src/components/rendering/components/RenderNode.tsx
@@ -0,0 +1,188 @@
+import type React from 'react'
+import type { ComponentInstance } from '@/lib/types/builder-types'
+import { Button, Input, Textarea, Label, Badge, Card, Switch, Checkbox, Separator, Alert, Progress, Slider, Avatar, AvatarFallback, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui'
+import { IRCWebchatDeclarative } from '@/components/IRCWebchatDeclarative'
+import { NotificationSummaryCard } from '@/components/NotificationSummaryCard'
+import type { User } from '@/lib/level-types'
+import { getDeclarativeRenderer } from '@/lib/declarative-component-renderer'
+
+interface RenderNodeProps {
+ component: ComponentInstance
+ renderChildren: () => React.ReactNode
+ user?: User
+}
+
+export function RenderNode({ component, renderChildren, user }: RenderNodeProps) {
+ const { type, props } = component
+ const renderer = getDeclarativeRenderer()
+
+ if (renderer.hasComponentConfig(type)) {
+ if (type === 'IRCWebchat' && user) {
+ return (
+
+ )
+ }
+
+ return (
+
+ Declarative Component: {type}
+
+ This is a package-defined component
+
+
+ )
+ }
+
+ switch (type) {
+ case 'Container':
+ return (
+
+ {renderChildren()}
+
+ )
+
+ case 'Flex':
+ return (
+
+ {renderChildren()}
+
+ )
+
+ case 'Grid':
+ return (
+
+ {renderChildren()}
+
+ )
+
+ case 'Stack':
+ return (
+
+ {renderChildren()}
+
+ )
+
+ case 'Card':
+ return (
+
+ {renderChildren()}
+
+ )
+
+ case 'NotificationSummary':
+ return (
+
+ )
+
+ case 'Button':
+ return (
+
+ )
+
+ case 'Input':
+ return (
+
+ )
+
+ case 'Textarea':
+ return (
+
+ )
+
+ case 'Label':
+ return
+
+ case 'Heading': {
+ const level = props.level || '1'
+ const className = props.className
+ const text = props.children || 'Heading'
+
+ if (level === '1') return {text}
+ if (level === '2') return {text}
+ if (level === '3') return {text}
+ if (level === '4') return {text}
+ return {text}
+ }
+
+ case 'Text':
+ return (
+
+ {props.children || 'Text'}
+
+ )
+
+ case 'Badge':
+ return (
+
+ {props.children || 'Badge'}
+
+ )
+
+ case 'Switch':
+ return
+
+ case 'Checkbox':
+ return
+
+ case 'Separator':
+ return
+
+ case 'Alert':
+ return (
+
+ {renderChildren()}
+
+ )
+
+ case 'Progress':
+ return
+
+ case 'Slider':
+ return
+
+ case 'Avatar':
+ return (
+
+ U
+
+ )
+
+ case 'Table':
+ return (
+
+
+
+ Column 1
+ Column 2
+
+
+
+
+ Data 1
+ Data 2
+
+
+
+ )
+
+ default:
+ return Unknown Component: {type}
+ }
+}