Skip to main content

[note] Material UI Getting Started

Styled Components API#

在 Material UI 中也有內建的 Styled Component API:

import { styled } from '@material-ui/core/styles';
const MyComponent = styled('div')({
backgroundColor: 'red',
});
const MyThemeComponent = styled('div')(({ theme }) => ({
padding: theme.spacing(1),
}));

Styled API 中單純只要用 props#

若在 Styled API 中單純只要使用到該 component 的 props 的話,在後面的 () 中可以直接回傳物件,並在物件中透過 callback 的方式取出 props 來用:

interface MyButtonProps {
color: 'red' | 'blue';
}
// 第二個 () 中直接回傳物件,並在其中使用 props
const StyledTableCell = styled(TableCell)({
color: (props: MyButtonProps) => {
console.log('props', props);
return props.color as string;
},
});
<StyledTableCell color="red" />;

Styled API 中同時使用 props 和 theme#

⚠️ 這種做法還沒嘗試成功,雖然可以成功拿到 props 的資料,但 React 會一直出現警告:React does not recognize the foo prop on a DOM element.

但如果不只是要使用 props 同時也需要使用 theme 的話,寫法不太一樣:

  • 在第一個 () 中,需要把客製化的 props 拿出來(在 TypeScript),把剩下的傳回原本的元件中
  • 在第二個 () 中,需要帶入回傳物件的函式,而在該函式的參數中可以取得該元件的 props 和 theme。同時,不能在回傳的物件中使用 console.log()
// 第二個 () 中直接帶入函式,並透過函式的參數可以取得元件上的 props 和 theme
const StyledTableCell = styled(({ foo, ...other }) => <TableCell {...other} />)((props) => {
const { foo, theme } = props as any;
return {
color: foo,
};
});
<StyledTableCell foo="bar" />;

最後還是用回較單純的 Hook API 用法。

Hook API#

在 TypeScript 中需要先把該元件對應的 Type 載入,例如,這裡使用 <TableCell> 元件,則需要從同一個位置載入 TableCellProps 這個 Type,若沒有載入這個 Type 又使用了 material ui 對元件提供的 props 時,TypeScript 會報錯:

import TableCell, { TableCellProps } from '@material-ui/core/TableCell';
import { Omit } from '@material-ui/types';
const useTableCellStyles = makeStyles((theme) => ({
tableCell: {
color: ({ abnormalStatus }: { abnormalStatus: AbnormalStatus }) => {
switch (abnormalStatus) {
case AbnormalStatus.Severe: {
return theme.palette.status.severe;
}
case AbnormalStatus.Mild: {
return theme.palette.status.mild;
}
case AbnormalStatus.Normal: {
return theme.palette.status.hint;
}
default: {
return theme.palette.status.hint;
}
}
},
},
}));
interface InterfaceStyledTableCell {
abnormalStatus: AbnormalStatus;
}
const StyledTableCell: React.FC<
InterfaceStyledTableCell & Omit<TableCellProps, keyof InterfaceStyledTableCell>
> = ({ abnormalStatus, children, ...other }) => {
const classes = useTableCellStyles({ abnormalStatus });
return (
<TableCell className={classes.tableCell} {...other}>
{children}
</TableCell>
);
};
<StyledTableCell abnormalStatus={AbnormalStatus.Unknown} />;

💡 之前嘗試不用 Omit 一樣能正常運作,像是這樣:React.FC<InterfaceStyledTableCell & TableCellProps

客製化(Customization)#

客製化主題樣式(Theme)#

Theming @ Customization

// 可以在 React 元件中使用 theme 物件
const theme = useTheme();

建立可共用樣式的 className#

keywords: createStyles, makeStyles#

createStyles#

// https://material-ui.com/styles/api/#createstyles-styles-styles
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) => createStyles({
root: {
backgroundColor: theme.color.red,
},
}));
export default function MyComponent {
const classes = useStyles();
return <div className={classes.root} />;
}

makeStyles#

// https://material-ui.com/styles/api/#makestyles-styles-options-hook
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles({
root: {
backgroundColor: 'red',
color: (props) => props.color,
},
});
export default function MyComponent(props) {
const classes = useStyles(props);
return <div className={classes.root} />;
}

TS: 在 theme 中添加其他客製化屬性(custom properties in theme with TypeScript)#

theme 是直接套用在 CSS 的 value 而不能直接拿來當 className:

Customization of Theme @ Material UI > Guides > TypeScript

import { createMuiTheme } from '@material-ui/core';
declare module '@material-ui/core/styles/createMuiTheme' {
// 能夠在 theme 中找到這個屬性
interface Theme {
utils: {
[key: string]: number | string;
};
}
// 能夠透過 createMuiTheme 新增屬性
interface ThemeOptions {
utils?: {
[key: string]: {
[key: string]: number | string;
};
};
}
}
// 在主題中添加客製化樣式
const theme = createMuiTheme({
// spacing: 4,
// palette,
// typography,
utils: {
fz18: {
fontSize: 18,
}, // ...
},
});

使用時有時需要搭配 as string

// 因為 backgroundColor 只能是 string,但 theme.utils.color.danger 的 type 是 string | number
// 所以要加上 as string
const useCardStyles = makeStyles((theme) => ({
cardHeader: {
backgroundColor: ({ abnormalStatus }: { abnormalStatus: AbnormalStatus }) => {
return theme.utils.color.danger as string
}
}
})

TS: 在 palette 中建立常用顏色#

Adding new colors @ Material UI > Customization > Palette

也可以直接在 createMuiTheme 的時候,透過 palette 這個屬性把常用的顏色設定進去。但若搭配 TypeScript 使用,需要額外對 module 添加定義,否則 TypeScript 會因為找不到該屬性而報錯:

// 在 palette 中多一個 status 屬性
declare module '@material-ui/core/styles/createPalette' {
interface Palette {
status: {
[key: string]: string;
};
}
interface PaletteOptions {
status: {
[key: string]: string;
};
}
}
const theme = createMuiTheme({
// ...
palette: {
text: {
white: 'white',
primary: '#616161', // rgb(97, 97, 97)
secondary: '#212121', // rgb(33, 33, 33)
hint: '#9e9e9e', // rgb(158, 158, 158)
},
// 客製化 status 的樣式
status: {
severe: '#a50f01', // 165, 15, 1
mild: '#cb8700', // 203, 135, 0
normal: '#424242', // 66, 66, 66
unknown: '#bdbdbd', // 189, 189, 189
},
},
});

如此一樣可以透過在 makeStyles 時透過 theme 取得這些色碼。

客製化元件樣式(Components)#

Customizing components @ Customization

使用 className 覆蓋樣式#

一般來說,可以使用 makeStyles 搭配 useStyles 來覆蓋掉原本元件的樣式,例如:

import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';
// STEP 1:使用 makeStyles 定義樣式
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: 'white',
},
}));
const StyledButton = () => {
// STEP 2:載入樣式
const classes = useStyles();
// STEP 3:透過 className 帶入樣式
return <Button className={classes.root}>My Button</Button>;
};

使用 classes 覆蓋樣式#

有些情況,對於階層比較深的元件,單純使用 className 可能無法覆蓋到底層的樣式,這時候可以 Material UI 元件中的 classes 屬性。在每一個 Material UI 的元件中,都會有該元件的 API 說明,在 API 文件中會有「CSS」的項目,告知可以哪些 CSS 是可以套用 classes 屬性來加以覆蓋。

舉例來說,在 Button API 中的 CSS 項目,有寫到 label

Imgur

因此,就可以透過在 Button 元件的 classes 屬性中使用 label 來改變它的樣式:

<Button
classes={{
root: classes.root, // class name, e.g. `classes-nesting-root-x`
label: classes.label, // class name, e.g. `classes-nesting-label-x`
}}
>
My Button
</Button>

其他#

在 makeStyles 中使用 animation#

Do keyframe animations work? @ Github Issues

makeStyles 中可以使用 animation,但是:

  • animation 需要定義在最外層
  • 使用 animation 的地方,animation name 前面要加上 $
const useStyles = makeStyles((theme) => ({
refreshButton: {
marginLeft: 15,
backgroundColor: theme.palette.grey[300],
color: theme.palette.text.secondary,
transitionDuration: '1s',
transitionProperty: 'transform',
// animation name 前面要加上 `$`
'& svg': {
animation: '$spin infinite 1.5s linear',
},
},
// @keyframes 要定義在最外層
'@keyframes spin': {
'0%': {
transform: 'rotate(360deg)',
},
'100%': {
transform: 'rotate(0deg)',
},
},
});

搭配使用第三方樣式套件#

keywords: styled components, emotion#

Style Library Interoperability

撰寫第三方套件#

若想要自行撰寫 material UI 的第三方套件,可以參考 notistack 的架構。

Last updated on