diff --git a/json-components-registry.json b/json-components-registry.json index dd3461d..c22e5e1 100644 --- a/json-components-registry.json +++ b/json-components-registry.json @@ -75,7 +75,8 @@ "canHaveChildren": true, "description": "Button with action icon", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "ActionCard", @@ -84,7 +85,8 @@ "canHaveChildren": true, "description": "ActionCard component", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "ActionIcon", @@ -93,7 +95,8 @@ "canHaveChildren": true, "description": "ActionIcon component", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "alert", @@ -116,7 +119,8 @@ "canHaveChildren": true, "description": "Alert notification message", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "AlertCircle", @@ -166,7 +170,8 @@ "canHaveChildren": true, "description": "AppLogo component", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "ArrowLeft", @@ -263,7 +268,8 @@ "canHaveChildren": false, "description": "User avatar image", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "AvatarGroup", @@ -272,7 +278,8 @@ "canHaveChildren": true, "description": "Group of user avatars", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "badge", @@ -295,7 +302,8 @@ "canHaveChildren": true, "description": "Small status or count indicator", "status": "supported", - "source": "atoms" + "source": "atoms", + "jsonCompatible": true }, { "type": "Badge as ShadcnBadge", diff --git a/src/components/json-definitions/action-button.json b/src/components/json-definitions/action-button.json new file mode 100644 index 0000000..2b479fc --- /dev/null +++ b/src/components/json-definitions/action-button.json @@ -0,0 +1,112 @@ +{ + "id": "action-button-root", + "type": "div", + "props": { + "className": "inline-flex" + }, + "conditional": { + "if": "tooltip", + "then": { + "id": "action-button-with-tooltip", + "type": "div", + "children": [ + { + "id": "action-button-tooltip-provider", + "type": "TooltipProvider", + "children": [ + { + "id": "action-button-tooltip", + "type": "Tooltip", + "children": [ + { + "id": "action-button-trigger", + "type": "TooltipTrigger", + "props": { + "asChild": true + }, + "children": [ + { + "id": "action-button-element", + "type": "Button", + "props": { + "disabled": "disabled", + "className": "className" + }, + "bindings": { + "variant": "variant", + "size": "size" + }, + "children": [ + { + "id": "action-button-icon", + "type": "span", + "props": { + "className": "mr-2" + }, + "bindings": { + "children": "icon" + }, + "conditional": { + "if": "icon" + } + }, + { + "id": "action-button-label", + "type": "span", + "bindings": { + "children": "label" + } + } + ] + } + ] + }, + { + "id": "action-button-tooltip-content", + "type": "TooltipContent", + "bindings": { + "children": "tooltip" + } + } + ] + } + ] + } + ] + }, + "else": { + "id": "action-button-direct", + "type": "Button", + "props": { + "disabled": "disabled", + "className": "className" + }, + "bindings": { + "variant": "variant", + "size": "size" + }, + "children": [ + { + "id": "action-button-icon-direct", + "type": "span", + "props": { + "className": "mr-2" + }, + "bindings": { + "children": "icon" + }, + "conditional": { + "if": "icon" + } + }, + { + "id": "action-button-label-direct", + "type": "span", + "bindings": { + "children": "label" + } + } + ] + } + } +} diff --git a/src/components/json-definitions/action-card.json b/src/components/json-definitions/action-card.json new file mode 100644 index 0000000..cee033d --- /dev/null +++ b/src/components/json-definitions/action-card.json @@ -0,0 +1,89 @@ +{ + "id": "action-card-root", + "type": "Card", + "props": { + "className": "cursor-pointer transition-all hover:shadow-md hover:border-primary/50" + }, + "bindings": { + "className": { + "source": "disabled", + "transform": "data ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer transition-all hover:shadow-md hover:border-primary/50'" + } + }, + "children": [ + { + "id": "action-card-content", + "type": "CardContent", + "props": { + "className": "p-4" + }, + "children": [ + { + "id": "action-card-flex", + "type": "Flex", + "props": { + "justify": "start", + "align": "start", + "gap": "md", + "className": "items-start gap-3" + }, + "children": [ + { + "id": "action-card-icon-wrapper", + "type": "div", + "props": { + "className": "flex-shrink-0 p-2 rounded-lg bg-primary/10 text-primary" + }, + "bindings": { + "children": "icon" + }, + "conditional": { + "if": "icon" + } + }, + { + "id": "action-card-text-container", + "type": "div", + "props": { + "className": "flex-1 min-w-0" + }, + "children": [ + { + "id": "action-card-title", + "type": "div", + "props": { + "className": "font-semibold text-sm mb-1" + }, + "bindings": { + "children": "title" + } + }, + { + "id": "action-card-description", + "type": "div", + "props": { + "className": "text-xs text-muted-foreground line-clamp-2" + }, + "bindings": { + "children": "description" + }, + "conditional": { + "if": "description" + } + } + ] + }, + { + "id": "action-card-caret", + "type": "CaretRight", + "props": { + "size": 16, + "className": "flex-shrink-0 text-muted-foreground" + } + } + ] + } + ] + } + ] +} diff --git a/src/components/json-definitions/action-icon.json b/src/components/json-definitions/action-icon.json new file mode 100644 index 0000000..f3e177a --- /dev/null +++ b/src/components/json-definitions/action-icon.json @@ -0,0 +1,10 @@ +{ + "id": "action-icon-root", + "type": "ActionIcon", + "bindings": { + "action": "action", + "size": "size", + "weight": "weight", + "className": "className" + } +} diff --git a/src/components/json-definitions/alert.json b/src/components/json-definitions/alert.json new file mode 100644 index 0000000..a41aba1 --- /dev/null +++ b/src/components/json-definitions/alert.json @@ -0,0 +1,58 @@ +{ + "id": "alert-root", + "type": "div", + "props": { + "role": "alert", + "className": "flex gap-3 p-4 rounded-lg border" + }, + "bindings": { + "className": { + "source": "variant", + "transform": "(() => { const config = { info: { classes: 'bg-blue-50 border-blue-200 text-blue-900' }, warning: { classes: 'bg-yellow-50 border-yellow-200 text-yellow-900' }, success: { classes: 'bg-green-50 border-green-200 text-green-900' }, error: { classes: 'bg-red-50 border-red-200 text-red-900' } }; return 'flex gap-3 p-4 rounded-lg border ' + (config[data]?.classes || config.info.classes); })()" + } + }, + "children": [ + { + "id": "alert-icon", + "type": "AlertIcon", + "bindings": { + "variant": "variant" + }, + "props": { + "className": "flex-shrink-0 mt-0.5" + } + }, + { + "id": "alert-content", + "type": "div", + "props": { + "className": "flex-1" + }, + "children": [ + { + "id": "alert-title", + "type": "div", + "props": { + "className": "font-semibold mb-1" + }, + "bindings": { + "children": "title" + }, + "conditional": { + "if": "title" + } + }, + { + "id": "alert-message", + "type": "div", + "props": { + "className": "text-sm" + }, + "bindings": { + "children": "children" + } + } + ] + } + ] +} diff --git a/src/components/json-definitions/app-logo.json b/src/components/json-definitions/app-logo.json new file mode 100644 index 0000000..c38ecfc --- /dev/null +++ b/src/components/json-definitions/app-logo.json @@ -0,0 +1,18 @@ +{ + "id": "app-logo-root", + "type": "div", + "props": { + "className": "w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center shrink-0" + }, + "children": [ + { + "id": "app-logo-icon", + "type": "Code", + "props": { + "size": 20, + "weight": "duotone", + "className": "text-white sm:w-6 sm:h-6" + } + } + ] +} diff --git a/src/components/json-definitions/avatar-group.json b/src/components/json-definitions/avatar-group.json new file mode 100644 index 0000000..db37b5f --- /dev/null +++ b/src/components/json-definitions/avatar-group.json @@ -0,0 +1,50 @@ +{ + "id": "avatar-group-root", + "type": "div", + "bindings": { + "className": { + "source": "className", + "transform": "data ? 'flex -space-x-2 ' + data : 'flex -space-x-2'" + } + }, + "children": [ + { + "id": "avatar-group-list", + "type": "AvatarList", + "bindings": { + "avatars": "avatars", + "max": "max", + "size": "size" + } + }, + { + "id": "avatar-group-remainder", + "type": "div", + "bindings": { + "className": { + "source": "size", + "transform": "(() => { const sizeClasses = { xs: 'h-6 w-6 text-xs', sm: 'h-8 w-8 text-xs', md: 'h-10 w-10 text-sm', lg: 'h-12 w-12 text-base' }; return 'relative inline-flex items-center justify-center rounded-full border-2 border-background bg-muted ' + (sizeClasses[data] || sizeClasses.md); })()" + } + }, + "conditional": { + "if": "remainingCount", + "transform": "avatars.length - (max || 5) > 0" + }, + "children": [ + { + "id": "avatar-group-count", + "type": "span", + "props": { + "className": "font-medium text-foreground" + }, + "bindings": { + "children": { + "source": "avatars", + "transform": "`+${Math.max(data.length - (max || 5), 0)}`" + } + } + } + ] + } + ] +} diff --git a/src/components/json-definitions/avatar.json b/src/components/json-definitions/avatar.json new file mode 100644 index 0000000..7adf6a5 --- /dev/null +++ b/src/components/json-definitions/avatar.json @@ -0,0 +1,37 @@ +{ + "id": "avatar-root", + "type": "div", + "bindings": { + "className": { + "source": "size", + "transform": "(() => { const sizeClasses = { xs: 'w-6 h-6 text-xs', sm: 'w-8 h-8 text-sm', md: 'w-10 h-10 text-base', lg: 'w-12 h-12 text-lg', xl: 'w-16 h-16 text-xl' }; return 'relative inline-flex items-center justify-center rounded-full bg-muted overflow-hidden ' + (sizeClasses[data] || sizeClasses.md); })()" + } + }, + "conditional": { + "if": "src", + "then": { + "id": "avatar-image", + "type": "img", + "bindings": { + "src": "src", + "alt": "alt" + }, + "props": { + "className": "w-full h-full object-cover" + } + }, + "else": { + "id": "avatar-fallback", + "type": "span", + "props": { + "className": "font-medium text-muted-foreground" + }, + "bindings": { + "children": { + "source": "fallback", + "transform": "data || (props.alt?.slice(0, 2).toUpperCase()) || '?'" + } + } + } + } +} diff --git a/src/components/json-definitions/badge.json b/src/components/json-definitions/badge.json new file mode 100644 index 0000000..8d14a5d --- /dev/null +++ b/src/components/json-definitions/badge.json @@ -0,0 +1,33 @@ +{ + "id": "badge-root", + "type": "Badge", + "bindings": { + "variant": "variant", + "className": { + "source": "size", + "transform": "(() => { const sizeClasses = { sm: 'text-xs px-2 py-0.5', md: 'text-sm px-2.5 py-0.5', lg: 'text-base px-3 py-1' }; return 'inline-flex items-center gap-1.5 ' + (sizeClasses[data] || sizeClasses.md); })()" + } + }, + "children": [ + { + "id": "badge-icon", + "type": "span", + "props": { + "className": "flex-shrink-0" + }, + "bindings": { + "children": "icon" + }, + "conditional": { + "if": "icon" + } + }, + { + "id": "badge-content", + "type": "span", + "bindings": { + "children": "children" + } + } + ] +} diff --git a/src/components/json-definitions/breadcrumb.json b/src/components/json-definitions/breadcrumb.json new file mode 100644 index 0000000..c155259 --- /dev/null +++ b/src/components/json-definitions/breadcrumb.json @@ -0,0 +1,122 @@ +{ + "id": "breadcrumb", + "type": "nav", + "bindings": { + "aria-label": { + "source": null, + "transform": "'Breadcrumb'" + }, + "className": { + "source": ["items", "className"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const className = data[1] || ''; return cn(['flex items-center gap-2', className])" + } + }, + "children": [ + { + "id": "breadcrumb-items", + "type": "Fragment", + "_map": { + "source": "items", + "itemVar": "item", + "indexVar": "index" + }, + "children": [ + { + "id": "breadcrumb-item", + "type": "div", + "bindings": { + "className": { + "source": null, + "transform": "'flex items-center gap-2'" + } + }, + "children": [ + { + "id": "breadcrumb-link", + "type": "a", + "bindings": { + "_if": { + "source": "item.href", + "transform": "data" + }, + "href": { + "source": "item.href", + "transform": "data || '#'" + }, + "onClick": { + "source": "item.onClick", + "transform": "data" + }, + "className": { + "source": ["item", "items"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const item = data[0]; const items = data[1] || []; const isLast = items.indexOf(item) === items.length - 1; const baseClass = 'text-sm transition-colors'; const styleClass = isLast ? 'text-foreground font-medium' : 'text-muted-foreground hover:text-foreground'; return cn([baseClass, styleClass])" + } + }, + "children": [ + { + "id": "link-text", + "type": "span", + "children": [{"type": "text", "content": {"source": "item.label"}}] + } + ] + }, + { + "id": "breadcrumb-button", + "type": "button", + "bindings": { + "_if": { + "source": ["item.href", "item.onClick"], + "transform": "!data[0] && data[1]" + }, + "onClick": { + "source": "item.onClick", + "transform": "data" + }, + "className": { + "source": ["item", "items"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const item = data[0]; const items = data[1] || []; const isLast = items.indexOf(item) === items.length - 1; const baseClass = 'text-sm transition-colors'; const styleClass = isLast ? 'text-foreground font-medium' : 'text-muted-foreground hover:text-foreground'; return cn([baseClass, styleClass])" + } + }, + "children": [ + { + "id": "button-text", + "type": "span", + "children": [{"type": "text", "content": {"source": "item.label"}}] + } + ] + }, + { + "id": "breadcrumb-span", + "type": "span", + "bindings": { + "_if": { + "source": ["item.href", "item.onClick"], + "transform": "!data[0] && !data[1]" + }, + "className": { + "source": ["item", "items"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const item = data[0]; const items = data[1] || []; const isLast = items.indexOf(item) === items.length - 1; const baseClass = 'text-sm'; const styleClass = isLast ? 'text-foreground font-medium' : 'text-muted-foreground'; return cn([baseClass, styleClass])" + } + }, + "children": [{"type": "text", "content": {"source": "item.label"}}] + }, + { + "id": "separator", + "type": "CaretRight", + "bindings": { + "_if": { + "source": ["item", "items"], + "transform": "const item = data[0]; const items = data[1] || []; return items.indexOf(item) < items.length - 1" + }, + "className": { + "source": null, + "transform": "'w-4 h-4 text-muted-foreground'" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/components/json-definitions/button.json b/src/components/json-definitions/button.json new file mode 100644 index 0000000..d1fbd0f --- /dev/null +++ b/src/components/json-definitions/button.json @@ -0,0 +1,114 @@ +{ + "id": "button", + "type": "button", + "bindings": { + "type": { + "source": "type", + "transform": "data || 'button'" + }, + "disabled": { + "source": ["disabled", "loading"], + "transform": "data[0] || data[1]" + }, + "className": { + "source": ["fullWidth", "className"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const fullWidth = data[0]; const className = data[1] || ''; const widthClass = fullWidth ? 'w-full' : ''; return cn([widthClass, className])" + }, + "onClick": { + "source": "onClick", + "transform": "data" + } + }, + "children": [ + { + "id": "button-loading-content", + "type": "div", + "bindings": { + "_if": { + "source": "loading", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'flex items-center gap-2'" + } + }, + "children": [ + { + "id": "spinner", + "type": "div", + "bindings": { + "className": { + "source": null, + "transform": "'h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin'" + } + } + }, + { + "id": "loading-text", + "type": "span", + "children": [ + {"type": "text", "content": {"source": "children"}} + ] + } + ] + }, + { + "id": "button-normal-content", + "type": "div", + "bindings": { + "_if": { + "source": "loading", + "transform": "!data" + }, + "className": { + "source": null, + "transform": "'flex items-center gap-2'" + } + }, + "children": [ + { + "id": "left-icon", + "type": "span", + "bindings": { + "_if": { + "source": "leftIcon", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'flex-shrink-0'" + } + }, + "children": [ + {"type": "slot", "source": "leftIcon"} + ] + }, + { + "id": "button-text", + "type": "span", + "children": [ + {"type": "text", "content": {"source": "children"}} + ] + }, + { + "id": "right-icon", + "type": "span", + "bindings": { + "_if": { + "source": "rightIcon", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'flex-shrink-0'" + } + }, + "children": [ + {"type": "slot", "source": "rightIcon"} + ] + } + ] + } + ] +} diff --git a/src/components/json-definitions/calendar.json b/src/components/json-definitions/calendar.json new file mode 100644 index 0000000..af139e3 --- /dev/null +++ b/src/components/json-definitions/calendar.json @@ -0,0 +1,26 @@ +{ + "id": "calendar", + "type": "Calendar", + "bindings": { + "mode": { + "source": "mode", + "transform": "data || 'single'" + }, + "selected": { + "source": "selected", + "transform": "data" + }, + "onSelect": { + "source": "onSelect", + "transform": "data" + }, + "disabled": { + "source": "disabled", + "transform": "data" + }, + "className": { + "source": "className", + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); return cn(['rounded-md border', data || ''])" + } + } +} diff --git a/src/components/json-definitions/card.json b/src/components/json-definitions/card.json new file mode 100644 index 0000000..e87013c --- /dev/null +++ b/src/components/json-definitions/card.json @@ -0,0 +1,17 @@ +{ + "id": "card", + "type": "div", + "bindings": { + "onClick": { + "source": "onClick", + "transform": "data" + }, + "className": { + "source": ["variant", "padding", "hover", "onClick", "className"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const variant = data[0] || 'default'; const padding = data[1] || 'md'; const hover = data[2]; const onClick = data[3]; const className = data[4] || ''; const variantStyles = { default: 'bg-card border border-border', bordered: 'bg-background border-2 border-border', elevated: 'bg-card shadow-lg border border-border', flat: 'bg-muted' }; const paddingStyles = { none: 'p-0', sm: 'p-3', md: 'p-6', lg: 'p-8' }; const hoverClass = (hover || onClick) ? 'hover:shadow-md hover:scale-[1.01] cursor-pointer' : ''; const cursorClass = onClick ? 'cursor-pointer' : ''; return cn(['rounded-lg transition-all', variantStyles[variant], paddingStyles[padding], hoverClass, cursorClass, className])" + } + }, + "children": [ + {"type": "slot", "source": "children"} + ] +} diff --git a/src/components/json-definitions/checkbox.json b/src/components/json-definitions/checkbox.json new file mode 100644 index 0000000..2a15ab0 --- /dev/null +++ b/src/components/json-definitions/checkbox.json @@ -0,0 +1,97 @@ +{ + "id": "checkbox", + "type": "label", + "bindings": { + "className": { + "source": ["disabled", "className"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const disabled = data[0]; const className = data[1] || ''; const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : ''; return cn(['flex items-center gap-2 cursor-pointer', disabledClass, className])" + } + }, + "children": [ + { + "id": "checkbox-button", + "type": "button", + "bindings": { + "type": { + "source": null, + "transform": "'button'" + }, + "role": { + "source": null, + "transform": "'checkbox'" + }, + "aria-checked": { + "source": ["indeterminate", "checked"], + "transform": "data[0] ? 'mixed' : (data[1] ? 'true' : 'false')" + }, + "disabled": { + "source": "disabled", + "transform": "data" + }, + "onClick": { + "source": ["disabled", "checked", "onChange"], + "transform": "const disabled = data[0]; const checked = data[1]; const onChange = data[2]; return !disabled ? () => onChange(!checked) : undefined" + }, + "className": { + "source": ["size", "checked", "indeterminate"], + "transform": "const cn = (classes) => classes.filter(Boolean).join(' '); const size = data[0] || 'md'; const checked = data[1]; const indeterminate = data[2]; const sizeStyles = { sm: 'w-4 h-4', md: 'w-5 h-5', lg: 'w-6 h-6' }; const stateClass = (checked || indeterminate) ? 'bg-primary border-primary text-primary-foreground' : 'bg-background border-input hover:border-ring'; return cn(['flex items-center justify-center rounded border-2 transition-colors', sizeStyles[size], stateClass])" + } + }, + "children": [ + { + "id": "indeterminate-icon", + "type": "Minus", + "bindings": { + "_if": { + "source": "indeterminate", + "transform": "data" + }, + "size": { + "source": "size", + "transform": "const iconSize = { sm: 12, md: 16, lg: 20 }; return iconSize[data || 'md']" + }, + "weight": { + "source": null, + "transform": "'bold'" + } + } + }, + { + "id": "check-icon", + "type": "Check", + "bindings": { + "_if": { + "source": ["checked", "indeterminate"], + "transform": "data[0] && !data[1]" + }, + "size": { + "source": "size", + "transform": "const iconSize = { sm: 12, md: 16, lg: 20 }; return iconSize[data || 'md']" + }, + "weight": { + "source": null, + "transform": "'bold'" + } + } + } + ] + }, + { + "id": "label-text", + "type": "span", + "bindings": { + "_if": { + "source": "label", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'text-sm font-medium select-none'" + } + }, + "children": [ + {"type": "text", "content": {"source": "label"}} + ] + } + ] +} diff --git a/src/components/json-definitions/context-menu.json b/src/components/json-definitions/context-menu.json new file mode 100644 index 0000000..ae91ae9 --- /dev/null +++ b/src/components/json-definitions/context-menu.json @@ -0,0 +1,236 @@ +{ + "id": "context-menu", + "type": "ContextMenu", + "bindings": {}, + "children": [ + { + "id": "trigger", + "type": "ContextMenuTrigger", + "bindings": { + "asChild": { + "source": null, + "transform": "true" + } + }, + "children": [ + {"type": "slot", "source": "trigger"} + ] + }, + { + "id": "content", + "type": "ContextMenuContent", + "children": [ + { + "id": "menu-items", + "type": "Fragment", + "_map": { + "source": "items", + "itemVar": "item", + "indexVar": "index" + }, + "children": [ + { + "id": "menu-item", + "type": "ContextMenuSeparator", + "bindings": { + "_if": { + "source": "item.separator", + "transform": "data" + } + } + }, + { + "id": "submenu", + "type": "ContextMenuSub", + "bindings": { + "_if": { + "source": ["item.submenu", "item.separator"], + "transform": "data[0] && data[0].length > 0 && !data[1]" + } + }, + "children": [ + { + "id": "submenu-trigger", + "type": "ContextMenuSubTrigger", + "children": [ + { + "id": "submenu-icon", + "type": "span", + "bindings": { + "_if": { + "source": "item.icon", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'mr-2'" + } + }, + "children": [ + {"type": "slot", "source": "item.icon"} + ] + }, + { + "id": "submenu-label", + "type": "span", + "children": [ + {"type": "text", "content": {"source": "item.label"}} + ] + } + ] + }, + { + "id": "submenu-content", + "type": "ContextMenuSubContent", + "children": [ + { + "id": "nested-items", + "type": "Fragment", + "_map": { + "source": "item.submenu", + "itemVar": "subitem", + "indexVar": "subindex" + }, + "children": [ + { + "id": "nested-item", + "type": "ContextMenuItem", + "bindings": { + "onSelect": { + "source": "subitem.onSelect", + "transform": "data" + }, + "disabled": { + "source": "subitem.disabled", + "transform": "data" + } + }, + "children": [ + { + "id": "nested-icon", + "type": "span", + "bindings": { + "_if": { + "source": "subitem.icon", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'mr-2'" + } + }, + "children": [ + {"type": "slot", "source": "subitem.icon"} + ] + }, + { + "id": "nested-label", + "type": "span", + "bindings": { + "className": { + "source": null, + "transform": "'flex-1'" + } + }, + "children": [ + {"type": "text", "content": {"source": "subitem.label"}} + ] + }, + { + "id": "nested-shortcut", + "type": "span", + "bindings": { + "_if": { + "source": "subitem.shortcut", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'ml-auto text-xs text-muted-foreground'" + } + }, + "children": [ + {"type": "text", "content": {"source": "subitem.shortcut"}} + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "regular-item", + "type": "ContextMenuItem", + "bindings": { + "_if": { + "source": ["item.submenu", "item.separator"], + "transform": "(!data[0] || data[0].length === 0) && !data[1]" + }, + "onSelect": { + "source": "item.onSelect", + "transform": "data" + }, + "disabled": { + "source": "item.disabled", + "transform": "data" + } + }, + "children": [ + { + "id": "item-icon", + "type": "span", + "bindings": { + "_if": { + "source": "item.icon", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'mr-2'" + } + }, + "children": [ + {"type": "slot", "source": "item.icon"} + ] + }, + { + "id": "item-label", + "type": "span", + "bindings": { + "className": { + "source": null, + "transform": "'flex-1'" + } + }, + "children": [ + {"type": "text", "content": {"source": "item.label"}} + ] + }, + { + "id": "item-shortcut", + "type": "span", + "bindings": { + "_if": { + "source": "item.shortcut", + "transform": "data" + }, + "className": { + "source": null, + "transform": "'ml-auto text-xs text-muted-foreground'" + } + }, + "children": [ + {"type": "text", "content": {"source": "item.shortcut"}} + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/lib/json-ui/interfaces/action-button.ts b/src/lib/json-ui/interfaces/action-button.ts new file mode 100644 index 0000000..fcd530e --- /dev/null +++ b/src/lib/json-ui/interfaces/action-button.ts @@ -0,0 +1,12 @@ +import { ReactNode } from 'react' + +export interface ActionButtonProps { + icon?: ReactNode + label: string + onClick: () => void + variant?: 'default' | 'outline' | 'ghost' | 'destructive' + size?: 'default' | 'sm' | 'lg' | 'icon' + tooltip?: string + disabled?: boolean + className?: string +} diff --git a/src/lib/json-ui/interfaces/action-card.ts b/src/lib/json-ui/interfaces/action-card.ts new file mode 100644 index 0000000..1b9787c --- /dev/null +++ b/src/lib/json-ui/interfaces/action-card.ts @@ -0,0 +1,10 @@ +import { ReactNode } from 'react' + +export interface ActionCardProps { + icon?: ReactNode + title: string + description?: string + onClick?: () => void + className?: string + disabled?: boolean +} diff --git a/src/lib/json-ui/interfaces/action-icon.ts b/src/lib/json-ui/interfaces/action-icon.ts new file mode 100644 index 0000000..9a2083a --- /dev/null +++ b/src/lib/json-ui/interfaces/action-icon.ts @@ -0,0 +1,6 @@ +export interface ActionIconProps { + action: 'add' | 'edit' | 'delete' | 'copy' | 'download' | 'upload' + size?: number + weight?: 'thin' | 'light' | 'regular' | 'bold' | 'fill' | 'duotone' + className?: string +} diff --git a/src/lib/json-ui/interfaces/alert.ts b/src/lib/json-ui/interfaces/alert.ts new file mode 100644 index 0000000..a7b924b --- /dev/null +++ b/src/lib/json-ui/interfaces/alert.ts @@ -0,0 +1,8 @@ +import { ReactNode } from 'react' + +export interface AlertProps { + variant?: 'info' | 'warning' | 'success' | 'error' + title?: string + children: ReactNode + className?: string +} diff --git a/src/lib/json-ui/interfaces/app-logo.ts b/src/lib/json-ui/interfaces/app-logo.ts new file mode 100644 index 0000000..d9d6259 --- /dev/null +++ b/src/lib/json-ui/interfaces/app-logo.ts @@ -0,0 +1,3 @@ +export interface AppLogoProps { + className?: string +} diff --git a/src/lib/json-ui/interfaces/avatar-group.ts b/src/lib/json-ui/interfaces/avatar-group.ts new file mode 100644 index 0000000..38bc414 --- /dev/null +++ b/src/lib/json-ui/interfaces/avatar-group.ts @@ -0,0 +1,10 @@ +export interface AvatarGroupProps { + avatars: { + src?: string + alt: string + fallback: string + }[] + max?: number + size?: 'xs' | 'sm' | 'md' | 'lg' + className?: string +} diff --git a/src/lib/json-ui/interfaces/avatar.ts b/src/lib/json-ui/interfaces/avatar.ts new file mode 100644 index 0000000..205ea21 --- /dev/null +++ b/src/lib/json-ui/interfaces/avatar.ts @@ -0,0 +1,7 @@ +export interface AvatarProps { + src?: string + alt?: string + fallback?: string + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' + className?: string +} diff --git a/src/lib/json-ui/interfaces/badge.ts b/src/lib/json-ui/interfaces/badge.ts new file mode 100644 index 0000000..45c4e01 --- /dev/null +++ b/src/lib/json-ui/interfaces/badge.ts @@ -0,0 +1,9 @@ +import { ReactNode } from 'react' + +export interface BadgeProps { + children: ReactNode + variant?: 'default' | 'secondary' | 'destructive' | 'outline' + size?: 'sm' | 'md' | 'lg' + icon?: ReactNode + className?: string +} diff --git a/src/lib/json-ui/interfaces/breadcrumb.ts b/src/lib/json-ui/interfaces/breadcrumb.ts new file mode 100644 index 0000000..3b15c43 --- /dev/null +++ b/src/lib/json-ui/interfaces/breadcrumb.ts @@ -0,0 +1,10 @@ +export interface BreadcrumbItem { + label: string + href?: string + onClick?: () => void +} + +export interface BreadcrumbProps { + items?: BreadcrumbItem[] + className?: string +} diff --git a/src/lib/json-ui/interfaces/button.ts b/src/lib/json-ui/interfaces/button.ts new file mode 100644 index 0000000..cc05644 --- /dev/null +++ b/src/lib/json-ui/interfaces/button.ts @@ -0,0 +1,15 @@ +import { ReactNode } from 'react' + +export interface ButtonProps { + children?: ReactNode + leftIcon?: ReactNode + rightIcon?: ReactNode + loading?: boolean + fullWidth?: boolean + disabled?: boolean + className?: string + variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link' + size?: 'default' | 'sm' | 'lg' | 'icon' + onClick?: () => void + type?: 'button' | 'submit' | 'reset' +} diff --git a/src/lib/json-ui/interfaces/calendar.ts b/src/lib/json-ui/interfaces/calendar.ts new file mode 100644 index 0000000..f81997e --- /dev/null +++ b/src/lib/json-ui/interfaces/calendar.ts @@ -0,0 +1,7 @@ +export interface CalendarProps { + selected?: Date + onSelect?: (date: Date | undefined) => void + mode?: 'single' | 'multiple' | 'range' + disabled?: Date | ((date: Date) => boolean) + className?: string +} diff --git a/src/lib/json-ui/interfaces/card.ts b/src/lib/json-ui/interfaces/card.ts new file mode 100644 index 0000000..476280f --- /dev/null +++ b/src/lib/json-ui/interfaces/card.ts @@ -0,0 +1,10 @@ +import { ReactNode } from 'react' + +export interface CardProps { + children?: ReactNode + variant?: 'default' | 'bordered' | 'elevated' | 'flat' + padding?: 'none' | 'sm' | 'md' | 'lg' + hover?: boolean + className?: string + onClick?: () => void +} diff --git a/src/lib/json-ui/interfaces/checkbox.ts b/src/lib/json-ui/interfaces/checkbox.ts new file mode 100644 index 0000000..d76d1cb --- /dev/null +++ b/src/lib/json-ui/interfaces/checkbox.ts @@ -0,0 +1,9 @@ +export interface CheckboxProps { + checked: boolean + onChange: (checked: boolean) => void + label?: string + indeterminate?: boolean + disabled?: boolean + size?: 'sm' | 'md' | 'lg' + className?: string +} diff --git a/src/lib/json-ui/interfaces/context-menu.ts b/src/lib/json-ui/interfaces/context-menu.ts new file mode 100644 index 0000000..09bad5f --- /dev/null +++ b/src/lib/json-ui/interfaces/context-menu.ts @@ -0,0 +1,16 @@ +import { ReactNode } from 'react' + +export interface ContextMenuItemType { + label: string + icon?: ReactNode + shortcut?: string + onSelect?: () => void + disabled?: boolean + separator?: boolean + submenu?: ContextMenuItemType[] +} + +export interface ContextMenuProps { + trigger: ReactNode + items: ContextMenuItemType[] +} diff --git a/src/lib/json-ui/interfaces/index.ts b/src/lib/json-ui/interfaces/index.ts index 6531037..6de4c16 100644 --- a/src/lib/json-ui/interfaces/index.ts +++ b/src/lib/json-ui/interfaces/index.ts @@ -29,3 +29,17 @@ export * from './app-main-panel' export * from './app-dialogs' export * from './data-source-manager' export * from './navigation-menu' +export * from './action-button' +export * from './action-card' +export * from './action-icon' +export * from './alert' +export * from './app-logo' +export * from './avatar' +export * from './avatar-group' +export * from './badge' +export * from './breadcrumb' +export * from './button' +export * from './calendar' +export * from './card' +export * from './checkbox' +export * from './context-menu' diff --git a/src/lib/json-ui/json-components.ts b/src/lib/json-ui/json-components.ts index 0e44922..35c6407 100644 --- a/src/lib/json-ui/json-components.ts +++ b/src/lib/json-ui/json-components.ts @@ -37,6 +37,14 @@ import type { DataSourceManagerProps, NavigationMenuProps, TreeListPanelProps, + ActionButtonProps, + ActionCardProps, + ActionIconProps, + AlertProps, + AppLogoProps, + AvatarProps, + AvatarGroupProps, + BadgeProps, } from './interfaces' // Import JSON definitions @@ -69,6 +77,14 @@ import appDialogsDef from '@/components/json-definitions/app-dialogs.json' import navigationMenuDef from '@/components/json-definitions/navigation-menu.json' import dataSourceManagerDef from '@/components/json-definitions/data-source-manager.json' import treeListPanelDef from '@/components/json-definitions/tree-list-panel.json' +import actionButtonDef from '@/components/json-definitions/action-button.json' +import actionCardDef from '@/components/json-definitions/action-card.json' +import actionIconDef from '@/components/json-definitions/action-icon.json' +import alertDef from '@/components/json-definitions/alert.json' +import appLogoDef from '@/components/json-definitions/app-logo.json' +import avatarDef from '@/components/json-definitions/avatar.json' +import avatarGroupDef from '@/components/json-definitions/avatar-group.json' +import badgeDef from '@/components/json-definitions/badge.json' // Create pure JSON components (no hooks) export const LoadingFallback = createJsonComponent(loadingFallbackDef) @@ -80,6 +96,14 @@ export const GitHubBuildStatus = createJsonComponent(git export const SeedDataManager = createJsonComponent(seedDataManagerDef) export const TreeCard = createJsonComponent(treeCardDef) export const AppDialogs = createJsonComponent(appDialogsDef) +export const ActionButton = createJsonComponent(actionButtonDef) +export const ActionCard = createJsonComponent(actionCardDef) +export const ActionIcon = createJsonComponent(actionIconDef) +export const Alert = createJsonComponent(alertDef) +export const AppLogo = createJsonComponent(appLogoDef) +export const Avatar = createJsonComponent(avatarDef) +export const AvatarGroup = createJsonComponent(avatarGroupDef) +export const Badge = createJsonComponent(badgeDef) // Create JSON components with hooks export const SaveIndicator = createJsonComponentWithHooks(saveIndicatorDef, {