diff --git a/fakemui/fakemui/atoms/index.js b/fakemui/fakemui/atoms/index.js index 8c00bcc1c..f52c2b799 100644 --- a/fakemui/fakemui/atoms/index.js +++ b/fakemui/fakemui/atoms/index.js @@ -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' diff --git a/fakemui/fakemui/data-display/Markdown.tsx b/fakemui/fakemui/data-display/Markdown.tsx new file mode 100644 index 000000000..3cde3f288 --- /dev/null +++ b/fakemui/fakemui/data-display/Markdown.tsx @@ -0,0 +1,53 @@ +import React from 'react' + +export interface MarkdownProps extends React.HTMLAttributes { + 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 = ({ + 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, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^# (.*$)/gim, '

$1

') + // Bold + .replace(/\*\*(.*?)\*\*/gim, '$1') + // Italic + .replace(/\*(.*?)\*/gim, '$1') + // Code blocks + .replace(/```([\s\S]*?)```/gim, '
$1
') + // Inline code + .replace(/`(.*?)`/gim, '$1') + // Links + .replace(/\[(.*?)\]\((.*?)\)/gim, '$1') + // Line breaks + .replace(/\n/gim, '
') + } + + return ( +
+ ) +} + +export default Markdown diff --git a/fakemui/fakemui/data-display/Separator.tsx b/fakemui/fakemui/data-display/Separator.tsx new file mode 100644 index 000000000..aacaae31d --- /dev/null +++ b/fakemui/fakemui/data-display/Separator.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +export interface SeparatorProps extends React.HTMLAttributes { + orientation?: 'horizontal' | 'vertical' + decorative?: boolean +} + +/** + * Separator component (alias for Divider) + * Used for visual separation between content sections + */ +export const Separator: React.FC = ({ + orientation = 'horizontal', + decorative = true, + className = '', + ...props +}) => ( +
+) + +export default Separator diff --git a/fakemui/fakemui/data-display/index.js b/fakemui/fakemui/data-display/index.js index 24d024500..24aea8942 100644 --- a/fakemui/fakemui/data-display/index.js +++ b/fakemui/fakemui/data-display/index.js @@ -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' diff --git a/fakemui/fakemui/inputs/FormField.tsx b/fakemui/fakemui/inputs/FormField.tsx new file mode 100644 index 000000000..4c13c739b --- /dev/null +++ b/fakemui/fakemui/inputs/FormField.tsx @@ -0,0 +1,51 @@ +import React from 'react' + +export interface FormFieldProps extends React.HTMLAttributes { + 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 = ({ + children, + label, + helperText, + error, + errorMessage, + required, + disabled, + fullWidth, + className = '', + ...props +}) => ( +
+ {label && ( + + )} +
+ {children} +
+ {(helperText || errorMessage) && ( +
+ {error ? errorMessage : helperText} +
+ )} +
+) + +export default FormField diff --git a/fakemui/fakemui/inputs/Input.tsx b/fakemui/fakemui/inputs/Input.tsx index 8d12be914..707b5d468 100644 --- a/fakemui/fakemui/inputs/Input.tsx +++ b/fakemui/fakemui/inputs/Input.tsx @@ -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 { + /** 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 + * + * + * ``` + */ export const Input = forwardRef( - ({ sm, md, lg, error, className = '', ...props }, ref) => ( - - ) + (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 = ( +
+ {startAdornment && {startAdornment}} + + {endAdornment && {endAdornment}} +
+ ) + + // If no label or helper text, return just the input + if (!label && !helperText && !errorMessage) { + return inputElement + } + + return ( +
+ {label && ( + + )} + {inputElement} + {error && errorMessage && ( + + {errorMessage} + + )} + {!error && helperText && ( + + {helperText} + + )} +
+ ) + } ) Input.displayName = 'Input' diff --git a/fakemui/fakemui/inputs/index.js b/fakemui/fakemui/inputs/index.js index 322e7ebe6..c26132d26 100644 --- a/fakemui/fakemui/inputs/index.js +++ b/fakemui/fakemui/inputs/index.js @@ -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' diff --git a/fakemui/fakemui/surfaces/index.js b/fakemui/fakemui/surfaces/index.js index d657139a0..4f4bb0eb1 100644 --- a/fakemui/fakemui/surfaces/index.js +++ b/fakemui/fakemui/surfaces/index.js @@ -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' diff --git a/fakemui/fakemui/utils/Iframe.tsx b/fakemui/fakemui/utils/Iframe.tsx new file mode 100644 index 000000000..5c7bf496e --- /dev/null +++ b/fakemui/fakemui/utils/Iframe.tsx @@ -0,0 +1,38 @@ +import React from 'react' + +export interface IframeProps extends React.IframeHTMLAttributes { + 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 = ({ + src, + title, + width = '100%', + height = 400, + allowFullScreen = true, + sandbox, + className = '', + ...props +}) => ( +