## 仕様 ><img src="../../image/2025-09-14-01-34-44.png" width="300"> - article.tsとdata.tsがある - data.tsはスニペット記事の元データの配列となっている - article.tsはprops経由で記事データを受け取り、コンポーネントを返す - navigation, トグルボタン, articleのスタイルを用意する - page.tsではgetStorageThemeのようなlocalStorageを扱う関数を用意する - localStorageからdefault themeを取得し、もしなければlight-themeと設定するようにしたい - useStateでthemeを取得し、初期化する値はgetStorageThemeから取得したthemeにする - トグルボタンを用意して、現在のthemeに応じてライト、ダークを切り替える - useEffectでclassNameがthemeに応じて切り替わり、localStorageの値も切り替わるようにする - 実装のイメージとしては、添付した画像、app/lesson3のdata.tsとpage.module.cssがサンプルとなる。 - cssについては現在のnextjs構成では適さない要素の指定もあるので、適宜修正すること。 ## 実装 > <video controls src="https://i.gyazo.com/25e74dbc2ae752103e341fbb73c767cc.mp4" width="300" title="ダーク・ライドモード"></video> [github](https://github.com/kmishima16/react-project-school/blob/main/app/lesson3/page.tsx) TODO - テーマの管理と切り替え機能 - ローカルストレージへのテーマの保存と読み込み - UIコンポーネントの表示 ### テーマ管理 今回は、page.tsの親要素の`style`属性に、テーマに関する`CSSProterties`の変数を代入する形で実装を行った。 CSS Modulesで定義した`page.module.css`の各クラスも[[CSS変数]]を参照するようにしている。 bgColor,primaryColor,secondaryColor,borderColor,shadowColorなどのCSS変数を用いるようにすることで、 useStateによる[[CSSProterties]]の切り替えに対応してスタイルが切り替わるようになる。 #### CSS変数を切り替える方法 ReactでstyleプロパティにReact.CSSPropertiesを指定し、その中にCSS変数を含めることで、その要素とその子孫要素におけるCSS変数の値が確定(上書き)されます。 - [インラインスタイルでCSSを渡すときの方はCSSProterties](https://qiita.com/penpeenpen/items/a587d2a0c76a1cb97fec) より大規模なアプリケーションであれば、Hooksを使ってCSS変数を切り替えるような仕組みにすることで、状態管理をhooksやcontextとして切り出しすることができる。 - [hooksでCSS変数を切り替える](https://zenn.dev/stin/articles/how-to-change-theme-with-css-modues) ### ローカルストレージのテーマ保存 ローカルストレージはグローバル変数`localStorage: Storage`として利用可能のようだった。 useStateの変数theme変更の副作用として、[[localStorage]]内にテーマを保存しておき、セッションが途切れても保存テーマを読み込めるようにしている。 localStorageからの値取得はコンポーネントの外で関数として定義しておく。 → ただし、こうするとレンダリングが終わってからuseEffectが走るので、一瞬ライトテーマになった後ダークテーマに切り替わってしまう・・・ ```ts function getStorageTheme(): Theme { if (typeof window !== "undefined") { const savedTheme: Theme = localStorage.getItem("theme") as Theme; return savedTheme || "light-theme"; } return "light-theme"; } ``` #### `if (typeof window !== "undefined")`について ブラウザ環境(window オブジェクトが存在するか)をチェックしてからアクセスしているため、 サーバーサイドでのレンダリング時(Next.jsなどのフレームワークを使用している場合)にエラーが発生するのを防いでいる。 SSRの場合、`window`プロパティが存在しない場合もあるので、エラーハンドリングは行っておく必要がある。 - [localStorageの利用をカスタムフック化させる事例](https://qiita.com/YuukiYoshida/items/7c1cecfa80aba9f7edc9) lealogさんも同じような調査をしていた - [NextJSでサーバ・クライアントで一度だけやりたい処理](https://leaysgur.github.io/posts/2020/09/09/132905/) ### CSSのマークアップ - containerはflex, flex-direction:columnにする - navigationはflex, justify-content: space-between, align-items: centerにする - titleはposition: relativeにしている - トグルボタンのコンテナ - display: flex, align-items:centerにしている - Icon: display: inline-block - themeToggle: 楕円形のボタン position: relative, outline: none, overflow:hidden - スライダーが絶対位置なので、この楕円を基準点にしている - outline:none にすることで、フォーカス時の枠線を非表示にしている - overflow: hiddenで、ボックスから要素がはみ出たものは隠すようにしている - toggleSlider: 小さい円のボタン position: absoluteで絶対位置の操作にして、topとleftで初期位置を決める - あとはdark, lightでスタイルを切り替える - darkになったときは、`translateX(24px)`で、右に移動させる - main: flex:1 にしているのはなぜ? - article - gridレイアウト:`grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));` - コンテナの幅に応じて、最低300pxの幅を持つ列をできるだけ多く配置してください。もしスペースが余ったら、すべての列が均等にそのスペースを分け合ってコンテナ幅いっぱいに広がるようにしてください。 - article: flex, flex-direction: column - タイトル - メタデータ: flex, space-between, align-items:center, flex-wrap: wrap - Date - ReadForLength - 余白のつけかたとか、フレキシブルボックスの使い分けとか、かなり参考になるなぁ🤔 #### mainで`flex:1`にしている理由は? このコードにおける `.main` の `flex: 1;` は、親要素である `.container` の中で、利用可能な残りのスペースをすべて占めるように伸長させるためのものです。 `.container` は `display: flex;` と `flex-direction: column;` を持っているため、子要素は縦に並びます。 `.main` に `flex: 1;` を指定することで、他の兄弟要素(この場合は `.navigation`)が必要なスペースを取った後、 残った垂直方向のスペースをすべて `.main` が埋めるようになります。 これにより、コンテンツの量に関わらず、メインコンテンツエリアが常にページの残りの部分を埋めるため、レイアウトの整合性を保つことができます。 ## 余白の付け方のパターン #CSSテク ### 中央揃えのテクニック (margin: 0 auto;) コンテナ要素を親要素の中央に配置する定番の方法です。 ```css .container { box-sizing: border-box; margin: 0 auto; padding: 20px; ``` ### 要素間の垂直方向の距離 (margin-bottom) 特定の要素の下に余白を作り、次の要素との間にスペースを設けます。 ```css .navigation { /* ... */ margin-bottom: 2rem; /* ... */ } .articleTitle { /* ... */ margin-bottom: 1rem; /* ... */ } ``` ### コンテンツの周りに余白を作る コンテナやカード、ボタンなどの内側に余白を持たせ、コンテンツが詰まって見えないようにします。 ```css .container { /* ... */ padding: 20px; /* ... */ } .article { padding: 2rem; /* ... */ } ``` ### パターン1: ヘッダー要素の横並びと両端揃え justify-content: space-between で子要素を両端に配置し、align-items: center で垂直方向の中央に揃えています。Webサイトのヘッダーでよく使われるレイアウトです。 ```css .navigation { display: flex; justify-content: space-between; align-items: center; /* ... */ } ``` ### パターン2: ページ全体の基本構造(縦積み) flex-direction: column を使うと、子要素を縦方向に並べることができます。ページ全体のレイアウトや、カード内のコンテンツを上から下に並べる際に便利です。 ```css .container { /* ... */ display: flex; flex-direction: column; /* ... */ } .article { /* ... */ display: flex; flex-direction: column; } ``` ### パターン3: 特定の要素を一番下に配置するテクニック これは少し応用的な使い方です。Flexコンテナの中で、特定の子要素に margin-top: auto を設定すると、その要素はコンテナの一番下まで押し下げられます。 このファイルでは、記事カード (.article) の高さが異なっても、記事の抜粋 (.articleSnippet) が常に下部に表示されるようにするために使われています。 ```css .article { padding: 2rem; height: 100%; display: flex; flex-direction: column; } .articleSnippet { /* ... */ flex: 1; margin-top: auto; } ```