Skip to content

Commit

Permalink
Merge pull request #114 from pagers-org/feat-main-page-layout
Browse files Browse the repository at this point in the history
feat: 메인페이지 레이아웃을 구현합니다
  • Loading branch information
hyjoong authored Sep 16, 2023
2 parents e86020d + 3f148f9 commit dcada7c
Show file tree
Hide file tree
Showing 31 changed files with 670 additions and 41 deletions.
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으로 변경
}
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';

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

0 comments on commit dcada7c

Please sign in to comment.