@galaxy-dev/metadsl-adapter-mui
v0.12.0
Published
MUI 5 adapter for MetaDSL
Readme
@galaxy-dev/metadsl-adapter-mui
Material-UI 5 adapter for MetaDSL.
Installation
pnpm add @galaxy-dev/metadsl-adapter-mui @mui/material @emotion/react @emotion/styledUsage
import { DSLRenderer } from '@galaxy-dev/metadsl-runtime';
import { muiAdapter } from '@galaxy-dev/metadsl-adapter-mui';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import formDSL from './form.json';
// Optional: Create MUI theme
const theme = createTheme({
palette: {
primary: { main: '#1976d2' },
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<DSLRenderer
dsl={formDSL}
adapter={muiAdapter}
locale="zh-CN"
onSubmit={(data) => console.log(data)}
/>
</ThemeProvider>
);
}Supported Components
- ✅ Input (TextField)
- ✅ Select
- ✅ Radio (RadioGroup)
- ✅ Checkbox
- ✅ Button
- ✅ Form (Box)
- ✅ Grid
- ✅ Flex (Box with flexbox)
- ✅ Tabs
Custom Styling
You can use MUI's sx prop or className to customize components:
{
"type": "input",
"name": "email",
"label": { "en-US": "Email", "zh-CN": "邮箱" },
"className": "custom-input",
"props": {
"sx": {
"backgroundColor": "#f5f5f5",
"borderRadius": 2
}
}
}Field Chrome & Sizing Utilities (Public API)
The sizing/chrome helpers used by every built-in field are exported from the package root and are semver-stable public API. Custom fields should import them instead of hand-rolling copies, so they stay pixel-consistent with the built-ins:
import {
FIELD_HEIGHTS, // { small: 40, medium: 48 }
FIELD_PADDING, // { horizontal: 14 }
getFieldSx, // input sizing sx for OutlinedInput/Select/Autocomplete
getFormControlSx, // FormControl-level sizing sx
getControlContainerSx, // alignment container for Checkbox/Switch/Slider/...
getComboFieldBorderSx, // bordered-container styles (PhoneField-like combos)
getContainerInputPadding, // padding for custom bordered containers
FieldShell, // FormControl + FormLabel + FormHelperText chrome
} from '@galaxy-dev/metadsl-adapter-mui';
function MyCustomField(props: MyCustomFieldProps) {
return (
<FieldShell label={label} helperText={helperText} error={error} disabled={disabled}>
<MuiOutlinedInput sx={getFieldSx(size, style)} /* ... */ />
</FieldShell>
);
}Notable Field Capabilities
- Formatted amount input: set
formaton anamountfield to switch to a text input with live thousands-separator grouping (8000→8,000) and caret preservation. The value contract is a raw decimal string (separators stripped) end-to-end - no float parsing anywhere, andmin/maxclamp via exact decimal string comparison (pass string bounds for values beyond float precision), so crypto amounts are precision-safe. Form state must round-trip the emitted string verbatim (the DSL runtime does). Withoutformatthe field stays a native number input emitting numbers. clearable:text,password, andamountfields can show a clear (×) button (themeable via thedsl-field__clearclass or a customclearIcon).- Real password field:
fieldType: 'password'renders a masked input with a show/hide toggle (showToggle,autoComplete). - Native input types:
textfields forwardinputType(email/url/tel/number/search) to the input'stype/inputMode; registeredemail/numberfieldTypes do this automatically. inputFilter: per-character regex filter ontextfields (e.g."[^0-9]"strips digits from name fields).pickerVariant:date/date-time/timefields accept'auto' | 'desktop' | 'mobile'; use'desktop'when hosting forms inside a Shadow DOM where native mobile pickers break.
Architecture
🚨 IMPORTANT: Adapter Component Design Principles
1. DSL is Library-Agnostic
DSL definitions are unified and contain NO library-specific properties.
The DSL schema (ButtonDSL, FieldDSL, etc.) only defines common, universal properties that work across ALL adapters:
- ✅
label,placeholder,helperText- universal text fields - ✅
disabled,visible,readOnly- universal states - ✅
variant: 'contained' | 'outlined' | 'text'- universal button styles - ❌ NO MUI-specific props (like
sx,color: 'inherit') - ❌ NO Ant Design-specific props (like
danger,ghost)
If you need library-specific customization, use the props field:
{
"type": "button",
"label": "Submit",
"variant": "contained",
"props": {
"sx": { "borderRadius": 2 }, // MUI specific
"startIcon": "<SaveIcon />" // MUI specific
}
}2. Adapters Should NOT Process i18n or Expressions
Adapter components should NEVER process i18n or expressions themselves.
All i18n translation and expression evaluation are handled by the runtime layer:
- ComponentRenderer - processes component props (label, text, title, etc.)
- FieldRenderer - processes field props (label, placeholder, helperText, defaultValue, etc.)
Adapter components receive already processed props (translated strings, evaluated booleans/numbers):
// ❌ WRONG: Do NOT do this in adapter components
import type { ButtonDSL } from '@galaxy-dev/metadsl-core';
export interface ButtonProps extends ButtonDSL {
// This would include I18nValue types which are NOT valid as ReactNode
}
export function Button({ label }: ButtonProps) {
const { i18n } = useDSLContext();
const translatedLabel = i18n.translate(label); // ❌ Don't translate here!
return <MuiButton>{translatedLabel}</MuiButton>;
}
// ✅ CORRECT: Adapter components receive processed props
export interface ButtonProps {
label?: string; // Already translated by ComponentRenderer
loading?: boolean; // Already evaluated by ComponentRenderer
disabled?: boolean; // Already evaluated by ComponentRenderer
// ...
}
export function Button({ label, loading, disabled }: ButtonProps) {
// Use props directly - they are already processed
return (
<MuiButton disabled={disabled}>{loading ? <CircularProgress size={20} /> : label}</MuiButton>
);
}Why this matters:
- Type Safety - I18nValue (Record<string, string>) is not assignable to ReactNode
- Separation of Concerns - Runtime handles logic, adapters handle presentation
- Performance - Avoid duplicate processing
- Consistency - Same processing logic for all adapters
Exception: Components that are NOT rendered through ComponentRenderer/FieldRenderer (like form containers that wrap FormRenderer) need to process their own props using usePropsProcessor.
License
MIT
