Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 메인페이지 레이아웃을 구현합니다 #114

Merged
merged 21 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
},
"homepage": "https://github.com/pagers-org/react-world#readme",
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-slot": "^1.0.2",
"clsx": "^2.0.0",
"lucide-react": "^0.274.0",
"next": "^13.4.19",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
Expand Down
135 changes: 102 additions & 33 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
13 changes: 13 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,16 @@ body,
--ring: 0 0% 14.9%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

.container {
max-width: 1536px;
}
15 changes: 12 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import './globals.css';
import { Metadata } from 'next';
import { ReactNode } from 'react';
import './globals.css';
import { Navbar } from '@/components/layout/Navbar';
import { ThemeProvider } from '@/components/theme/ThemeProvider';

export const metadata: Metadata = {
title: 'react world',
Expand All @@ -9,8 +11,15 @@ export const metadata: Metadata = {

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="ko">
<body>{children}</body>
<html lang="ko" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="container">
<Navbar />
{children}
</div>
</ThemeProvider>
</body>
</html>
);
}
12 changes: 11 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import React from 'react';
import { PostCardList } from '@/components/home/PostCardList';
import { TagList } from '@/components/home/TagList';
import { TEMP_TAGS } from '@/constants';

const RootPage = () => {
return <div>RootPage</div>;
return (
<div>
<div className="flex">
<PostCardList />
<TagList tags={TEMP_TAGS} />
</div>
</div>
);
};

export default RootPage;
21 changes: 21 additions & 0 deletions src/components/home/PostCard/PostCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Meta, StoryObj } from '@storybook/react';
import { PostCard } from '.';
import { PostCardList } from '../PostCardList';

const sotryMeta: Meta = {
title: 'components/home/Postcard',
component: PostCard,
parameters: {
nextjs: {
appDirectory: true,
},
},
};

export default sotryMeta;

type Story = StoryObj<typeof PostCardList>;

export const Default: Story = {
args: {},
};
29 changes: 29 additions & 0 deletions src/components/home/PostCard/PostCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import Link from 'next/link';
import { Badge } from '@/components/ui/Badge/Badge';
// import Image from 'next/image';
import { PostCardProps } from './PostCard.type';
import { TEMP_CONTENT } from '@/constants';

export const PostCard = ({ post }: PostCardProps) => {
const { title, content } = post;
return (
<div className="flex items-center h-[200px] gap-[50px]">
{/* <Image /> */}
<div className="min-w-[200px] min-h-[200px] bg-slate-500"></div>
<div className="flex-col">
<div className="flex items-center">
<span className="text-sm text-gray-400 mr-2">January 20th</span>
<Badge variant="outline">tag</Badge>
</div>
<Link href="/post/1">
<p className="text-2xl mb-2">{title}</p>
<span className="text-gray-300">
{content}
{TEMP_CONTENT}
</span>
</Link>
</div>
</div>
);
};
6 changes: 6 additions & 0 deletions src/components/home/PostCard/PostCard.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface PostCardProps {
post: {
title: string;
content: string;
};
}
1 change: 1 addition & 0 deletions src/components/home/PostCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PostCard';
15 changes: 15 additions & 0 deletions src/components/home/PostCardList/PostCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';

import React from 'react';
import { TEMP_POSTS } from '@/constants';
import { PostCard } from '../PostCard';

export const PostCardList = () => {
return (
<div className="space-y-20">
{TEMP_POSTS.map((post, index) => (
<PostCard key={index} post={post} />
))}
</div>
);
};
1 change: 1 addition & 0 deletions src/components/home/PostCardList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PostCardList';
17 changes: 17 additions & 0 deletions src/components/home/TagList/TagList.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Meta, StoryObj } from '@storybook/react';
import { TagList } from '.';
import { TEMP_TAGS } from '@/constants';

const sotryMeta: Meta = {
title: 'components/home/TagList',
component: TagList,
};
export default sotryMeta;

type Story = StoryObj<typeof TagList>;

export const Default: Story = {
args: {
tags: TEMP_TAGS,
},
};
18 changes: 18 additions & 0 deletions src/components/home/TagList/TagList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';

import React from 'react';
import { Badge } from '@/components/ui/Badge/Badge';
import { TagListProps } from './TagList.type';

export const TagList = ({ tags }: TagListProps) => {
return (
<div className="w-[400px] py-2">
<p className="text-xl font-bold mb-3">Popular Tags</p>
{tags?.map((tag, index) => (
<Badge key={index} variant="outline" className="mr-1">
{tag}
</Badge>
))}
</div>
);
};
3 changes: 3 additions & 0 deletions src/components/home/TagList/TagList.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface TagListProps {
tags: string[]; // TODO: tag값 union type으로 변경
}
Comment on lines +1 to +3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props 타입을 밖으로 빼시는군요!
뭔가 재사용 되지 않을 타입이라 view에 들어있는걸 선호하는데 다른 분들 생각이 궁금하네요.

Copy link
Collaborator Author

@hyjoong hyjoong Sep 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네, 기존에 스타일 및 타입 파일은 항상 별도로 분리하는 컨벤션으로 작업했었는데, 헌진님이 말씀하신 것 처럼 재사용되지 않는 타입 파일들은 따로 분리하지 않고, 재사용되는 타입일 경우에만 분리하는 방식도 좋아보이네요. 이 부분은 리펙토링할 때 참고해보겠습니다! 감사합니다

1 change: 1 addition & 0 deletions src/components/home/TagList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './TagList';
20 changes: 20 additions & 0 deletions src/components/layout/Navbar/Navbar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Meta, StoryObj } from '@storybook/react';
import { Navbar } from '.';

const sotryMeta: Meta = {
title: 'components/layout/Navbar',
component: Navbar,
parameters: {
nextjs: {
appDirectory: true,
},
},
};

export default sotryMeta;

type Story = StoryObj<typeof Navbar>;

export const Default: Story = {
args: {},
};
32 changes: 32 additions & 0 deletions src/components/layout/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Button } from '@/components/ui/Button/Button';
import { ModeToggle } from '@/components/theme/ThemeToggle';

export const Navbar = () => {
const router = useRouter();
const [isLogin, setIsLogin] = useState(false);

const handleButtonClick = () => {
if (isLogin) {
setIsLogin(false);
} else {
router.push('/sign-in');
}
};

return (
<div className="flex justify-between items-center h-[100px] py-4">
<p>Logo</p>
<p className="text-4xl font-bold">Henlog</p>
<div className="flex">
<Button variant="ghost" onClick={handleButtonClick}>
{isLogin ? 'Logout' : 'Sign in'}
</Button>
<ModeToggle />
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/layout/Navbar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Navbar';
9 changes: 9 additions & 0 deletions src/components/theme/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client';

import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes/dist/types';

export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};
40 changes: 40 additions & 0 deletions src/components/theme/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import * as React from 'react';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
Comment on lines +4 to +5
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오.. 라이브러리를 잘 쓰지 않는편이다 보니 처음 보는 라이브러리들인데 좋은것들 알아갑니다~!
두 라이브러리 다 들어가서 조금 살펴보니 직접 구현할때 사용했던 방법들과 유사하게 되어있어서 나중에라도 참고해서 직접 만들어 보는것도 좋을거 같아요~


import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/Dropdown/DropdownMenu';
import { Button } from '../ui/Button';

export function ModeToggle() {
const { setTheme } = useTheme();

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
47 changes: 47 additions & 0 deletions src/components/ui/Badge/Badge.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Meta, StoryObj } from '@storybook/react';
import { Badge } from './Badge';

const sotryMeta: Meta = {
title: 'components/ui/Badge',
component: Badge,
argTypes: {
variant: {
control: {
type: 'select',
options: ['default', 'secondary', 'destructive', 'outline'],
},
},
},
};

export default sotryMeta;

type Story = StoryObj<typeof Badge>;

export const Default: Story = {
args: {
children: 'Badge',
variant: 'default',
},
};

export const Secondary: Story = {
args: {
children: 'Badge',
variant: 'secondary',
},
};

export const Destructive: Story = {
args: {
children: 'Badge',
variant: 'destructive',
},
};

export const Outline: Story = {
args: {
children: 'Badge',
variant: 'outline',
},
};
21 changes: 21 additions & 0 deletions src/components/ui/Badge/Badge.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { cva, type VariantProps } from 'class-variance-authority';

export const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
);
13 changes: 13 additions & 0 deletions src/components/ui/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';

import { cn } from '@/lib/utils';
import { badgeVariants } from './Badge.styles';
import { BadgeProps } from './Badge.type';

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}

export { Badge, badgeVariants };
Loading