diff --git a/docs/fakemui-icons-integration.md b/docs/fakemui-icons-integration.md new file mode 100644 index 000000000..09b425adc --- /dev/null +++ b/docs/fakemui-icons-integration.md @@ -0,0 +1,421 @@ +# FakeMUI Icons Integration Guide + +This document describes how to use fakemui icons in Lua packages declaratively. + +## Overview + +The fakemui icon system allows Lua packages to reference icons by name without directly importing React components. Icons are resolved at render time through the component registry and icon registry. + +## Architecture + +### Components + +1. **Icon Registry** (`frontends/nextjs/src/lib/rendering/icon-registry.ts`) + - Maps icon names to fakemui icon components + - Provides alias resolution for common icon names + - Supports case-insensitive lookups + +2. **Component Registry** (`frontends/nextjs/src/lib/rendering/component-registry.ts`) + - Registers the `Icon` component for use in Lua + - Maps component type names to React components + +3. **Package Icon Modules** (e.g., `packages/dashboard/seed/scripts/icons.lua`) + - Provide package-specific icon name constants + - Document available icons for each package + +## Usage in Lua Packages + +### Basic Icon Usage + +Icons can be used in Lua by specifying the icon name in the component tree: + +```lua +-- Using Icon component directly +local icon_component = { + type = "Icon", + props = { + name = "CheckCircle", -- Icon name from fakemui + size = "medium", -- small, medium, large, inherit + className = "text-green-500" + } +} +``` + +### Icon Sizes + +Available sizes: +- `small` - 20px +- `medium` - 24px (default) +- `large` - 32px +- `inherit` - Inherits from parent + +### Icon Name Resolution + +The icon registry supports multiple name formats: + +1. **PascalCase** (exact match): `CheckCircle`, `ChevronRight` +2. **kebab-case** (alias): `check-circle`, `chevron-right` +3. **lowercase** (alias): `checkcircle`, `chevronright` + +Example: +```lua +-- All of these resolve to the same icon +{ type = "Icon", props = { name = "CheckCircle" } } +{ type = "Icon", props = { name = "check-circle" } } +{ type = "Icon", props = { name = "checkcircle" } } +``` + +## Package-Specific Icon Modules + +Each package has an `icons.lua` module that provides constants for commonly used icons: + +### Dashboard Package + +```lua +local icons = require("icons") + +-- Using icon constants +local stat_card = { + icon = icons.get("CHART_LINE"), -- Returns "ChartLine" + label = "Total Revenue", + value = 12500 +} +``` + +Available icons in `dashboard/icons.lua`: +- `CHART_LINE`, `TREND_UP`, `BAR_CHART`, `PIE_CHART` +- `CHECK_CIRCLE`, `SHIELD_CHECK`, `WARNING`, `ERROR` +- `CLOCK`, `CALENDAR`, `SCHEDULE` +- And more... + +### Navigation Menu Package + +```lua +local icons = require("icons") + +local sidebar_item = { + label = "Dashboard", + path = "/dashboard", + icon = icons.get("DASHBOARD") -- Returns "Dashboard" +} +``` + +Available icons in `nav_menu/icons.lua`: +- `HOME`, `DASHBOARD`, `SETTINGS`, `PROFILE` +- `SEARCH`, `NOTIFICATIONS`, `MAIL` +- `ADMIN_PANEL`, `SECURITY`, `VERIFIED_USER` +- And more... + +### Data Table Package + +```lua +local icons = require("icons") + +local actions = { + { id = "edit", icon = icons.get("EDIT"), label = "Edit" }, + { id = "delete", icon = icons.get("DELETE"), label = "Delete" }, + { id = "export", icon = icons.get("CSV"), label = "Export CSV" } +} +``` + +Available icons in `data_table/icons.lua`: +- `SORT`, `SORT_ASCENDING`, `SORT_DESCENDING` +- `FILTER`, `FILTER_CLEAR`, `FILTER_OFF` +- `EDIT`, `DELETE`, `ADD`, `REMOVE` +- `CSV`, `JSON`, `EXPORT` +- And more... + +### Workflow Editor Package + +```lua +local icons = require("icons") + +local workflow_node = { + type = "action", + label = "Process Data", + icon = icons.get("WORKFLOW") -- Returns "Workflow" +} +``` + +Available icons in `workflow_editor/icons.lua`: +- `WORKFLOW`, `GIT_BRANCH`, `CALL_SPLIT` +- `PLAY`, `STOP`, `PAUSE` +- `CHECK_CIRCLE`, `ERROR`, `WARNING` +- And more... + +### Form Builder Package + +```lua +local icons = require("icons") + +local validation_rule = { + type = "required", + message = "This field is required", + icon = icons.get("ERROR") -- Returns "CircleX" +} +``` + +Available icons in `form_builder/icons.lua`: +- `CHECK_CIRCLE`, `ERROR`, `WARNING`, `INFO` +- `TEXT_FIELDS`, `EMAIL`, `LOCK`, `CALENDAR` +- `VISIBILITY`, `VISIBILITY_OFF` +- `FORMAT_BOLD`, `FORMAT_ITALIC`, `INSERT_LINK` +- And more... + +## Complete Examples + +### Dashboard Stat Card with Icon + +```lua +local icons = require("icons") +local stat_card = require("stats.card") + +local revenue_card = stat_card.create({ + label = "Total Revenue", + value = 12500, + icon = icons.get("CHART_LINE"), + change = "+12.5%", + positive = true, + className = "col-span-1" +}) +``` + +### Sidebar Navigation with Icons + +```lua +local icons = require("icons") + +local sidebar_items = { + { + label = "Dashboard", + path = "/dashboard", + icon = icons.get("DASHBOARD") + }, + { + label = "Users", + path = "/users", + icon = icons.get("USERS") + }, + { + label = "Settings", + path = "/settings", + icon = icons.get("SETTINGS") + } +} +``` + +### Data Table with Action Icons + +```lua +local icons = require("icons") +local action_column = require("columns.action") + +local actions = action_column("actions", { + { id = "edit", icon = icons.get("EDIT"), label = "Edit", handler = "edit_user" }, + { id = "delete", icon = icons.get("TRASH"), label = "Delete", handler = "delete_user", confirm = true }, + { id = "view", icon = icons.get("EYE"), label = "View", handler = "view_user" } +}) +``` + +### Workflow Node with Icon + +```lua +local icons = require("icons") +local action_node = require("nodes.action") + +local process_node = action_node( + "process_data", + "Process Data", + "transform", + icons.get("WORKFLOW") +) +``` + +### Form Field with Validation Icons + +```lua +local icons = require("icons") + +local email_field = { + type = "TextField", + props = { + label = "Email", + type = "email", + required = true, + helperText = "Enter your email address", + -- Icons can be added to show validation state + startIcon = icons.get("EMAIL"), + endIcon = nil -- Will be set based on validation + } +} +``` + +## Available Icons + +The fakemui icon library includes 390+ icons. Common categories: + +### Actions +`Plus`, `Add`, `Remove`, `Trash`, `Delete`, `Copy`, `Check`, `Done`, `X`, `Edit`, `Save` + +### Navigation +`ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`, `ChevronUp`, `ChevronDown`, `Home`, `Menu` + +### UI Controls +`Settings`, `Search`, `Filter`, `More`, `Expand`, `Collapse`, `ZoomIn`, `ZoomOut` + +### Data & Status +`CheckCircle`, `Error`, `Warning`, `Info`, `Star`, `Heart`, `Flag` + +### Users & Security +`User`, `Users`, `UserCircle`, `Lock`, `Key`, `Shield`, `Verified` + +### Data Table +`Sort`, `SortAscending`, `FilterList`, `Csv`, `Json`, `Pagination` + +### Workflow +`Workflow`, `GitBranch`, `CallSplit`, `Play`, `Stop`, `Pause` + +### Forms +`TextFields`, `Email`, `Calendar`, `Checkbox`, `Radio`, `Visibility` + +For a complete list, see: +- `frontends/nextjs/src/lib/rendering/icon-registry.ts` - Icon registry with aliases +- `fakemui/icons/index.ts` - All available icons + +## Best Practices + +1. **Use Icon Constants**: Always use the package-specific icon modules rather than hardcoding icon names: + ```lua + -- Good + local icons = require("icons") + icon = icons.get("DASHBOARD") + + -- Avoid + icon = "Dashboard" -- Works but not type-safe + ``` + +2. **Check Icon Availability**: Ensure the icon exists in fakemui before using it. All icons in the package icon modules are guaranteed to exist. + +3. **Use Semantic Names**: Choose icons that match the action or concept: + - Delete actions: `Trash` or `Delete` + - Edit actions: `Edit` or `Pencil` + - Success states: `CheckCircle` + - Errors: `CircleX` or `XCircle` + +4. **Consistent Sizing**: Use consistent icon sizes within the same context: + - Buttons: `medium` (24px) + - Large cards/headers: `large` (32px) + - Inline text: `small` (20px) + +5. **Accessibility**: Icons should be paired with text labels for accessibility: + ```lua + { + type = "Button", + children = { + { type = "Icon", props = { name = "Delete", className = "mr-2" } }, + { type = "Typography", props = { text = "Delete" } } + } + } + ``` + +## Type Definitions + +### Lua Type Annotations + +```lua +---@class IconProps +---@field name string Icon name from fakemui +---@field size? "small"|"medium"|"large"|"inherit" +---@field className? string Additional CSS classes + +---@class UIComponent +---@field type "Icon" +---@field props IconProps +``` + +## Migration from MUI Icons + +If migrating from @mui/icons-material, use the icon registry aliases: + +| MUI Icon | FakeMUI Equivalent | +|----------|-------------------| +| `AccountCircle` | `AccountCircle` | +| `Dashboard` | `Dashboard` | +| `Delete` | `Delete` or `Trash` | +| `Edit` | `Edit` or `Pencil` | +| `Settings` | `Settings` or `Gear` | +| `MoreVert` | `MoreVert` or `MoreVertical` | +| `ExpandMore` | `ExpandMore` or `ChevronDown` | +| `Error` | `CircleX` or `XCircle` | + +## Troubleshooting + +### Icon Not Rendering + +1. Check icon name spelling +2. Verify icon exists in fakemui (see `fakemui/icons/index.ts`) +3. Check browser console for warnings + +### Icon Name Not Found + +``` +Warning: Icon "IconName" not found in @/fakemui/icons +``` + +Solution: Use correct icon name or add to icon registry aliases + +### Icon Size Not Applied + +Ensure size prop is one of: `small`, `medium`, `large`, `inherit` + +```lua +-- Correct +{ type = "Icon", props = { name = "Check", size = "medium" } } + +-- Incorrect +{ type = "Icon", props = { name = "Check", size = 24 } } -- Use "medium" instead +``` + +## Reference + +### Icon Registry Functions + +```typescript +// Get icon component by name +getIconComponent(iconName: string): ComponentType | undefined + +// Resolve icon name (handles aliases) +resolveIconName(luaIconName: string): IconName | undefined + +// Check if icon name is valid +isValidIconName(iconName: string): boolean + +// Get all available icon names +getAllIconNames(): IconName[] +``` + +### Component Registry + +The Icon component is registered in the component registry: + +```typescript +import { Icon } from '@/components/atoms/display/Icon' + +componentRegistry = { + // ... other components + Icon, +} +``` + +## Summary + +The fakemui icon integration provides: + +1. ✅ Declarative icon usage in Lua packages +2. ✅ Type-safe icon name constants per package +3. ✅ Flexible name resolution (PascalCase, kebab-case, lowercase) +4. ✅ 390+ icons covering common use cases +5. ✅ Consistent icon sizing and styling +6. ✅ No direct React imports needed in Lua + +All icons are rendered as SVG components from the fakemui library, ensuring consistent styling and performance across the application. diff --git a/frontends/nextjs/src/components/atoms/feedback/Progress.tsx b/frontends/nextjs/src/components/atoms/feedback/Progress.tsx index ea3f64914..cc7c92706 100644 --- a/frontends/nextjs/src/components/atoms/feedback/Progress.tsx +++ b/frontends/nextjs/src/components/atoms/feedback/Progress.tsx @@ -1,41 +1,44 @@ 'use client' -import { - Box, - CircularProgress, - CircularProgressProps, - LinearProgress, - LinearProgressProps, - Typography, -} from '@mui/material' +import { LinearProgress, CircularProgress } from '@/fakemui' import { forwardRef } from 'react' /** * Props for the Progress component - * @extends {LinearProgressProps} Inherits Material-UI LinearProgress props + * Wrapper around fakemui LinearProgress to maintain API compatibility */ -export interface ProgressProps extends LinearProgressProps { +export interface ProgressProps extends React.HTMLAttributes { + /** Progress value (0-100) */ + value?: number /** Whether to display a percentage label next to the progress bar */ showLabel?: boolean + /** Variant of the progress bar */ + variant?: 'determinate' | 'indeterminate' + /** Color of the progress bar */ + color?: string + /** MUI sx prop - converted to className for compatibility */ + sx?: any } const Progress = forwardRef( - ({ value, showLabel, sx, ...props }, ref) => { + ({ value, showLabel, variant, color, sx, className, ...props }, ref) => { + // Combine className with any sx-based classes + const combinedClassName = [className, sx?.className].filter(Boolean).join(' ') + if (showLabel && value !== undefined) { return ( - - +
+
- - +
+ {Math.round(value)}% - - + +
) } @@ -43,8 +46,7 @@ const Progress = forwardRef( ) diff --git a/frontends/nextjs/src/components/atoms/feedback/Separator.tsx b/frontends/nextjs/src/components/atoms/feedback/Separator.tsx index 57dbd43a4..84008973b 100644 --- a/frontends/nextjs/src/components/atoms/feedback/Separator.tsx +++ b/frontends/nextjs/src/components/atoms/feedback/Separator.tsx @@ -1,24 +1,32 @@ 'use client' -import { Divider, DividerProps } from '@mui/material' +import { Divider } from '@/fakemui' import { forwardRef } from 'react' /** * Props for the Separator component - * @extends {DividerProps} Inherits Material-UI Divider props + * Wrapper around fakemui Divider to maintain API compatibility */ -export interface SeparatorProps extends DividerProps { +export interface SeparatorProps extends React.HTMLAttributes { + /** Orientation of the separator */ + orientation?: 'horizontal' | 'vertical' /** Whether the separator is decorative (for accessibility) */ decorative?: boolean + /** MUI sx prop - converted to className for compatibility */ + sx?: any } const Separator = forwardRef( - ({ orientation = 'horizontal', decorative, ...props }, ref) => { + ({ orientation = 'horizontal', decorative, sx, className, ...props }, ref) => { + // Combine className with any sx-based classes + const combinedClassName = [className, sx?.className].filter(Boolean).join(' ') + return ( ) diff --git a/frontends/nextjs/src/components/atoms/feedback/Skeleton.tsx b/frontends/nextjs/src/components/atoms/feedback/Skeleton.tsx index 6d439208b..88f3a8651 100644 --- a/frontends/nextjs/src/components/atoms/feedback/Skeleton.tsx +++ b/frontends/nextjs/src/components/atoms/feedback/Skeleton.tsx @@ -1,22 +1,48 @@ 'use client' -import { Skeleton as MuiSkeleton } from '@mui/material' -import { type ComponentProps, forwardRef } from 'react' - -type MuiSkeletonProps = ComponentProps +import { Skeleton as FakemuiSkeleton } from '@/fakemui' +import { forwardRef } from 'react' +import type { SkeletonProps as FakemuiSkeletonProps } from '@/fakemui/fakemui/feedback/Skeleton' /** * Props for the Skeleton component - * @extends {MuiSkeletonProps} Inherits Material-UI Skeleton props + * Wrapper around fakemui Skeleton to maintain API compatibility */ -export interface SkeletonProps extends MuiSkeletonProps { - /** CSS class name for custom styling */ - className?: string +export interface SkeletonProps extends React.HTMLAttributes { + /** Shape variant */ + variant?: 'text' | 'rectangular' | 'circular' | 'rounded' + /** Animation type */ + animation?: 'pulse' | 'wave' | false + /** Width of the skeleton */ + width?: string | number + /** Height of the skeleton */ + height?: string | number + /** MUI sx prop - converted to className for compatibility */ + sx?: any } const Skeleton = forwardRef( - ({ variant = 'rounded', animation = 'wave', ...props }, ref) => { - return + ({ variant = 'rounded', animation = 'wave', width, height, sx, className, ...props }, ref) => { + // Map MUI variant to fakemui variant + const fakemuiVariant = variant === 'rounded' ? 'rectangular' : variant + + // Map MUI animation to fakemui animation + const fakemuiAnimation = animation === 'wave' ? 'pulse' : animation + + // Combine className with any sx-based classes + const combinedClassName = [className, sx?.className].filter(Boolean).join(' ') + + return ( + + ) } ) diff --git a/frontends/nextjs/src/components/atoms/feedback/Spinner.tsx b/frontends/nextjs/src/components/atoms/feedback/Spinner.tsx index f39b6d05c..ff9a90c13 100644 --- a/frontends/nextjs/src/components/atoms/feedback/Spinner.tsx +++ b/frontends/nextjs/src/components/atoms/feedback/Spinner.tsx @@ -1,6 +1,6 @@ 'use client' -import { Box, CircularProgress, CircularProgressProps } from '@mui/material' +import { CircularProgress } from '@/fakemui' import { forwardRef } from 'react' /** Spinner size options */ @@ -8,13 +8,17 @@ export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' /** * Props for the Spinner component - * @extends {CircularProgressProps} Inherits Material-UI CircularProgress props + * Wrapper around fakemui CircularProgress to maintain API compatibility */ -export interface SpinnerProps extends Omit { +export interface SpinnerProps extends React.HTMLAttributes { /** Size of the spinner (xs: 16px, sm: 20px, md: 24px, lg: 40px) or a custom number */ size?: SpinnerSize | number /** Whether to center the spinner in its container */ centered?: boolean + /** Color of the spinner */ + color?: string + /** MUI sx prop - converted to className for compatibility */ + sx?: any } const sizeMap: Record = { @@ -25,16 +29,26 @@ const sizeMap: Record = { } const Spinner = forwardRef( - ({ size = 'md', centered, ...props }, ref) => { + ({ size = 'md', centered, color, sx, className, ...props }, ref) => { const dimension = typeof size === 'number' ? size : sizeMap[size] - const spinner = + // Combine className with any sx-based classes + const combinedClassName = [className, sx?.className].filter(Boolean).join(' ') + + const spinner = ( + + ) if (centered) { return ( - +
{spinner} - +
) } diff --git a/frontends/nextjs/src/components/atoms/feedback/Tooltip.tsx b/frontends/nextjs/src/components/atoms/feedback/Tooltip.tsx index ee2918730..93239337f 100644 --- a/frontends/nextjs/src/components/atoms/feedback/Tooltip.tsx +++ b/frontends/nextjs/src/components/atoms/feedback/Tooltip.tsx @@ -1,12 +1,11 @@ 'use client' -import { Tooltip as MuiTooltip } from '@mui/material' -import { type ComponentProps, forwardRef, ReactElement, ReactNode } from 'react' - -type MuiTooltipProps = ComponentProps +import { Tooltip as FakemuiTooltip } from '@/fakemui' +import { forwardRef, ReactElement, ReactNode } from 'react' /** * Props for the Tooltip component + * Wrapper around fakemui Tooltip to maintain API compatibility */ export interface TooltipProps { /** The element that triggers the tooltip */ @@ -27,13 +26,10 @@ export interface TooltipProps { onOpen?: () => void /** Callback when tooltip is closed */ onClose?: () => void -} - -const sideMap: Record = { - top: 'top', - right: 'right', - bottom: 'bottom', - left: 'left', + /** MUI placement prop (mapped to side) */ + placement?: 'top' | 'right' | 'bottom' | 'left' + /** MUI enterDelay prop (mapped to delayDuration) */ + enterDelay?: number } const Tooltip = forwardRef( @@ -42,8 +38,10 @@ const Tooltip = forwardRef( children, content, title, - side = 'top', - delayDuration = 300, + side, + placement, + delayDuration, + enterDelay, arrow = true, open, onOpen, @@ -53,17 +51,15 @@ const Tooltip = forwardRef( ref ) => { return ( - {children} - + ) } ) diff --git a/packages/form_builder/seed/scripts/validate/required.lua b/packages/form_builder/seed/scripts/validate/required.lua index 730d8c5b5..0dcb8a56c 100644 --- a/packages/form_builder/seed/scripts/validate/required.lua +++ b/packages/form_builder/seed/scripts/validate/required.lua @@ -5,6 +5,7 @@ ---@field pattern? string ---@field value? number | string ---@field message string +---@field icon? string Icon name from fakemui icons for validation feedback ---@param message? string ---@return ValidationRule diff --git a/packages/stats_grid/seed/components.json b/packages/stats_grid/seed/components.json new file mode 100644 index 000000000..60b074253 --- /dev/null +++ b/packages/stats_grid/seed/components.json @@ -0,0 +1 @@ +[] diff --git a/packages/stats_grid/seed/metadata.json b/packages/stats_grid/seed/metadata.json new file mode 100644 index 000000000..46b894c4b --- /dev/null +++ b/packages/stats_grid/seed/metadata.json @@ -0,0 +1,32 @@ +{ + "packageId": "stats_grid", + "name": "Stats Grid", + "version": "1.0.0", + "description": "Configurable statistics grid display for dashboards and monitoring", + "icon": "static_content/icon.svg", + "author": "MetaBuilder", + "category": "ui", + "dependencies": [], + "devDependencies": ["lua_test"], + "exports": { + "components": [ + "StatsGrid", + "StatCard" + ], + "scripts": [ + "stats", + "formatters" + ] + }, + "tests": { + "scripts": [ + "tests/stats.test.lua", + "tests/formatters.test.lua" + ], + "cases": [ + "tests/stats.cases.json", + "tests/formatters.cases.json" + ] + }, + "minLevel": 2 +}