config: fakemui,tsx,index (12 files)

This commit is contained in:
Richard Ward
2025-12-30 13:01:09 +00:00
parent 8fc4991ae9
commit 690bf9e48a
12 changed files with 413 additions and 9 deletions

View File

@@ -1,4 +1,5 @@
export { Title, Subtitle } from './Title'
export { Heading } from './Heading'
export { Label } from './Label'
export { Text } from './Text'
export { StatBadge } from './StatBadge'

View File

@@ -0,0 +1,53 @@
import React from 'react'
export interface MarkdownProps extends React.HTMLAttributes<HTMLDivElement> {
content?: string
children?: string
className?: string
}
/**
* Markdown renderer component
* Renders markdown content as HTML
* Uses the 'marked' library when available
*/
export const Markdown: React.FC<MarkdownProps> = ({
content,
children,
className = '',
...props
}) => {
const markdownContent = content || children || ''
// Simple markdown to HTML conversion for basic cases
// In production, use 'marked' library
const renderMarkdown = (md: string): string => {
return md
// Headers
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
// Bold
.replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
// Italic
.replace(/\*(.*?)\*/gim, '<em>$1</em>')
// Code blocks
.replace(/```([\s\S]*?)```/gim, '<pre><code>$1</code></pre>')
// Inline code
.replace(/`(.*?)`/gim, '<code>$1</code>')
// Links
.replace(/\[(.*?)\]\((.*?)\)/gim, '<a href="$2">$1</a>')
// Line breaks
.replace(/\n/gim, '<br />')
}
return (
<div
className={`markdown ${className}`}
dangerouslySetInnerHTML={{ __html: renderMarkdown(markdownContent) }}
{...props}
/>
)
}
export default Markdown

View File

@@ -0,0 +1,26 @@
import React from 'react'
export interface SeparatorProps extends React.HTMLAttributes<HTMLHRElement> {
orientation?: 'horizontal' | 'vertical'
decorative?: boolean
}
/**
* Separator component (alias for Divider)
* Used for visual separation between content sections
*/
export const Separator: React.FC<SeparatorProps> = ({
orientation = 'horizontal',
decorative = true,
className = '',
...props
}) => (
<hr
className={`separator separator--${orientation} ${className}`}
role={decorative ? 'presentation' : 'separator'}
aria-orientation={orientation}
{...props}
/>
)
export default Separator

View File

@@ -7,3 +7,5 @@ export { List, ListItem, ListItemButton, ListItemIcon, ListItemText, ListItemAva
export { Table, TableHead, TableBody, TableFooter, TableRow, TableCell, TableContainer, TablePagination, TableSortLabel } from './Table'
export { Tooltip } from './Tooltip'
export { Typography } from './Typography'
export { Markdown } from './Markdown'
export { Separator } from './Separator'

View File

@@ -0,0 +1,51 @@
import React from 'react'
export interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode
label?: string
helperText?: string
error?: boolean
errorMessage?: string
required?: boolean
disabled?: boolean
fullWidth?: boolean
}
/**
* FormField wraps form inputs with label, helper text, and error handling
* Compatible with Lua package declarative rendering
*/
export const FormField: React.FC<FormFieldProps> = ({
children,
label,
helperText,
error,
errorMessage,
required,
disabled,
fullWidth,
className = '',
...props
}) => (
<div
className={`form-field ${error ? 'form-field--error' : ''} ${disabled ? 'form-field--disabled' : ''} ${fullWidth ? 'form-field--full-width' : ''} ${className}`}
{...props}
>
{label && (
<label className="form-field__label">
{label}
{required && <span className="form-field__required">*</span>}
</label>
)}
<div className="form-field__control">
{children}
</div>
{(helperText || errorMessage) && (
<div className={`form-field__helper ${error ? 'form-field__helper--error' : ''}`}>
{error ? errorMessage : helperText}
</div>
)}
</div>
)
export default FormField

View File

@@ -1,20 +1,139 @@
import React, { forwardRef } from 'react'
import React, { forwardRef, useId } from 'react'
/**
* Valid input sizes
*/
export type InputSize = 'sm' | 'md' | 'lg'
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
/** Input size */
size?: InputSize
/** @deprecated Use size="sm" instead */
sm?: boolean
/** @deprecated Use size="md" instead */
md?: boolean
/** @deprecated Use size="lg" instead */
lg?: boolean
/** Error state styling */
error?: boolean
/** Error message to display */
errorMessage?: string
/** Label for the input */
label?: string
/** Helper text displayed below input */
helperText?: string
/** Full width input */
fullWidth?: boolean
/** Start adornment element */
startAdornment?: React.ReactNode
/** End adornment element */
endAdornment?: React.ReactNode
}
/**
* Get size class from props
*/
const getSizeClass = (props: InputProps): string => {
if (props.size) return `input--${props.size}`
if (props.sm) return 'input--sm'
if (props.lg) return 'input--lg'
if (props.md) return 'input--md'
return ''
}
/**
* Input component with label and error support
*
* @example
* ```tsx
* <Input label="Email" type="email" placeholder="Enter email" />
* <Input error errorMessage="This field is required" />
* ```
*/
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ sm, md, lg, error, className = '', ...props }, ref) => (
<input
ref={ref}
className={`input ${sm ? 'input--sm' : ''} ${md ? 'input--md' : ''} ${lg ? 'input--lg' : ''} ${error ? 'input--error' : ''} ${className}`}
{...props}
/>
)
(props, ref) => {
const {
size,
sm,
md,
lg,
error,
errorMessage,
label,
helperText,
fullWidth,
startAdornment,
endAdornment,
className = '',
id: idProp,
'aria-describedby': ariaDescribedBy,
...restProps
} = props
const generatedId = useId()
const id = idProp ?? generatedId
const helperId = `${id}-helper`
const errorId = `${id}-error`
const classes = [
'input',
getSizeClass(props),
error ? 'input--error' : '',
fullWidth ? 'input--full-width' : '',
startAdornment ? 'input--has-start' : '',
endAdornment ? 'input--has-end' : '',
className,
].filter(Boolean).join(' ')
// Build aria-describedby
const describedByParts: string[] = []
if (ariaDescribedBy) describedByParts.push(ariaDescribedBy)
if (error && errorMessage) describedByParts.push(errorId)
if (helperText && !error) describedByParts.push(helperId)
const describedBy = describedByParts.length > 0 ? describedByParts.join(' ') : undefined
const inputElement = (
<div className={`input-wrapper ${fullWidth ? 'input-wrapper--full-width' : ''}`}>
{startAdornment && <span className="input__adornment input__adornment--start">{startAdornment}</span>}
<input
ref={ref}
id={id}
className={classes}
aria-invalid={error}
aria-describedby={describedBy}
{...restProps}
/>
{endAdornment && <span className="input__adornment input__adornment--end">{endAdornment}</span>}
</div>
)
// If no label or helper text, return just the input
if (!label && !helperText && !errorMessage) {
return inputElement
}
return (
<div className={`input-field ${fullWidth ? 'input-field--full-width' : ''}`}>
{label && (
<label htmlFor={id} className="input__label">
{label}
{restProps.required && <span className="input__required" aria-hidden="true"> *</span>}
</label>
)}
{inputElement}
{error && errorMessage && (
<span id={errorId} className="input__error-message" role="alert">
{errorMessage}
</span>
)}
{!error && helperText && (
<span id={helperId} className="input__helper-text">
{helperText}
</span>
)}
</div>
)
}
)
Input.displayName = 'Input'

View File

@@ -20,3 +20,4 @@ export { ToggleButton, ToggleButtonGroup } from './ToggleButton'
export { Autocomplete } from './Autocomplete'
export { Rating } from './Rating'
export { ButtonBase, InputBase, FilledInput, OutlinedInput } from './InputBase'
export { FormField } from './FormField'

View File

@@ -1,5 +1,5 @@
export { Paper } from './Paper'
export { Card, CardHeader, CardContent, CardActions, CardActionArea, CardMedia } from './Card'
export { Card, CardHeader, CardContent, CardActions, CardActionArea, CardMedia, CardTitle, CardDescription, CardFooter } from './Card'
export { Accordion, AccordionSummary, AccordionDetails, AccordionActions } from './Accordion'
export { AppBar, Toolbar } from './AppBar'
export { Drawer } from './Drawer'

View File

@@ -0,0 +1,38 @@
import React from 'react'
export interface IframeProps extends React.IframeHTMLAttributes<HTMLIFrameElement> {
src: string
title: string
width?: string | number
height?: string | number
allowFullScreen?: boolean
sandbox?: string
}
/**
* Iframe component for embedded content
* Used by Lua packages for embedding external content
*/
export const Iframe: React.FC<IframeProps> = ({
src,
title,
width = '100%',
height = 400,
allowFullScreen = true,
sandbox,
className = '',
...props
}) => (
<iframe
src={src}
title={title}
width={width}
height={height}
allowFullScreen={allowFullScreen}
sandbox={sandbox}
className={`iframe ${className}`}
{...props}
/>
)
export default Iframe

View File

@@ -12,3 +12,4 @@ export { useMediaQuery, useMediaQueryUp, useMediaQueryDown, useMediaQueryBetween
export { GlobalStyles } from './GlobalStyles'
export { classNames } from './classNames'
export { ToastProvider, useToast } from './ToastContext'
export { Iframe } from './Iframe'

View File

@@ -0,0 +1,35 @@
{
"transferForm": {
"valid": [
{
"fromId": "supergod_001",
"fromName": "Current SuperGod",
"toId": "god_002",
"toName": "Promoted God",
"description": "transfer from supergod to god"
},
{
"fromId": "supergod_001",
"fromName": "Admin User",
"toId": "admin_003",
"toName": "New Admin",
"description": "transfer between admins"
},
{
"fromId": "original_owner",
"fromName": "Original Owner",
"toId": "new_owner",
"toName": "New Owner",
"description": "full ownership transfer"
}
]
},
"transferHistory": {
"columns": [
{ "id": "date", "label": "Date", "type": "date" },
{ "id": "from", "label": "From User" },
{ "id": "to", "label": "To User" },
{ "id": "reason", "label": "Reason" }
]
}
}

View File

@@ -0,0 +1,77 @@
-- Transfer Tests for ui_level6
-- Parameterized tests for supergod-level transfer functions
local describe = require("lua_test.describe")
local it = require("lua_test.it")
local it_each = require("lua_test.it_each")
local expect = require("lua_test.expect")
local cases = require("tests.transfer.cases")
local transferForm = require("transfer.transfer_form")
local transferHistory = require("transfer.transfer_history")
describe("transfer (level6)", function()
describe("transferForm", function()
it_each(cases.transferForm.valid, "$description", function(case)
local fromUser = { id = case.fromId, name = case.fromName }
local toUser = { id = case.toId, name = case.toName }
local form = transferForm(fromUser, toUser)
expect(form.type).toBe("power_transfer_form")
expect(form.fromUser.id).toBe(case.fromId)
expect(form.toUser.id).toBe(case.toId)
expect(form.confirmationRequired).toBe(true)
expect(form.warningMessage).toBeTruthy()
end)
it("should require confirmation", function()
local fromUser = { id = "supergod_1", name = "SuperGod" }
local toUser = { id = "new_god", name = "NewGod" }
local form = transferForm(fromUser, toUser)
expect(form.confirmationRequired).toBe(true)
end)
it("should include warning message", function()
local fromUser = { id = "supergod_1", name = "SuperGod" }
local toUser = { id = "new_god", name = "NewGod" }
local form = transferForm(fromUser, toUser)
expect(form.warningMessage).toContain("cannot be undone")
end)
end)
describe("transferHistory", function()
it("should return history component configuration", function()
local history = transferHistory()
expect(history.type).toBe("transfer_history")
expect(history.columns).toBeTruthy()
end)
it_each(cases.transferHistory.columns, "should have $label column", function(case)
local history = transferHistory()
local found = false
for _, col in ipairs(history.columns) do
if col.id == case.id then
expect(col.label).toBe(case.label)
if case.type then
expect(col.type).toBe(case.type)
end
found = true
break
end
end
expect(found).toBe(true)
end)
it("should have all required columns", function()
local history = transferHistory()
expect(#history.columns).toBeGreaterThanOrEqual(4)
end)
end)
end)
return {
name = "transfer.test",
description = "Tests for supergod transfer functions"
}