1. 概要
上記、記事ではNext.jsやApollo Clientを使い、GraphQLよりデータを取得する内容でした。今回からはNext.jsやReactの基礎内容となります。
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. nodeのインストール
こちらを参考
3. プロジェクトを作成
npx create-next-app@latest sass-start
$ npx create-next-app@latest sass-start
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /home/sondon/dev/wsl/nodejs/react/sass-related/sass-start.
cd sass-start
4. 必要なライブラリをインストール
npm install --save-dev sass
npm install the-new-css-reset
npm install @mui/material @mui/styled-engine-sc styled-components @emotion/react @emotion/styled @mui/icons-material
code .
5. ソースコード
5-1-1. src/scss/common/_variables.scss
$layoutSpacing: 10px;
$headerHeight: 45px;
$sidebarWidth: 200px;
5-1-2. src/scss/common/_mixin.scss
@mixin p-supporting($color: black, $align: left, $size: 15px) {
color: $color;
display: block;
cursor: pointer;
@if ($color == red) {
font: {
weight: bold;
size: $size;
}
} @else if($color == blue) {
font: {
size: $size;
}
text: {
align: $align;
}
} @else {
cursor: help;
}
}
5-1-3. src/scss/common/_mq.scss
$breakpoints: (
"sm": "screen and (min-width: 480px)",
"md": "screen and (min-width: 600px)",
"lg": "screen and (min-width: 960px)",
"xl": "screen and (min-width: 1280px)"
) !default;
@mixin mq($breakpoint: md) {
@media #{map-get($breakpoints, $breakpoint)} {
@content;
}
}
5-1-4. src/scss/common/_index.scss
@forward "variables";
@forward "mixin";
@forward "mq";
5-2-1. src/lib/common/definitions.ts
export type SidebarLinkType = {
label: string;
path: string;
icon: any;
targetSegment: string | null;
};
5-2-2. src/lib/common/sidebar-links.tsx
import HomeIcon from "@mui/icons-material/Home";
import AdjustIcon from "@mui/icons-material/Adjust";
import { SidebarLinkType } from "./definitions";
const sidebarHome: SidebarLinkType = {
label: "Home",
path: "/",
icon: <HomeIcon />,
targetSegment: null,
};
const sidebarComponents: SidebarLinkType = {
label: "Components",
path: "/components",
icon: <AdjustIcon />,
targetSegment: "components",
};
export const sidebarLinks: SidebarLinkType[] = [sidebarHome, sidebarComponents];
5-2-3. src/lib/footer.tsx
import styles from "../app/layout.module.scss";
const Footer = () => {
return (
<footer className={styles.main_footer}>Copyright © 2023 isub</footer>
);
};
export default Footer;
5-2-4. src/lib/header.tsx
import Image from "next/image";
import styles from "../app/layout.module.scss";
const Header = () => {
return (
<div className={styles.main_header}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
);
};
export default Header;
5-2-5. src/lib/sidebar.tsx
"use client";
import Link from "next/link";
import { useSelectedLayoutSegment } from "next/navigation";
import styles from "../app/layout.module.scss";
import { sidebarLinks } from "./common/sidebar-links";
import { getWhichSelected } from "../lib/utils/util";
const Sidebar = () => {
const activeSegment = useSelectedLayoutSegment();
return (
<aside className={styles.main_sidebar}>
<p>
<a className={styles.red}>Hello red</a>
</p>
<p>
<a className={styles.blue}>Hi blue</a>
</p>
<p>
<a>Hey there</a>
</p>
<hr />
<ul>
{sidebarLinks.map((link, idx) => {
return (
<li key={idx}>
{link.icon}
<Link
href={link.path}
className={getWhichSelected(
activeSegment,
link.targetSegment,
styles.selected,
styles.unselected
)}
>
{link.label}
</Link>
</li>
);
})}
</ul>
</aside>
);
};
export default Sidebar;
5-2-6. src/lib/utils/util.ts
export const getWhichSelected = (
x: string | null,
y: string | null,
selectedClass: string,
unselectedClass: string
): string => (x === y ? selectedClass : unselectedClass);
5-3-1. src/app/globals.scss
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
5-3-2. src/app/layout.module.scss
@use "../scss/common/index" as *;
.main {
height: 100%;
box-sizing: border-box;
}
.main_header {
background-color: lightgoldenrodyellow;
height: $headerHeight - 10px;
@include mq(lg) {
height: $headerHeight + 10px !important;
}
padding: $layoutSpacing;
}
.main_container {
height: calc(100% - $headerHeight);
display: flex;
& .main_sidebar {
background-color: #f4f6f9;
width: $sidebarWidth;
padding: $layoutSpacing;
word-break: break-all;
& p:hover {
@include p-supporting;
& .red {
@include p-supporting($color: red, $size: 20px);
}
& .blue {
@include p-supporting($color: blue, $align: center, $size: 30px);
}
}
& hr {
background-color: darkgrey;
height: 1px;
border: none;
margin: 10px 0px 10px 0px;
}
& ul {
& li {
display: flex;
align-items: center;
margin: 10px;
& .unselected {
text-decoration: none;
}
& .selected {
text-decoration: underline;
font: {
weight: bold;
style: italic;
}
}
}
}
}
& .main_contents {
min-height: 500px;
width: calc(100% - $sidebarWidth);
padding: $layoutSpacing;
}
}
.main_footer {
background-color: darkgrey;
text-align: center;
padding: $layoutSpacing;
color: white;
}
5-3-3. src/app/layout.tsx
import "the-new-css-reset/css/reset.css";
import "./globals.scss";
import type { Metadata } from "next";
import { Noto_Sans_JP, Inter } from "next/font/google";
import styles from "./layout.module.scss";
import Header from "../lib/header";
import Footer from "../lib/footer";
import Sidebar from "../lib/sidebar";
const inter = Inter({ subsets: ["latin"] });
const notoSansJp = Noto_Sans_JP({
weight: ["400", "500", "700"],
subsets: ["latin"],
display: "swap",
});
export const metadata: Metadata = {
title: "Next.js Exercise",
description: "Sondon at isub",
};
const RootLayout = ({ children }: { children: React.ReactNode }) => {
return (
<html lang="en">
<body className={`${notoSansJp.className} ${inter.className}`}>
<main className={styles.main}>
<Header />
<div className={styles.main_container}>
<Sidebar />
<div className={styles.main_contents}>{children}</div>
</div>
<Footer />
</main>
</body>
</html>
);
};
export default RootLayout;
5-3-4. src/app/page.module.scss
.home {
text-align: center;
position: absolute;
top: 25%;
left: 40%;
}
5-3-5. src/app/page.tsx
import scss from "./page.module.scss";
const Home = () => {
return (
<div className={scss.home}>
<p>下記をベースにサンプルを書いていきます。</p>
<ul>
<li>Next</li>
<li>React</li>
<li>Redux</li>
<li>Typescript</li>
<li>Sass</li>
</ul>
</div>
);
};
export default Home;
5-3-6. src/app/components/page.module.scss
.components {
color: blue;
}
5-3-7. src/app/components/page.tsx
import scss from "./page.module.scss";
const Components = () => {
return <div className={scss.components}>Components</div>;
};
export default Components;
6. サーバーを起動
npm run dev
7. ブラウザで確認
- http://localhost:3000
8. ディレクトリの構造
.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
│ ├── next.svg
│ └── vercel.svg
├── src
│ ├── app
│ │ ├── components
│ │ │ ├── page.module.scss
│ │ │ └── page.tsx
│ │ ├── favicon.ico
│ │ ├── globals.scss
│ │ ├── layout.module.scss
│ │ ├── layout.tsx
│ │ ├── page.module.scss
│ │ └── page.tsx
│ ├── lib
│ │ ├── common
│ │ │ ├── definitions.ts
│ │ │ └── sidebar-links.tsx
│ │ ├── footer.tsx
│ │ ├── header.tsx
│ │ ├── sidebar.tsx
│ │ └── utils
│ │ └── util.ts
│ └── scss
│ └── common
│ ├── _index.scss
│ ├── _mixin.scss
│ ├── _mq.scss
│ └── _variables.scss
└── tsconfig.json
9 directories, 26 files
9. 備考
Next.jsやSass・Typescriptを使い、シンプルなレイアウトを構成する内容でした。
10. 参考
- Docs | Next.js (nextjs.org)
- Quick Start – React
- TypeScript: JavaScript With Syntax For Types. (typescriptlang.org)
- Sass: Syntactically Awesome Style Sheets (sass-lang.com)
- GitHub – elad2412/the-new-css-reset: The New Simple and Lighter CSS Reset
- Material UI: React components based on Material Design (mui.com)
投稿者プロフィール
-
開発好きなシステムエンジニアです。
卓球にハマってます。