Skip to content

Commit

Permalink
Merge pull request #11 from P-r4Tes/feat/asidebar
Browse files Browse the repository at this point in the history
Feat/asidebar Sidebar Component
  • Loading branch information
entrolEC authored Mar 8, 2024
2 parents bf5f58f + 6a5ea57 commit 93143bf
Show file tree
Hide file tree
Showing 23 changed files with 2,388 additions and 1,959 deletions.
3,965 changes: 2,028 additions & 1,937 deletions .pnp.cjs

Large diffs are not rendered by default.

24 changes: 13 additions & 11 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { StorybookConfig } from "@storybook/nextjs";
import path, { join, dirname } from "path";

import path, { dirname, join } from "path";

/**
* This function is used to resolve the absolute path of a package.
Expand All @@ -10,16 +11,7 @@ function getAbsolutePath(value: string): any {
}
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
webpackFinal: async config => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
"@": path.resolve(__dirname, "../src"),
};
}
return config;
},
staticDirs: ["../src/assets"],
staticDir: ["../public"],
addons: [
getAbsolutePath("@storybook/addon-links"),
getAbsolutePath("@storybook/addon-essentials"),
Expand All @@ -42,5 +34,15 @@ const config: StorybookConfig = {
docs: {
autodocs: "tag",
},

webpackFinal: async config => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
"@": path.resolve(__dirname, "../src"),
};
}
return config;
},
};
export default config;
11 changes: 10 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
],
}
};

export default nextConfig;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
},
"dependencies": {
"@yarnpkg/plugin-typescript": "^4.0.0",
"clsx": "^2.1.0",
"firebase": "^10.8.1",
"next": "14.1.0",
"prettier-plugin-tailwindcss": "^0.5.11",
"react": "^18",
"react-dom": "^18"
},
Expand Down
4 changes: 2 additions & 2 deletions src/__test__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe("Page", () => {
it("renders a heading", () => {
render(<Page />);

const Text = screen.getByText("Calendar App");
expect(Text).toBeVisible();
const Element = screen.getByTestId("root-layout");
expect(Element).toBeInTheDocument();
});
});
14 changes: 14 additions & 0 deletions src/api/groups.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { isEmpty, isStringArgumentsValid } from "@/lib/functions/stringValidation";
import { deleteFirebase, getFirebase, popFirebaseArray, postFirebase, pushFirebaseArray } from "./firebase";
import { collection, getDocs } from "firebase/firestore";
import { db } from "@/lib/firebaseConfig";

export const postGroup = (body: group) => {
if (isEmpty(body.name)) throw new Error("Invalid name");
Expand Down Expand Up @@ -36,3 +38,15 @@ export const popGroupSchedule = (id: string, value: string) => {
if (!isStringArgumentsValid(id, value)) throw new Error("Invalid string arguments");
popFirebaseArray<"groups">("groups", id, "schedules", value);
};

export const getAllGroups = async () => {
try {
const groupsRef = collection(db, "groups");
const querySnapshot = await getDocs(groupsRef);
const groups: (group & id)[] = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }) as group & id);
return groups;
} catch (error) {
console.error("Error getting documents: ", error);
throw new Error("Failed to fetch groups");
}
};
3 changes: 3 additions & 0 deletions src/app/[group]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <p>hello</p>;
}
14 changes: 13 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { Metadata } from "next";
import "./globals.css";
import Header from "@/components/Header/Header";
import { Suspense } from "react";
import SidebarContainer from "@/components/Sidebar/SidebarContainer";

export const metadata: Metadata = {
title: "Calendar App",
Expand All @@ -13,7 +16,16 @@ export default function RootLayout({
}>) {
return (
<html lang="ko">
<body>{children}</body>
<body className="flex">
<Suspense>
<SidebarContainer />
</Suspense>

<div className="flex flex-col flex-1">
<Header />
{children}
</div>
</body>
</html>
);
}
9 changes: 3 additions & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getGroup } from "@/api/groups";
import { getSchedule } from "@/api/schedules";
import { getTag } from "@/api/tags";
import { getUser } from "@/api/users";
import Calendar from "@/components/Calendar/Calendar";
import { useEffect } from "react";

export default function Home() {
Expand All @@ -21,14 +22,10 @@ export default function Home() {

fetchData();
}, []);
const handleButton = () => {
console.log("안녕하세요?");
};

return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1>Calendar App</h1>
<button onClick={() => handleButton()}>안녕하세요?</button>
<main className="flex flex-col min-h-screen" data-testid="root-layout">
<Calendar />
</main>
);
}
35 changes: 35 additions & 0 deletions src/components/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState } from "react";
import { viewType } from "./types";
import MonthView from "./Content/MonthView";

const BUTTON_STYLE = "bg-blue-500 text-white rounded-md p-2";

const Calendar = () => {
// 이 부분은 상당히 React스러운데 나중에 라우팅으로 돌리고 Next스럽게 리팩터링해도 좋을 것 같습니다.
const [viewType, setViewType] = useState<viewType>("month");

const handleClick = (e: React.MouseEvent<HTMLElement>) => {
const target = e.target as HTMLButtonElement;
const value = target.value as viewType;
setViewType(value);
};

return (
<div>
<section onClick={handleClick} className="flex gap-2">
<button className={BUTTON_STYLE} value={"month"}>
MONTH
</button>
<button className={BUTTON_STYLE} value={"week"}>
WEEK
</button>
<button className={BUTTON_STYLE} value={"day"}>
DAY
</button>
</section>
{viewType === "month" && <MonthView />}
</div>
);
};

export default Calendar;
18 changes: 18 additions & 0 deletions src/components/Calendar/Content/MonthView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";

const MonthView = () => {
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
return (
<section className="flex flex-col flex-1">
<div className="flex w-full">
{days.map((day, index) => (
<div key={index} className="flex-1">
{day}
</div>
))}
</div>
</section>
);
};

export default MonthView;
1 change: 1 addition & 0 deletions src/components/Calendar/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type viewType = "month" | "week" | "day";
23 changes: 23 additions & 0 deletions src/components/Header/Header.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Meta, StoryObj } from "@storybook/react";
import Header from "./Header";

const meta = {
title: "Header",
component: Header,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ["autodocs"],
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
layout: "fullscreen",
},
} satisfies Meta<typeof Header>;

export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
user: {
name: "Jane Doe",
},
},
};
14 changes: 14 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";

const Header = () => {
return (
<header className="flex flex-col justify-center items-center bg-yellow-200">
<section>Search Section</section>
<section className="w-full">
<h1>2024 년 03월</h1>
</section>
</header>
);
};

export default Header;
15 changes: 15 additions & 0 deletions src/components/Sidebar/AddGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Link from "next/link";
import PlusIcon from "@/components/Sidebar/PlusIcon";

type GroupProps = {
href: string;
};
const AddGroup = ({ href }: GroupProps) => {
return (
<Link href={href} className="w-full h-auto rounded-full bg-slate-800 p-2">
<PlusIcon className="text-white" />
</Link>
);
};

export default AddGroup;
39 changes: 39 additions & 0 deletions src/components/Sidebar/Group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Image from "next/image";
import Link from "next/link";
import clsx from "clsx";
import { MouseEventHandler } from "react";

// 임시 이미지
const tempSrc = "https://avatars.githubusercontent.com/u/161491870?s=200&v=4";

type GroupProps = Pick<group, "name"> & {
href: string;
selected: boolean;
onSelect?: MouseEventHandler<HTMLAnchorElement>;
};
const Group = ({ name, href, selected, onSelect }: GroupProps) => {
return (
<Link
href={href}
className={clsx("", {
"border-2 border-slate-100": selected,
})}
onClick={onSelect}
>
<Image
className="rounded-full"
src={tempSrc}
alt={`그룹 ${name}`}
sizes="100vw"
style={{
width: "100%",
height: "auto",
}}
width={360}
height={360}
/>
</Link>
);
};

export default Group;
18 changes: 18 additions & 0 deletions src/components/Sidebar/PlusIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SVGProps } from "react";
import clsx from "clsx";

const PlusIcon = ({ ...props }: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={3}
stroke="currentColor"
className={clsx("w-4 h-4", props?.className)}
{...props}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
);

export default PlusIcon;
28 changes: 28 additions & 0 deletions src/components/Sidebar/Sidebar.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Meta, StoryObj } from "@storybook/react";
import Sidebar from "./Sidebar";

const meta = {
title: "Sidebar",
component: Sidebar,
tags: ["autodocs"],
parameters: {
layout: "fullscreen",
},
} satisfies Meta<typeof Sidebar>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
groups: [
{
id: "test1",
name: "샘플",
users: ["user1"],
schedules: ["schedules"],
},
],
},
};
38 changes: 38 additions & 0 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";
import React, { MouseEventHandler, useState } from "react";
import Group from "@/components/Sidebar/Group";
import AddGroup from "@/components/Sidebar/AddGroup";

type SidebarProps = {
groups: (group & id)[];
};
const Sidebar = ({ groups }: SidebarProps) => {
const [selectedIdx, setSelectedIdx] = useState(0);
const height = groups.length * 80 + 80;

return (
<div className="flex h-screen">
<aside className={"flex items-center w-20 bg-[#383A57] rounded-l-3xl"} style={{ height: height }}>
<div className="flex flex-col items-center space-y-4 p-4">
{groups.map((group, idx) => {
const onSelect: MouseEventHandler<HTMLAnchorElement> = () => {
setSelectedIdx(idx);
};
return (
<Group
key={group.id}
name={group.name}
href={group.id}
selected={idx === selectedIdx}
onSelect={onSelect}
/>
);
})}
<AddGroup href="." />
</div>
</aside>
</div>
);
};

export default Sidebar;
Loading

0 comments on commit 93143bf

Please sign in to comment.