yucatio@システムエンジニア

趣味で作ったものいろいろ

date-fnsを使用してカレンダー作成する(その4: Material-UIのmakeStylesとpropsを使用してスタイルを変更する)

前回は組み込みコンポーネントをMaterial-UIのコンポーネントに置き換え、簡単なスタイルを適用しました。 今回はuseStyleにプロパティを渡して、プロパティごとに表示を切り替えます。

★前回の記事

yucatio.hatenablog.com

今回の変更点

  • 日曜日と土曜日の日付をそれぞれ赤色、青色にする
  • 表示対象でない月の日付は薄い色にする
  • 今日の日付を目立たせる

f:id:yucatio:20191223081351p:plain

デモ : date-fns calendar

なお、ブログ主はデザインセンスがないので、その辺はご容赦ください。

プロパティの値によってスタイルを変化させる

今回のカレンダーでは、以下のように平日か土曜日か日曜日か、および表示対象の月かどうかで文字の色を変更します。

表示対象の月の日 前後の月の日
平日 グレー
土曜日 薄い青
日曜日 薄い赤

Adapting based on props のページを参考に、propsの値によってスタイルを変更します。

CalendarTableCellというカスタムコンポーネントを追加し、TableCellを置き換えます。 CalendarTableCellには、曜日を表すwdayと、表示対象の月かどうかを表すisTargetMonthを渡します。表示対象の月かを判定するには、date-fnsのisSameMonthを使用します。

CalendarTableCellでは、propsuseCalendarCellStylesに渡し、classesを得ます。

useCalendarCellStylescolorの値に関数を指定し、propsを受け取れるようにします。 colorに指定した関数の中では、propsからwdayisTargetMonthを受け取り、その値によって色を返します。

import React, { useState }  from 'react'
// 中略

// 追加ここから
import isSameMonth from 'date-fns/isSameMonth'

import blue from '@material-ui/core/colors/blue'
import red from '@material-ui/core/colors/red'
// 追加ここまで

// 中略

// 追加ここから
const useCalendarCellStyles = makeStyles(theme => ({
  calendarCell: {
    color: ({wday, isTargetMonth}) => {
      if(isTargetMonth) {
        switch(wday) {
          case 0: // Sunday
            return red[500]
          case 6: // Saturday
            return blue[500]
          default:
            return theme.palette.text.primary
        }
      } else {
        // previous or next month
        switch(wday) {
            case 0: // Sunday
            return red[200]
          case 6: // Saturday
            return blue[200]
          default:
            return theme.palette.text.secondary
        }
      }
    },
  },
}))

function CalendarTableCell(props) {
  const {wday, isTargetMonth, children, ...other} = props
  const classes = useCalendarCellStyles(props)
  return (<TableCell className={classes.calendarCell} {...other}>{children}</TableCell>)
}
// 追加ここまで

function App() {
  const [targetDate, setTargetDate] = useState(new Date())
  const classes = useStyles()
  const calendar = getCalendarArray(targetDate)

  return (
    <div>
      <CssBaseline />
      <Paper className={classes.paper}>
        <Grid container justify="space-between">
          {/* 中略 */}
        </Grid>

        <Typography variant="h4" align="center" className={classes.yearmonth}>{format(targetDate, 'y年M月')}</Typography>
        <Table>
          <TableHead>
            {/* 中略 */}
          </TableHead>
          <TableBody>
            {calendar.map((weekRow, rowNum) => (
              <TableRow key={rowNum}>
                {weekRow.map(date => (
                  {/* 変更ここから */}
                  <CalendarTableCell key={getDay(date)} wday={getDay(date)} isTargetMonth={isSameMonth(date, targetDate)} align="center">
                    {getDate(date)}
                  </CalendarTableCell>
                  {/* 変更ここまで */}
                ))}
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </Paper>
    </div>
  )
}

export default App

実行結果です。表示対象の月とそれ以外、また曜日でも文字の色が変わりました。

f:id:yucatio:20191224125713p:plain

f:id:yucatio:20191224125822p:plain

今日の日付を目立たせる

最後に、今日の日付を目立たせます。今回はは背景色を変えます。 表示している日付が今日かどうかはdate-fnsのisSameDayを利用します。

import React, { useState }  from 'react'
// 中略

// 追加ここから
import isSameDay from 'date-fns/isSameDay'

import pink from '@material-ui/core/colors/pink'
// 追加ここまで

// 中略

const useCalendarCellStyles = makeStyles(theme => ({
  calendarCell: {
    color: ({wday, isTargetMonth}) => {
      // 中略
    },
    // 追加ここから
    backgroundColor: ({isToday}) =>
      isToday ? pink[50] : "transparent"
    // 追加ここまで
  },
}));

function CalendarTableCell(props) {
  // isTodayを追加
  const {wday, isTargetMonth, isToday, children, ...other} = props
  const classes = useCalendarCellStyles(props)
  return (<TableCell className={classes.calendarCell} {...other}>{children}</TableCell>)
}

function App() {
  const [targetDate, setTargetDate] = useState(new Date())
  const classes = useStyles()
  const calendar = getCalendarArray(targetDate)
  const today = new Date()  // 追加

  return (
    <div>
      <CssBaseline />
      <Paper className={classes.paper}>
        <Grid container justify="space-between">
          {/* 中略 */}
        </Grid>

        <Typography variant="h4" align="center" className={classes.yearmonth}>{format(targetDate, 'y年M月')}</Typography>
        <Table>
          <TableHead>
            {/* 中略 */}
          </TableHead>
          <TableBody>
            {calendar.map((weekRow, rowNum) => (
              <TableRow key={rowNum}>
                {weekRow.map(date => (
                  {/* isToday={isSameDay(date, today)} を追加 */}
                  <CalendarTableCell key={getDay(date)} wday={getDay(date)} isTargetMonth={isSameMonth(date, targetDate)} isToday={isSameDay(date, today)} align="center">
                    {getDate(date)}
                  </CalendarTableCell>
                ))}
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </Paper>
    </div>
  )
}

export default App

実行結果です。今日の日付の背景色が変わりました。

f:id:yucatio:20191223081351p:plain

ソースコード

ここまでのソースコードです。

GitHub - yucatio/date-fns-calendar at 3bebfa7d00b30156676517c1be7879c08b0022ee

環境

  • npm: 6.12.1
  • react: 16.12.0
  • date-fns: 2.8.1
  • Material-UI: 4.8.0

あとがき

簡単なカレンダー機能なのでブログの1記事になるかと思いましたがまさかの4記事になりました。

date-fns, react-hooks, Material-UIのmakeStylesと比較的新しめの機能に触れるいい機会になりました。

date-fnsは今回カレンダーを作成するのに必要な機能がほぼ盛り込まれていて大変便利だと感じました。

Material-UIのカスタムスタイルの使用には、今までwithStylesを使用してきましたが、useStyleの方がすっきり書けて良いと思いました。