- 記事一覧 >
- ブログ記事
Material UI(MUI)v5 x-date-pickers日本語化・土日の色調整・週単位選択
はじめに
MUI(Material-UI)v5 x-date-pickers を日本語化して、週単位の選択機能を設けました。
日本語化については、下記参考記事により、ほぼ実現できましたが、参考記事に、
DatePicker が @mui/lab パッケージから @mui/x-date-pickers パッケージに変更になったことで、日本語化できなくなりました。
という記述があり、確かに一部うまく日本語化できませんでした。その部分も解決し、さらに、土日の色を変えるのと、週単位の選択機能を実装しましたので、紹介していきたいと思います。
参考記事:MUI v5 DatePicker の使い方 その 2 ~日本語化~(https://zenn.dev/longbridge/articles/e6f0d6ac0a3559
)
【検証環境】
Windows 10 Pro x64
Visual Studio Code 1.69.0
Raspberry Pi Desktop OS(Linux raspberry 4.19.0-13-amd64)
node 14.16.0
npm 6.14.11
react 18.2.0
recharts 2.1.12
mui/material 5.8.7
mui/x-date-pickers 5.0.0
mui/x-date-pickers-pro 5.0.0
mui/date-fns 2.28.0
MUI(Material-UI)v5
環境により、状況が異なる可能性があるため、大前提として、前回記事
Material UI(MUI) Recharts WebSocket FastAPI でリアルタイムグラフ描画
の続きとします。
MUI(Material-UI)v5 を導入して、ダッシュボードが実装されているものとします。なお、ダッシュボードは、ただ単に既存のテンプレートを導入しただけです。
x-date-pickers 導入
依存パッケージをインストールします。
date-fns は、ロケール情報の ja オブジェクトを取り出すのに必要です。
$ npm install @mui/x-date-pickers
$ npm install @mui/x-date-pickers-pro
$ npm install date-fns
$ npm install @date-io/date-fns
まず、基本形の日付選択で、DatePickerDay.tsx
として、<DatePickerDay />
コンポーネントを作成します。
import * as React from "react";
import { Box, TextField } from "@mui/material";
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers-pro";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
const DatePickerDay: React.FC = () => {
const [value, setValue] = React.useState<Date | null>(null);
const handleChange = (newValue: Date | null) => {
setValue(newValue);
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Box sx={{ m: 2 }}>
<DatePicker
label="DatePickerDay"
value={value}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
/>
</Box>
</LocalizationProvider>
);
};
export default DatePickerDay;
src/dashboard/Dashboard.tsx
を大きく変えて、以下の内容とします。(ほとんど省略しています。 <Container>...</Container>
内を <DatePickerDay />
だけにしています。import 削除も必要です。)
Dashboard.tsx
が突然出てきましたが、前回記事を見てください。
・・・
import DatePickerDay from "../DatePickerDay";
・・・
<Toolbar />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<DatePickerDay />
</Container>
</Box>
・・・
確認します。
$ npm start
OK!
ですが、まだ何もしていませんので、表示が英語です。
日本語化の前に未来の日付を選択できないようにしておきます。
maxDate={new Date()}
を追加です。
<DatePicker
label="DatePickerDay"
maxDate={new Date()} //未来の日付無効
value={value}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
/>
日本語化
最初の方は、
参考記事:MUI v5 DatePickerの使い方 その2 ~日本語化~(https://zenn.dev/longbridge/articles/e6f0d6ac0a3559)
の内容通りです。ありがとうございました。
LocalizationProvider コンポーネントの引数に adapterLocale={ja}
を追加します。
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} //ja追加
>
<Box sx={{ m: 2 }}>
<DatePicker
これだけで、細かいところを除いて、問題無いような感じになります。
inputFormat="yyyy年MM月dd日"
mask="____年__月__日"
を追加して、選択済みの日付の表示を 2022/07/13
→ 2022年07月13日
と変えます。
また、renderInput=
を変更して、プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)を "****年**月**日"
としています。
<DatePicker
label="DatePickerDay"
maxDate={new Date()} //未来の日付無効
value={value}
onChange={handleChange}
inputFormat="yyyy年MM月dd日" //選択済みの日付の表示
mask="____年__月__日"
renderInput={(params) => (
<TextField
{...params}
inputProps={{
...params.inputProps,
placeholder: "****年**月**日", //プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)
}}
/>
)}
/>
mask="____年__月__日"
はinputFormat
で自動的に適用されて、不要かもしれません。設定しなくても動作して、"____年__月__日"
以外を設定すると、エラーになり、用途がよく分かりませんでした。
次に、LocalizationProvider コンポーネントの引数に dateFormats={{ monthAndYear: 'yyyy年 MM月' }}
を追加して、カレンダー左上の日付表示を 7月 2022
→ 2022年 07月
と変えます。
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} //ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月" }} //カレンダー左上の日付表示
>
<Box sx={{ m: 2 }}>
<DatePicker
<
と >
にマウスカーソルを合わせたときに表示される英語 "Previous month", "Next month" を日本語にします。
LocalizationProvider コンポーネントに localeText={{previousMonth: "前月を表示", nextMonth: "次月を表示",}}
プロパティを追加します。
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} //ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月" }} //カレンダー左上の日付表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
}}
>
<Box sx={{ m: 2 }}>
<DatePicker
DatePicker コンポーネントに
leftArrowButtonText="前月を表示"
とrightArrowButtonText="次月を表示"
プロパティを渡しても変更できましたが、コンソールに以下の警告が表示されました。Props for translation are deprecated. See https://mui.com/x/react-date-pickers/localization for more information.
deprecated props observed:
- leftArrowButtonText
- rightArrowButtonText
さて、PC の場合、これで完璧になったように見えますが、スマホの場合、形が変わって、OK, CANCEL ボタンが日本語になっていません。さらに、左上が 13 7月
になっています。
OK, CANCEL ボタンの対応を行います。
この対応も localeText=
です。定義を見ると、いろいろ変えられるのが分かります。
LocalizationProvider コンポーネントの localeText=
に cancelButtonLabel: "キャンセル", okButtonLabel: "選択",
を追加します。
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} //ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月" }} //カレンダー左上の日付表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
cancelButtonLabel: "キャンセル", // スマホ画面のCANCELボタン
okButtonLabel: "選択", // スマホ画面のOKボタン
}}
>
<Box sx={{ m: 2 }}>
<DatePicker
toolbarFormat="yyyy年MM月dd日"
で左上(選択中の日付)を 13 7月
→ 2022年07月13日
の表示にします。
<DatePicker
label="DatePickerDay"
maxDate={new Date()} //未来の日付無効
value={value}
onChange={handleChange}
inputFormat="yyyy年MM月dd日" //選択済みの日付の表示
mask="____年__月__日"
toolbarFormat="yyyy年MM月dd日" // スマホ画面の左上 選択中日付表示
renderInput={(params) => (
<TextField
{...params}
inputProps={{
...params.inputProps,
placeholder: "****年**月**日", //プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)
}}
/>
)}
/>
問題無くなりましたが、2022年07月13日
の文字が大きすぎるので、調整します。const styles = {
と DialogProps={{ sx: styles.mobiledialogprops }}
を追加します。
const styles = {
mobiledialogprops: {
".MuiDatePickerToolbar-title": {
fontSize: "1.5rem",
},
},
};
return (
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} //ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月" }} //カレンダー左上の日付表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
cancelButtonLabel: "キャンセル", // スマホ画面のCANCELボタン
okButtonLabel: "選択", // スマホ画面のOKボタン
}}
>
<Box sx={{ m: 2 }}>
<DatePicker
label="DatePickerDay"
maxDate={new Date()} //未来の日付無効
value={value}
onChange={handleChange}
inputFormat="yyyy年MM月dd日" //選択済みの日付の表示
mask="____年__月__日"
toolbarFormat="yyyy年MM月dd日" // スマホ画面の左上 選択中日付表示
renderInput={(params) => (
<TextField
{...params}
inputProps={{
...params.inputProps,
placeholder: "****年**月**日", //プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)
}}
/>
)}
DialogProps={{ sx: styles.mobiledialogprops }} // スマホ画面の左上 選択中日付表示 文字の大きさ調整
/>
完了!
あと、細かい話しですが、年を選ぶ画面で、年数の数字しか表示されません。これを ○○年
表示にします。また、2021年
、2022年
の選択肢しか無いようにします。
LocalizationProvider コンポーネントの dateFormats=
を year: 'yyyy年'
に、DatePicker コンポーネントの minDate={new Date('2021-01-01')}
で目的は達成します。
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} // ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月", year: "yyyy年" }} // カレンダー左上の日付表示 年選択を○○年表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
cancelButtonLabel: "キャンセル", // スマホ画面のCANCELボタン
okButtonLabel: "選択", // スマホ画面のOKボタン
}}
>
<Box sx={{ m: 2 }}>
<DatePicker
label="DatePickerDay"
minDate={new Date("2021-01-01")} // 選択範囲は2021年~
ヨシ!
土日の色変更
全日が同じ色ではなく、土曜日を青色で、日曜日を赤色で表示したいです。(日本特有の配色みたいです。)
日付のレンダリングをカスタマイズする方法で実現しました。
日付のレンダリング関数 renderWeekEndPickerDay
で sx: { color: "red" }
sx: { color: "blue" }
を日付の PickersDay コンポーネントにセットして、色を変えています。
const renderWeekEndPickerDay = (
date: Date,
_selectedDates: Array<Date | null>,
pickersDayProps: PickersDayProps<Date>
) => {
const switchDayColor = (getday: number) => {
switch (
getday // Sun=0,Sat=6
) {
case 0:
return { color: "red" };
case 6:
return { color: "blue" };
default:
return {};
}
};
const newPickersDayProps = {
...pickersDayProps,
sx: switchDayColor(date.getDay()),
};
return <PickersDay {...newPickersDayProps} />;
};
return (
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} // ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月", year: "yyyy年" }} // カレンダー左上の日付表示 年選択を○○年表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
cancelButtonLabel: "キャンセル", // スマホ画面のCANCELボタン
okButtonLabel: "選択", // スマホ画面のOKボタン
}}
>
<Box sx={{ m: 2 }}>
<DatePicker
label="DatePickerDay"
minDate={new Date("2021-01-01")} // 選択範囲は2021年~
maxDate={new Date()} // 未来の日付無効
value={value}
onChange={handleChange}
inputFormat="yyyy年MM月dd日" // 選択済みの日付の表示
mask="____年__月__日"
toolbarFormat="yyyy年MM月dd日" // スマホ画面の左上 選択中日付表示
renderInput={(params) => (
<TextField
{...params}
inputProps={{
...params.inputProps,
placeholder: "****年**月**日", // プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)
}}
/>
)}
DialogProps={{ sx: styles.mobiledialogprops }} // スマホ画面の左上 選択中日付表示 文字の大きさ調整
renderDay={renderWeekEndPickerDay} // 週末の色調整
/>
Good!!
ここまで来ると、見だしの "土" "日" 表示まで色を変えたいです。
...が、これは同じような方法では、ダメで、CSS を強引に書き換えるに至りました。
見だしの "土" "日" 表示は、PaperProps に属していて、class=PrivatePickers* の div タグの中にある class=css-* の div タグ直下の span の内、最初(nth-of-type(1))が日曜日、7番目が土曜日という理屈で、色をセットしています。
スマホの場合、DialogProps の方に属すようでしたので、DialogProps、PaperProps 両方に必要でした。
Nice!!
ここまでの全ソースコードです。
↓
import * as React from "react";
import { Box, TextField } from "@mui/material";
import {
LocalizationProvider,
DatePicker,
PickersDayProps,
PickersDay,
} from "@mui/x-date-pickers-pro";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import ja from "date-fns/locale/ja";
const DatePickerDay: React.FC = () => {
const [value, setValue] = React.useState<Date | null>(null);
const handleChange = (newValue: Date | null) => {
console.log(newValue);
setValue(newValue);
};
const styles = {
mobiledialogprops: {
".MuiDatePickerToolbar-title": {
fontSize: "1.5rem",
},
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(1)': {
color: "rgba(255, 0, 0, 0.6)", // 日 = 赤
},
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(7)': {
color: "rgba(0, 0, 255, 0.6)", // 土 = 青
},
},
paperprops: {
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(1)': {
color: "rgba(255, 0, 0, 0.6)", // 日 = 赤
},
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(7)': {
color: "rgba(0, 0, 255, 0.6)", // 土 = 青
},
},
};
const renderWeekEndPickerDay = (
date: Date,
_selectedDates: Array<Date | null>,
pickersDayProps: PickersDayProps<Date>
) => {
const switchDayColor = (getday: number) => {
switch (
getday // Sun=0,Sat=6
) {
case 0:
return { color: "red" };
case 6:
return { color: "blue" };
default:
return {};
}
};
const newPickersDayProps = {
...pickersDayProps,
sx: switchDayColor(date.getDay()),
};
return <PickersDay {...newPickersDayProps} />;
};
return (
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} // ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月", year: "yyyy年" }} // カレンダー左上の日付表示 年選択を○○年表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
cancelButtonLabel: "キャンセル", // スマホ画面のCANCELボタン
okButtonLabel: "選択", // スマホ画面のOKボタン
}}
>
<Box sx={{ m: 2 }}>
<DatePicker
label="DatePickerDay"
minDate={new Date("2021-01-01")} // 選択範囲は2021年~
maxDate={new Date()} // 未来の日付無効
value={value}
onChange={handleChange}
inputFormat="yyyy年MM月dd日" // 選択済みの日付の表示
mask="____年__月__日"
toolbarFormat="yyyy年MM月dd日" // スマホ画面の左上 選択中日付表示
renderInput={(params) => (
<TextField
{...params}
inputProps={{
...params.inputProps,
placeholder: "****年**月**日", // プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)
}}
/>
)}
DialogProps={{ sx: styles.mobiledialogprops }} // スマホ画面の左上 選択中日付表示 文字の大きさ調整
PaperProps={{ sx: styles.paperprops }} // 見だしの "土" "日" 表示色調整
renderDay={renderWeekEndPickerDay} // 週末の色調整
/>
</Box>
</LocalizationProvider>
);
};
export default DatePickerDay;
週単位の選択機能
続いて、週単位の選択機能を実装していきます。
実装と言っても、公式サイト(https://mui.com/x/react-date-pickers/date-picker/
) の Customized day rendering のところに、ちょうど良いのが有って、renderWeekPickerDay
をコピペするとほぼ終わりです。
ただ、これ、2点気になりました。
・週の前半だけ、後半だけのところを選択すると、切れているように見える。
・見だしの曜日と日付のところがまっすぐに揃っていない。
原因と対応内容は、以下です。
・週の前半だけ、後半だけのところを選択すると、切れているように見える。
カレンダーの表示月以外の日はデフォルトでレンダリング対象から外れています。
レンダリング対象から外れるため、青く帯が塗られる部分もレンダリングされないということになります。
showDaysOutsideCurrentMonth: true
により、カレンダーの表示月以外の日もレンダリングするようにしました。ただ、それだけ行うと、カレンダーの表示月以外の日も選択可能になり、その月のカレンダーに遷移してしまいます。
カレンダーの表示月以外の日は、disabled: true
になるようにして、クリックしても反応しないようにしました。
・見だしの曜日と日付のところがまっすぐに揃っていない。
日付同士を塗りつぶす関係上、隙間ができるとまずいため、disableMargin: true
がセットされています。
見だしの曜日も同じように margin: 0
にしました。(オプションのようなものは無さそうなので、強引に CSS で。)
Great!!
ここまでの全ソースコードです。
↓
import * as React from "react";
import { Box, styled, TextField } from "@mui/material";
import {
LocalizationProvider,
DatePicker,
PickersDayProps,
PickersDay,
} from "@mui/x-date-pickers-pro";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import ja from "date-fns/locale/ja";
import endOfWeek from "date-fns/endOfWeek";
import isSameDay from "date-fns/isSameDay";
import isWithinInterval from "date-fns/isWithinInterval";
import startOfWeek from "date-fns/startOfWeek";
type CustomPickerDayProps = PickersDayProps<Date> & {
dayIsBetween: boolean;
isFirstDay: boolean;
isLastDay: boolean;
};
const CustomPickersDay = styled(PickersDay, {
shouldForwardProp: (prop) =>
prop !== "dayIsBetween" && prop !== "isFirstDay" && prop !== "isLastDay",
})<CustomPickerDayProps>(({ theme, dayIsBetween, isFirstDay, isLastDay }) => ({
...(dayIsBetween && {
borderRadius: 0,
backgroundColor: theme.palette.primary.main,
color: theme.palette.common.white,
"&:hover, &:focus": {
backgroundColor: theme.palette.primary.dark,
},
}),
...(isFirstDay && {
borderTopLeftRadius: "50%",
borderBottomLeftRadius: "50%",
}),
...(isLastDay && {
borderTopRightRadius: "50%",
borderBottomRightRadius: "50%",
}),
})) as React.ComponentType<CustomPickerDayProps>;
const DatePickerDay: React.FC = () => {
const [value, setValue] = React.useState<Date | null>(null);
const handleChange = (newValue: Date | null) => {
console.log(newValue);
setValue(newValue);
};
const styles = {
mobiledialogprops: {
".MuiDatePickerToolbar-title": {
fontSize: "1.5rem",
},
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(1)': {
color: "rgba(255, 0, 0, 0.6)", // 日 = 赤
},
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(7)': {
color: "rgba(0, 0, 255, 0.6)", // 土 = 青
},
'div[class^="PrivatePickers"] div[class^="css-"]>span': {
margin: 0, // 見だしの曜日のところのマージン
},
},
paperprops: {
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(1)': {
color: "rgba(255, 0, 0, 0.6)", // 日 = 赤
},
'div[class^="PrivatePickers"] div[class^="css-"]>span:nth-of-type(7)': {
color: "rgba(0, 0, 255, 0.6)", // 土 = 青
},
'div[class^="PrivatePickers"] div[class^="css-"]>span': {
margin: 0, // 見だしの曜日のところのマージン
},
},
};
const renderWeekPickerDay = (
date: Date,
selectedDates: Array<Date | null>,
pickersDayProps: PickersDayProps<Date>
) => {
const switchDayColor = (getday: number) => {
switch (
getday // Sun=0,Sat=6
) {
case 0:
return { color: "red" };
case 6:
return { color: "blue" };
default:
return {};
}
};
const newPickersDayProps = {
...pickersDayProps, // デフォルトのPickersDayコンポーネントのプロパティ
...{
showDaysOutsideCurrentMonth: true, // カレンダーの表示月以外の日もレンダリング
disableMargin: true, // 日付同士の margin: 0
disabled: pickersDayProps.outsideCurrentMonth // カレンダーの表示月以外の日は選択不可
? true
: pickersDayProps.disabled,
sx: switchDayColor(date.getDay()),
},
};
if (!value) {
return <PickersDay {...newPickersDayProps} />; // 選択されていない場合、色塗り無しでレンダリング
}
const start = startOfWeek(value);
const end = endOfWeek(value);
const dayIsBetween = isWithinInterval(date, { start, end });
const isFirstDay = isSameDay(date, start);
const isLastDay = isSameDay(date, end);
return (
// 選択されている場合、色塗り有りでレンダリング
<CustomPickersDay
{...newPickersDayProps}
dayIsBetween={dayIsBetween}
isFirstDay={isFirstDay}
isLastDay={isLastDay}
/>
);
};
return (
<LocalizationProvider
dateAdapter={AdapterDateFns}
adapterLocale={ja} // ja追加
dateFormats={{ monthAndYear: "yyyy年 MM月", year: "yyyy年" }} // カレンダー左上の日付表示 年選択を○○年表示
localeText={{
previousMonth: "前月を表示", // < のツールチップ
nextMonth: "次月を表示", // > のツールチップ
cancelButtonLabel: "キャンセル", // スマホ画面のCANCELボタン
okButtonLabel: "選択", // スマホ画面のOKボタン
}}
>
<Box
sx={{
m: 2,
width: "250px",
}}
>
<DatePicker
label="週選択"
minDate={new Date("2021-01-01")} // 選択範囲は2021年~
maxDate={new Date()} // 未来の日付無効
value={value}
onChange={handleChange}
inputFormat="yyyy年MM月dd日の週" // 選択済みの日付の表示
mask="____年__月__日の週"
toolbarFormat="yyyy年MM月dd日の週" // スマホ画面の左上 選択中日付表示
renderInput={(params) => (
<TextField
{...params}
fullWidth
inputProps={{
...params.inputProps,
placeholder: "****年**月**日の週", // プレースホルダー(フォーカスを合わせたときに薄く表示される入力例)
}}
/>
)}
DialogProps={{ sx: styles.mobiledialogprops }} // スマホ画面の左上 選択中日付表示 文字の大きさ調整
PaperProps={{ sx: styles.paperprops }} // 見だしの "土" "日" 表示色調整
renderDay={renderWeekPickerDay} // 週末の色調整 週選択対応
/>
</Box>
</LocalizationProvider>
);
};
export default DatePickerDay;
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。