Extend change props
We have a lot of props that require customization, let’s do them
Paper
Paper lacks the ability to customize its padding. To customize styles, we create a separate ChangeStyle plugin for this.
addBlock({ name: 'paper', allowContent: { name: ['*', '!root', '!paper'], }, style: { // This is the name of the layer, with the help of // layers you can separate styles if you have a complex block // The root layer should always be there root: { // If you don't want the default value, // you can set it to undefined padding: 4, }, }, components: { view: ({ children }) => { return <MuiPaper sx={{ p: 4 }}>{children}</MuiPaper>; view: ({ block, children }) => { return <MuiPaper sx={{ ...block.style.root }}>{children}</MuiPaper>; }, editor: ({ block, children }) => { const { view } = editor; const ref = useRef<HTMLDivElement>(null);
return ( <> <MuiPaper sx={{ p: 4 }}> <MuiPaper ref={ref} sx={{ ...block.style.root }}> {children}
<Box display="flex" justifyContent="center"> <AddNewBlock id={block.id} /> </Box> </MuiPaper>
<view.ui.ActionPopover referenceRef={ref}> <BlockToolbar id={block.id} /> </view.ui.ActionPopover> </> )import { Plugin } from '@chamaeleon/core';import TextField from '@mui/material/TextField';
export function ChangeStyle(): Plugin { return { name: 'change-style', apply(editor, { addStyleView }) { addStyleView({ // The addStyleView filter is different from addPropsView, // the first parameter comes a style specification, // exactly the object that we pass to addBlock filter(styleSpec) { return 'padding' in styleSpec; }, // "style" is the equivalent of block.style[layer] component: ({ block, style, layer }) => { return ( <TextField label="Padding" value={style.padding} onChange={(event) => { const parsed = parseFloat(event.target.value);
const isNumber = String(parsed).length === event.target.value.length;
editor.commands.changeStyle(block.id, layer, { padding: isNumber ? parsed : event.target.value, }); }} /> ); }, }); }, };}Adding to editor
import { ChangeStyle } from './builder/plugins/change-style';
const editor = new Editor({ plugins: [ // ... Root(), Paper(), Stack(), Text(), TextField(), Button(), BlockSettings() ChangeProps(), ChangeStyle() ],});Now let’s add to our BlockSettings the rendering of our components to change styles
function DrawerBody(...) { const targetBlock = editor.state.getBlock(target);
return ( <> <Stack spacing={4} p={3}> ... {editor.view.pluginPropsViews.map( ({ id, view: { filter, component: Component } }) => { if (!filter(targetBlock)) return null;
return <Component key={id} editor={editor} block={targetBlock} />; }, )}
{Object.entries(targetBlock.type.style) .map(([layer, cssProperties]) => { return ( <Stack key={layer} spacing={3}> <Typography sx={{ lineHeight: '3rem', borderBottom: '1px solid #ccc' }} > {layer} </Typography>
{editor.view.pluginStyleViews.map( ({ id, view: { filter, component: Component } }) => { if (!targetBlock) return null;
if (!filter(cssProperties, targetBlock, layer)) return null;
return ( <Component key={id} editor={editor} layer={layer} styleSpec={cssProperties} style={targetBlock.style[layer] || {}} block={targetBlock} /> ); }, )} </Stack> ); }) .flat()} </Stack> </> );}Great, we now have the ability to edit the padding, now if we need to add it to another component, all we need to do is add the padding to the component’s styles section
Stack
For the Stack we need to add spacing and direction prop settings
addBlock({ name: 'stack', allowContent: { name: ['*', '!root'], }, props: { spacing: { default: 4, }, direction: { default: 'column', }, }, components: { view: ({ children }) => { view: ({ block, children }) => { return ( <MuiStack spacing={4}> <MuiStack spacing={block.props.spacing} direction={block.props.direction} > {children} </MuiStack> ); }, editor: ({ block, children }) => { const { view } = editor;
const ref = useRef<HTMLDivElement>(null);
return ( <> <MuiStack spacing={4}> <MuiStack ref={ref} spacing={block.props.spacing} direction={block.props.direction} sx={{ px: 2 }} > {children}
<Box display="flex" justifyContent="center"> <AddNewBlock id={block.id} /> </Box> </MuiStack>
<view.ui.ActionPopover referenceRef={ref}> <BlockToolbar id={block.id} /> </view.ui.ActionPopover> </> ); }, ... },});addPropsView({ filter(block) { return block.props.spacing !== undefined; }, component: ({ block }) => { return ( <TextField label="Spacing" value={block.props.spacing} onChange={(event) => { const parsed = parseFloat(event.target.value);
const isNumber = String(parsed).length === event.target.value.length;
editor.commands.changeProperty( block.id, 'spacing', isNumber ? parsed : event.target.value, ); }} /> ); },});
addPropsView({ filter(block) { return block.props.direction !== undefined; }, component: ({ block }) => { return ( <TextField select label="Direction" value={block.props.direction} onChange={(event) => { editor.commands.changeProperty( block.id, 'direction', event.target.value, ); }} > {['row', 'row-reverse', 'column', 'column-reverse'].map((item) => ( <MenuItem key={item} value={item}> {item} </MenuItem> ))} </TextField> ); },});Text
For the Text block, add padding styles
addBlock({ name: 'text', props: { content: { default: 'Enter your text', }, }, style: { root: { padding: '0 0 16px 0', }, }, components: { view: ({ block }) => { return ( <Typography sx={{ pb: 2 }}>{block.props.content}</Typography> <Typography sx={{ ...block.style.root }}> {block.props.content} </Typography> ); }, editor: ({ block }) => { editor: ({ block, editor }) => { const { view } = editor;
const ref = useRef<HTMLParagraphElement>(null);
return ( <> <Typography sx={{ pb: 2 }}>{block.props.content}</Typography> <Typography ref={ref} sx={{ ...block.style.root }}> {block.props.content} </Typography>
<view.ui.ActionPopover referenceRef={ref}> <BlockToolbar id={block.id} /> </view.ui.ActionPopover> </> ); }, palette: () => { return <Typography>Text</Typography>; }, },});TextField
For TextField you need to add editing label and fieldName props
addBlock({ name: 'text-field', props: { label: { default: 'Label', }, fieldName: { default: '', }, }, components: { view: ... editor: ({ block }) => { const { view } = editor;
const ref = useRef<HTMLDivElement>(null);
const { control } = useFormContext();
return ( <> <Controller name={block.props.fieldName} control={control} shouldUnregister render={({ field }) => ( <MuiTextField inputRef={ref} label={block.props.label} variant="outlined" {...field} value={field.value || ''} /> )} />
<view.ui.ActionPopover referenceRef={ref}> <BlockToolbar id={block.id} /> </view.ui.ActionPopover> </> ); }, palette: () => { return <Typography>TextField</Typography>; }, },});Since we already have a component for changing the content prop, we can generate similar components by changing only the prop keys, collecting them in an array
[ { label: 'Content', propName: 'content', valueIfEmpty: 'Empty' }, { label: 'Label', propName: 'label' }, { label: 'FieldName', propName: 'fieldName' },].forEach(({ label, propName, valueIfEmpty }) => { addPropsView({ filter(block) { return block.props.content !== undefined; return block.props[propName] !== undefined; }, component: ({ block }) => { return ( <TextField label="Content" value={block.props.content} label={label} value={block.props[propName]} onChange={(event) => editor.commands.changeProperty( block.id, "Content", event.target.value || 'Empty', propName, event.target.value || valueIfEmpty || '', ) } /> ); }, });});Button
Make the rest of the button type and variant props, as with TextField, you can group selects into an array
[ { label: 'Direction', propName: 'direction', items: ['row', 'row-reverse', 'column', 'column-reverse'], }, { label: 'Type', propName: 'type', items: ['button', 'submit', 'reset'], }, { label: 'Variant', propName: 'variant', items: ['contained', 'outlined', 'text'], },].forEach(({ label, propName, items }) => { addPropsView({ filter(block) { return block.props[propName] !== undefined; return block.props.direction !== undefined; }, component: ({ block }) => { return ( <TextField select label="Direction" value={block.props.direction} label={label} value={block.props[propName]} onChange={(event) => { editor.commands.changeProperty( block.id, "direction", propName, event.target.value, ); }} > {['row', 'row-reverse', 'column', 'column-reverse'].map((item) => ( {items.map((item) => ( <MenuItem key={item} value={item}> {item} </MenuItem> ))} </TextField> ); }, });});