mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Refactor component tree builder
This commit is contained in:
@@ -1,43 +1,25 @@
|
||||
import { useState } from 'react'
|
||||
import { ComponentNode } from '@/types/project'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Plus, Trash, Tree, CaretRight, CaretDown, Sparkle } from '@phosphor-icons/react'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { AIService } from '@/lib/ai-service'
|
||||
import { toast } from 'sonner'
|
||||
import componentTreeBuilderData from '@/data/component-tree-builder.json'
|
||||
import { ComponentInspector } from '@/components/component-tree-builder/ComponentInspector'
|
||||
import { ComponentTreeToolbar } from '@/components/component-tree-builder/ComponentTreeToolbar'
|
||||
import { ComponentTreeView } from '@/components/component-tree-builder/ComponentTreeView'
|
||||
import {
|
||||
addChildNode,
|
||||
createComponentNode,
|
||||
deleteNodeFromTree,
|
||||
findNodeById,
|
||||
updateNodeInTree,
|
||||
} from '@/components/component-tree-builder/tree-utils'
|
||||
|
||||
interface ComponentTreeBuilderProps {
|
||||
components: ComponentNode[]
|
||||
onComponentsChange: (components: ComponentNode[]) => void
|
||||
}
|
||||
|
||||
const MUI_COMPONENTS = [
|
||||
'Box',
|
||||
'Container',
|
||||
'Grid',
|
||||
'Stack',
|
||||
'Paper',
|
||||
'Card',
|
||||
'CardContent',
|
||||
'CardActions',
|
||||
'Button',
|
||||
'TextField',
|
||||
'Typography',
|
||||
'AppBar',
|
||||
'Toolbar',
|
||||
'List',
|
||||
'ListItem',
|
||||
'ListItemText',
|
||||
'Divider',
|
||||
'Avatar',
|
||||
'Chip',
|
||||
'IconButton',
|
||||
]
|
||||
const { muiComponents, prompts } = componentTreeBuilderData
|
||||
|
||||
export function ComponentTreeBuilder({
|
||||
components,
|
||||
@@ -46,79 +28,34 @@ export function ComponentTreeBuilder({
|
||||
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null)
|
||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set())
|
||||
|
||||
const findNodeById = (
|
||||
nodes: ComponentNode[],
|
||||
id: string
|
||||
): ComponentNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node
|
||||
const found = findNodeById(node.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedNode = selectedNodeId ? findNodeById(components, selectedNodeId) : null
|
||||
const selectedNode = selectedNodeId
|
||||
? findNodeById(components, selectedNodeId)
|
||||
: null
|
||||
|
||||
const addRootComponent = () => {
|
||||
const newNode: ComponentNode = {
|
||||
id: `node-${Date.now()}`,
|
||||
type: 'Box',
|
||||
const newNode = createComponentNode({
|
||||
name: `Component${components.length + 1}`,
|
||||
props: {},
|
||||
children: [],
|
||||
}
|
||||
})
|
||||
onComponentsChange([...components, newNode])
|
||||
setSelectedNodeId(newNode.id)
|
||||
}
|
||||
|
||||
const addChildComponent = (parentId: string) => {
|
||||
const newNode: ComponentNode = {
|
||||
id: `node-${Date.now()}`,
|
||||
type: 'Box',
|
||||
name: 'NewComponent',
|
||||
props: {},
|
||||
children: [],
|
||||
}
|
||||
|
||||
const addChild = (nodes: ComponentNode[]): ComponentNode[] => {
|
||||
return nodes.map((node) => {
|
||||
if (node.id === parentId) {
|
||||
return { ...node, children: [...node.children, newNode] }
|
||||
}
|
||||
return { ...node, children: addChild(node.children) }
|
||||
})
|
||||
}
|
||||
|
||||
onComponentsChange(addChild(components))
|
||||
const newNode = createComponentNode()
|
||||
onComponentsChange(addChildNode(components, parentId, newNode))
|
||||
setExpandedNodes(new Set([...expandedNodes, parentId]))
|
||||
setSelectedNodeId(newNode.id)
|
||||
}
|
||||
|
||||
const deleteNode = (nodeId: string) => {
|
||||
const deleteFromTree = (nodes: ComponentNode[]): ComponentNode[] => {
|
||||
return nodes
|
||||
.filter((node) => node.id !== nodeId)
|
||||
.map((node) => ({ ...node, children: deleteFromTree(node.children) }))
|
||||
}
|
||||
|
||||
onComponentsChange(deleteFromTree(components))
|
||||
onComponentsChange(deleteNodeFromTree(components, nodeId))
|
||||
if (selectedNodeId === nodeId) {
|
||||
setSelectedNodeId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const updateNode = (nodeId: string, updates: Partial<ComponentNode>) => {
|
||||
const updateInTree = (nodes: ComponentNode[]): ComponentNode[] => {
|
||||
return nodes.map((node) => {
|
||||
if (node.id === nodeId) {
|
||||
return { ...node, ...updates }
|
||||
}
|
||||
return { ...node, children: updateInTree(node.children) }
|
||||
})
|
||||
}
|
||||
|
||||
onComponentsChange(updateInTree(components))
|
||||
onComponentsChange(updateNodeInTree(components, nodeId, updates))
|
||||
}
|
||||
|
||||
const toggleExpand = (nodeId: string) => {
|
||||
@@ -132,13 +69,13 @@ export function ComponentTreeBuilder({
|
||||
}
|
||||
|
||||
const generateComponentWithAI = async () => {
|
||||
const description = prompt('Describe the component you want to create:')
|
||||
const description = prompt(prompts.generateComponentDescription)
|
||||
if (!description) return
|
||||
|
||||
try {
|
||||
toast.info('Generating component with AI...')
|
||||
const component = await AIService.generateComponent(description)
|
||||
|
||||
|
||||
if (component) {
|
||||
onComponentsChange([...components, component])
|
||||
setSelectedNodeId(component.id)
|
||||
@@ -153,151 +90,28 @@ export function ComponentTreeBuilder({
|
||||
}
|
||||
}
|
||||
|
||||
const renderTreeNode = (node: ComponentNode, level: number = 0) => {
|
||||
const isExpanded = expandedNodes.has(node.id)
|
||||
const isSelected = selectedNodeId === node.id
|
||||
const hasChildren = node.children.length > 0
|
||||
|
||||
return (
|
||||
<div key={node.id}>
|
||||
<button
|
||||
onClick={() => setSelectedNodeId(node.id)}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 rounded text-sm transition-colors ${
|
||||
isSelected
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'hover:bg-muted text-foreground'
|
||||
}`}
|
||||
style={{ paddingLeft: `${level * 20 + 12}px` }}
|
||||
>
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
toggleExpand(node.id)
|
||||
}}
|
||||
className="hover:text-accent"
|
||||
>
|
||||
{isExpanded ? <CaretDown size={16} /> : <CaretRight size={16} />}
|
||||
</button>
|
||||
)}
|
||||
{!hasChildren && <div className="w-4" />}
|
||||
<Tree size={16} />
|
||||
<span className="font-medium">{node.name}</span>
|
||||
<span className="text-muted-foreground text-xs ml-auto">{node.type}</span>
|
||||
</button>
|
||||
{isExpanded &&
|
||||
node.children.map((child) => renderTreeNode(child, level + 1))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex gap-4 p-6">
|
||||
<div className="w-80 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-sm uppercase tracking-wide">
|
||||
Component Tree
|
||||
</h3>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={generateComponentWithAI}
|
||||
className="h-8 w-8 p-0"
|
||||
title="Generate component with AI"
|
||||
>
|
||||
<Sparkle size={16} weight="duotone" />
|
||||
</Button>
|
||||
<Button size="sm" onClick={addRootComponent} className="h-8 w-8 p-0">
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea className="flex-1 border rounded-lg">
|
||||
<div className="p-2 space-y-1">
|
||||
{components.map((node) => renderTreeNode(node))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<ComponentTreeToolbar
|
||||
onGenerate={generateComponentWithAI}
|
||||
onAddRoot={addRootComponent}
|
||||
/>
|
||||
<ComponentTreeView
|
||||
nodes={components}
|
||||
selectedNodeId={selectedNodeId}
|
||||
expandedNodes={expandedNodes}
|
||||
onSelectNode={setSelectedNodeId}
|
||||
onToggleExpand={toggleExpand}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card className="flex-1 p-6">
|
||||
{selectedNode ? (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-lg font-semibold">Component Properties</h4>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => deleteNode(selectedNode.id)}
|
||||
>
|
||||
<Trash size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Component Name</Label>
|
||||
<Input
|
||||
value={selectedNode.name}
|
||||
onChange={(e) =>
|
||||
updateNode(selectedNode.id, { name: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Component Type</Label>
|
||||
<Select
|
||||
value={selectedNode.type}
|
||||
onValueChange={(value) =>
|
||||
updateNode(selectedNode.id, { type: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{MUI_COMPONENTS.map((comp) => (
|
||||
<SelectItem key={comp} value={comp}>
|
||||
{comp}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Props (JSON)</Label>
|
||||
<Textarea
|
||||
value={JSON.stringify(selectedNode.props, null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
const props = JSON.parse(e.target.value)
|
||||
updateNode(selectedNode.id, { props })
|
||||
} catch (err) {
|
||||
// Invalid JSON while typing - ignore
|
||||
}
|
||||
}}
|
||||
className="font-mono text-sm h-64"
|
||||
placeholder='{"variant": "contained", "color": "primary"}'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => addChildComponent(selectedNode.id)}>
|
||||
<Plus size={16} className="mr-2" />
|
||||
Add Child Component
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex items-center justify-center text-muted-foreground">
|
||||
<div className="text-center">
|
||||
<Tree size={48} className="mx-auto mb-4 opacity-50" />
|
||||
<p>Select a component to edit properties</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
<ComponentInspector
|
||||
selectedNode={selectedNode}
|
||||
muiComponents={muiComponents}
|
||||
onDelete={deleteNode}
|
||||
onUpdate={updateNode}
|
||||
onAddChild={addChildComponent}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
105
src/components/component-tree-builder/ComponentInspector.tsx
Normal file
105
src/components/component-tree-builder/ComponentInspector.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { ComponentNode } from '@/types/project'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Plus, Trash, Tree } from '@phosphor-icons/react'
|
||||
|
||||
interface ComponentInspectorProps {
|
||||
selectedNode: ComponentNode | null
|
||||
muiComponents: string[]
|
||||
onDelete: (nodeId: string) => void
|
||||
onUpdate: (nodeId: string, updates: Partial<ComponentNode>) => void
|
||||
onAddChild: (parentId: string) => void
|
||||
}
|
||||
|
||||
export function ComponentInspector({
|
||||
selectedNode,
|
||||
muiComponents,
|
||||
onDelete,
|
||||
onUpdate,
|
||||
onAddChild,
|
||||
}: ComponentInspectorProps) {
|
||||
return (
|
||||
<Card className="flex-1 p-6">
|
||||
{selectedNode ? (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-lg font-semibold">Component Properties</h4>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => onDelete(selectedNode.id)}
|
||||
>
|
||||
<Trash size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Component Name</Label>
|
||||
<Input
|
||||
value={selectedNode.name}
|
||||
onChange={(event) =>
|
||||
onUpdate(selectedNode.id, { name: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Component Type</Label>
|
||||
<Select
|
||||
value={selectedNode.type}
|
||||
onValueChange={(value) =>
|
||||
onUpdate(selectedNode.id, { type: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{muiComponents.map((comp) => (
|
||||
<SelectItem key={comp} value={comp}>
|
||||
{comp}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Props (JSON)</Label>
|
||||
<Textarea
|
||||
value={JSON.stringify(selectedNode.props, null, 2)}
|
||||
onChange={(event) => {
|
||||
try {
|
||||
const props = JSON.parse(event.target.value)
|
||||
onUpdate(selectedNode.id, { props })
|
||||
} catch (err) {
|
||||
// Invalid JSON while typing - ignore
|
||||
}
|
||||
}}
|
||||
className="font-mono text-sm h-64"
|
||||
placeholder='{"variant": "contained", "color": "primary"}'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => onAddChild(selectedNode.id)}>
|
||||
<Plus size={16} className="mr-2" />
|
||||
Add Child Component
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex items-center justify-center text-muted-foreground">
|
||||
<div className="text-center">
|
||||
<Tree size={48} className="mx-auto mb-4 opacity-50" />
|
||||
<p>Select a component to edit properties</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Plus, Sparkle } from '@phosphor-icons/react'
|
||||
|
||||
interface ComponentTreeToolbarProps {
|
||||
onGenerate: () => void
|
||||
onAddRoot: () => void
|
||||
}
|
||||
|
||||
export function ComponentTreeToolbar({
|
||||
onGenerate,
|
||||
onAddRoot,
|
||||
}: ComponentTreeToolbarProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-sm uppercase tracking-wide">
|
||||
Component Tree
|
||||
</h3>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onGenerate}
|
||||
className="h-8 w-8 p-0"
|
||||
title="Generate component with AI"
|
||||
>
|
||||
<Sparkle size={16} weight="duotone" />
|
||||
</Button>
|
||||
<Button size="sm" onClick={onAddRoot} className="h-8 w-8 p-0">
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
76
src/components/component-tree-builder/ComponentTreeView.tsx
Normal file
76
src/components/component-tree-builder/ComponentTreeView.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { ComponentNode } from '@/types/project'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { CaretDown, CaretRight, Tree } from '@phosphor-icons/react'
|
||||
|
||||
interface ComponentTreeViewProps {
|
||||
nodes: ComponentNode[]
|
||||
selectedNodeId: string | null
|
||||
expandedNodes: Set<string>
|
||||
onSelectNode: (nodeId: string) => void
|
||||
onToggleExpand: (nodeId: string) => void
|
||||
}
|
||||
|
||||
export function ComponentTreeView({
|
||||
nodes,
|
||||
selectedNodeId,
|
||||
expandedNodes,
|
||||
onSelectNode,
|
||||
onToggleExpand,
|
||||
}: ComponentTreeViewProps) {
|
||||
const renderTreeNode = (node: ComponentNode, level: number = 0) => {
|
||||
const isExpanded = expandedNodes.has(node.id)
|
||||
const isSelected = selectedNodeId === node.id
|
||||
const hasChildren = node.children.length > 0
|
||||
|
||||
return (
|
||||
<div key={node.id}>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelectNode(node.id)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
onSelectNode(node.id)
|
||||
}
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 rounded text-sm transition-colors cursor-pointer ${
|
||||
isSelected
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'hover:bg-muted text-foreground'
|
||||
}`}
|
||||
style={{ paddingLeft: `${level * 20 + 12}px` }}
|
||||
>
|
||||
{hasChildren ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
onToggleExpand(node.id)
|
||||
}}
|
||||
className="hover:text-accent"
|
||||
aria-label={isExpanded ? 'Collapse node' : 'Expand node'}
|
||||
>
|
||||
{isExpanded ? <CaretDown size={16} /> : <CaretRight size={16} />}
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-4" />
|
||||
)}
|
||||
<Tree size={16} />
|
||||
<span className="font-medium">{node.name}</span>
|
||||
<span className="text-muted-foreground text-xs ml-auto">
|
||||
{node.type}
|
||||
</span>
|
||||
</div>
|
||||
{isExpanded &&
|
||||
node.children.map((child) => renderTreeNode(child, level + 1))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="flex-1 border rounded-lg">
|
||||
<div className="p-2 space-y-1">{nodes.map((node) => renderTreeNode(node))}</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
56
src/components/component-tree-builder/tree-utils.ts
Normal file
56
src/components/component-tree-builder/tree-utils.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ComponentNode } from '@/types/project'
|
||||
|
||||
export const createComponentNode = (
|
||||
overrides: Partial<ComponentNode> = {}
|
||||
): ComponentNode => ({
|
||||
id: `node-${Date.now()}`,
|
||||
type: 'Box',
|
||||
name: 'NewComponent',
|
||||
props: {},
|
||||
children: [],
|
||||
...overrides,
|
||||
})
|
||||
|
||||
export const findNodeById = (
|
||||
nodes: ComponentNode[],
|
||||
id: string
|
||||
): ComponentNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node
|
||||
const found = findNodeById(node.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const addChildNode = (
|
||||
nodes: ComponentNode[],
|
||||
parentId: string,
|
||||
childNode: ComponentNode
|
||||
): ComponentNode[] =>
|
||||
nodes.map((node) => {
|
||||
if (node.id === parentId) {
|
||||
return { ...node, children: [...node.children, childNode] }
|
||||
}
|
||||
return { ...node, children: addChildNode(node.children, parentId, childNode) }
|
||||
})
|
||||
|
||||
export const deleteNodeFromTree = (
|
||||
nodes: ComponentNode[],
|
||||
nodeId: string
|
||||
): ComponentNode[] =>
|
||||
nodes
|
||||
.filter((node) => node.id !== nodeId)
|
||||
.map((node) => ({ ...node, children: deleteNodeFromTree(node.children, nodeId) }))
|
||||
|
||||
export const updateNodeInTree = (
|
||||
nodes: ComponentNode[],
|
||||
nodeId: string,
|
||||
updates: Partial<ComponentNode>
|
||||
): ComponentNode[] =>
|
||||
nodes.map((node) => {
|
||||
if (node.id === nodeId) {
|
||||
return { ...node, ...updates }
|
||||
}
|
||||
return { ...node, children: updateNodeInTree(node.children, nodeId, updates) }
|
||||
})
|
||||
27
src/data/component-tree-builder.json
Normal file
27
src/data/component-tree-builder.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"muiComponents": [
|
||||
"Box",
|
||||
"Container",
|
||||
"Grid",
|
||||
"Stack",
|
||||
"Paper",
|
||||
"Card",
|
||||
"CardContent",
|
||||
"CardActions",
|
||||
"Button",
|
||||
"TextField",
|
||||
"Typography",
|
||||
"AppBar",
|
||||
"Toolbar",
|
||||
"List",
|
||||
"ListItem",
|
||||
"ListItemText",
|
||||
"Divider",
|
||||
"Avatar",
|
||||
"Chip",
|
||||
"IconButton"
|
||||
],
|
||||
"prompts": {
|
||||
"generateComponentDescription": "Describe the component you want to create:"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user