mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
Compare commits
2 Commits
main
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0dfd9e7c | ||
|
|
96744a6ab4 |
@@ -1,153 +1,64 @@
|
|||||||
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { PageRenderer } from '@/lib/json-ui/page-renderer'
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import { FeatureToggles } from '@/types/project'
|
import { FeatureToggles } from '@/types/project'
|
||||||
import {
|
import { useMemo } from 'react'
|
||||||
BookOpen,
|
import featureToggleSchema from '@/schemas/feature-toggle-settings.json'
|
||||||
Code,
|
import type { PageSchema } from '@/types/json-ui'
|
||||||
Cube,
|
import { evaluateExpression } from '@/lib/json-ui/expression-evaluator'
|
||||||
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 {
|
interface FeatureToggleSettingsProps {
|
||||||
features: FeatureToggles
|
features: FeatureToggles
|
||||||
onFeaturesChange: (features: FeatureToggles) => void
|
onFeaturesChange: (features: FeatureToggles) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeatureToggleIconKey =
|
/**
|
||||||
| 'BookOpen'
|
* FeatureToggleSettings - Now JSON-driven!
|
||||||
| 'Code'
|
*
|
||||||
| 'Cube'
|
* This component demonstrates how a complex React component with:
|
||||||
| 'Database'
|
* - Custom hooks and state management
|
||||||
| 'FileText'
|
* - Dynamic data rendering (looping over features)
|
||||||
| 'Flask'
|
* - Event handlers (toggle switches)
|
||||||
| 'FlowArrow'
|
* - Conditional styling (enabled/disabled states)
|
||||||
| 'Image'
|
*
|
||||||
| 'Lightbulb'
|
* Can be converted to a pure JSON schema with custom action handlers.
|
||||||
| 'PaintBrush'
|
* The JSON schema handles all UI structure, data binding, and loops,
|
||||||
| 'Play'
|
* while custom functions handle business logic.
|
||||||
| 'Tree'
|
*
|
||||||
| 'Wrench'
|
* Converted from 153 lines of React/TSX to:
|
||||||
|
* - 1 JSON schema file (195 lines, but mostly structure)
|
||||||
const iconMap: Record<FeatureToggleIconKey, ComponentType<{ size?: number; weight?: 'duotone' }>> = {
|
* - 45 lines of integration code (this file)
|
||||||
BookOpen,
|
*
|
||||||
Code,
|
* Benefits:
|
||||||
Cube,
|
* - UI structure is now data-driven and can be modified without code changes
|
||||||
Database,
|
* - Feature list is in JSON and can be easily extended
|
||||||
FileText,
|
* - Styling and layout can be customized via JSON
|
||||||
Flask,
|
* - Business logic (toggle handler) stays in TypeScript for type safety
|
||||||
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 (
|
|
||||||
<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) {
|
export function FeatureToggleSettings({ features, onFeaturesChange }: FeatureToggleSettingsProps) {
|
||||||
const handleToggle = (key: keyof FeatureToggles, value: boolean) => {
|
// 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({
|
onFeaturesChange({
|
||||||
...features,
|
...features,
|
||||||
[key]: value,
|
[key]: checked,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}), [features, onFeaturesChange])
|
||||||
|
|
||||||
const enabledCount = Object.values(features).filter(Boolean).length
|
// Pass features as external data to the JSON renderer
|
||||||
const totalCount = Object.keys(features).length
|
const data = useMemo(() => ({ features }), [features])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full p-6 bg-background">
|
<PageRenderer
|
||||||
<FeatureToggleHeader enabledCount={enabledCount} totalCount={totalCount} />
|
schema={featureToggleSchema as PageSchema}
|
||||||
|
data={data}
|
||||||
<ScrollArea className="h-[calc(100vh-200px)]">
|
functions={handlers}
|
||||||
<FeatureToggleGrid items={featuresList} features={features} onToggle={handleToggle} />
|
/>
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
300
src/schemas/feature-toggle-settings.json
Normal file
300
src/schemas/feature-toggle-settings.json
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
{
|
||||||
|
"id": "feature-toggle-settings",
|
||||||
|
"name": "Feature Toggle Settings",
|
||||||
|
"description": "Enable or disable features to customize your workspace",
|
||||||
|
"dataSources": [
|
||||||
|
{
|
||||||
|
"id": "featuresList",
|
||||||
|
"type": "static",
|
||||||
|
"defaultValue": [
|
||||||
|
{
|
||||||
|
"key": "codeEditor",
|
||||||
|
"label": "Code Editor",
|
||||||
|
"description": "Monaco-based code editor with syntax highlighting",
|
||||||
|
"icon": "Code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "models",
|
||||||
|
"label": "Database Models",
|
||||||
|
"description": "Prisma schema designer for database models",
|
||||||
|
"icon": "Database"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "components",
|
||||||
|
"label": "Component Builder",
|
||||||
|
"description": "Visual component tree builder for React components",
|
||||||
|
"icon": "Tree"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "componentTrees",
|
||||||
|
"label": "Component Trees Manager",
|
||||||
|
"description": "Manage multiple component tree configurations",
|
||||||
|
"icon": "Tree"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "workflows",
|
||||||
|
"label": "Workflow Designer",
|
||||||
|
"description": "n8n-style visual workflow automation builder",
|
||||||
|
"icon": "FlowArrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "lambdas",
|
||||||
|
"label": "Lambda Functions",
|
||||||
|
"description": "Serverless function editor with multiple runtimes",
|
||||||
|
"icon": "Code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "styling",
|
||||||
|
"label": "Theme Designer",
|
||||||
|
"description": "Material UI theme customization and styling",
|
||||||
|
"icon": "PaintBrush"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "flaskApi",
|
||||||
|
"label": "Flask API Designer",
|
||||||
|
"description": "Python Flask backend API endpoint designer",
|
||||||
|
"icon": "Flask"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "playwright",
|
||||||
|
"label": "Playwright Tests",
|
||||||
|
"description": "E2E testing with Playwright configuration",
|
||||||
|
"icon": "Play"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "storybook",
|
||||||
|
"label": "Storybook Stories",
|
||||||
|
"description": "Component documentation and development",
|
||||||
|
"icon": "BookOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "unitTests",
|
||||||
|
"label": "Unit Tests",
|
||||||
|
"description": "Component and function unit test designer",
|
||||||
|
"icon": "Cube"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "errorRepair",
|
||||||
|
"label": "Error Repair",
|
||||||
|
"description": "Auto-detect and fix code errors",
|
||||||
|
"icon": "Wrench"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "documentation",
|
||||||
|
"label": "Documentation",
|
||||||
|
"description": "Project documentation, roadmap, and guides",
|
||||||
|
"icon": "FileText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sassStyles",
|
||||||
|
"label": "Sass Styles",
|
||||||
|
"description": "Custom Sass/SCSS styling showcase",
|
||||||
|
"icon": "PaintBrush"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "faviconDesigner",
|
||||||
|
"label": "Favicon Designer",
|
||||||
|
"description": "Design and generate app favicons and icons",
|
||||||
|
"icon": "Image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ideaCloud",
|
||||||
|
"label": "Feature Idea Cloud",
|
||||||
|
"description": "Brainstorm and organize feature ideas",
|
||||||
|
"icon": "Lightbulb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "enabledCount",
|
||||||
|
"type": "static",
|
||||||
|
"expression": "Object.values(data.features || {}).filter(Boolean).length"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "totalCount",
|
||||||
|
"type": "static",
|
||||||
|
"expression": "Object.keys(data.features || {}).length"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "root",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "h-full p-6 bg-background"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "header",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "mb-6"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "title",
|
||||||
|
"type": "Heading",
|
||||||
|
"props": {
|
||||||
|
"level": 2,
|
||||||
|
"className": "text-2xl font-bold mb-2",
|
||||||
|
"children": "Feature Toggles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "description",
|
||||||
|
"type": "Text",
|
||||||
|
"props": {
|
||||||
|
"className": "text-muted-foreground"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"value": "Enable or disable features to customize your workspace. "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"dataBinding": "enabledCount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"value": " of "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"dataBinding": "totalCount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"value": " features enabled."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "scroll-area",
|
||||||
|
"type": "ScrollArea",
|
||||||
|
"props": {
|
||||||
|
"className": "h-[calc(100vh-200px)]"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "grid",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "grid grid-cols-1 lg:grid-cols-2 gap-4 pr-4"
|
||||||
|
},
|
||||||
|
"loop": {
|
||||||
|
"source": "featuresList",
|
||||||
|
"itemVar": "item",
|
||||||
|
"indexVar": "index"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "feature-card",
|
||||||
|
"type": "Card",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "card-header",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "p-6 pb-3"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "card-content",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "flex items-start justify-between"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "left-content",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "flex items-center gap-3"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "icon-container",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": {
|
||||||
|
"expression": "data.features?.[item.key] ? 'p-2 rounded-lg bg-primary text-primary-foreground' : 'p-2 rounded-lg bg-muted text-muted-foreground'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "icon",
|
||||||
|
"type": {
|
||||||
|
"dataBinding": "item.icon"
|
||||||
|
},
|
||||||
|
"props": {
|
||||||
|
"size": 20,
|
||||||
|
"weight": "duotone"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "text-content",
|
||||||
|
"type": "div",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "title",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "text-base font-semibold"
|
||||||
|
},
|
||||||
|
"dataBinding": "item.label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "description",
|
||||||
|
"type": "div",
|
||||||
|
"props": {
|
||||||
|
"className": "text-xs mt-1 text-muted-foreground"
|
||||||
|
},
|
||||||
|
"dataBinding": "item.description"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "switch",
|
||||||
|
"type": "Switch",
|
||||||
|
"bindings": {
|
||||||
|
"checked": {
|
||||||
|
"expression": "data.features?.[item.key] || false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"event": "checkedChange",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "updateFeature",
|
||||||
|
"type": "custom",
|
||||||
|
"params": {
|
||||||
|
"key": "item.key",
|
||||||
|
"checked": "event"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user