React超基礎2

# はじめに
【再掲】
1年前にやったreactを忘れてしまったので、基礎から思い出しをしました。
ハンズオン形式でと思ったのですが、思った以上に基礎的なものになってしまいました。
かなり勉強初期段階の方向けの資料になっております。


# Step1　ヘッダー土台
簡易的なヘッダー（ロゴとナビゲーションのみ）を作成。
スマホの際は、ハンバーガーメニューを表示するため、非表示にする。
App.tsx

```react
import "./App.css";
import Header from "./components/Header";

function App() {
	return <Header></Header>;
}

export default App;
```

Header.tsx
```react
import styled from "@emotion/styled";

const Header = () => {
	return (
		<StyledHeader>
			<h1>MySite</h1>
			<StyledNav>
				<a href="#">Home</a>
				<a href="#">About</a>
				<a href="#">Contact</a>
			</StyledNav>
		</StyledHeader>
	);
};

const StyledHeader = styled.header`
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 1rem 2rem;
`;

const StyledNav = styled.nav`
	display: flex;
	gap: 1rem;

	@media (max-width: 768px) {
		display: none;
	}
`;
export default Header;
```
**【ポイント】**
* gap: 1rem は、横並びになった各子要素の間に 1rem（＝約16px）のスペース作る。

# Step2 ハンバーガーメニュー
スマホ表示の際に、ハンバーガーメニューを表示させる。
ハンバーガーメニューをクリックすると、メニューが右横から表示するようにする。

Header.tsx
```react
import styled from "@emotion/styled";
import { useState } from "react";

const Header = () => {
	const [isOpen, setIsOpen] = useState(false);
	const closeMenu = () => setIsOpen(false);

	return (
		<>
			<StyledHeader>
				<h1>MySite</h1>
				<StyledHamburger onClick={() => setIsOpen(!isOpen)}>☰</StyledHamburger>

				<StyledNav>
					<StyledLink href="#">Home</StyledLink>
					<StyledLink href="#">About</StyledLink>
					<StyledLink href="#">Contact</StyledLink>
				</StyledNav>
			</StyledHeader>

			{isOpen && <StyledOverlay onClick={closeMenu} />}

			<StyledMobileMenuContainer isOpen={isOpen}>
				<CloseButton onClick={closeMenu}>×</CloseButton>
				<StyledLink href="#">Home</StyledLink>
				<StyledLink href="#">About</StyledLink>
				<StyledLink href="#">Contact</StyledLink>
			</StyledMobileMenuContainer>
		</>
	);
};

const StyledHeader = styled.header`
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 1rem 2rem;
	flex-wrap: wrap;
`;

const StyledNav = styled.nav`
	display: flex;
	gap: 1rem;

	@media (max-width: 768px) {
		display: none;
	}
`;

const StyledHamburger = styled.button`
	display: none;
	font-size: 2rem;
	background: none;
	border: none;
	cursor: pointer;

	@media (max-width: 768px) {
		display: block;
	}
`;

const StyledMobileMenuContainer = styled.nav<{ isOpen: boolean }>`
	display: none;

	@media (max-width: 768px) {
		position: fixed;
		top: 0;
		right: 0;
		height: 100vh;
		width: 250px;
		background: white;
		display: flex;
		flex-direction: column;
		padding: 2rem 1.5rem;
		box-shadow: -2px 0 10px rgba(0, 0, 0, 0.15);
		transform: translateX(${({ isOpen }) => (isOpen ? "0" : "100%")});
		transition: transform 0.3s ease-in-out;
		z-index: 200;

		a {
			padding: 0.75rem 0;
			border-bottom: 1px solid #ddd;
		}
	}
`;

const StyledLink = styled.a`
	text-decoration: none;
	font-weight: normal;
	color: black;
	font-size: 1rem;

	&:hover {
		opacity: 0.7;
	}
`;

const CloseButton = styled.button`
	position: absolute;
	top: 1rem;
	right: 1rem;
	font-size: 1.5rem;
	background: none;
	border: none;
	cursor: pointer;
`;

const StyledOverlay = styled.div`
	position: fixed;
	top: 0;
	left: 0;
	width: 100vw;
	height: 100vh;
	background: rgba(0, 0, 0, 0.4);
	z-index: 150;
`;
export default Header;

```
**【ポイント】**
* transform + transition によるスライドアニメーションを実装。
    * isOpen を使ってスタイルを切り替えることがEmotionで可能に。
* `isOpen &&`で条件付きレンダリングを行うことができる。

# Srep3 マインセクション以降
その後は、Reactの仕様というより、通常の実装のみ。
そのため、一部のコードのみを共有します。
Hero.tsx
```react
import styled from "@emotion/styled";

const Hero = () => {
	return (
		<StyledHeroSection>
			<StyledHeroContent>
				<StyledHeroTitle>シンプルで伝わるデザイン。</StyledHeroTitle>
				<StyledHeroSubtitle>
					あなたのビジネスを、次のステージへ。
				</StyledHeroSubtitle>
				<StyledCTAButton>お問い合わせ</StyledCTAButton>
			</StyledHeroContent>
		</StyledHeroSection>
	);
};

const StyledHeroSection = styled.section`
	padding: 4rem 1rem;
	text-align: center;
	background: #f0f4f8;

	@media (min-width: 768px) {
		padding: 6rem 2rem;
	}
`;

const StyledHeroContent = styled.div`
	max-width: 800px;
	margin: 0 auto;
`;

const StyledHeroTitle = styled.h1`
	font-size: 2.5rem;
	margin-bottom: 1rem;

	@media (max-width: 768px) {
		font-size: 1.5rem;
	}
`;

const StyledHeroSubtitle = styled.p`
	font-size: 1rem;
	color: #555;
	margin-bottom: 2rem;
`;

const StyledCTAButton = styled.button`
	padding: 0.75rem 2rem;
	font-size: 1rem;
	background: #0070f3;
	color: white;
	border: none;
	border-radius: 4px;
	cursor: pointer;

	&:hover {
		opacity: 0.8;
	}
`;

export default Hero;
```
App.tsx
```react
import "./App.css";
import CTA from "./components/CTA";
import Feature from "./components/Feature";
import Header from "./components/Header";
import Hero from "./components/Hero";

function App() {
	return (
		<>
			<Header />
			<Hero />
			<Feature />
			<CTA />
		</>
	);
}

export default App;
```
※Feature、CTAコンポーネントは割愛いたします。