Merge pull request #183 from johndoe6345789/codex/update-dynamic-component-resolution-in-registry

Refactor JSON UI component registry to dynamic resolution
This commit is contained in:
2026-01-18 18:15:58 +00:00
committed by GitHub

View File

@@ -1,63 +1,7 @@
import { ComponentType } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { InputOtp } from '@/components/ui/input-otp'
import { Textarea } from '@/components/ui/textarea'
import { Label } from '@/components/ui/label'
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator'
import { Alert as ShadcnAlert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { AlertDialog } from '@/components/ui/alert-dialog'
import { AspectRatio } from '@/components/ui/aspect-ratio'
import { Carousel } from '@/components/ui/carousel'
import { ChartContainer as Chart } from '@/components/ui/chart'
import { Collapsible } from '@/components/ui/collapsible'
import { Command } from '@/components/ui/command'
import { Switch } from '@/components/ui/switch'
import { Checkbox } from '@/components/ui/checkbox'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { DropdownMenu } from '@/components/ui/dropdown-menu'
import { Menubar } from '@/components/ui/menubar'
import { NavigationMenu } from '@/components/ui/navigation-menu'
import { Table as ShadcnTable, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Skeleton as ShadcnSkeleton } from '@/components/ui/skeleton'
import { Progress } from '@/components/ui/progress'
import { Pagination } from '@/components/ui/pagination'
import { ResizablePanelGroup as Resizable } from '@/components/ui/resizable'
import { Sheet } from '@/components/ui/sheet'
import { Sidebar } from '@/components/ui/sidebar'
import { Toaster as Sonner } from '@/components/ui/sonner'
import { ToggleGroup } from '@/components/ui/toggle-group'
import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { CircularProgress, Divider, ProgressBar } from '@/components/atoms'
import * as AtomComponents from '@/components/atoms'
import * as MoleculeComponents from '@/components/molecules'
import * as OrganismComponents from '@/components/organisms'
import {
ComponentBindingDialogWrapper,
ComponentTreeWrapper,
DataSourceEditorDialogWrapper,
GitHubBuildStatusWrapper,
LazyBarChartWrapper,
LazyD3BarChartWrapper,
LazyLineChartWrapper,
SaveIndicatorWrapper,
SeedDataManagerWrapper,
StorageSettingsWrapper,
} from '@/lib/json-ui/wrappers'
import * as PhosphorIcons from '@phosphor-icons/react'
import jsonComponentsRegistry from '../../../json-components-registry.json'
import {
ArrowLeft, ArrowRight, Check, X, Plus, Minus, MagnifyingGlass,
Funnel, Download, Upload, PencilSimple, Trash, Eye, EyeClosed,
CaretUp, CaretDown, CaretLeft, CaretRight,
Gear, User, Bell, Envelope, Calendar, Clock, Star,
Heart, ShareNetwork, LinkSimple, Copy, FloppyDisk, ArrowClockwise, WarningCircle,
Info, Question, House, List as ListIcon, DotsThreeVertical, DotsThree
} from '@phosphor-icons/react'
import { JSONUIShowcase } from '@/components/JSONUIShowcase'
export interface UIComponentRegistry {
[key: string]: ComponentType<any>
@@ -89,19 +33,6 @@ const jsonRegistry = jsonComponentsRegistry as JsonComponentRegistry
const getRegistryEntryName = (entry: JsonRegistryEntry): string | undefined =>
entry.export ?? entry.name ?? entry.type
const buildRegistryFromNames = (
names: string[],
components: Record<string, ComponentType<any>>
): UIComponentRegistry => {
return names.reduce<UIComponentRegistry>((registry, name) => {
const component = components[name]
if (component) {
registry[name] = component
}
return registry
}, {})
}
const jsonRegistryEntries = jsonRegistry.components ?? []
const registryEntryByType = new Map(
jsonRegistryEntries
@@ -111,7 +42,6 @@ const registryEntryByType = new Map(
})
.filter((entry): entry is [string, JsonRegistryEntry] => Boolean(entry))
)
const atomComponentMap = AtomComponents as Record<string, ComponentType<any>>
const deprecatedComponentInfo = jsonRegistryEntries.reduce<Record<string, DeprecatedComponentInfo>>(
(acc, entry) => {
const entryName = getRegistryEntryName(entry)
@@ -125,30 +55,130 @@ const deprecatedComponentInfo = jsonRegistryEntries.reduce<Record<string, Deprec
},
{}
)
const atomRegistryNames = jsonRegistryEntries
.filter((entry) => entry.source === 'atoms')
.map((entry) => getRegistryEntryName(entry))
.filter((name): name is string => Boolean(name))
const moleculeRegistryNames = jsonRegistryEntries
.filter((entry) => entry.source === 'molecules')
.map((entry) => getRegistryEntryName(entry))
.filter((name): name is string => Boolean(name))
const organismRegistryNames = jsonRegistryEntries
.filter((entry) => entry.source === 'organisms')
.map((entry) => getRegistryEntryName(entry))
.filter((name): name is string => Boolean(name))
const shadcnRegistryNames = jsonRegistryEntries
.filter((entry) => entry.source === 'ui')
.map((entry) => getRegistryEntryName(entry))
.filter((name): name is string => Boolean(name))
const wrapperRegistryNames = jsonRegistryEntries
.filter((entry) => entry.source === 'wrappers')
.map((entry) => getRegistryEntryName(entry))
.filter((name): name is string => Boolean(name))
const iconRegistryNames = jsonRegistryEntries
.filter((entry) => entry.source === 'icons')
.map((entry) => getRegistryEntryName(entry))
.filter((name): name is string => Boolean(name))
const buildComponentMapFromExports = (
exports: Record<string, unknown>
): Record<string, ComponentType<any>> => {
return Object.entries(exports).reduce<Record<string, ComponentType<any>>>((acc, [key, value]) => {
if (value && (typeof value === 'function' || typeof value === 'object')) {
acc[key] = value as ComponentType<any>
}
return acc
}, {})
}
const buildComponentMapFromModules = (
modules: Record<string, unknown>
): Record<string, ComponentType<any>> => {
return Object.values(modules).reduce<Record<string, ComponentType<any>>>((acc, moduleExports) => {
if (!moduleExports || typeof moduleExports !== 'object') {
return acc
}
Object.entries(buildComponentMapFromExports(moduleExports as Record<string, unknown>)).forEach(
([key, component]) => {
acc[key] = component
}
)
return acc
}, {})
}
const atomModules = import.meta.glob('@/components/atoms/*.tsx', { eager: true })
const moleculeModules = import.meta.glob('@/components/molecules/*.tsx', { eager: true })
const organismModules = import.meta.glob('@/components/organisms/*.tsx', { eager: true })
const uiModules = import.meta.glob('@/components/ui/**/*.{ts,tsx}', { eager: true })
const wrapperModules = import.meta.glob('@/lib/json-ui/wrappers/*.tsx', { eager: true })
const atomComponentMap = buildComponentMapFromModules(atomModules)
const moleculeComponentMap = buildComponentMapFromModules(moleculeModules)
const organismComponentMap = buildComponentMapFromModules(organismModules)
const uiComponentMap = buildComponentMapFromModules(uiModules)
const wrapperComponentMap = buildComponentMapFromModules(wrapperModules)
const iconComponentMap = buildComponentMapFromExports(PhosphorIcons)
const sourceAliases: Record<string, Record<string, string>> = {
atoms: {
PageHeader: 'BasicPageHeader',
SearchInput: 'BasicSearchInput',
},
molecules: {},
organisms: {},
ui: {
Chart: 'ChartContainer',
Resizable: 'ResizablePanelGroup',
},
wrappers: {},
}
const iconAliases: Record<string, string> = {
Search: 'MagnifyingGlass',
Filter: 'Funnel',
Edit: 'PencilSimple',
EyeOff: 'EyeClosed',
ChevronUp: 'CaretUp',
ChevronDown: 'CaretDown',
ChevronLeft: 'CaretLeft',
ChevronRight: 'CaretRight',
Settings: 'Gear',
Mail: 'Envelope',
Share: 'ShareNetwork',
Link: 'LinkSimple',
Save: 'FloppyDisk',
RefreshCw: 'ArrowClockwise',
AlertCircle: 'WarningCircle',
HelpCircle: 'Question',
Home: 'House',
Menu: 'List',
MoreVertical: 'DotsThreeVertical',
MoreHorizontal: 'DotsThree',
}
const explicitComponentAllowlist: Record<string, ComponentType<any>> = {
JSONUIShowcase,
}
const buildRegistryFromEntries = (
source: string,
componentMap: Record<string, ComponentType<any>>,
aliases: Record<string, string> = {}
): UIComponentRegistry => {
return jsonRegistryEntries
.filter((entry) => entry.source === source)
.reduce<UIComponentRegistry>((registry, entry) => {
const entryName = getRegistryEntryName(entry)
if (!entryName) {
return registry
}
const aliasName = aliases[entryName]
const component =
componentMap[entryName] ??
(aliasName ? componentMap[aliasName] : undefined) ??
explicitComponentAllowlist[entryName]
if (component) {
registry[entryName] = component
}
return registry
}, {})
}
const buildIconRegistry = (): UIComponentRegistry => {
return jsonRegistryEntries
.filter((entry) => entry.source === 'icons')
.reduce<UIComponentRegistry>((registry, entry) => {
const entryName = getRegistryEntryName(entry)
if (!entryName) {
return registry
}
const aliasName = iconAliases[entryName]
const component =
iconComponentMap[entryName] ??
(aliasName ? iconComponentMap[aliasName] : undefined)
if (component) {
registry[entryName] = component
}
return registry
}, {})
}
export const primitiveComponents: UIComponentRegistry = {
div: 'div' as any,
@@ -169,176 +199,38 @@ export const primitiveComponents: UIComponentRegistry = {
nav: 'nav' as any,
}
const shadcnComponentMap: Record<string, ComponentType<any>> = {
AlertDialog,
AspectRatio,
Button,
Carousel,
Chart,
Collapsible,
Command,
DropdownMenu,
Input,
InputOtp,
Textarea,
Label,
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
Badge,
Separator,
Alert: ShadcnAlert,
AlertDescription,
AlertTitle,
Switch,
Checkbox,
RadioGroup,
RadioGroupItem,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Table: ShadcnTable,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
Menubar,
NavigationMenu,
Skeleton: ShadcnSkeleton,
Pagination,
Progress,
Resizable,
Sheet,
Sidebar,
Sonner,
ToggleGroup,
Avatar: ShadcnAvatar,
AvatarFallback,
AvatarImage,
}
export const shadcnComponents: UIComponentRegistry = buildRegistryFromNames(
shadcnRegistryNames,
shadcnComponentMap
export const shadcnComponents: UIComponentRegistry = buildRegistryFromEntries(
'ui',
uiComponentMap,
sourceAliases.ui
)
export const atomComponents: UIComponentRegistry = {
...buildRegistryFromNames(
atomRegistryNames,
atomComponentMap
),
DatePicker: atomComponentMap.DatePicker,
FileUpload: atomComponentMap.FileUpload,
CircularProgress,
Divider,
ProgressBar,
DataList: (AtomComponents as Record<string, ComponentType<any>>).DataList,
DataTable: (AtomComponents as Record<string, ComponentType<any>>).DataTable,
ListItem: (AtomComponents as Record<string, ComponentType<any>>).ListItem,
MetricCard: (AtomComponents as Record<string, ComponentType<any>>).MetricCard,
Timeline: (AtomComponents as Record<string, ComponentType<any>>).Timeline,
}
const breadcrumbComponent = AtomComponents.Breadcrumb ?? AtomComponents.BreadcrumbNav
if (breadcrumbComponent) {
atomComponents.Breadcrumb = breadcrumbComponent as ComponentType<any>
}
export const moleculeComponents: UIComponentRegistry = {
...buildRegistryFromNames(
moleculeRegistryNames,
MoleculeComponents as Record<string, ComponentType<any>>
),
AppBranding: (MoleculeComponents as Record<string, ComponentType<any>>).AppBranding,
LabelWithBadge: (MoleculeComponents as Record<string, ComponentType<any>>).LabelWithBadge,
NavigationGroupHeader: (MoleculeComponents as Record<string, ComponentType<any>>).NavigationGroupHeader,
}
export const organismComponents: UIComponentRegistry = buildRegistryFromNames(
organismRegistryNames,
OrganismComponents as Record<string, ComponentType<any>>
export const atomComponents: UIComponentRegistry = buildRegistryFromEntries(
'atoms',
atomComponentMap,
sourceAliases.atoms
)
const wrapperComponentMap: Record<string, ComponentType<any>> = {
ComponentBindingDialogWrapper,
ComponentTreeWrapper,
DataSourceEditorDialogWrapper,
GitHubBuildStatusWrapper,
SaveIndicatorWrapper,
LazyBarChartWrapper,
LazyLineChartWrapper,
LazyD3BarChartWrapper,
SeedDataManagerWrapper,
StorageSettingsWrapper,
}
export const jsonWrapperComponents: UIComponentRegistry = buildRegistryFromNames(
wrapperRegistryNames,
wrapperComponentMap
export const moleculeComponents: UIComponentRegistry = buildRegistryFromEntries(
'molecules',
moleculeComponentMap,
sourceAliases.molecules
)
const iconComponentMap: Record<string, ComponentType<any>> = {
ArrowLeft,
ArrowRight,
Check,
X,
Plus,
Minus,
Search: MagnifyingGlass,
Filter: Funnel,
Download,
Upload,
Edit: PencilSimple,
Trash,
Eye,
EyeOff: EyeClosed,
ChevronUp: CaretUp,
ChevronDown: CaretDown,
ChevronLeft: CaretLeft,
ChevronRight: CaretRight,
Settings: Gear,
User,
Bell,
Mail: Envelope,
Calendar,
Clock,
Star,
Heart,
Share: ShareNetwork,
Link: LinkSimple,
Copy,
Save: FloppyDisk,
RefreshCw: ArrowClockwise,
AlertCircle: WarningCircle,
Info,
HelpCircle: Question,
Home: House,
Menu: ListIcon,
MoreVertical: DotsThreeVertical,
MoreHorizontal: DotsThree,
}
export const iconComponents: UIComponentRegistry = buildRegistryFromNames(
iconRegistryNames,
iconComponentMap
export const organismComponents: UIComponentRegistry = buildRegistryFromEntries(
'organisms',
organismComponentMap,
sourceAliases.organisms
)
export const jsonWrapperComponents: UIComponentRegistry = buildRegistryFromEntries(
'wrappers',
wrapperComponentMap,
sourceAliases.wrappers
)
export const iconComponents: UIComponentRegistry = buildIconRegistry()
export const uiComponentRegistry: UIComponentRegistry = {
...primitiveComponents,
...shadcnComponents,