diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 736e039..371c40d 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -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 @@ -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> -): UIComponentRegistry => { - return names.reduce((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> const deprecatedComponentInfo = jsonRegistryEntries.reduce>( (acc, entry) => { const entryName = getRegistryEntryName(entry) @@ -125,30 +55,130 @@ const deprecatedComponentInfo = jsonRegistryEntries.reduce 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 +): Record> => { + return Object.entries(exports).reduce>>((acc, [key, value]) => { + if (value && (typeof value === 'function' || typeof value === 'object')) { + acc[key] = value as ComponentType + } + return acc + }, {}) +} + +const buildComponentMapFromModules = ( + modules: Record +): Record> => { + return Object.values(modules).reduce>>((acc, moduleExports) => { + if (!moduleExports || typeof moduleExports !== 'object') { + return acc + } + Object.entries(buildComponentMapFromExports(moduleExports as Record)).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> = { + atoms: { + PageHeader: 'BasicPageHeader', + SearchInput: 'BasicSearchInput', + }, + molecules: {}, + organisms: {}, + ui: { + Chart: 'ChartContainer', + Resizable: 'ResizablePanelGroup', + }, + wrappers: {}, +} + +const iconAliases: Record = { + 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> = { + JSONUIShowcase, +} + +const buildRegistryFromEntries = ( + source: string, + componentMap: Record>, + aliases: Record = {} +): UIComponentRegistry => { + return jsonRegistryEntries + .filter((entry) => entry.source === source) + .reduce((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((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> = { - 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>).DataList, - DataTable: (AtomComponents as Record>).DataTable, - ListItem: (AtomComponents as Record>).ListItem, - MetricCard: (AtomComponents as Record>).MetricCard, - Timeline: (AtomComponents as Record>).Timeline, -} - -const breadcrumbComponent = AtomComponents.Breadcrumb ?? AtomComponents.BreadcrumbNav -if (breadcrumbComponent) { - atomComponents.Breadcrumb = breadcrumbComponent as ComponentType -} - -export const moleculeComponents: UIComponentRegistry = { - ...buildRegistryFromNames( - moleculeRegistryNames, - MoleculeComponents as Record> - ), - AppBranding: (MoleculeComponents as Record>).AppBranding, - LabelWithBadge: (MoleculeComponents as Record>).LabelWithBadge, - NavigationGroupHeader: (MoleculeComponents as Record>).NavigationGroupHeader, -} - -export const organismComponents: UIComponentRegistry = buildRegistryFromNames( - organismRegistryNames, - OrganismComponents as Record> +export const atomComponents: UIComponentRegistry = buildRegistryFromEntries( + 'atoms', + atomComponentMap, + sourceAliases.atoms ) -const wrapperComponentMap: Record> = { - 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> = { - 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,