iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
自我挑戰組

Material UI in React系列 第 29

Material UI in React [ Day 29 ] Customization Component 自訂組件 (part2)

...接續前一篇

2.一次性情況的動態變化

在上一篇中講解如何覆蓋 Material-UI 組件的樣式,現在,讓我們看看如何使這些覆蓋動態化。這裡有五種選擇;每個都有其優點和缺點。

a. Dynamic CSS

這個方式是透過 withStyles() 的方式更改樣式,直接透過 styles 內的參數做更改。

// Like https://github.com/brunobertolini/styled-by
const styledBy = (property, mapping) => (props) => mapping[props[property]];

const styles = {
  root: {
    background: styledBy('color', {
      default: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
      blue: 'linear-gradient(45deg, #2196F3 30%, #21CBF3 90%)',
    }),
    borderRadius: 3,
    border: 0,
    color: 'white',
    height: 48,
    padding: '0 30px',
    boxShadow: styledBy('color', {
      default: '0 3px 5px 2px rgba(255, 105, 135, .3)',
      blue: '0 3px 5px 2px rgba(33, 203, 243, .3)',
    }),
  },
};

const StyledButton = withStyles(styles)(({ classes, color, ...other }) => (
  <Button className={classes.root} {...other} />
));

export default function DynamicCSS() {
  const [color, setColor] = React.useState('default');

  const handleChange = (event) => {
    setColor(event.target.checked ? 'blue' : 'default');
  };

  return (
    <React.Fragment>
      <FormControlLabel
        control={
          <Switch
            checked={color === 'blue'}
            onChange={handleChange}
            color="primary"
            value="dynamic-class-name"
          />
        }
        label="Blue"
      />
      <StyledButton color={color}>Dynamic CSS</StyledButton>
    </React.Fragment>
  );
}

b. Class name branch

這個方法就是常用的 makeStyles() 的做法。

const useStyles = makeStyles({
  button: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    borderRadius: 3,
    border: 0,
    color: 'white',
    height: 48,
    padding: '0 30px',
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  },
  buttonBlue: {
    background: 'linear-gradient(45deg, #2196F3 30%, #21CBF3 90%)',
    boxShadow: '0 3px 5px 2px rgba(33, 203, 243, .3)',
  },
});

export default function DynamicClassName() {
  const classes = useStyles();
  const [color, setColor] = React.useState('default');

  const handleChange = (event) => {
    setColor(event.target.checked ? 'blue' : 'default');
  };

  return (
    <React.Fragment>
      <FormControlLabel
        control={
          <Switch
            checked={color === 'blue'}
            onChange={handleChange}
            color="primary"
            value="dynamic-class-name"
          />
        }
        label="Blue"
      />
      <Button
        className={clsx(classes.button, {
          [classes.buttonBlue]: color === 'blue',
        })}
      >
        {'Class name branch'}
      </Button>
    </React.Fragment>
  );
}

c. CSS variables

者個方法跟上面差不多,但是是以 CSS 變數作為更改的值。

const useStyles = makeStyles({
  button: {
    background: 'linear-gradient(45deg, var(--background-start) 30%, var(--background-end) 90%)',
    borderRadius: 3,
    border: 0,
    color: 'white',
    height: 48,
    padding: '0 30px',
    boxShadow: '0 3px 5px 2px var(--box-shadow)',
  },
});

const blue = {
  '--background-start': '#2196F3',
  '--background-end': '#21CBF3',
  '--box-shadow': 'rgba(33, 203, 243, .3)',
};

const defaultColor = {
  '--background-start': '#FE6B8B',
  '--background-end': '#FF8E53',
  '--box-shadow': 'rgba(255, 105, 135, .3)',
};

export default function DynamicCSSVariables() {
  const classes = useStyles();
  const [color, setColor] = React.useState(defaultColor);
  // 和上面不同的是以變數更改而不是使用className
  const handleChange = (event) => {
    setColor(event.target.checked ? blue : defaultColor);
  };

  return (
    <React.Fragment>
      <FormControlLabel
        control={
          <Switch
            checked={color === blue}
            onChange={handleChange}
            color="primary"
            value="dynamic-class-name"
          />
        }
        label="Blue"
      />
      <Button className={classes.button} style={color}>
        {'CSS variables'}
      </Button>
    </React.Fragment>
  );
}

d. Inline-styles

這個方法會直接使用 inline-styles 去更改。

const styles = {
  button: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    borderRadius: 3,
    border: 0,
    color: 'white',
    height: 48,
    padding: '0 30px',
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  },
  buttonBlue: {
    background: 'linear-gradient(45deg, #2196f3 30%, #21cbf3 90%)',
    boxShadow: '0 3px 5px 2px rgba(33, 203, 243, .30)',
  },
};

export default function DynamicInlineStyle() {
  const [color, setColor] = React.useState('default');

  const handleChange = (event) => {
    setColor(event.target.checked ? 'blue' : 'default');
  };

  return (
    <React.Fragment>
      <FormControlLabel
        control={
          <Switch
            checked={color === 'blue'}
            onChange={handleChange}
            color="primary"
            value="dynamic-class-name"
          />
        }
        label="Blue"
      />
      <Button
        style={{
          ...styles.button,
          ...(color === 'blue' ? styles.buttonBlue : {}),
        }}
      >
        {'dynamic inline-style'}
      </Button>
    </React.Fragment>
  );
}

e. Theme nesting

這個做法之前有講過了,透過不同的 theme provider 來更改。

export default function DynamicThemeNesting() {
  const [color, setColor] = React.useState('default');

  const handleChange = (event) => {
    setColor(event.target.checked ? 'blue' : 'default');
  };

  const theme = React.useMemo(() => {
    if (color === 'blue') {
      return createTheme({
        palette: {
          secondary: {
            main: blue[500],
            contrastText: '#fff',
          },
        },
      });
    }
    return createTheme();
  }, [color]);

  return (
    <React.Fragment>
      <FormControlLabel
        control={
          <Switch
            checked={color === 'blue'}
            onChange={handleChange}
            color="primary"
            value="dynamic-class-name"
          />
        }
        label="Blue"
      />
      <ThemeProvider theme={theme}>
        <Button variant="contained" color="secondary">
          {'Theme nesting'}
        </Button>
      </ThemeProvider>
    </React.Fragment>
  );
}

3.在不同context中重用的組件的特定變體

你可能需要創建組件的變體並在不同的 context 中使用它,例如產品頁面上的彩色按鈕,但你可能希望保持代碼單純。
最好的方法是遵循方法 1,然後通過導出自定義組件以在任何需要的地方使用,從而利用 React 的組合能力。

4.Material Design 變體

Material Design 規範記錄了某些組件的不同變體,例如按鈕如何具有不同的形狀:text(以前的"flat")、contained的(以前的"raised")、FAB 等等。
詳細支援組件

5. Global theme

為了促進組件之間的一致性,並從整體上管理用戶界面外觀,Material-UI 提供了一種應用全局更改的機制。

Global CSS 覆蓋

const GlobalCss = withStyles({
  // @global is handled by jss-plugin-global.
  '@global': {
    // 要指向 [class*="MuiButton-root"] 而不是 nest themes.
    '.MuiButton-root': {
      fontSize: '1rem',
    },
  },
})(() => null);

export default function GlobalCssOverride() {
  return (
    <React.Fragment>
      <GlobalCss />
      <Button>font-size: 1rem</Button>
    </React.Fragment>
  );
}

Global theme 覆蓋

直接立一個新的 theme 去做覆蓋的動作,利用 theme 的 overrides 鍵來更改 Material-UI 注入到 DOM 中的每個樣式。

const theme = createTheme({
  overrides: {
    MuiButton: {
      root: {
        fontSize: '1rem',
      },
    },
  },
});

export default function GlobalThemeOverride() {
  return (
    <ThemeProvider theme={theme}>
      <Button>font-size: 1rem</Button>
    </ThemeProvider>
  );
}

以上就是今天的全部內容了,有在 follow 官方網站的朋友應該有發現最近已經發布 ver. 5 的版本了,我現在介紹和使用的是 ver.4,有機會會再出一個新版本更新的內容。


上一篇
Material UI in React [ Day 28 ] Customization Component 自訂組件 (part1)
下一篇
Material UI in React [ Day 30 ] 總結
系列文
Material UI in React30

尚未有邦友留言

立即登入留言