Material UI Sync plugin ๐งช
Material UI Sync plugin ๐งช
Sync๋ ๋์์ธ(design)์์ ๋ฐ๋ก ์ฝ๋๋ก Material UI ํ ๋ง๋ฅผ ์์ฑํด์ฃผ๋ Figma ํ๋ฌ๊ทธ์ธ์ด์์.
:::error Material UI Sync ํ๋ฌ๊ทธ์ธ์ ๊ฐ๋ฐ์ 2024๋ ์ ์ค๋จ๋์์ด์. ์์ผ๋ก ์ด ํ๋ก์ ํธ์ ์ ๋ฐ์ดํธ๊ฐ ์์ ๊ฒ์ผ๋ก ๊ธฐ๋ํด์๋ ์ ๋ฉ๋๋ค. :::
์ถ์ฒ: ๋ฌธ์
๋ณธ๋ฌธ
Introduction (์๊ฐ)
Material UI Sync๋ Material UI for Figma Design Kit์์ ํ ๋ง๋ฅผ ์์ฑํ ์ ์๊ฒ ํด์ฃผ๋ Figma ํ๋ฌ๊ทธ์ธ์ด์์.
Sync๋ Material UI for Figma Design Kit v5.16.0 ์ด์๊ณผ ํจ๊ป ๋์ํด์.
Running the plugin (ํ๋ฌ๊ทธ์ธ ์คํํ๊ธฐ)
Material UI for Figma Design Kit์ ์์ ํ ์ต์ ๋ฒ์ ์ ์ค์นํ์ง ์์๋ค๋ฉด, ๋์ Community ๋ฒ์ ์ ์ฌ์ฉํด์ ํ๋ฌ๊ทธ์ธ์ ํ ์คํธํ ์ ์์ด์.
์ค์นํ๊ณ Figma์์ ์ด์๋ค๋ฉด, Community ํญ์ Material UI Sync ํ๋ฌ๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํด์ **Open inโฆ**์ ํด๋ฆญํ๊ณ Material UI for Figma Design Kit์ ์ ํํด์.
Customizing design tokens (๋์์ธ ํ ํฐ ์ปค์คํฐ๋ง์ด์ง)
๋์์ธ ํ ํฐ์ Design Kit์ ๋ก์ปฌ ๋ณ์ ์ปฌ๋ ์ (local variable collections)์ ์ ์๋์ด ์์ผ๋ฉฐ, ์์ ํ๋ ํธ, ๋ธ๋ ์ดํฌํฌ์ธํธ, ๋ชจ์(shape), ๊ฐ๊ฒฉ(spacing) ํ ํฐ์ ํฌํจํด์. ํ์ดํฌ๊ทธ๋ํผ์ ๊ทธ๋ฆผ์ ๊ด๋ จ ํ ํฐ์ ๋ก์ปฌ ์คํ์ผ ์ปฌ๋ ์ (local styles collection)์์ ์ฐพ์ ์ ์์ด์.
Altering existing tokens (๊ธฐ์กด ํ ํฐ ์์ ํ๊ธฐ)
Design Kit๋ Material UI React ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ธฐ๋ณธ ํ ๋ง์ ๋งคํ๋๋ ๋์์ธ ํ ํฐ์ด ๊ฐ๋ ๋ด๊ฒจ ์์ด์.
๊ธฐ์กด ํ ํฐ์ ์ปค์คํฐ๋ง์ด์ฆํ๋ ค๋ฉด, ์๋ ๊ทธ๋ฆผ์ฒ๋ผ ํํฐ ์์ด์ฝ์ ํด๋ฆญํด์ ๋ก์ปฌ ๋ณ์ ๋ชจ๋ฌ์ ์ด์ด์. ์ปฌ๋ ์ ์ ์๋ ๋ณ์๋ค(ํ๋ ํธ, ๋ธ๋ ์ดํฌํฌ์ธํธ, ๋ชจ์, ๊ฐ๊ฒฉ ๋ฑ)์ ์ํ๋ ๋๋ก ์กฐ์ ํด์.
๊ทธ๋ฐ ๋ค์ Material UI Sync ํ๋ฌ๊ทธ์ธ์ ์ด๊ณ Generate theme์ ํด๋ฆญํด์.
์์ ๋ ํ ํฐ์ด ํฌํจ๋ ํ ๋ง๊ฐ ์์ฑ๋์ด ํ๋ฌ๊ทธ์ธ์ Theme ํญ์ ํ์๋ผ์.
Storybook ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํญ์ผ๋ก ์ด๋ํ๋ฉด ์์ฑ๋ ํ ๋ง์ ์ปค์คํฐ๋ง์ด์ฆ๋ ํ ํฐ์ ๋ฏธ๋ฆฌ ๋ณผ ์๋ ์์ด์.
Adding new tokens (์ ํ ํฐ ์ถ๊ฐํ๊ธฐ)
๊ธฐ์กด ๋ก์ปฌ ๋ณ์ ์ปฌ๋ ์ ์ ์ ๋ณ์๋ฅผ ์ถ๊ฐํ๊ฑฐ๋, ๋ก์ปฌ ์คํ์ผ ์ปฌ๋ ์ ์ ์ elevation ๋ฐ typography ์คํ์ผ์ ์ถ๊ฐํด์, ๊ธฐ์กด ํ ํฐ ์งํฉ์ ์์ ๋ง์ ๊ฒ์ผ๋ก ํ์ฅํ ์ ์์ด์. ์ปค์คํ ํ ํฐ์ ์ถ๊ฐํ ํ์๋ Regenerate theme์ ํด๋ฆญํด์ ์ด ํ ํฐ๋ค์ ํ ๋ง์ ํฌํจ์์ผ์.
Customizing components (์ปดํฌ๋ํธ ์ปค์คํฐ๋ง์ด์ง)
:::warning ์ด ๊ธฐ๋ฅ์ ์คํ์ ์ด๋ฉฐ Button, Switch, Typography ์ปดํฌ๋ํธ๋ก ์ ํ๋ผ์. :::
Sync ํ๋ฌ๊ทธ์ธ์ ์ปค์คํฐ๋ง์ด์ฆ๋ ์ปดํฌ๋ํธ์ ๋ํ ํ ๋ง ์คํ์ผ๋ ์์ฑํ ์ ์์ด์, Figma ์์์ ์ปดํฌ๋ํธ์ ๋ฃฉ์คํ์ ์์ ํ ๋ฐ๊พธ๊ณ ์์ ๋ง์ ์ปค์คํ ๋์์ธ ์์คํ ์ ๋ง๋ค ์ ์๊ฒ ํด์ค์.
์๋ฅผ ๋ค์ด, Switch ์ปดํฌ๋ํธ์ ์ฒดํฌ๋ ์ํ, ์ค๊ฐ ํฌ๊ธฐ, primary ์์์ ์ปค์คํฐ๋ง์ด์ฆํด์ iOS ๋ฃฉ์คํ์ ์ฌํํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์์:
:::warning Sync๊ฐ ์ปค์คํ ์ปดํฌ๋ํธ ์คํ์ผ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ถ์ถํ๊ณ ํ ๋ง๋ฅผ ์์ฑํ๋ ค๋ฉด, Design Kit์ ์ปดํฌ๋ํธ ๋ ์ด์ด ๊ณ์ธต ๊ตฌ์กฐ์ ๋ ์ด์ด ์ด๋ฆ์ด ๋ณ๊ฒฝ๋์ง ์์ ์ํ๋ก ์ ์ง๋์ด์ผ ํด์. :::
Sync ํ๋ฌ๊ทธ์ธ์ ์ปค์คํฐ๋ง์ด์ฆ๋ Switch์ ๋ํด ๋ค์ ํ ๋ง ์ฝ๋๋ฅผ ์์ฑํด์:
{
components: {
MuiSwitch: {
styleOverrides: {
root: {
'&.MuiSwitch-sizeMedium:has(.MuiSwitch-colorPrimary)': {
'&:has(.Mui-checked):not(:has(.Mui-disabled)):not(:has(.Mui-focusVisible))':
{
width: '40px',
height: '21px',
padding: '0',
'& .MuiSwitch-switchBase': {
transform: 'translateX(19px) translateY(2px)',
padding: '0',
'& .MuiSwitch-thumb': {
width: '17px',
height: '17px',
background: '#FAFAFA',
},
'& + .MuiSwitch-track': {
width: '38px',
height: '21px',
background: 'var(--mui-palette-success-light)',
opacity: '1',
},
},
},
},
},
},
},
},
}
์์ฑ๋ ํ ๋ง๋ ์์์ ์ ์ํ ํน์ Switch ๊ตฌ์ฑ์ ํด๋นํ๋ ํด๋์ค๋ฅผ ๋์์ผ๋ก ํ๋ฏ๋ก, ์คํ์ผ์ Material UI ์ปดํฌ๋ํธ์ props์ ์ํ๊ฐ ์ปค์คํฐ๋ง์ด์ฆ๋ Figma ์ปดํฌ๋ํธ์ ์ผ์นํ ๋๋ง ์ ์ฉ๋ผ์.
๋ค๋ฅธ ์ํ๋ฅผ ์ปค์คํฐ๋ง์ด์ฆํ๋ ค๋ฉด, ๋ค์ ๋จ๊ณ์ ๋ฐ๋ผ Figma์์ ๊ฐ ๋ณํ(variant)์ ์ํ๋ ๋์์ธ ๋ณ๊ฒฝ์ ์ ์ฉํด์ผ ํด์:
- ๋จ์ผ "๊ธฐ๋ณธ(base)" ๋ณํ์ ์ปค์คํฐ๋ง์ด์ฆํด์โ์๋ฅผ ๋ค์ด ์ฒดํฌ๋ ์ํ, ์ค๊ฐ ํฌ๊ธฐ, primary ์์์ Switch ์ปดํฌ๋ํธ์.
- ์ด ๋ณํ์ ๋ณต์ ํ๊ณ , ์ปค์คํฐ๋ง์ด์ฆํ๋ ค๋ ๋ค์ ๋ณํ์ ๋์์ผ๋ก ์ด๋ฆ์ ๋ฐ๊ฟ์โ์๋ฅผ ๋ค์ด ๋ณต์ ๋ณธ์ ์ด๋ฆ์
Checked=True, Size=Medium, Color=Primary, State=Enabled์์Checked=False, Size=Medium, Color=Primary, State=Enabled๋ก ๋ฐ๊ฟ์. - ๊ฐ์ ๋ณํ์ ์ด์ ๋ฒ์ ์ ์ญ์ ํด์.
- ์ ๋ฒ์ ์ ๋ณํ ๊ทธ๋ฆฌ๋์ ์ฌ๋ฐ๋ฅธ ์นธ์ผ๋ก ์ด๋ํด์.
- ๋ณํ์ ํ์ ๋ ์ด์ด๋ค์ ํ์ํ ์คํ์ผ ์กฐ์ ์ ํด์.
์ด ๊ณผ์ ์ ์ปค์คํฐ๋ง์ด์ฆํ๋ ค๋ ๊ฐ ๋ณํ๋ง๋ค ๋ฐ๋ณตํด์. ๋ค์๊ณผ ๊ฐ์ ๋ชจ์ต์ด ๋ ์ ์์ด์:
์ฌ๊ธฐ์์ Sync๋ฅผ ์คํํด ์ ํ ๋ง๋ฅผ ์์ฑํ๋ฉด, ์ ์์ ์์ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ์์ฑ๋ผ์:
{
components: {
MuiSwitch: {
styleOverrides: {
root: {
'&.MuiSwitch-sizeMedium:has(.MuiSwitch-colorPrimary)': {
width: '40px',
height: '21px',
padding: '0',
'& .MuiSwitch-switchBase': {
padding: '0',
'& .MuiSwitch-thumb': {
width: '17px',
height: '17px',
background: '#FAFAFA',
},
'& + .MuiSwitch-track': {
width: '38px',
height: '21px',
borderRadius: '100px',
opacity: '1',
},
},
'&:not(:has(.Mui-checked)):not(:has(.Mui-disabled)):not(:has(.Mui-focusVisible))': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(3px) translateY(2px)',
'& + .MuiSwitch-track': {
background: '#BDBDBD',
},
},
},
'&:not(:has(.Mui-checked)):has(.Mui-disabled):not(:has(.Mui-focusVisible))': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(3px) translateY(2px)',
'& + .MuiSwitch-track': {
background: 'rgba(229, 229, 229, 0.99)',
},
},
},
'&:not(:has(.Mui-checked)):not(:has(.Mui-disabled)):has(.Mui-focusVisible)': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(3px) translateY(2px)',
'& + .MuiSwitch-track': {
border: '1px solid #000',
background: '#BDBDBD',
},
},
},
'&:has(.Mui-checked):has(.Mui-disabled):not(:has(.Mui-focusVisible))': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(19px) translateY(2px)',
'& + .MuiSwitch-track': {
background: 'rgba(187, 231, 188, 0.99)',
},
},
},
'&:not(:has(.Mui-checked)):not(:has(.Mui-disabled)):not(:has(.Mui-focusVisible)):hover': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(3px) translateY(2px)',
'& + .MuiSwitch-track': {
background: '#616161',
},
},
},
'&:has(.Mui-checked):not(:has(.Mui-disabled)):not(:has(.Mui-focusVisible))': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(19px) translateY(2px)',
'& + .MuiSwitch-track': {
background: 'var(--mui-palette-success-light)',
},
},
},
'&:has(.Mui-checked):not(:has(.Mui-disabled)):not(:has(.Mui-focusVisible)):hover': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(19px) translateY(2px)',
'& + .MuiSwitch-track': {
background: 'var(--mui-palette-success-dark)',
},
},
},
'&:has(.Mui-checked):not(:has(.Mui-disabled)):has(.Mui-focusVisible)': {
'& .MuiSwitch-switchBase': {
transform: 'translateX(19px) translateY(2px)',
'& + .MuiSwitch-track': {
border: '1px solid #000',
background: 'var(--mui-palette-success-light)',
},
},
},
},
},
},
},
},
}
:::info
์์ฑ๋ ํ
๋ง์๋ ํน์ ํ์ ํด๋์ค๋ฅผ ๋์์ผ๋ก ํ๋ CSS has() ์ ํ์๊ฐ ํฌํจ๋ ์ ์์ด์. ์ด ์ ํ์๋ ์์ ์ ๋ธ๋ผ์ฐ์ ์ง์์ด ์ ํ์ ์ด์๊ธฐ ๋๋ฌธ์ ๋ฌธ์์ ๋ค๋ฅธ ํ
๋ง ๊ด๋ จ ์์ ์์๋ ์ฌ์ฉ๋์ง ์์์ด์. ์ง๊ธ์ ๋ชจ๋ ์ต์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ง์ํด์.
:::
Storybook ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ํ์ธํด์ ์ปดํฌ๋ํธ์ Material UI ๋ฒ์ ์ ํ ์คํธํ ์๋ ์์ด์.
Using the generated theme (์์ฑ๋ ํ ๋ง ์ฌ์ฉํ๊ธฐ)
Sync๋ก ์์ฑ๋ ํ ๋ง๋ฅผ ์ฝ๋๋ฒ ์ด์ค์ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ์๋ ๋ค์๊ณผ ๊ฐ์์:
import { createTheme, ThemeProvider } from '@mui/material/styles';
const theme = createTheme({
cssVariables: true,
shape: {
borderRadiusRound: 999,
},
components: {
MuiSwitch: {
styleOverrides: {
root: {
'&.MuiSwitch-sizeMedium:has(.MuiSwitch-colorPrimary)': {
'&:has(.Mui-checked):not(:has(.Mui-disabled)):not(:has(.Mui-focusVisible))':
{
width: '40px',
height: '21px',
padding: '0',
'& .MuiSwitch-switchBase': {
transform: 'translateX(19px) translateY(2px)',
padding: '0',
'& .MuiSwitch-thumb': {
width: '17px',
height: '17px',
background: '#FAFAFA',
},
'& + .MuiSwitch-track': {
width: '38px',
height: '21px',
background: 'var(--mui-palette-success-light)',
borderRadius: 'var(--mui-shape-borderRadiusRound)',
opacity: '1',
},
},
},
},
},
},
},
},
});
export default function MyApp(props) {
const { Component, pageProps } = props;
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
}
Feedback and bug reports (ํผ๋๋ฐฑ๊ณผ ๋ฒ๊ทธ ๋ฆฌํฌํธ)
ํผ๋๋ฐฑ์ด ์๋ค๋ฉด ์๊ฒฌ์ ๋ค๋ ค์ฃผ์ธ์.
๋ ์์๋ณด๊ธฐ (Learn more)
- Material UI for Figma Design Kit โ Figma์์ ์ฌ์ฉํ๋ ๋์์ธ ํคํธ
- Material UI Sync ํ๋ฌ๊ทธ์ธ โ Figma ์ปค๋ฎค๋ํฐ ํ๋ฌ๊ทธ์ธ ํ์ด์ง