withStylesによるスタイルの適用(タスク一覧編)(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
タスク一覧にスタイルを適用する
前回と同様にタスク一覧にもスタイルを適用します。Paperの周りにマージンを設定し、Paperの中に余白を設定します。
src/components/todos/index.js
import { compose } from 'redux' // 追加 import { withStyles } from '@material-ui/core/styles' // 追加 // stylesを追加 const styles = theme => ({ todoListRoot: { padding: theme.spacing.unit * 3, }, todoListContent: { maxWidth: 950, padding: theme.spacing.unit * 3, marginLeft : 'auto', marginRight : 'auto', }, }) class TodoComponent extends React.Component { // 略 render() { const {isOwnTodos, match: { params: {uid}}, classes} = this.props; // classesを追加 return ( <div className={classes.todoListRoot}> {/* classNameを追加 */} <Paper className={classes.todoListContent}> {/* classNameを追加 */} {/* 略 */} </Paper> </div> ) } } TodoComponent.propTypes = { // 略 classes: PropTypes.object.isRequired, // 追加 } // composeを使用してwithStylesとconnectを合成するように変更 export default compose( withStyles(styles), connect( mapStateToProps, mapDispatchToProps ))(TodoComponent)
実行結果です。Paperとナビボタンとの間が空きました。Paperの幅が最大950pxに制限され、左右均等に余白ができました。Paperの内部にも余白ができています。
続いて読み込み中のぐるぐるとタスクなしの文言の周りにも余白を追加します。
src/components/todos/TodoList.js
import { withStyles } from '@material-ui/core/styles' // 追加 // stylesを追加 const styles = theme => ({ message: { marginTop: theme.spacing.unit * 2, marginBottom: theme.spacing.unit * 2, }, }) const TodoList = ({displayName, todos, isOwnTodos, onTodoClick, classes}) => { // classesを追加 if (!isLoaded(todos)) { return <CircularProgress className={classes.message} /> // classNameを追加 } if (isEmpty(todos)) { return <Typography variant="body1" className={classes.message}>タスクがありません。</Typography> // classNameを追加 } // 略 } TodoList.propTypes = { // 略 classes: PropTypes.object.isRequired, // 追加 } export default withStyles(styles)(TodoList) // 変更
実行結果です。ぐるぐるとタスクなしの文言の周りに余白が追加されました。
以上でwithStylesを使用してタスク一覧にスタイルを適用することができました。
参考
★次回の記事
★目次
withStylesによるスタイルの適用(最近の更新編)(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
Material-UIにおいて個別にスタイルを適用するには、stylesオブジェクト(またはstyles関数)とwithStyleを使用します。
前回適用したPaperの周りにマージンと中に余白(padding)を設定します。
ダッシュボードにスタイルを適用する
ダッシュボートのマージンと余白を設定します。
src/components/dashboard/index.js
({/* xxx */}
のコメントは実行前に削除してください)
import PropTypes from 'prop-types' // 追加 import { withStyles } from '@material-ui/core/styles' // 追加 // stylesを追加 const styles = theme => ({ // #1 root: { padding: theme.spacing.unit * 5, }, content: { maxWidth: 800, marginLeft : 'auto', marginRight : 'auto', }, }) const Dashboard = ({classes}) => ( {/* #2 */} <div className={classes.root}> {/* #3 */} <div className={classes.content}> {/* #4 */} <RecentUpdatedTodos /> </div> </div> ) // propTypesを追加 Dashboard.propTypes = { classes: PropTypes.object.isRequired, } export default withStyles(styles)(Dashboard) // #5
theme
を引数に取る関数styles
を定義します(#1)。theme
はUIの設定が入ったオブジェクトです。デフォルトの設定は Default Theme - Material-UI に書いてあります。- (#1)で設定したcss設定を
classes
という名前で下位コンポーネントに渡します(#5)。withStyles
という関数を使用します。 - Dashboardコンポーネントで
classes
プロパティを受け取ります(#2)。 className
にstylesで定義した内容を指定します(#3, #4)。
実行結果です。Paperとナビボタンとの間が空きました。Paperの幅が最大800pxに制限され、左右均等に余白ができました。
Google Cromeのデベロッパーツールでも確認できました。
引き続き”最近の更新”の文字周りと読み込み中表示のCircularProgress、"データがありません。”の表示の周りにmargin及びpaddingを適用します。
withStyles関数をfirebaseConnectやconnectと同時に使う時は、compose関数の中に並べます。
src/components/dashboard/recentUpdatedTodos/index.js
import { withStyles } from '@material-ui/core/styles' // 追加 // stylesを追加 const styles = theme => ({ title: { paddingTop: theme.spacing.unit * 3, paddingRight: theme.spacing.unit * 3, paddingLeft: theme.spacing.unit * 3, }, message: { marginTop: theme.spacing.unit * 2, marginBottom: theme.spacing.unit * 2, marginRight: theme.spacing.unit * 3, marginLeft: theme.spacing.unit * 3, } }) const RecentUpdatedList = (todos, classes) => { // classesを追加 if (!isLoaded(todos)) { return <CircularProgress className={classes.message} /> // classNameを追加 } if (isEmpty(todos)) { return <Typography variant="body1" className={classes.message}>データがありません。</Typography> // classNameを追加 } // 略 } let RecentUpdatedTodos = ({todos, classes}) => { // classesを追加 return ( <Paper> <Typography variant="h5" className={classes.title}>最近の更新</Typography> {/* classNameを追加 */} {RecentUpdatedList(todos, classes)} {/* classesを引数に追加 */} </Paper> ) } RecentUpdatedTodos.propTypes = { // 略 classes: PropTypes.object.isRequired, // 追加 } RecentUpdatedTodos = compose( withStyles(styles), // 追加 firebaseConnect(firebaseQueries), connect( mapStateToProps ))(RecentUpdatedTodos)
実行結果です。”最近の更新”の文字とPaperの縁の間に余白ができました。
読み込み中のぐるぐるの周りにも余白を追加しました。
以上でwithStylesを使用してダッシュボードにスタイルを適用することができました。
★次回の記事
★目次
Material-UIのPaperを使用する(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
Material-UIのPaperを使用し、コンテンツが紙の上に載っているようなUIにします。 最新の更新一覧とタスク一覧をPaperの上に載せます。
最新の更新一覧でPaperを使う
最新の更新をPaperで囲みます。<div>
を<Paper>
に変更します。
src/components/dashboard/recentUpdatedTodos/index.js
({/* xxx */}
のコメントは実行前に削除してください)
import Paper from '@material-ui/core/Paper' // 追加 let RecentUpdatedTodos = ({todos}) => { return ( <Paper> {/* 変更 */} <Typography variant="h5">最近の更新</Typography> {RecentUpdatedList(todos)} </Paper> {/* 変更 */} ) }
実行結果です。白い背景になりました。次回でマージンを入れると背景から浮いた感じがわかるようになります。
タスク一覧でPaperを使う
タスク一覧をPaperで囲みます。
今後の実装のため、ルート要素の<div>
は残しておいて、その下に<Paper>
コンポーネントを挿入します。
src/components/todos/index.js
import Paper from '@material-ui/core/Paper' // 追加 class TodoComponent extends React.Component { // 略 render() { const {isOwnTodos, match: { params: {uid}}} = this.props; return ( <div> <Paper> {/* 追加 */} <Title isOwnTodos={isOwnTodos} uid={uid} /> {isOwnTodos && <AddTodo uid={uid} />} <Notice /> <VisibleTodoList uid={uid} isOwnTodos={isOwnTodos} /> <Footer /> </Paper> {/* 追加 */} </div> ) } }
実行結果です。こちらも白い背景になり、グレーの全体部分から浮いたようになりました。
以上でコンテンツをPaperに載せることができました。
参考
★次回の記事
★目次
Material-UIのTypographyを使用する(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
Material-UIのコンポーネントで囲まれていない文字列をTypographyで囲っていきます。 なお、一部はこの後のページでMaterial-UIのコンポーネントに変換するので、変換しない文字列もあります。
NoMatchページ
始めにNoMatch.jsを変更します。variantにh2
を指定します。
src/components/NoMatch.js
({/* xxx */}
のコメントは実行前に削除してください)
import Typography from '@material-ui/core/Typography' // 追加 const NoMatch = () => ( <Typography variant="h2">ページが見つかりません</Typography> {/* 変更 */} )
実行結果です。
ダッシュボード画面
"最近の更新”と”データがありません”の文字列をTypographyで囲みます。
src/components/dashboard/recentUpdatedTodos/index.js
import Typography from '@material-ui/core/Typography' // 追加 const RecentUpdatedList = (todos) => { // 略 if (isEmpty(todos)) { return <Typography variant="body1">データがありません。</Typography> // 変更 } // 略 } let RecentUpdatedTodos = ({todos}) => { return ( <div> <Typography variant="h5">最近の更新</Typography> {/* 変更 */} {RecentUpdatedList(todos)} </div> ) }
実行結果です。
タスク一覧のコンポーネントの順番変更
タスク一覧の文字列をTypographyに変更する前にコンポーネントの順番を変更します。
タスク追加フォーム “{ユーザ名}のタスク一覧” タスク一覧
という順番から
“{ユーザ名}のタスク一覧” タスク追加フォーム タスク一覧
という順番に変更します。
ユーザ名を表示するのコンポーネントはTitle
という名前のコンポーネントにします。
src/components/todos/Title.js
(新規作成)
import React from 'react' import { compose } from 'redux' import { connect } from 'react-redux' import { firebaseConnect } from 'react-redux-firebase' import PropTypes from 'prop-types' const Title = ({displayName, isOwnTodos}) => { const name = isOwnTodos ? 'あなた' : `${displayName} さん`; return ( <React.Fragment> {displayName && <div>{name}のタスク一覧</div>} </React.Fragment> ) } Title.propTypes = { displayName: PropTypes.string, isOwnTodos: PropTypes.bool.isRequired, } const firebaseQueries = ({uid}) => ( [ {path: `users/${uid}/displayName`, type: 'once'}, ] ) const mapStateToProps = ({firebase: {data : {users}}}, {uid}) => ({ displayName: users && users[uid] && users[uid].displayName, }) export default compose( firebaseConnect(firebaseQueries), connect( mapStateToProps ))(Title)
src/components/todos/index.js
import Title from './Title' // 追加 class TodoComponent extends React.Component { // 略 render() { const {isOwnTodos, match: { params: {uid}}} = this.props; return ( <div> <Title isOwnTodos={isOwnTodos} uid={uid} /> {/* 追加 */} {isOwnTodos && <AddTodo uid={uid} />} <Notice /> <VisibleTodoList uid={uid} isOwnTodos={isOwnTodos} /> <Footer /> </div> ) } }
src/containers/todos/VisibleTodoList.js
const mapStateToProps = ({visibilityFilter, firebase: {data : {users, todos}}}, {uid}) => { // #2 return { // displayName: users && users[uid] && users[uid].displayName, を削除 todos: getVisibleTodos(todos && todos[uid], visibilityFilter) } } const firebaseQueries = ({uid}) => ( [ // {path: `users/${uid}/displayName`, type: 'once'}, を削除 `todos/${uid}` ] )
src/components/todos/TodoList.js
const TodoList = ({todos, isOwnTodos, onTodoClick}) => { // displayNameを削除 // 略 // const name = isOwnTodos ? 'あなた' : `${displayName} さん`; を削除 return ( {/* <div> {displayName && <div>{name}のタスク一覧</div>} の削除 */} <List> {Object.keys(todos).map( (key) => ( <Todo key={key} isOwnTodos={isOwnTodos} {...todos[key]} onClick={isOwnTodos ? (() => onTodoClick(key)) : (() => {})} /> ) )} </List> {/* </div> の削除 */} ) } TodoList.propTypes = { // displayName: PropTypes.string, は削除 isOwnTodos: PropTypes.bool.isRequired, // 略 }
以上でユーザ名の表示をタスク追加フォームの上に移動することができました。
タスク一覧画面
"{ユーザ名}のタスク一覧"と"タスクがありません"の文字列をTypographyで囲みます。"{ユーザ名}のタスク一覧"の方は、gutterBottom
を指定して、下に余白を設定しています。
src/components/todos/Title.js
({/* xxx */}
のコメントは実行前に削除してください)
import Typography from '@material-ui/core/Typography' // 追加 const Title = ({displayName, isOwnTodos}) => { const name = isOwnTodos ? 'あなた' : `${displayName} さん`; return ( <React.Fragment> {displayName && <Typography variant="h5" gutterBottom>{name}のタスク一覧</Typography>} {/* 変更 */} </React.Fragment> ) }
src/components/todos/TodoList.js
import Typography from '@material-ui/core/Typography' // 追加 const TodoList = ({todos, isOwnTodos, onTodoClick}) => { // 略 if (isEmpty(todos)) { return <Typography variant="body1">タスクがありません。</Typography> // 変更 } // 略 }
実行結果です。
以上で通常のテキストをTypographyに変更できました。
参考
★次回の記事
★目次
Material-UIのCircularProgressを使用する(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
データの読み込み中、"読み込み中"という文言での表示から、CircularProgressへ変更します。CircularProgressは読み込み中を示すぐるぐるです。
"最近の更新"の読み込み表示の変更
src/components/dashboard/recentUpdatedTodos/index.js
import CircularProgress from '@material-ui/core/CircularProgress' // 追加 const RecentUpdatedList = (todos) => { if (!isLoaded(todos)) { return <CircularProgress /> // 変更 } // 略 }
実行結果です。青色(primary色)で読み込み中を表す円がぐるぐる回ります。
タスク一覧の読み込み表示の変更
src/components/todos/TodoList.js
import CircularProgress from '@material-ui/core/CircularProgress' // 追加 const TodoList = ({displayName, todos, isOwnTodos, onTodoClick}) => { if (!isLoaded(todos)) { return <CircularProgress /> // 変更 } // 略 }
実行結果です。こちらも青色(primary色)で読み込み中を表す円がぐるぐる回ります。
ログイン中表示の変更
ログイン中の表示は後々のためにinherit
(親コンポーネントから継承)にします。
src/components/login/index.js
import CircularProgress from '@material-ui/core/CircularProgress' // 追加 let Login = ({ auth, loginWithGoogle, logout }) => { if (!isLoaded(auth)) { return <CircularProgress color="inherit" /> // 変更 } // 略 }
実行結果です。
以上で読み込み中の時にCircularProgressを表示することができました。
参考
★次回の記事
★目次
Material-UIのListを使用する(タスク一覧編)(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
前回に引き続き、<ul>
を<List>
に、<li>
を<ListItem>
に変更します。
今回はタスク一覧です。
タスク一覧でMaterial-UIのListを使用する
<ul>
を<List>
に変更します。また、後で必要になるので、isOwnTodos
をpropsとしてTodoコンポーネントに渡します。
src/components/todos/TodoList.js
({/* xxx */}
のコメントは実行前に削除してください)
import List from '@material-ui/core/List' // 追加 const TodoList = ({displayName, todos, isOwnTodos, onTodoClick}) => { // 中略 return ( <div> {displayName && <div>{name}のタスク一覧</div>} <List> {/* 変更 */} {Object.keys(todos).map( (key) => ( <Todo key={key} isOwnTodos={isOwnTodos} // 追加 {...todos[key]} onClick={isOwnTodos ? (() => onTodoClick(key)) : (() => {})} /> ) )} </List> {/* 変更 */} </div> ) }
続いて<li>
を<ListItem>
に変更します。
<ListItem>
内のテキストは<ListItemText>
で囲みます。
src/components/todos/Todo.js
({/* xxx */}
のコメントは実行前に削除してください)
import ListItem from '@material-ui/core/ListItem' // 追加 import ListItemText from '@material-ui/core/ListItemText' // 追加 const Todo = ({onClick, completed, text}) => ( <ListItem // 変更 onClick={onClick} {/* styleは下のspanに移動 */} > <ListItemText> {/* 追加 */} <span style={ {textDecoration: completed ? 'line-through' : 'none'}}>{text}</span> {/* spanで囲む*/} </ListItemText> {/* 追加 */} </ListItem> {/* 変更 */} )
実行結果です。Material-UIのListに変更できました。タスクの完了フラグの更新も正常に動作します。
自身のタスクのみボタンUIにする
自分自身のタスクを表示している時のみクリックで完了フラグを切り替えられるので、その時のみ表示をbutton
にします。
src/components/todos/Todo.js
({/* xxx */}
のコメントは実行前に削除してください)
const Todo = ({isOwnTodos, onClick, completed, text}) => ( // isOwnTodosを追加 <ListItem button={isOwnTodos} // 追加 onClick={onClick} > {/* 中略 */} </ListItem> ) Todo.propTypes = { isOwnTodos: PropTypes.bool.isRequired, // 追加 // 略 }
実行結果です。
自身のタスクの場合↓マウスオーバーで選択した項目の背景色が変わります。
ログアウト時または他のユーザのタスクの場合↓マウスオーバーしても色は変わりません。
完了マークと未完了マークの表示
完了か未完了かをアイコンでも表示します。
- 完了の場合は緑色のチェックマークを表示します。
- 未完了の場合は、自身のタスクの場合のみ中抜きの四角(□)を表示する。このことによって、自分のタスクは”押せそう”な感じにします。
- ListItemTextの位置をそろえるために
inset
属性を追加します。
src/components/todos/Todo.js
({/* xxx */}
のコメントは実行前に削除してください)
import ListItemIcon from '@material-ui/core/ListItemIcon' // 追加 import green from '@material-ui/core/colors/green' // 追加 import Done from '@material-ui/icons/Done' // 追加 import CheckBoxOutlineBlank from '@material-ui/icons/CheckBoxOutlineBlank' // 追加 // CheckIcon関数コンポーネントを追加 const CheckIcon = (isOwnTodos, completed) => { // 完了の場合 if (completed) { return ( <ListItemIcon> <Done nativeColor={green[500]} /> </ListItemIcon> ) } // 未完了の場合 // 自身のタスクの場合 if (isOwnTodos) { return ( <ListItemIcon> <CheckBoxOutlineBlank /> </ListItemIcon> ) } return null } const Todo = ({isOwnTodos, onClick, completed, text}) => ( <ListItem button={isOwnTodos} onClick={onClick} > {CheckIcon(isOwnTodos, completed)} {/* 追加 */} <ListItemText inset> {/* insetを追加 */} <span style={ {textDecoration: completed ? 'line-through' : 'none'}}>{text}</span> </ListItemText> </ListItem> )
自身のタスクの場合↓
ログアウト時または他のユーザのタスクの場合↓
以上でタスク一覧をMaterial-UIのListに変更できました。
参考
- List React component - Material-UI
- Color - Material-UI
- Material-UIのアイコンの色の変え方 - Javaエンジニア、React+Redux+Firebaseでアプリを作る
★次回の記事
★目次
Material-UIのListを使用する(最近の更新編)(STEP 4 : Material-UIの導入 - React + Redux + Firebase チュートリアル)
★前回の記事
Material-UIのListを導入します。
<ul>
を<List>
に、<li>
を<ListItem>
に変更します。
最近の更新リストでMaterial-UIのListを使用する
<ul>
を<List>
に変更します。
src/components/dashboard/recentUpdatedTodos/index.js
import List from '@material-ui/core/List' // 追加 let RecentUpdatedTodos = ({todos}) => { // 中略 return ( <div> {header} <List> {/* 変更 */} {todos.map(({key, value:todo}) => <UserUpdatedTodos key={key} {...todo}/> )} </List> {/* 変更 */} </div> ) }
続いて<li>
を<ListItem>
に変更します。
<ListItem>
内のテキストは<ListItemText>
で囲みます。
src/components/dashboard/recentUpdatedTodos/UserUpdatedTodo.js
({/* xxx */}
のコメントは実行前に削除してください)
import ListItem from '@material-ui/core/ListItem' // 追加 import ListItemText from '@material-ui/core/ListItemText' // 追加 const UserUpdatedTodo = ({text, eventType, uid, displayName, _updatedAt}) => ( <ListItem> {/* 変更 */} <ListItemText> {/* 追加 */} <Link to={`/users/${uid}/todos`}>{displayName}</Link>さんが {text} を{ eventType === 'CREATE' ? '作成' : '更新'} しました。 ({ moment(_updatedAt).fromNow()}) </ListItemText> {/* 追加 */} </ListItem> {/* 変更 */} )
実行結果です。
Material-UIのListに変更ができました。
見た目の調整
さらに変更していきます。
divider(境界線)を入れる
リストの各項目の間に線を入れます。
src/components/dashboard/recentUpdatedTodos/UserUpdatedTodo.js
const UserUpdatedTodo = ({text, eventType, uid, displayName, _updatedAt}) => ( <ListItem divider> {/* dividerを追加 */} <ListItemText> {/* 略 */} </ListItemText> </ListItem> )
実行結果です。項目間にグレーの線が入りました。
secondaryテキスト
更新時間(○時間前の表示)をsecondaryテキストとして表示します。
src/components/dashboard/recentUpdatedTodos/UserUpdatedTodo.js
const UserUpdatedTodos = ({text, eventType, uid, displayName, avatarUrl, _updatedAt}) => ( <ListItem divider> <ListItemText secondary={moment(_updatedAt).fromNow()}> {/* secondaryを追加 */} <Link to={`/users/${uid}/todos`}>{displayName}</Link>さんが {text} を{ eventType === 'CREATE' ? '作成' : '更新'} しました。 {/* 更新時間を削除 */} </ListItemText> </ListItem> )
実行結果です。更新時間が2行目に表示され、文字色が薄くなりました。
項目を選択した時にリンク先に飛ぶように変更
現在の実装では、ユーザ名をクリックした時にリンク先に飛ぶようにしていますが、リストの項目が選択された時にリンク先に飛ぶように変更します。
ListItem
のbutton
属性を使用することで各項目がボタンになり、マウスオーバーした時に色が変わります。
react-routerのLink
とListItem
を同時に使うには、ListItem
のcomponent
属性にLink
コンポーネントを渡します。
src/components/dashboard/recentUpdatedTodos/UserUpdatedTodo.js
const UserUpdatedTodos = ({text, eventType, uid, displayName, avatarUrl, _updatedAt}) => ( <ListItem divider button component={Link} to={`/users/${uid}/todos`}> {/* 変更 */} <ListItemText secondary={moment(_updatedAt).fromNow()} > {displayName}さんが {text} を{eventType === 'CREATE' ? '作成' : '更新'}しました。 {/* Linkタグを削除 */} </ListItemText> </ListItem> )
実行結果です。マウスオーバー時に色が変わります。クリックするとリンク先に飛びます。
以上で最近の更新リストをMaterial-UIのListに変更できました。
参考
★次回の記事
★目次