config: packages,lua,config (16 files)

This commit is contained in:
Richard Ward
2025-12-30 14:27:23 +00:00
parent 087a9d2526
commit 1fd0873cb8
16 changed files with 598 additions and 85 deletions

View File

@@ -1,44 +1,35 @@
'use client'
import { Typography } from '@mui/material'
import { Label as FakemuiLabel } from '@/fakemui'
import { forwardRef, LabelHTMLAttributes } from 'react'
/**
* Props for the Label component
* @extends {LabelHTMLAttributes} Inherits HTML label element attributes
* Wrapper around fakemui Label to maintain API compatibility
*/
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
/** Whether to display a required indicator (*) */
required?: boolean
/** Whether to style the label as an error state */
error?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
}
const Label = forwardRef<HTMLLabelElement, LabelProps>(
({ children, required, error, ...props }, ref) => {
({ children, required, error, sx, className, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [
className,
sx?.className,
error ? 'label--error' : '',
].filter(Boolean).join(' ')
return (
<Typography
component="label"
ref={ref}
variant="body2"
fontWeight={500}
sx={{
display: 'inline-block',
mb: 0.5,
color: error ? 'error.main' : 'text.primary',
'&.Mui-disabled': {
opacity: 0.5,
},
}}
{...props}
>
<label ref={ref} className={`label ${combinedClassName}`} {...props}>
{children}
{required && (
<Typography component="span" color="error.main" sx={{ ml: 0.5 }}>
*
</Typography>
)}
</Typography>
{required && <span className="label__required"> *</span>}
</label>
)
}
)

View File

@@ -1,55 +1,59 @@
'use client'
import { Link as MuiLink, LinkProps as MuiLinkProps } from '@mui/material'
import { Link as FakemuiLink } from '@/fakemui'
import NextLink, { LinkProps as NextLinkProps } from 'next/link'
import { forwardRef } from 'react'
export interface LinkProps extends Omit<MuiLinkProps, 'href'> {
/**
* Props for the Link component
* Wrapper around fakemui Link with Next.js integration
*/
export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
/** Link href (Next.js or external URL) */
href: NextLinkProps['href']
/** Whether this is an external link */
external?: boolean
/** Link underline style */
underline?: 'none' | 'hover' | 'always'
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** MUI component prop (ignored for compatibility) */
component?: any
}
const Link = forwardRef<HTMLAnchorElement, LinkProps>(
({ href, external, children, sx, ...props }, ref) => {
({ href, external, children, underline = 'hover', sx, className, component, ...props }, ref) => {
// Combine className with any sx-based classes
const combinedClassName = [className, sx?.className].filter(Boolean).join(' ')
if (external) {
return (
<MuiLink
<FakemuiLink
ref={ref}
href={href as string}
target="_blank"
rel="noopener noreferrer"
sx={{
color: 'primary.main',
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
...sx,
}}
underline={underline}
className={combinedClassName}
{...props}
>
{children}
</MuiLink>
</FakemuiLink>
)
}
// For internal links, wrap fakemui Link with Next.js Link
return (
<MuiLink
ref={ref}
component={NextLink}
href={href}
sx={{
color: 'primary.main',
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
...sx,
}}
{...props}
>
{children}
</MuiLink>
<NextLink href={href} passHref legacyBehavior>
<FakemuiLink
ref={ref}
underline={underline}
className={combinedClassName}
{...props}
>
{children}
</FakemuiLink>
</NextLink>
)
}
)

View File

@@ -1,6 +1,6 @@
'use client'
import { Typography, TypographyProps } from '@mui/material'
import { Text as FakemuiText, Typography } from '@/fakemui'
import { forwardRef } from 'react'
export type TextVariant =
@@ -20,44 +20,77 @@ export type TextVariant =
export type TextWeight = 'light' | 'regular' | 'medium' | 'semibold' | 'bold'
export type TextAlign = 'left' | 'center' | 'right' | 'justify'
export interface TextProps extends Omit<TypographyProps, 'variant' | 'align'> {
/**
* Props for the Text component
* Wrapper around fakemui Text/Typography to maintain API compatibility
*/
export interface TextProps extends React.HTMLAttributes<HTMLElement> {
/** Typography variant (MUI compatibility) */
variant?: TextVariant
/** Font weight */
weight?: TextWeight
/** Text alignment */
align?: TextAlign
/** Muted/secondary text style */
muted?: boolean
/** Truncate text with ellipsis */
truncate?: boolean
/** MUI sx prop - converted to className for compatibility */
sx?: any
/** MUI component prop - specify HTML element */
component?: React.ElementType
}
const weightMap = {
light: 300,
regular: 400,
medium: 500,
semibold: 600,
bold: 700,
light: 'font-light',
regular: 'font-normal',
medium: 'font-medium',
semibold: 'font-semibold',
bold: 'font-bold',
}
const Text = forwardRef<HTMLElement, TextProps>(
(
{ variant = 'body1', weight = 'regular', align = 'left', muted, truncate, sx, ...props },
{ variant = 'body1', weight = 'regular', align = 'left', muted, truncate, sx, className, component, ...props },
ref
) => {
// For heading variants, use Typography
if (variant && ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(variant)) {
const combinedClassName = [
className,
sx?.className,
weightMap[weight],
muted ? 'text-secondary' : '',
truncate ? 'truncate' : '',
`text-${align}`,
].filter(Boolean).join(' ')
return (
<Typography
ref={ref as any}
variant={variant}
className={combinedClassName}
as={component}
{...props}
/>
)
}
// For body/caption variants, use fakemui Text
const combinedClassName = [
className,
sx?.className,
weightMap[weight],
`text-${align}`,
].filter(Boolean).join(' ')
return (
<Typography
ref={ref}
variant={variant}
align={align}
sx={{
fontWeight: weightMap[weight],
...(muted && {
color: 'text.secondary',
}),
...(truncate && {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}),
...sx,
}}
<FakemuiText
ref={ref as any}
secondary={muted}
truncate={truncate}
className={combinedClassName}
as={component || 'span'}
{...props}
/>
)

View File

@@ -7,6 +7,41 @@ local M = {}
---@param props StatCardProps
---@return UIComponent
function M.create(props)
-- Build children array with optional icon
local contentChildren = {}
-- Add icon if provided
if props.icon then
table.insert(contentChildren, {
type = "Icon",
props = { name = props.icon, size = "large", className = "mb-2" }
})
end
-- Add label
table.insert(contentChildren, {
type = "Typography",
props = { variant = "overline", text = props.label, className = "text-muted-foreground" }
})
-- Add value
table.insert(contentChildren, {
type = "Typography",
props = { variant = "h4", text = tostring(props.value) }
})
-- Add change indicator if provided
if props.change then
table.insert(contentChildren, {
type = "Typography",
props = {
variant = "caption",
text = props.change,
className = props.positive and "text-green-500" or "text-red-500"
}
})
end
return {
type = "Card",
props = { className = props.className },
@@ -14,14 +49,7 @@ function M.create(props)
{
type = "CardContent",
props = { className = "p-6" },
children = {
{ type = "Typography", props = { variant = "overline", text = props.label, className = "text-muted-foreground" } },
{ type = "Typography", props = { variant = "h4", text = tostring(props.value) } },
props.change and {
type = "Typography",
props = { variant = "caption", text = props.change, className = props.positive and "text-green-500" or "text-red-500" }
} or nil
}
children = contentChildren
}
}
}

View File

@@ -8,6 +8,7 @@
---@class StatCardProps
---@field label string
---@field value number|string
---@field icon? string Icon name from fakemui icons
---@field change? string
---@field positive? boolean
---@field className? string

View File

@@ -0,0 +1,73 @@
-- Icon mappings for form builder components
-- This module provides icon names that work with fakemui icons
---@class FormBuilderIcons
local M = {}
---Common form icon names mapped to fakemui icons
M.icons = {
-- Validation states
CHECK_CIRCLE = "CheckCircle",
CHECK_CIRCLE_OUTLINE = "CheckCircleOutline",
ERROR = "CircleX",
ERROR_OUTLINE = "ErrorOutline",
WARNING = "Warning",
WARNING_AMBER = "WarningAmber",
INFO = "Info",
INFO_OUTLINED = "InfoOutlined",
-- Field types
TEXT_FIELDS = "TextFields",
EMAIL = "Email",
LOCK = "Lock",
CALENDAR = "Calendar",
DATE_RANGE = "DateRange",
ACCESS_TIME = "AccessTime",
CHECKBOX = "Checkbox",
RADIO = "Radio",
TOGGLE_ON = "ToggleOn",
-- Actions
ADD = "Add",
REMOVE = "Remove",
EDIT = "Edit",
DELETE = "Delete",
SAVE = "Save",
CLEAR = "Clear",
CLOSE = "Close",
-- Form controls
VISIBILITY = "Visibility",
VISIBILITY_OFF = "VisibilityOff",
SEARCH = "Search",
FILTER_LIST = "FilterList",
-- File & media
ATTACH_FILE = "AttachFile",
INSERT_PHOTO = "InsertPhoto",
UPLOAD = "Upload",
FILE_UPLOAD = "UploadSimple",
-- Rich text
FORMAT_BOLD = "FormatBold",
FORMAT_ITALIC = "FormatItalic",
FORMAT_UNDERLINE = "FormatUnderline",
FORMAT_LIST_BULLETED = "FormatListBulleted",
FORMAT_LIST_NUMBERED = "FormatListNumbered",
INSERT_LINK = "InsertLink",
-- Form structure
FORM = "Form",
ARTICLE = "Article",
DESCRIPTION = "Description",
LABEL = "LocalOffer",
}
---Get icon name for a form element
---@param key string Icon key (e.g., "CHECK_CIRCLE")
---@return string icon_name The fakemui icon name
function M.get(key)
return M.icons[key] or "Form"
end
return M

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,32 @@
{
"packageId": "smtp_config",
"name": "SMTP Config",
"version": "1.0.0",
"description": "SMTP configuration editor for email settings",
"icon": "static_content/icon.svg",
"author": "MetaBuilder",
"category": "config",
"dependencies": [],
"devDependencies": ["lua_test"],
"exports": {
"components": [
"SMTPConfigEditor",
"SMTPTestPanel"
],
"scripts": [
"smtp",
"validate"
]
},
"tests": {
"scripts": [
"tests/smtp.test.lua",
"tests/validate.test.lua"
],
"cases": [
"tests/smtp.cases.json",
"tests/validate.cases.json"
]
},
"minLevel": 5
}

View File

@@ -0,0 +1,61 @@
{
"defaults": {
"host": "smtp.example.com",
"port": 587,
"username": "",
"password": "",
"fromEmail": "noreply@metabuilder.com",
"fromName": "MetaBuilder System",
"secure": true
},
"fields": [
{
"name": "host",
"label": "SMTP Host",
"type": "text",
"placeholder": "smtp.example.com",
"required": true
},
{
"name": "port",
"label": "SMTP Port",
"type": "number",
"placeholder": "587",
"required": true
},
{
"name": "username",
"label": "Username",
"type": "text",
"placeholder": "your-username",
"required": true
},
{
"name": "password",
"label": "Password",
"type": "password",
"placeholder": "your-password",
"required": true
},
{
"name": "fromEmail",
"label": "From Email",
"type": "email",
"placeholder": "noreply@metabuilder.com",
"required": true
},
{
"name": "fromName",
"label": "From Name",
"type": "text",
"placeholder": "MetaBuilder System",
"required": true
},
{
"name": "secure",
"label": "Use Secure Connection (TLS/SSL)",
"type": "boolean",
"required": false
}
]
}

View File

@@ -0,0 +1,94 @@
-- SMTP configuration module
require("smtp.types")
local json = require("json")
---@class SMTPModule
local M = {}
-- Load SMTP configuration data from JSON
---@type SMTPConfigData
M.configData = json.load("config.json")
---Get default SMTP configuration
---@return SMTPConfig
function M.getDefaults()
return M.configData.defaults
end
---Get all field definitions
---@return SMTPField[]
function M.getFields()
return M.configData.fields
end
---Get a specific field definition
---@param name string
---@return SMTPField|nil
function M.getField(name)
for _, field in ipairs(M.configData.fields) do
if field.name == name then
return field
end
end
return nil
end
---Validate SMTP configuration
---@param config SMTPConfig
---@return ValidationResult
function M.validate(config)
local errors = {}
-- Validate host
if not config.host or config.host == "" then
errors.host = "SMTP host is required"
end
-- Validate port
if not config.port or config.port < 1 or config.port > 65535 then
errors.port = "Port must be between 1 and 65535"
end
-- Validate username
if not config.username or config.username == "" then
errors.username = "Username is required"
end
-- Validate password
if not config.password or config.password == "" then
errors.password = "Password is required"
end
-- Validate fromEmail
if not config.fromEmail or config.fromEmail == "" then
errors.fromEmail = "From email is required"
elseif not string.match(config.fromEmail, "^[^%s@]+@[^%s@]+%.[^%s@]+$") then
errors.fromEmail = "Invalid email format"
end
-- Validate fromName
if not config.fromName or config.fromName == "" then
errors.fromName = "From name is required"
end
return {
valid = next(errors) == nil,
errors = errors
}
end
---Create a default configuration
---@return SMTPConfig
function M.createDefault()
return {
host = M.configData.defaults.host,
port = M.configData.defaults.port,
username = M.configData.defaults.username,
password = M.configData.defaults.password,
fromEmail = M.configData.defaults.fromEmail,
fromName = M.configData.defaults.fromName,
secure = M.configData.defaults.secure
}
end
return M

View File

@@ -0,0 +1,34 @@
-- Type definitions for SMTP configuration
---@class SMTPConfig
---@field host string
---@field port number
---@field username string
---@field password string
---@field fromEmail string
---@field fromName string
---@field secure boolean
---@class SMTPField
---@field name string
---@field label string
---@field type "text"|"number"|"email"|"password"|"boolean"
---@field placeholder? string
---@field required boolean
---@class SMTPConfigData
---@field defaults SMTPConfig
---@field fields SMTPField[]
---@class SMTPEditorProps
---@field config? SMTPConfig
---@field testEmail? string
---@class ValidationResult
---@field valid boolean
---@field errors table<string, string>
---@class UIComponent
---@field type string
---@field props? table
---@field children? UIComponent[]

View File

@@ -0,0 +1,18 @@
-- Add a workflow step
require("editor.types")
local M = {}
---@param step_type string
---@param config table?
---@return WorkflowStep
function M.add_step(step_type, config)
return {
type = "workflow_step",
step_type = step_type,
config = config or {},
position = { x = 0, y = 0 }
}
end
return M

View File

@@ -0,0 +1,19 @@
-- Connect two workflow steps
require("editor.types")
local M = {}
---@param from_id string
---@param to_id string
---@param condition string?
---@return Connection
function M.connect_steps(from_id, to_id, condition)
return {
type = "connection",
from = from_id,
to = to_id,
condition = condition
}
end
return M

View File

@@ -0,0 +1,19 @@
-- Render workflow editor component
require("editor.types")
local M = {}
---@param workflow Workflow?
---@return WorkflowEditor
function M.render(workflow)
return {
type = "workflow_editor",
props = {
id = workflow and workflow.id,
name = workflow and workflow.name or "New Workflow",
steps = workflow and workflow.steps or {}
}
}
end
return M

View File

@@ -0,0 +1,33 @@
-- Type definitions for workflow editor
---@class Workflow
---@field id string?
---@field name string?
---@field steps table[]?
---@class Position
---@field x number
---@field y number
---@class WorkflowStep
---@field type string
---@field step_type string
---@field config table
---@field position Position
---@class Connection
---@field type string
---@field from string
---@field to string
---@field condition string?
---@class WorkflowEditorProps
---@field id string?
---@field name string
---@field steps table[]
---@class WorkflowEditor
---@field type string
---@field props WorkflowEditorProps
return {}

View File

@@ -0,0 +1,72 @@
-- Icon mappings for workflow editor components
-- This module provides icon names that work with fakemui icons
---@class WorkflowIcons
local M = {}
---Common workflow icon names mapped to fakemui icons
M.icons = {
-- Workflow elements
WORKFLOW = "Workflow",
GIT_BRANCH = "GitBranch",
CALL_SPLIT = "CallSplit",
ACCOUNT_TREE = "AccountTree",
-- Node types
PLAY = "Play",
PLAY_ARROW = "PlayArrow",
STOP = "Stop",
PAUSE = "Pause",
CHECK_CIRCLE = "CheckCircle",
CIRCLE_X = "CircleX",
-- Actions
ADD = "Add",
ADD_CIRCLE = "AddCircle",
REMOVE = "Remove",
REMOVE_CIRCLE = "RemoveCircle",
EDIT = "Edit",
DELETE = "Delete",
-- Flow control
ARROW_RIGHT = "ArrowRight",
ARROW_DOWN = "ArrowDown",
CALL_SPLIT = "CallSplit",
NAVIGATE_NEXT = "NavigateNext",
-- Status
CHECK = "Check",
DONE = "Done",
ERROR = "CircleX",
WARNING = "Warning",
INFO = "Info",
PENDING = "Clock",
-- Tools
BUILD = "Build",
CODE = "Code",
TERMINAL = "Terminal",
SETTINGS = "Settings",
-- Save & Export
SAVE = "Save",
DOWNLOAD = "Download",
UPLOAD = "Upload",
EXPORT = "Export",
-- View controls
ZOOM_IN = "ZoomIn",
ZOOM_OUT = "ZoomOut",
FULLSCREEN = "Fullscreen",
FULLSCREEN_EXIT = "FullscreenExit",
CENTER_FOCUS = "CenterFocusStrong",
}
---Get icon name for a workflow element
---@param key string Icon key (e.g., "WORKFLOW")
---@return string icon_name The fakemui icon name
function M.get(key)
return M.icons[key] or "Workflow"
end
return M