Copyright © 2008 ~ 2024 MUKI space* / omegaBook theme All Rights Reserved.

確定要使用 Material UI (以下簡稱 MUI) 當作我這次的 UI Tools 後,我第一個要做的事情,就是先把幾個主要配色定義好。

因為要做的是股市網站,所以想用紅色跟綠色做主要搭配,還是選擇我愛的復古紅,然後配上復古綠跟常用的復古黑 (自以為取名一堆復古 XXD)

MUI 的官方文件也寫得很詳細,如果我需要客製化自己的顏色風格,可以參考 Palette 以及 Color 兩份文件,主要看 Palette 即可,Color 是列出 MUI 所有的顏色設定,以及提供線上工具可以直接試色。

預設的顏色

MUI 預設的幾個狀態跟 Bootstrap 一樣:

  • primary - 網站主要的介面 / 元素
  • secondary - 網站次要的介面 / 元素
  • error - 告訴使用者應該注意的元素
  • warning - 潛在的危險動作,或是重要的訊息
  • info - 高亮一般(中性)的資訊
  • success - 告訴(指示)使用者觸發的操作已成功完成

我們可以選擇直接替換這幾個狀態,也可以客製化全新的顏色。

用 MUI 的 color 替換預設狀態的顏色

▼ 首先可以從 MUI color 文件看到他提供的 Color palette

有用過 TailwindCSS 的朋友,對這種顏色設計方式應該不陌生。

▼ 如果要使用,就是先 import 想要的顏色再使用它,舉例如下

import { teal } from '@mui/material/colors'

function App() {
  return (
    <>
    <Button variant="contained" style={{ backgroundColor: teal[300] }}>Button</Button>
	</>
  )
}
export default App

這樣的寫法就跟使用 TailwindCSS 的 <Button variant="contained" className="!bg-teal-300">Primary</Button>,對我來說,應該就是看誰的顏色更符合網站需求,就用誰家的 XXD

▼ 假設你喜歡 MUI 的 teal,想要將他替換成 primary 的顏色 (預設為藍色),我們可以用 ThemeProvider 做設定

import { createTheme, ThemeProvider } from '@mui/material/styles'
import Button from '@mui/material/Button'
import { teal } from '@mui/material/colors'

// 這個寫法是直接將 primary 改成 teal 的顏色,預設為 teal[500]
const theme = createTheme({
  palette: {
    primary: teal
  }
})

function App() {
return (
    <ThemeProvider theme={theme}>
      <Button variant="contained">Primary</Button>
    </ThemeProvider>
  )
}

export default App

▼ 想要使用特定編號的顏色,例如 teal-300,就要改寫在 primary 物件裡

// 以上省略...
// 改寫在 primary.main 裡面,以設定特定編號的顏色
const theme = createTheme({
  palette: {
    primary: {
      main: teal[300]
    }
  }
})

// 以下省略...

用自定的顏色替換預設的顏色

這次我們想要將 primary 的顏色替換成 #ABCDEF,不過 MUI 裡面沒有提供這個色碼,所以我們要自己設定。

每個顏色都有四個參數能設定,分別是:

  • main
  • light
  • dark
  • contrastText

除了 main 是必填外,其他三個參數可以由系統自動計算,或是指定色碼

// 以上省略...
const theme = createTheme({
  palette: {
    primary: {
      main: '#ABCDEF',
      light: '#FF0000',
      // dark: 沒有填寫就會利用 palette.primary.main 自動計算顏色數值
      // contrastText: 同上
    }
  }
})

客製化全新的顏色

如果不想要動到 primary... 等預設的顏色,我們也可以自己定義新的顏色。

▼ 我想新增一個名為 retro 的顏色,色碼是 #FF0000。我用的是 TypeScript,所以記得要建立 PaletteColor 以及 SimplePaletteColorOptionsinterface,使用時才不會報錯。

import { createTheme } from '@mui/material/styles'

declare module '@mui/material/styles' {
  interface PaletteColor {
    retro?: string
  }

  interface SimplePaletteColorOptions {
    retro?: string
  }
}

let theme = createTheme()

theme = createTheme(theme, {
  palette: {
    retro: theme.palette.augmentColor({
      color: {
        main: '#FF0000'
      },
      name: 'retro'
    })
  }
})

▼ 用 ThemeProvider 指定設定檔,以 <Button /> 為例子,使用 TypeScript 的話,記得要再指定 ButtonPropsColorOverridesinterface,並把 <Button color> 設為 retro

declare module '@mui/material/Button' {
  interface ButtonPropsColorOverrides {
    retro: true
  }
}

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="contained" color="retro">Retro Color</Button>
    </ThemeProvider>
  )
}

▼ 以此類推,如果想要在 <Alert /> 元件使用這個 retro color,就要加入 <Alert />interface,以下是全部的程式碼,歡迎參考

import { createTheme, ThemeProvider } from '@mui/material/styles'
import Button from '@mui/material/Button'
import Alert from '@mui/material/Alert'
import { Box, Stack } from '@mui/system'
import { Typography } from '@mui/material'

declare module '@mui/material/styles' {
  interface PaletteColor {
    retro?: string
  }

  interface SimplePaletteColorOptions {
    retro?: string
  }
}

declare module '@mui/material/Button' {
  interface ButtonPropsColorOverrides {
    retro: true
  }
}

declare module '@mui/material/Alert' {
  interface AlertPropsColorOverrides {
    retro: true
  }
}

let theme = createTheme()

theme = createTheme(theme, {
  palette: {
    retro: theme.palette.augmentColor({
      color: {
        main: '#FF0000'
      },
      name: 'retro'
    })
  }
})

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Stack>
        <Alert severity="error" color="retro">Error 的 Alert 元件</Alert>
      </Stack>
      <Stack direction="row">
        <Button variant="contained" color="retro">紅色按鈕</Button>
      </Stack>
    </ThemeProvider>
  )
}

export default App

▼ 最後呈現的樣子

如何使用 light, dark 的顏色

如果沒有特別指定 light, dark 的顏色,MUI 也會根據 main color 去計算這兩種顏色,那麼該怎麼使用這兩個顏色呢?

▼ 我們沒辦法直接用 color 這個參數,這樣寫是有問題的

// ⭕️ 正確寫法
<Button variant="contained" color="retro">紅色按鈕</Button>

// ❌ 錯誤寫法,console 會報錯
<Button variant="contained" color="retro.dark">紅色的深色按鈕</Button>

▼ 比較好的寫法,是使用 sx prop

<Alert severity="error" sx={{ bgcolor: 'retro.dark' }}>Error 的 Alert 元件</Alert>

可以把 sx 看成是 innerStyle,可以覆蓋 MUI 預設的 CSS,有較高的權重。而 MUI 的所有元件都可以使用 sx prop,是官方推薦的做法。

拆分檔案,將 theme 設定獨立出來

如果客製化的設定較多,我們可以把 theme 設定獨立出來。

假設我有三個字定義的顏色參數:

  • retroDark
  • retroRed
  • retroGreen

▼ 先寫好 style 的 interface

declare module '@mui/material/styles' {
  interface PaletteColor {
    retroRed?: string
    retroDark?: string
    retroGreen?: string
  }

  interface SimplePaletteColorOptions {
    retroRed?: string
    retroDark?: string
    retroGreen?: string
  }
}

▼ 設定這些 style 要用在哪些元件上?只要有用到的元件,都需要定義 interface,我這邊用了 ButtonAlert

declare module '@mui/material/Button' {
  interface ButtonPropsColorOverrides {
    retroRed: true
    retroDark: true
    retroGreen: true
  }
}

declare module '@mui/material/Alert' {
  interface AlertPropsColorOverrides {
    retroRed: true
    retroDark: true
    retroGreen: true
  }
}

▼ 最後可以用迴圈產 createTheme 的物件,這樣就不用重複寫很多次啦 XD

type ColorKeys = 'retroDark' | 'retroRed' | 'retroGreen'

const colors: Record<ColorKeys, string> = {
  retroDark: '#000000',
  retroRed: '#FF0000',
  retroGreen: '#FFFF00'
}

let theme = createTheme()
for (const color in colors) {
  const key = color as ColorKeys
  theme = createTheme(theme, {
    palette: {
      [color]: theme.palette.augmentColor({
        color: {
          main: colors[key]
        },
        name: color
      })
    }
  })
}

客製化其他的元件

我發現使用 MUI Button 時,他有將 Button 的文字樣式設定成全大寫,也就是用了這個 CSS 屬性:text-transform: uppercase;

▼ 我在按鈕寫了一個 hi

<Button variant="contained" color="retro">hi</Button>

▼ 渲染後,文字會強制大寫

這點真的很困擾我,好想知道 MUI 的設計師為什麼要這麼做,畢竟正常的設計邏輯,應該是有需要再設定,而不是不需要時去設定

▼ 如果想要移除大寫的 CSS,一樣在 createTheme 處理,跑完上面的顏色迴圈後,再補上各個元件的客製化設定

theme = createTheme(theme, {
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          // 移除預設全大寫的設定
          textTransform: 'none'
        }
      }
    }
  }
})

▼ 最後可以單獨把 createTheme 的程式碼拉出來,用 import 載入,頁面才不會落落長

import { createTheme } from '@mui/material/styles'

declare module '@mui/material/styles' {
  interface PaletteColor {
    retroRed?: string
    retroDark?: string
    retroGreen?: string
  }

  interface SimplePaletteColorOptions {
    retroRed?: string
    retroDark?: string
    retroGreen?: string
  }
}

declare module '@mui/material/Button' {
  interface ButtonPropsColorOverrides {
    retroRed: true
    retroDark: true
    retroGreen: true
  }
}


type ColorKeys = 'retroDark' | 'retroRed' | 'retroGreen'

const colors: Record<ColorKeys, string> = {
  retroDark: '#000000',
  retroRed: '#FF0000',
  retroGreen: '#FFFF00'
}

let theme = createTheme()
for (const color in colors) {
  const key = color as ColorKeys
  theme = createTheme(theme, {
    palette: {
      [color]: theme.palette.augmentColor({
        color: {
          main: colors[key]
        },
        name: color
      })
    }
  })
}

theme = createTheme(theme, {
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          textTransform: 'none'
        }
      }
    }
  }
})

export default theme
import { ThemeProvider } from '@mui/material/styles'
import theme from './global.theme.config'

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="contained" color="retroDark">Retro Dark</Button>
    </ThemeProvider>
  )
}

export default App

小結

第一次用歐美風的 CSS Library,希望能跟他相處愉快(合掌)。大家有任何問題,或是有哪裡寫錯了,都歡迎留言指正唷,感謝。

歡迎給我點鼓勵,讓我知道你來過 :)

5
2
Subscribe
Notify of
guest

0 則留言
Inline Feedbacks
View all comments