今回は、時刻を選択するピッカーと仮定して進めていきます。
コンポーネントを作成する
はじめに、ピッカー単体を描画する役割を持つItemPickerコンポーネントを作成します。
import React from 'react';
import styled from 'styled-components'
import Spacer from './Spacer'
const StyledItemPicker = styled.div`
text-align: center;
& .item-name {
color: ${({ textColor }) => textColor ? textColor : `#333`};
}
& select {
background-color: transparent;
border: none;
border-radius: .2em;
color: ${({ textColor }) => textColor ? textColor : `#333`};
cursor: pointer;
font-size: 1em;
outline: none;
padding: .2em;
}
`
const ItemPicker = ({ className, itemName, values, textColor, handleChange }) => {
return (
<StyledItemPicker className={className} textColor={textColor}>
<div className="item-name">
{itemName}
</div>
<Spacer size=".5em" />
<select id={itemName} name={itemName} onChange={handleChange}>
{
values.map((value, i) => (
<option
id={value}
name={value}
value={value}
key={i}
>
{typeof value !== 'string' ? String(value).padStart(2, "0") : value}
</option>
))
}
</select>
</StyledItemPicker>
)
}
export default ItemPicker
このItemPickerコンポーネントは次のプロパティを受け取ります。
プロパティ名 | 値 |
---|---|
className | クラス名 |
itemName | 選択項目の名前 |
values | 選択項目の値がセットされた配列 |
textColor | 文字の色 |
handleChange | セレクトボックスの値が選択されたときに実行する関数 |
ItemPickerコンポーネント内で使用しているSpacerコンポーネントは、コンポーネント間に余白を加える役割を持ちます。SpacerコンポーネントについてはReactで余白をどうスタイリングするかを参考にしました。
このItemPickerコンポーネントを使用すると、ブラウザに次のようなピッカーが表示されます。
次に、選択項目の数だけピッカーを描画する役割を持つItemPickersコンポーネントを作成します。
import React from 'react';
import styled from 'styled-components'
import ItemPicker from './ItemPicker'
import Spacer from './Spacer'
const StyledItemPickers = styled.div`
display: flex;
justify-content: center;
`
const ItemPickers = ({ className, items, textColor, handleChange }) => {
// プロパティ名を値に持つ配列に変換
const itemNames = Object.keys(items)
return (
<StyledItemPickers className={className} textColor={textColor}>
{
itemNames.map((itemName, i) => (
<>
<Spacer size=".2em" horizontal={true} />
<ItemPicker
className="item"
itemName={itemName}
values={items[itemName]}
handleChange={handleChange}
key={i}
/>
<Spacer size=".5em" horizontal={true} />
</>
))}
</StyledItemPickers>
)
}
export default ItemPickers
ItemPickersコンポーネントでは、先ほど作成したItemPickerコンポーネントを選択項目の数だけ追加しています。
また、このItemPickersコンポーネントは次のプロパティを受け取ります。
プロパティ名 | 値 |
---|---|
className | クラス名 |
items | プロパティ名が各選択項目のタイトル、値が各選択項目の値がセットされた配列で構成されるオブジェクト |
textColor | 文字の色 |
handleChange | セレクトボックスの値が選択されたときに実行する関数 |
このItemPickersコンポーネントを使用する際、items
には次のような形式のオブジェクトを渡します。
{
hours: [0, 1, 2, 3, ..., 59],
mins: [0, 1, 2, 3, ..., 59],
secs: [0, 1, 2, 3, ..., 59]
}
ピッカーを表示する
では、作成したItemPickersコンポーネントを使用してブラウザにピッカーを表示します。
import React, {
useState,
useCallback
} from 'react';
import styled, { createGlobalStyle } from 'styled-components'
import ItemPickers from './components/ItemPickers'
const TIMES = {
hours: [...Array(60)].map((u, i) => i),
mins: [...Array(60)].map((u, i) => i),
secs: [...Array(60)].map((u, i) => i)
}
const GlobalStyle = createGlobalStyle`
body {
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
font-size: 4vmin;
margin: 0;
height: 100vh;
width: 100%;
}
`;
const App = () => {
const [selectItems, setSelectItems] = useState(() => {
const newItems = {}
Object.keys(TIMES).forEach((name, i) => {
newItems[name] = TIMES[name][0]
})
return newItems
})
const handleChange = useCallback((e) => {
setSelectItems(p => ({
...p,
[e.target.id]: Number(e.target.value)
}))
}, [])
return (
<>
<GlobalStyle />
<div>
<ItemPickers
className="time-picker"
items={TIMES}
handleChange={handleChange}
/>
</div>
</>
);
};
export default App
これにより、ブラウザに次のようなピッカーが表示されます。
selectItems
は選択中の各値を次のようなオブジェクト形式で保存するステートです。
selectItems
ステートの初期値は、プロパティ名にTIMESオブジェクトのプロパティ名、プロパティの値にTIMESオブジェクトの各プロパティの値である配列内の先頭の数値で構成されたオブジェクトです。
const [selectItems, setSelectItems] = useState(() => {
const newItems = {}
Object.keys(TIMES).forEach((name, i) => {
newItems[name] = TIMES[name][0]
})
return newItems
})
handleChange関数は、selectItemsオブジェクトを選択した各項目の値に更新します。その方法は、選択したoption要素の値e.target.value
をselect要素のid名と一致するselectItemsステートのプロパティ名の値に上書きします。
setSelectItems(p => ({
...p,
[e.target.id]: Number(e.target.value)
}))
ピッカーで選択した値をブラウザに反映させる
ピッカーで選択した各値をブラウザに反映させてみます。
// 省略
import TimeDisplay from './components/TimeDisplay'
import Spacer from './components/Spacer'
// 省略
const App = () => {
// 省略
return (
<>
<GlobalStyle />
<div>
<ItemPickers
// 省略
/>
<Spacer size="1.6em" />
<TimeDisplay
className="time"
time={selectItems}
delimiter=":"
fontSize="2.8em"
/>
</div>
</>
);
};
export default App
次のようにピッカーで選択中の値が、ピッカーの下に表示されるようになりました。
上記の時刻の部分は、TimeDisplayコンポーネントを使用して表示しています。
TimeDisplayコンポーネントは、時刻のデータをデジタル時計のように表示する役割を持つコンポーネントです。
import React from 'react';
import styled from 'styled-components'
const StyledTimeDisplay = styled.div`
color: ${({ textColor }) => textColor ? textColor : `#333`};
font-family: 'Share Tech Mono', monospace;
font-weight: bold;
font-size: ${({ fontSize }) => fontSize ? fontSize : '1em'};
`;
const TimeDisplay = ({ className, time, delimiter, fontSize, textColor }) => {
const newTime = Array.isArray(time) ? time : Object.values(time)
return (
<StyledTimeDisplay
className={className}
fontSize={fontSize}
textColor={textColor}
>
{
newTime.map((n, i, array) => (
<>
{/* padStartは文字列のメソッドであるため、Stringでnを文字列に変換 */}
<span>{String(n).padStart(2, "0")}</span>
{/* 末尾の区切り文字は表示しない */}
{(i !== array.length - 1) && delimiter}
</>
))
}
</StyledTimeDisplay>
);
};
export default TimeDisplay
timeプロパティには時刻のデータを次のようなオブジェクトか配列で指定します。
// オブジェクト
{
hour: 0,
min: 1,
sec: 40
}
// 配列
[0, 1, 40]
受け取ったtime
がオブジェクトだとmapメソッドが使えないため、配列に変換します。
const newTime = Array.isArray(time) ? time : Object.values(time)
DEMO
下記で実際の動作を確認できます。