Skip to content

Commit dcada7c

Browse files
authored
Merge pull request #114 from pagers-org/feat-main-page-layout
feat: 메인페이지 레이아웃을 구현합니다
2 parents e86020d + 3f148f9 commit dcada7c

31 files changed

+670
-41
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
},
3636
"homepage": "https://github.com/pagers-org/react-world#readme",
3737
"dependencies": {
38+
"@radix-ui/react-dropdown-menu": "^2.0.5",
3839
"@radix-ui/react-slot": "^1.0.2",
3940
"clsx": "^2.0.0",
4041
"lucide-react": "^0.274.0",
4142
"next": "^13.4.19",
43+
"next-themes": "^0.2.1",
4244
"react": "^18.2.0",
4345
"react-dom": "^18.2.0",
4446
"tailwind-merge": "^1.14.0",

pnpm-lock.yaml

Lines changed: 102 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

postcss.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
};

src/app/globals.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,16 @@ body,
7373
--ring: 0 0% 14.9%;
7474
}
7575
}
76+
77+
@layer base {
78+
* {
79+
@apply border-border;
80+
}
81+
body {
82+
@apply bg-background text-foreground;
83+
}
84+
}
85+
86+
.container {
87+
max-width: 1536px;
88+
}

src/app/layout.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import './globals.css';
12
import { Metadata } from 'next';
23
import { ReactNode } from 'react';
3-
import './globals.css';
4+
import { Navbar } from '@/components/layout/Navbar';
5+
import { ThemeProvider } from '@/components/theme/ThemeProvider';
46

57
export const metadata: Metadata = {
68
title: 'react world',
@@ -9,8 +11,15 @@ export const metadata: Metadata = {
911

1012
export default function RootLayout({ children }: { children: ReactNode }) {
1113
return (
12-
<html lang="ko">
13-
<body>{children}</body>
14+
<html lang="ko" suppressHydrationWarning>
15+
<body>
16+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
17+
<div className="container">
18+
<Navbar />
19+
{children}
20+
</div>
21+
</ThemeProvider>
22+
</body>
1423
</html>
1524
);
1625
}

src/app/page.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import React from 'react';
2+
import { PostCardList } from '@/components/home/PostCardList';
3+
import { TagList } from '@/components/home/TagList';
4+
import { TEMP_TAGS } from '@/constants';
25

36
const RootPage = () => {
4-
return <div>RootPage</div>;
7+
return (
8+
<div>
9+
<div className="flex">
10+
<PostCardList />
11+
<TagList tags={TEMP_TAGS} />
12+
</div>
13+
</div>
14+
);
515
};
616

717
export default RootPage;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import { PostCard } from '.';
3+
import { PostCardList } from '../PostCardList';
4+
5+
const sotryMeta: Meta = {
6+
title: 'components/home/Postcard',
7+
component: PostCard,
8+
parameters: {
9+
nextjs: {
10+
appDirectory: true,
11+
},
12+
},
13+
};
14+
15+
export default sotryMeta;
16+
17+
type Story = StoryObj<typeof PostCardList>;
18+
19+
export const Default: Story = {
20+
args: {},
21+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import Link from 'next/link';
3+
import { Badge } from '@/components/ui/Badge/Badge';
4+
// import Image from 'next/image';
5+
import { PostCardProps } from './PostCard.type';
6+
import { TEMP_CONTENT } from '@/constants';
7+
8+
export const PostCard = ({ post }: PostCardProps) => {
9+
const { title, content } = post;
10+
return (
11+
<div className="flex items-center h-[200px] gap-[50px]">
12+
{/* <Image /> */}
13+
<div className="min-w-[200px] min-h-[200px] bg-slate-500"></div>
14+
<div className="flex-col">
15+
<div className="flex items-center">
16+
<span className="text-sm text-gray-400 mr-2">January 20th</span>
17+
<Badge variant="outline">tag</Badge>
18+
</div>
19+
<Link href="/post/1">
20+
<p className="text-2xl mb-2">{title}</p>
21+
<span className="text-gray-300">
22+
{content}
23+
{TEMP_CONTENT}
24+
</span>
25+
</Link>
26+
</div>
27+
</div>
28+
);
29+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface PostCardProps {
2+
post: {
3+
title: string;
4+
content: string;
5+
};
6+
}

src/components/home/PostCard/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './PostCard';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { TEMP_POSTS } from '@/constants';
5+
import { PostCard } from '../PostCard';
6+
7+
export const PostCardList = () => {
8+
return (
9+
<div className="space-y-20">
10+
{TEMP_POSTS.map((post, index) => (
11+
<PostCard key={index} post={post} />
12+
))}
13+
</div>
14+
);
15+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './PostCardList';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import { TagList } from '.';
3+
import { TEMP_TAGS } from '@/constants';
4+
5+
const sotryMeta: Meta = {
6+
title: 'components/home/TagList',
7+
component: TagList,
8+
};
9+
export default sotryMeta;
10+
11+
type Story = StoryObj<typeof TagList>;
12+
13+
export const Default: Story = {
14+
args: {
15+
tags: TEMP_TAGS,
16+
},
17+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { Badge } from '@/components/ui/Badge/Badge';
5+
import { TagListProps } from './TagList.type';
6+
7+
export const TagList = ({ tags }: TagListProps) => {
8+
return (
9+
<div className="w-[400px] py-2">
10+
<p className="text-xl font-bold mb-3">Popular Tags</p>
11+
{tags?.map((tag, index) => (
12+
<Badge key={index} variant="outline" className="mr-1">
13+
{tag}
14+
</Badge>
15+
))}
16+
</div>
17+
);
18+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface TagListProps {
2+
tags: string[]; // TODO: tag값 union type으로 변경
3+
}

src/components/home/TagList/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './TagList';
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import { Navbar } from '.';
3+
4+
const sotryMeta: Meta = {
5+
title: 'components/layout/Navbar',
6+
component: Navbar,
7+
parameters: {
8+
nextjs: {
9+
appDirectory: true,
10+
},
11+
},
12+
};
13+
14+
export default sotryMeta;
15+
16+
type Story = StoryObj<typeof Navbar>;
17+
18+
export const Default: Story = {
19+
args: {},
20+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client';
2+
3+
import React, { useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { Button } from '@/components/ui/Button/Button';
6+
import { ModeToggle } from '@/components/theme/ThemeToggle';
7+
8+
export const Navbar = () => {
9+
const router = useRouter();
10+
const [isLogin, setIsLogin] = useState(false);
11+
12+
const handleButtonClick = () => {
13+
if (isLogin) {
14+
setIsLogin(false);
15+
} else {
16+
router.push('/sign-in');
17+
}
18+
};
19+
20+
return (
21+
<div className="flex justify-between items-center h-[100px] py-4">
22+
<p>Logo</p>
23+
<p className="text-4xl font-bold">Henlog</p>
24+
<div className="flex">
25+
<Button variant="ghost" onClick={handleButtonClick}>
26+
{isLogin ? 'Logout' : 'Sign in'}
27+
</Button>
28+
<ModeToggle />
29+
</div>
30+
</div>
31+
);
32+
};

src/components/layout/Navbar/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Navbar';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { ThemeProvider as NextThemesProvider } from 'next-themes';
5+
import { type ThemeProviderProps } from 'next-themes/dist/types';
6+
7+
export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
8+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
9+
};

src/components/theme/ThemeToggle.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { Moon, Sun } from 'lucide-react';
5+
import { useTheme } from 'next-themes';
6+
7+
import {
8+
DropdownMenu,
9+
DropdownMenuContent,
10+
DropdownMenuItem,
11+
DropdownMenuTrigger,
12+
} from '../ui/Dropdown/DropdownMenu';
13+
import { Button } from '../ui/Button';
14+
15+
export function ModeToggle() {
16+
const { setTheme } = useTheme();
17+
18+
return (
19+
<DropdownMenu>
20+
<DropdownMenuTrigger asChild>
21+
<Button variant="outline" size="icon">
22+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
23+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
24+
<span className="sr-only">Toggle theme</span>
25+
</Button>
26+
</DropdownMenuTrigger>
27+
<DropdownMenuContent align="end">
28+
<DropdownMenuItem onClick={() => setTheme('light')}>
29+
Light
30+
</DropdownMenuItem>
31+
<DropdownMenuItem onClick={() => setTheme('dark')}>
32+
Dark
33+
</DropdownMenuItem>
34+
<DropdownMenuItem onClick={() => setTheme('system')}>
35+
System
36+
</DropdownMenuItem>
37+
</DropdownMenuContent>
38+
</DropdownMenu>
39+
);
40+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import { Badge } from './Badge';
3+
4+
const sotryMeta: Meta = {
5+
title: 'components/ui/Badge',
6+
component: Badge,
7+
argTypes: {
8+
variant: {
9+
control: {
10+
type: 'select',
11+
options: ['default', 'secondary', 'destructive', 'outline'],
12+
},
13+
},
14+
},
15+
};
16+
17+
export default sotryMeta;
18+
19+
type Story = StoryObj<typeof Badge>;
20+
21+
export const Default: Story = {
22+
args: {
23+
children: 'Badge',
24+
variant: 'default',
25+
},
26+
};
27+
28+
export const Secondary: Story = {
29+
args: {
30+
children: 'Badge',
31+
variant: 'secondary',
32+
},
33+
};
34+
35+
export const Destructive: Story = {
36+
args: {
37+
children: 'Badge',
38+
variant: 'destructive',
39+
},
40+
};
41+
42+
export const Outline: Story = {
43+
args: {
44+
children: 'Badge',
45+
variant: 'outline',
46+
},
47+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { cva, type VariantProps } from 'class-variance-authority';
2+
3+
export const badgeVariants = cva(
4+
'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',
5+
{
6+
variants: {
7+
variant: {
8+
default:
9+
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
10+
secondary:
11+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
12+
destructive:
13+
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
14+
outline: 'text-foreground',
15+
},
16+
},
17+
defaultVariants: {
18+
variant: 'default',
19+
},
20+
},
21+
);

src/components/ui/Badge/Badge.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as React from 'react';
2+
3+
import { cn } from '@/lib/utils';
4+
import { badgeVariants } from './Badge.styles';
5+
import { BadgeProps } from './Badge.type';
6+
7+
function Badge({ className, variant, ...props }: BadgeProps) {
8+
return (
9+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
10+
);
11+
}
12+
13+
export { Badge, badgeVariants };

0 commit comments

Comments
 (0)