From a65a994ec459b4da62a9b12516ef1d4e9eee7827 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 18 Jan 2026 22:07:45 +0000
Subject: [PATCH] feat: Implement JSON components with wrappers for
LoadingFallback, NavigationItem, PageHeaderContent
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
---
src/components/molecules/TreeCard.tsx | 75 +++++++++++++++++++
src/components/molecules/TreeListHeader.tsx | 53 +++++++++++++
src/components/molecules/index.ts | 6 ++
.../wrappers/LoadingFallbackWrapper.tsx | 16 ++++
.../wrappers/NavigationItemWrapper.tsx | 27 +++++++
.../wrappers/PageHeaderContentWrapper.tsx | 22 ++++++
src/lib/json-ui/wrappers/TreeCardWrapper.tsx | 33 ++++++++
.../definitions/loading-fallback.json | 32 ++++++++
.../wrappers/definitions/navigation-item.json | 37 +++++++++
.../definitions/page-header-content.json | 33 ++++++++
.../wrappers/definitions/tree-card.json | 63 ++++++++++++++++
src/lib/json-ui/wrappers/index.ts | 4 +
12 files changed, 401 insertions(+)
create mode 100644 src/components/molecules/TreeCard.tsx
create mode 100644 src/components/molecules/TreeListHeader.tsx
create mode 100644 src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx
create mode 100644 src/lib/json-ui/wrappers/NavigationItemWrapper.tsx
create mode 100644 src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx
create mode 100644 src/lib/json-ui/wrappers/TreeCardWrapper.tsx
create mode 100644 src/lib/json-ui/wrappers/definitions/loading-fallback.json
create mode 100644 src/lib/json-ui/wrappers/definitions/navigation-item.json
create mode 100644 src/lib/json-ui/wrappers/definitions/page-header-content.json
create mode 100644 src/lib/json-ui/wrappers/definitions/tree-card.json
diff --git a/src/components/molecules/TreeCard.tsx b/src/components/molecules/TreeCard.tsx
new file mode 100644
index 0000000..e42a52c
--- /dev/null
+++ b/src/components/molecules/TreeCard.tsx
@@ -0,0 +1,75 @@
+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 (
+
+
+
+
+ {tree.name}
+ {tree.description && (
+
+ {tree.description}
+
+ )}
+
+
+ {tree.rootNodes.length} components
+
+
+
+
+ e.stopPropagation()}>
+
+ }
+ variant="ghost"
+ size="sm"
+ onClick={onEdit}
+ title="Edit tree"
+ />
+ }
+ variant="ghost"
+ size="sm"
+ onClick={onDuplicate}
+ title="Duplicate tree"
+ />
+ }
+ variant="ghost"
+ size="sm"
+ onClick={onDelete}
+ disabled={disableDelete}
+ title="Delete tree"
+ />
+
+
+
+
+ )
+}
diff --git a/src/components/molecules/TreeListHeader.tsx b/src/components/molecules/TreeListHeader.tsx
new file mode 100644
index 0000000..d381b2c
--- /dev/null
+++ b/src/components/molecules/TreeListHeader.tsx
@@ -0,0 +1,53 @@
+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 (
+
+
+
+
+ Component Trees
+
+ }
+ size="sm"
+ onClick={onCreateNew}
+ />
+
+
+
+ }
+ >
+ Import JSON
+
+ }
+ >
+ Export JSON
+
+
+
+ )
+}
diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts
index a23d2d5..92926f1 100644
--- a/src/components/molecules/index.ts
+++ b/src/components/molecules/index.ts
@@ -18,3 +18,9 @@ export { SearchInput } from './SearchInput'
export { BindingEditor } from './BindingEditor'
export { DataSourceEditorDialog } from './DataSourceEditorDialog'
export { ComponentBindingDialog } from './ComponentBindingDialog'
+export { TreeCard } from './TreeCard'
+export { TreeListHeader } from './TreeListHeader'
+export { LoadingFallbackWrapper as LoadingFallback } from '@/lib/json-ui/wrappers'
+export { NavigationItemWrapper as NavigationItem } from '@/lib/json-ui/wrappers'
+export { PageHeaderContentWrapper as PageHeaderContent } from '@/lib/json-ui/wrappers'
+export { preloadMonacoEditor } from './LazyMonacoEditor'
diff --git a/src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx b/src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx
new file mode 100644
index 0000000..caa2251
--- /dev/null
+++ b/src/lib/json-ui/wrappers/LoadingFallbackWrapper.tsx
@@ -0,0 +1,16 @@
+import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
+import loadingFallbackDef from './definitions/loading-fallback.json'
+
+interface LoadingFallbackWrapperProps {
+ message?: string
+}
+
+export function LoadingFallbackWrapper({ message = 'Loading...' }: LoadingFallbackWrapperProps) {
+ return (
+
+ )
+}
diff --git a/src/lib/json-ui/wrappers/NavigationItemWrapper.tsx b/src/lib/json-ui/wrappers/NavigationItemWrapper.tsx
new file mode 100644
index 0000000..6746b54
--- /dev/null
+++ b/src/lib/json-ui/wrappers/NavigationItemWrapper.tsx
@@ -0,0 +1,27 @@
+import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
+import navigationItemDef from './definitions/navigation-item.json'
+
+interface NavigationItemWrapperProps {
+ icon: React.ReactNode
+ label: string
+ isActive: boolean
+ badge?: number
+ onClick: () => void
+}
+
+export function NavigationItemWrapper(props: NavigationItemWrapperProps) {
+ return (
+
+
+
+ )
+}
diff --git a/src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx b/src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx
new file mode 100644
index 0000000..bd68fd0
--- /dev/null
+++ b/src/lib/json-ui/wrappers/PageHeaderContentWrapper.tsx
@@ -0,0 +1,22 @@
+import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
+import pageHeaderContentDef from './definitions/page-header-content.json'
+
+interface PageHeaderContentWrapperProps {
+ title: string
+ icon: React.ReactNode
+ description?: string
+}
+
+export function PageHeaderContentWrapper(props: PageHeaderContentWrapperProps) {
+ return (
+
+ )
+}
diff --git a/src/lib/json-ui/wrappers/TreeCardWrapper.tsx b/src/lib/json-ui/wrappers/TreeCardWrapper.tsx
new file mode 100644
index 0000000..b64409e
--- /dev/null
+++ b/src/lib/json-ui/wrappers/TreeCardWrapper.tsx
@@ -0,0 +1,33 @@
+import { ComponentRenderer } from '@/lib/json-ui/component-renderer'
+import treeCardDefinition from './definitions/tree-card.json'
+import { ComponentTree } from '@/types/project'
+
+interface TreeCardWrapperProps {
+ tree: ComponentTree
+ isSelected: boolean
+ onSelect: () => void
+ onEdit: () => void
+ onDuplicate: () => void
+ onDelete: () => void
+ disableDelete?: boolean
+}
+
+export function TreeCardWrapper(props: TreeCardWrapperProps) {
+ return (
+
+
+
e.stopPropagation()}>
+
+
+
+
+
+ )
+}
diff --git a/src/lib/json-ui/wrappers/definitions/loading-fallback.json b/src/lib/json-ui/wrappers/definitions/loading-fallback.json
new file mode 100644
index 0000000..a2c7101
--- /dev/null
+++ b/src/lib/json-ui/wrappers/definitions/loading-fallback.json
@@ -0,0 +1,32 @@
+{
+ "id": "loading-container",
+ "type": "div",
+ "props": {
+ "className": "flex items-center justify-center h-full w-full"
+ },
+ "children": [
+ {
+ "id": "loading-content",
+ "type": "div",
+ "props": {
+ "className": "flex flex-col items-center gap-3"
+ },
+ "children": [
+ {
+ "id": "loading-spinner",
+ "type": "LoadingSpinner"
+ },
+ {
+ "id": "loading-message",
+ "type": "p",
+ "props": {
+ "className": "text-sm text-muted-foreground"
+ },
+ "bindings": {
+ "children": "message"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/lib/json-ui/wrappers/definitions/navigation-item.json b/src/lib/json-ui/wrappers/definitions/navigation-item.json
new file mode 100644
index 0000000..4015dd6
--- /dev/null
+++ b/src/lib/json-ui/wrappers/definitions/navigation-item.json
@@ -0,0 +1,37 @@
+{
+ "id": "nav-item-button",
+ "type": "button",
+ "bindings": {
+ "className": {
+ "source": "isActive",
+ "transform": "data ? 'w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors bg-primary text-primary-foreground' : 'w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors hover:bg-muted text-foreground'"
+ }
+ },
+ "children": [
+ {
+ "id": "nav-item-icon",
+ "type": "IconWrapper",
+ "bindings": {
+ "icon": "icon",
+ "variant": { "source": "isActive", "transform": "data ? 'default' : 'muted'" }
+ },
+ "props": { "size": "md" }
+ },
+ {
+ "id": "nav-item-label",
+ "type": "Text",
+ "props": { "className": "flex-1 text-left font-medium", "variant": "small" },
+ "bindings": { "children": "label" }
+ },
+ {
+ "id": "nav-item-badge",
+ "type": "Badge",
+ "bindings": {
+ "variant": { "source": "isActive", "transform": "data ? 'secondary' : 'destructive'" },
+ "children": "badge"
+ },
+ "props": { "className": "ml-auto" },
+ "conditional": { "if": "badge !== undefined && badge > 0" }
+ }
+ ]
+}
diff --git a/src/lib/json-ui/wrappers/definitions/page-header-content.json b/src/lib/json-ui/wrappers/definitions/page-header-content.json
new file mode 100644
index 0000000..8cd8ad3
--- /dev/null
+++ b/src/lib/json-ui/wrappers/definitions/page-header-content.json
@@ -0,0 +1,33 @@
+{
+ "id": "page-header-container",
+ "type": "div",
+ "props": { "className": "flex items-center gap-3" },
+ "children": [
+ {
+ "id": "page-header-icon",
+ "type": "TabIcon",
+ "bindings": { "icon": "icon" },
+ "props": { "variant": "gradient" }
+ },
+ {
+ "id": "page-header-content",
+ "type": "div",
+ "props": { "className": "min-w-0" },
+ "children": [
+ {
+ "id": "page-header-title",
+ "type": "h2",
+ "props": { "className": "text-lg sm:text-xl font-bold truncate" },
+ "bindings": { "children": "title" }
+ },
+ {
+ "id": "page-header-description",
+ "type": "p",
+ "props": { "className": "text-xs sm:text-sm text-muted-foreground hidden sm:block" },
+ "bindings": { "children": "description" },
+ "conditional": { "if": "description" }
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/lib/json-ui/wrappers/definitions/tree-card.json b/src/lib/json-ui/wrappers/definitions/tree-card.json
new file mode 100644
index 0000000..313b71e
--- /dev/null
+++ b/src/lib/json-ui/wrappers/definitions/tree-card.json
@@ -0,0 +1,63 @@
+{
+ "id": "tree-card-container",
+ "type": "Card",
+ "bindings": {
+ "className": {
+ "source": "isSelected",
+ "transform": "data ? 'cursor-pointer transition-all p-4 ring-2 ring-primary bg-accent' : 'cursor-pointer transition-all p-4 hover:bg-accent/50'"
+ }
+ },
+ "children": [
+ {
+ "id": "tree-card-content",
+ "type": "Stack",
+ "props": { "spacing": "sm" },
+ "children": [
+ {
+ "id": "tree-card-header",
+ "type": "Flex",
+ "props": { "justify": "between", "align": "start", "gap": "sm" },
+ "children": [
+ {
+ "id": "tree-card-info",
+ "type": "Stack",
+ "props": { "spacing": "xs", "className": "flex-1 min-w-0" },
+ "children": [
+ {
+ "id": "tree-card-name",
+ "type": "Heading",
+ "props": { "level": 4, "className": "text-sm truncate" },
+ "bindings": { "children": "tree.name" }
+ },
+ {
+ "id": "tree-card-description",
+ "type": "Text",
+ "props": { "variant": "caption", "className": "line-clamp-2" },
+ "bindings": { "children": "tree.description" },
+ "conditional": { "if": "tree.description" }
+ },
+ {
+ "id": "tree-card-badge-container",
+ "type": "div",
+ "children": [
+ {
+ "id": "tree-card-badge",
+ "type": "Badge",
+ "props": { "variant": "outline", "className": "text-xs" },
+ "bindings": {
+ "children": {
+ "source": "tree.rootNodes.length",
+ "transform": "`${data} components`"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/lib/json-ui/wrappers/index.ts b/src/lib/json-ui/wrappers/index.ts
index 11b179b..0377fde 100644
--- a/src/lib/json-ui/wrappers/index.ts
+++ b/src/lib/json-ui/wrappers/index.ts
@@ -8,3 +8,7 @@ export { GitHubBuildStatusWrapper } from './GitHubBuildStatusWrapper'
export { ComponentBindingDialogWrapper } from './ComponentBindingDialogWrapper'
export { DataSourceEditorDialogWrapper } from './DataSourceEditorDialogWrapper'
export { ComponentTreeWrapper } from './ComponentTreeWrapper'
+export { LoadingFallbackWrapper } from './LoadingFallbackWrapper'
+export { NavigationItemWrapper } from './NavigationItemWrapper'
+export { PageHeaderContentWrapper } from './PageHeaderContentWrapper'
+export { LoadingFallbackWrapper } from './LoadingFallbackWrapper'