diff --git a/fakemui/icons/AccountCircle.tsx b/fakemui/icons/AccountCircle.tsx
new file mode 100644
index 000000000..7979a6525
--- /dev/null
+++ b/fakemui/icons/AccountCircle.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const AccountCircle = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/AspectRatio.tsx b/fakemui/icons/AspectRatio.tsx
new file mode 100644
index 000000000..5d53dd7da
--- /dev/null
+++ b/fakemui/icons/AspectRatio.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const AspectRatio = (props: IconProps) => (
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/Checkbox.tsx b/fakemui/icons/Checkbox.tsx
new file mode 100644
index 000000000..60ea74336
--- /dev/null
+++ b/fakemui/icons/Checkbox.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const Checkbox = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/ClosedCaption.tsx b/fakemui/icons/ClosedCaption.tsx
new file mode 100644
index 000000000..1be9be583
--- /dev/null
+++ b/fakemui/icons/ClosedCaption.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const ClosedCaption = (props: IconProps) => (
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/CropFree.tsx b/fakemui/icons/CropFree.tsx
new file mode 100644
index 000000000..f9680e32a
--- /dev/null
+++ b/fakemui/icons/CropFree.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const CropFree = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/CropPortrait.tsx b/fakemui/icons/CropPortrait.tsx
new file mode 100644
index 000000000..eb6f693d7
--- /dev/null
+++ b/fakemui/icons/CropPortrait.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const CropPortrait = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/FormatAlignLeft.tsx b/fakemui/icons/FormatAlignLeft.tsx
new file mode 100644
index 000000000..52d2a1bd2
--- /dev/null
+++ b/fakemui/icons/FormatAlignLeft.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const FormatAlignLeft = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/Gamepad.tsx b/fakemui/icons/Gamepad.tsx
new file mode 100644
index 000000000..0cdfd1e08
--- /dev/null
+++ b/fakemui/icons/Gamepad.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Gamepad = (props: IconProps) => (
+
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/GridView.tsx b/fakemui/icons/GridView.tsx
new file mode 100644
index 000000000..4c9862165
--- /dev/null
+++ b/fakemui/icons/GridView.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const GridView = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/Headphones.tsx b/fakemui/icons/Headphones.tsx
new file mode 100644
index 000000000..85537ace5
--- /dev/null
+++ b/fakemui/icons/Headphones.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Headphones = (props: IconProps) => (
+
+
+
+
+
+)
diff --git a/fakemui/icons/HighQuality.tsx b/fakemui/icons/HighQuality.tsx
new file mode 100644
index 000000000..a9db5d265
--- /dev/null
+++ b/fakemui/icons/HighQuality.tsx
@@ -0,0 +1,15 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const HighQuality = (props: IconProps) => (
+
+
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/Joystick.tsx b/fakemui/icons/Joystick.tsx
new file mode 100644
index 000000000..c454cd5f0
--- /dev/null
+++ b/fakemui/icons/Joystick.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Joystick = (props: IconProps) => (
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/LocalOffer.tsx b/fakemui/icons/LocalOffer.tsx
new file mode 100644
index 000000000..d92ba5bc1
--- /dev/null
+++ b/fakemui/icons/LocalOffer.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const LocalOffer = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/LooksOne.tsx b/fakemui/icons/LooksOne.tsx
new file mode 100644
index 000000000..34ac8ba6c
--- /dev/null
+++ b/fakemui/icons/LooksOne.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const LooksOne = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/Mic.tsx b/fakemui/icons/Mic.tsx
new file mode 100644
index 000000000..db1fb8d13
--- /dev/null
+++ b/fakemui/icons/Mic.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Mic = (props: IconProps) => (
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/MicOff.tsx b/fakemui/icons/MicOff.tsx
new file mode 100644
index 000000000..d272603fb
--- /dev/null
+++ b/fakemui/icons/MicOff.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const MicOff = (props: IconProps) => (
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/Minus.tsx b/fakemui/icons/Minus.tsx
new file mode 100644
index 000000000..a3d3c5359
--- /dev/null
+++ b/fakemui/icons/Minus.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const Minus = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/Radio.tsx b/fakemui/icons/Radio.tsx
new file mode 100644
index 000000000..bd4636544
--- /dev/null
+++ b/fakemui/icons/Radio.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Radio = (props: IconProps) => (
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/Repeat.tsx b/fakemui/icons/Repeat.tsx
new file mode 100644
index 000000000..aaaab44ef
--- /dev/null
+++ b/fakemui/icons/Repeat.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Repeat = (props: IconProps) => (
+
+
+
+
+
+)
diff --git a/fakemui/icons/RepeatOne.tsx b/fakemui/icons/RepeatOne.tsx
new file mode 100644
index 000000000..938388651
--- /dev/null
+++ b/fakemui/icons/RepeatOne.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const RepeatOne = (props: IconProps) => (
+
+
+
+
+ 1
+
+)
diff --git a/fakemui/icons/Shuffle.tsx b/fakemui/icons/Shuffle.tsx
new file mode 100644
index 000000000..231591990
--- /dev/null
+++ b/fakemui/icons/Shuffle.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Shuffle = (props: IconProps) => (
+
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/Speed.tsx b/fakemui/icons/Speed.tsx
new file mode 100644
index 000000000..891973aba
--- /dev/null
+++ b/fakemui/icons/Speed.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Speed = (props: IconProps) => (
+
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/Subtitles.tsx b/fakemui/icons/Subtitles.tsx
new file mode 100644
index 000000000..35c5a7f22
--- /dev/null
+++ b/fakemui/icons/Subtitles.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Subtitles = (props: IconProps) => (
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/TableChart.tsx b/fakemui/icons/TableChart.tsx
new file mode 100644
index 000000000..8991bc5ac
--- /dev/null
+++ b/fakemui/icons/TableChart.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const TableChart = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/TextFields.tsx b/fakemui/icons/TextFields.tsx
new file mode 100644
index 000000000..68c916bcc
--- /dev/null
+++ b/fakemui/icons/TextFields.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const TextFields = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/Timer.tsx b/fakemui/icons/Timer.tsx
new file mode 100644
index 000000000..68042e538
--- /dev/null
+++ b/fakemui/icons/Timer.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { Icon, IconProps } from './Icon'
+
+export const Timer = (props: IconProps) => (
+
+
+
+
+
+
+
+
+)
diff --git a/fakemui/icons/ToggleOn.tsx b/fakemui/icons/ToggleOn.tsx
new file mode 100644
index 000000000..825e56874
--- /dev/null
+++ b/fakemui/icons/ToggleOn.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const ToggleOn = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/TouchApp.tsx b/fakemui/icons/TouchApp.tsx
new file mode 100644
index 000000000..93c2f6980
--- /dev/null
+++ b/fakemui/icons/TouchApp.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const TouchApp = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/Verified.tsx b/fakemui/icons/Verified.tsx
new file mode 100644
index 000000000..9e41926f5
--- /dev/null
+++ b/fakemui/icons/Verified.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const Verified = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/ViewColumn.tsx b/fakemui/icons/ViewColumn.tsx
new file mode 100644
index 000000000..30224ec8c
--- /dev/null
+++ b/fakemui/icons/ViewColumn.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const ViewColumn = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/ViewStream.tsx b/fakemui/icons/ViewStream.tsx
new file mode 100644
index 000000000..25b8d2cca
--- /dev/null
+++ b/fakemui/icons/ViewStream.tsx
@@ -0,0 +1,7 @@
+import { Icon, IconProps } from './Icon'
+
+export const ViewStream = (props: IconProps) => (
+
+
+
+)
diff --git a/fakemui/icons/index.ts b/fakemui/icons/index.ts
index 5b5008f3b..c2e50b41a 100644
--- a/fakemui/icons/index.ts
+++ b/fakemui/icons/index.ts
@@ -279,3 +279,56 @@ export { Notifications } from './Notifications'
export { NotificationsOff } from './NotificationsOff'
export { ContentCopy } from './ContentCopy'
export { ContentPaste } from './ContentPaste'
+
+// Component Icon Map
+export { Article } from './Article'
+export { Checkbox } from './Checkbox'
+export { CropFree } from './CropFree'
+export { CropPortrait } from './CropPortrait'
+export { GridView } from './GridView'
+export { LocalOffer } from './LocalOffer'
+export { LooksOne } from './LooksOne'
+export { Minus } from './Minus'
+export { TableChart } from './TableChart'
+export { TextFields } from './TextFields'
+export { ToggleOn } from './ToggleOn'
+export { TouchApp } from './TouchApp'
+export { Verified } from './Verified'
+export { ViewColumn } from './ViewColumn'
+export { ViewStream } from './ViewStream'
+export { AccountCircle } from './AccountCircle'
+export { FormatAlignLeft } from './FormatAlignLeft'
+
+// Media Center Icons
+export { FilmSlate } from './FilmSlate'
+export { Queue } from './Queue'
+export { Tv } from './Tv'
+export { Transform } from './Transform'
+export { PictureAsPdf } from './PictureAsPdf'
+export { Article } from './Article'
+export { MenuBook } from './MenuBook'
+export { SportsEsports } from './SportsEsports'
+export { CameraAlt } from './CameraAlt'
+export { FastForward } from './FastForward'
+export { FastRewind } from './FastRewind'
+export { Fullscreen } from './Fullscreen'
+export { FullscreenExit } from './FullscreenExit'
+export { People } from './People'
+export { ArrowUpDown } from './ArrowUpDown'
+export { SkipPrevious } from './SkipPrevious'
+export { SkipNext } from './SkipNext'
+export { Shuffle } from './Shuffle'
+export { Repeat } from './Repeat'
+export { RepeatOne } from './RepeatOne'
+export { Gamepad } from './Gamepad'
+export { Joystick } from './Joystick'
+export { Radio } from './Radio'
+export { Headphones } from './Headphones'
+export { Mic } from './Mic'
+export { MicOff } from './MicOff'
+export { Subtitles } from './Subtitles'
+export { ClosedCaption } from './ClosedCaption'
+export { HighQuality } from './HighQuality'
+export { AspectRatio } from './AspectRatio'
+export { Speed } from './Speed'
+export { Timer } from './Timer'
diff --git a/frontends/nextjs/src/lib/rendering/component-registry.ts b/frontends/nextjs/src/lib/rendering/component-registry.ts
new file mode 100644
index 000000000..5e63c2016
--- /dev/null
+++ b/frontends/nextjs/src/lib/rendering/component-registry.ts
@@ -0,0 +1,224 @@
+/**
+ * Maps Lua component type names to fakemui React components
+ * Used by the declarative renderer to resolve component types from Lua packages
+ */
+
+import {
+ Box,
+ Stack,
+ Grid,
+ Container,
+ Flex,
+ Paper,
+ Card,
+ CardHeader,
+ CardContent,
+ CardActions,
+ Button,
+ IconButton,
+ Input,
+ TextField,
+ Textarea,
+ Select,
+ NativeSelect,
+ Checkbox,
+ Radio,
+ RadioGroup,
+ Switch,
+ Slider,
+ FormControl,
+ FormGroup,
+ FormLabel,
+ FormHelperText,
+ ButtonGroup,
+ Typography,
+ Avatar,
+ Badge,
+ Chip,
+ Divider,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+ Table,
+ TableHead,
+ TableBody,
+ TableRow,
+ TableCell,
+ TableContainer,
+ Tabs,
+ Tab,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Alert,
+ Snackbar,
+ Menu,
+ MenuItem,
+ Tooltip,
+ AppBar,
+ Toolbar,
+ Breadcrumbs,
+ Link,
+ CircularProgress,
+ LinearProgress,
+ Skeleton,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Stepper,
+ Step,
+ StepLabel,
+ Pagination,
+} from '@/fakemui'
+
+import type { ComponentType } from 'react'
+
+/**
+ * Type definition for component props from Lua
+ */
+export interface LuaComponentProps {
+ [key: string]: unknown
+ children?: React.ReactNode
+}
+
+/**
+ * Component registry mapping Lua type names to React components
+ */
+export const componentRegistry: Record> = {
+ // Layout
+ Box,
+ Stack,
+ Grid,
+ Container,
+ Flex,
+
+ // Surfaces
+ Paper,
+ Card,
+ CardHeader,
+ CardContent,
+ CardActions,
+ CardTitle: CardHeader, // Alias
+ CardFooter: CardActions, // Alias
+
+ // Inputs
+ Button,
+ IconButton,
+ ButtonGroup,
+ Input,
+ TextField,
+ TextArea: Textarea,
+ Textarea,
+ Select,
+ NativeSelect,
+ Checkbox,
+ Radio,
+ RadioGroup,
+ Switch,
+ Slider,
+
+ // Form elements
+ FormControl,
+ FormGroup,
+ FormLabel,
+ FormHelperText,
+
+ // Typography & Data Display
+ Typography,
+ Text: Typography,
+ Avatar,
+ Badge,
+ Chip,
+ Divider,
+
+ // Lists
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+
+ // Tables
+ Table,
+ TableHead,
+ TableHeader: TableHead, // Alias
+ TableBody,
+ TableRow,
+ TableCell,
+ TableContainer,
+
+ // Navigation
+ Tabs,
+ Tab,
+ TabsList: Tabs, // Map to Tabs container
+ TabsTrigger: Tab, // Map to Tab
+ TabsContent: Box, // Content wrapper
+ Breadcrumbs,
+ Link,
+ Menu,
+ MenuItem,
+ Pagination,
+ Stepper,
+ Step,
+ StepLabel,
+
+ // Feedback
+ Alert,
+ Snackbar,
+ Toast: Snackbar, // Alias
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ CircularProgress,
+ LinearProgress,
+ Progress: LinearProgress, // Alias
+ Spinner: CircularProgress, // Alias
+ Skeleton,
+
+ // Surfaces
+ AppBar,
+ Toolbar,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+
+ // Utils
+ Tooltip,
+}
+
+/**
+ * Get a React component by its Lua type name
+ * @param typeName - The component type name from Lua
+ * @returns The corresponding React component or undefined
+ */
+export function getComponentByType(typeName: string): ComponentType | undefined {
+ return componentRegistry[typeName]
+}
+
+/**
+ * Check if a component type is registered
+ * @param typeName - The component type name to check
+ * @returns True if the component is registered
+ */
+export function hasComponent(typeName: string): boolean {
+ return typeName in componentRegistry
+}
+
+/**
+ * Get all registered component type names
+ * @returns Array of component type names
+ */
+export function getRegisteredComponentTypes(): string[] {
+ return Object.keys(componentRegistry)
+}
+
+/**
+ * Register a custom component for Lua rendering
+ * @param typeName - The type name to use in Lua
+ * @param component - The React component to render
+ */
+export function registerComponent(typeName: string, component: ComponentType): void {
+ componentRegistry[typeName] = component
+}
diff --git a/frontends/nextjs/tsconfig.json b/frontends/nextjs/tsconfig.json
index 407c3629c..131d94958 100644
--- a/frontends/nextjs/tsconfig.json
+++ b/frontends/nextjs/tsconfig.json
@@ -29,6 +29,12 @@
"@/*": [
"./src/*"
],
+ "@/fakemui": [
+ "../../fakemui"
+ ],
+ "@/fakemui/*": [
+ "../../fakemui/*"
+ ],
"@/dbal": [
"../../dbal/development/src"
],
diff --git a/packages/ui_level3/seed/metadata.json b/packages/ui_level3/seed/metadata.json
index 59ad99f46..cf28a8f60 100644
--- a/packages/ui_level3/seed/metadata.json
+++ b/packages/ui_level3/seed/metadata.json
@@ -26,11 +26,13 @@
"tests": {
"scripts": [
"tests/metadata.test.lua",
- "tests/components.test.lua"
+ "tests/components.test.lua",
+ "tests/moderation.test.lua"
],
"cases": [
"tests/metadata.cases.json",
- "tests/components.cases.json"
+ "tests/components.cases.json",
+ "tests/moderation.cases.json"
]
},
"minLevel": 3