Merge pull request #174 from webzard-io/refactor/style-trait

refactor style trait
This commit is contained in:
yz-yu 2021-12-20 23:07:57 +08:00 committed by GitHub
commit 968be3744f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 178 additions and 52 deletions

View File

@ -82,9 +82,13 @@ export const ApplicationFixture: Record<string, Application> = {
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: "{{!usersTable.selectedItem ? 'display: none' : ''}}",
},
styles: [
{
styleSlot: 'content',
style: "{{!usersTable.selectedItem ? 'display: none' : ''}}",
},
]
}
},
],
},
@ -110,7 +114,7 @@ export const ApplicationFixture: Record<string, Application> = {
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
properties: { styles: [{styleSlot: 'content', style: 'padding: 0; border: none' }]},
},
],
},
@ -174,7 +178,7 @@ export const ApplicationFixture: Record<string, Application> = {
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
properties: { styles: [{styleSlot: 'content', style: 'padding: 0; border: none' }]},
},
],
},
@ -241,7 +245,6 @@ export const ApplicationFixture: Record<string, Application> = {
type: 'core/v1/slot',
properties: { container: { id: 'hstack4', slot: 'content' } },
},
{ type: 'core/v1/style', properties: { string: { kind: {}, type: {} } } },
],
},
{
@ -255,7 +258,7 @@ export const ApplicationFixture: Record<string, Application> = {
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
properties: { styles: [{styleSlot: 'content', style: 'padding: 0; border: none' }]},
},
],
},
@ -281,7 +284,7 @@ export const ApplicationFixture: Record<string, Application> = {
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
properties: { styles: [{styleSlot: 'content', style: 'padding: 0; border: none' }]},
},
],
},
@ -329,7 +332,7 @@ export const ApplicationFixture: Record<string, Application> = {
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
properties: { styles: [{styleSlot: 'content', style: 'padding: 0; border: none' }]},
},
],
},

View File

@ -15,6 +15,7 @@ export const CssEditor: React.FC<{
onBlur?: (v: string) => void;
}> = ({ defaultCode, onChange, onBlur }) => {
const style = css`
width: 100%;
.CodeMirror {
width: 100%;
height: 120px;
@ -44,6 +45,8 @@ export const CssEditor: React.FC<{
},
theme: 'ayu-mirage',
});
} else {
cm.current.setValue(defaultCode);
}
const changeHandler = (instance: CodeMirror.Editor) => {
onChange?.(instance.getValue());
@ -57,7 +60,7 @@ export const CssEditor: React.FC<{
cm.current?.off('change', changeHandler);
cm.current?.off('blur', blurHandler);
};
}, [defaultCode]);
}, [onBlur, onChange, defaultCode]);
return <Box className={style} ref={wrapperEl}></Box>;
};

View File

@ -1,61 +1,178 @@
import { useMemo } from 'react';
import { FormControl, FormLabel, VStack, Box } from '@chakra-ui/react';
import { useCallback, useMemo } from 'react';
import {
FormControl,
FormLabel,
VStack,
HStack,
IconButton,
Text,
Select,
} from '@chakra-ui/react';
import { ApplicationComponent } from '@sunmao-ui/core';
import { Registry } from '@sunmao-ui/runtime';
import { CssEditor } from '../../../components/CodeEditor';
import { eventBus } from '../../../eventBus';
import { genOperation } from '../../../operations';
import { AddIcon, CloseIcon } from '@chakra-ui/icons';
import produce from 'immer';
import { formWrapperCSS } from '../style';
type Props = {
registry: Registry;
component: ApplicationComponent;
};
type Styles = Array<{
styleSlot: string;
style: string;
}>;
export const StyleTraitForm: React.FC<Props> = props => {
const { component, registry } = props;
const styleSlots = useMemo(() => {
return registry.getComponentByType(component.type).spec.styleSlots;
}, [component, registry]);
const styles = useMemo(() => {
return component.traits.filter(t => t.type === 'core/v1/style');
}, [component]);
if (!styleSlots.length) {
return null;
}
return (
<VStack width="full">
<Box fontWeight="bold" textAlign="left" width="100%">
Styles
</Box>
{styleSlots.map(styleSlot => {
const styleTrait = styles.find(s => s.properties.styleSlot === styleSlot);
if (!styleTrait) {
const styleTraitIndex = useMemo(() => {
return component.traits.findIndex(t => t.type === 'core/v1/style');
}, [component]);
const styleTrait = component.traits[styleTraitIndex];
const styles = (styleTrait?.properties.styles as Styles) || [];
const createStyleTrait = () => {
eventBus.send(
'operation',
genOperation('createTrait', {
componentId: component.id,
traitType: 'core/v1/style',
properties: {
styles: [
{
styleSlot: styleSlots[0],
style: '',
},
],
},
})
);
};
const updateStyles = useCallback(
(newStyles: Styles) => {
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
componentId: component.id,
traitIndex: styleTraitIndex,
properties: {
styles: newStyles,
},
})
);
},
[component, styleTraitIndex]
);
const addStyle = useCallback(() => {
const newStyles: Styles = styles.concat({
styleSlot: styleSlots[0],
style: '',
});
updateStyles(newStyles);
}, [updateStyles, styleSlots, styles]);
const onClickCreate = () => {
if (!styleTrait) {
createStyleTrait();
} else {
addStyle();
}
};
const changeStyleContent = useCallback(
(i: number, value: string) => {
const newStyles = produce(styles, draft => {
draft[i].style = value;
});
updateStyles(newStyles);
},
[updateStyles, styles]
);
const changeStyleSlot = useCallback(
(i: number, newSlot: string) => {
const newStyles = produce(styles, draft => {
draft[i].styleSlot = newSlot;
});
updateStyles(newStyles);
},
[updateStyles, styles]
);
const styleForms = useMemo(
() =>
styles.map(({ style, styleSlot }, i) => {
if (styles.length === 0) {
return null;
}
const removeStyle = () => {
const newStyles = styles.filter((_, j) => j !== i);
updateStyles(newStyles);
};
return (
<FormControl id={styleSlot} key={styleSlot}>
<FormLabel>{styleSlot}</FormLabel>
<CssEditor
defaultCode={styleTrait.properties.style as string}
onBlur={v =>
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
componentId: component.id,
traitIndex: component.traits.indexOf(styleTrait),
properties: {
styleSlot,
style: v,
},
})
)
}
/>
</FormControl>
<VStack key={`${styleSlot}-${i}`} css={formWrapperCSS} spacing="2">
<FormControl id={styleSlot}>
<FormLabel marginInlineEnd="0">
<HStack width="full" justify="space-between">
<Text>Style Slot</Text>
<IconButton
aria-label="remove style"
size="sm"
variant="ghost"
colorScheme="red"
icon={<CloseIcon />}
onClick={removeStyle}
/>
</HStack>
</FormLabel>
<Select
value={styleSlot}
onChange={e => changeStyleSlot(i, e.target.value)}
>
{styleSlots.map(s => (
<option key={s} value={s}>
{s}
</option>
))}
</Select>
</FormControl>
<CssEditor defaultCode={style} onBlur={v => changeStyleContent(i, v)} />
</VStack>
);
})}
}),
[styles, changeStyleContent, changeStyleSlot, updateStyles]
);
return (
<VStack width="full">
<HStack width="full" justify="space-between">
<strong>Styles</strong>
<IconButton
aria-label="Styles"
size="sm"
variant="ghost"
colorScheme="blue"
icon={<AddIcon />}
onClick={onClickCreate}
/>
</HStack>
{styleForms}
</VStack>
);
};

View File

@ -2,22 +2,25 @@ import { createTrait } from '@sunmao-ui/core';
import { Static, Type } from '@sinclair/typebox';
import { TraitImplementation } from '../../types/RuntimeSchema';
const StyleTrait: TraitImplementation<Static<typeof PropsSchema>> = ({
styleSlot,
style,
}) => {
const StyleTrait: TraitImplementation<Static<typeof PropsSchema>> = ({ styles }) => {
const customStyle: Record<string, string> = {};
styles.forEach(style => {
customStyle[style.styleSlot] = style.style;
});
return {
props: {
customStyle: {
[styleSlot]: style,
},
customStyle,
},
};
};
const PropsSchema = Type.Object({
styleSlot: Type.String(),
style: Type.String(),
styles: Type.Array(
Type.Object({
styleSlot: Type.String(),
style: Type.String(),
})
),
});
export default {