diff --git a/.env.example b/.env.example
index 4d0fb3d0..fde7d11b 100644
--- a/.env.example
+++ b/.env.example
@@ -1 +1,2 @@
-NEXT_PUBLIC_API_MOCKING=enabled # or disabled
\ No newline at end of file
+NEXT_PUBLIC_API_MOCKING=enabled # or disabled
+CHROMATIC_TOKEN=your-token
diff --git a/package.json b/package.json
index 3dcf635a..f83fc01d 100644
--- a/package.json
+++ b/package.json
@@ -14,12 +14,14 @@
"chromatic": "bash ./scripts/chromatic_publish.sh"
},
"dependencies": {
+ "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
+ "@radix-ui/react-tabs": "^1.0.4",
"@tanstack/react-query": "^5.0.0",
"autoprefixer": "^10.4.16",
"class-variance-authority": "^0.7.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dc8f4090..351df7af 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@radix-ui/react-avatar':
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
@@ -23,6 +26,9 @@ dependencies:
'@radix-ui/react-switch':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
+ '@radix-ui/react-tabs':
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
'@tanstack/react-query':
specifier: ^5.0.0
version: 5.0.0(react-dom@18.0.0)(react@18.0.0)
@@ -2202,6 +2208,30 @@ packages:
react: 18.0.0
react-dom: 18.0.0(react@18.0.0)
+ /@radix-ui/react-avatar@1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0):
+ resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/react-context': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
+ '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@types/react': 18.0.0
+ '@types/react-dom': 18.0.0
+ react: 18.0.0
+ react-dom: 18.0.0(react@18.0.0)
+ dev: false
+
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies:
@@ -2790,6 +2820,34 @@ packages:
react-dom: 18.0.0(react@18.0.0)
dev: false
+ /@radix-ui/react-tabs@1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0):
+ resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-context': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
+ '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.0.0)(react@18.0.0)
+ '@types/react': 18.0.0
+ '@types/react-dom': 18.0.0
+ react: 18.0.0
+ react-dom: 18.0.0(react@18.0.0)
+ dev: false
+
/@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0):
resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==}
peerDependencies:
diff --git a/public/images/google.png b/public/images/google.png
new file mode 100644
index 00000000..bda6281c
Binary files /dev/null and b/public/images/google.png differ
diff --git a/public/images/kakao.png b/public/images/kakao.png
new file mode 100644
index 00000000..8b92976d
Binary files /dev/null and b/public/images/kakao.png differ
diff --git a/public/images/logo.svg b/public/images/logo.svg
new file mode 100644
index 00000000..e1a6deea
--- /dev/null
+++ b/public/images/logo.svg
@@ -0,0 +1,21 @@
+
diff --git a/public/images/x-icon.svg b/public/images/x-icon.svg
new file mode 100644
index 00000000..8f4a1cbf
--- /dev/null
+++ b/public/images/x-icon.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/app/(root)/(routes)/(auth)/login/page.tsx b/src/app/(root)/(routes)/(auth)/login/page.tsx
index 712ff4ec..663bbf6f 100644
--- a/src/app/(root)/(routes)/(auth)/login/page.tsx
+++ b/src/app/(root)/(routes)/(auth)/login/page.tsx
@@ -1,5 +1,16 @@
-import React from 'react'
+import { FunctionComponent } from 'react'
+import LoginForm from '@/components/domain/LoginForm'
-export default function LoginPage() {
- return
LoginPage
+interface LoginPageProps {}
+
+const LoginPage: FunctionComponent = ({}) => {
+ return (
+
+
+
+ )
}
+
+export default LoginPage
diff --git a/src/app/(root)/(routes)/(home)/page.tsx b/src/app/(root)/(routes)/(home)/page.tsx
index 0f000334..0fe1bf9f 100644
--- a/src/app/(root)/(routes)/(home)/page.tsx
+++ b/src/app/(root)/(routes)/(home)/page.tsx
@@ -1,5 +1,3 @@
-import Link from 'next/link'
-import Button from '@/components/ui/Button'
import { DarkModeButton } from '@/components/ui/DarkModeButton'
export default function HomePage() {
@@ -7,8 +5,6 @@ export default function HomePage() {
hi
- 로긴
-
)
}
diff --git a/src/app/@authModal/(...)login/page.tsx b/src/app/@authModal/(...)login/page.tsx
index 5bbc7b70..b37b2f02 100644
--- a/src/app/@authModal/(...)login/page.tsx
+++ b/src/app/@authModal/(...)login/page.tsx
@@ -1,5 +1,30 @@
-import React from 'react'
+'use client'
-export default function LoginModalPage() {
- return LoginModalPage
+import { FunctionComponent } from 'react'
+import { useRouter } from 'next/navigation'
+import LoginForm from '@/components/domain/LoginForm'
+import { Dialog, DialogContent } from '@/components/ui/Dialog'
+
+interface LoginModalPageProps {}
+
+const LoginModalPage: FunctionComponent = () => {
+ const router = useRouter()
+ return (
+
+ )
}
+
+export default LoginModalPage
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index d64b5807..95f6b0cc 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,7 @@
import { Suspense } from 'react'
import type { Metadata } from 'next'
+import Head from 'next/head'
+import Header from '@/components/domain/Header'
import { Environment } from '@/config/environment'
import TanstackQueryContext from '@/contexts/TanstackQueryContext'
import ThemeProviderContext from '@/contexts/ThemeProviderContext'
@@ -8,7 +10,7 @@ import '@/styles/globals.css'
export const metadata: Metadata = {
title: '나비장터',
- description: 'Generated by create next app',
+ description: '물물교환 플랫폼 나비장터입니다.',
}
if (Environment.apiMocking() === 'enabled') {
@@ -25,12 +27,18 @@ export default function RootLayout({
}) {
return (
+
+
+
- {children}
- {authModal}
+
+
+ {children}
+ {authModal}
+
diff --git a/src/components/domain/Header/Header.tsx b/src/components/domain/Header/Header.tsx
index c861ac81..e0848a85 100644
--- a/src/components/domain/Header/Header.tsx
+++ b/src/components/domain/Header/Header.tsx
@@ -19,7 +19,7 @@ type HeaderProps = {
const Header = ({ isLogin = false }: HeaderProps) => {
return (
-
+
diff --git a/src/components/domain/LoginForm/LoginForm.stories.tsx b/src/components/domain/LoginForm/LoginForm.stories.tsx
new file mode 100644
index 00000000..4a67a72d
--- /dev/null
+++ b/src/components/domain/LoginForm/LoginForm.stories.tsx
@@ -0,0 +1,16 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import LoginForm from './LoginForm'
+
+const meta = {
+ title: 'DOMAIN/LoginForm',
+ component: LoginForm,
+ tags: ['autodocs'],
+ argTypes: {},
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Normal: Story = {
+ args: {},
+}
diff --git a/src/components/domain/LoginForm/LoginForm.tsx b/src/components/domain/LoginForm/LoginForm.tsx
new file mode 100644
index 00000000..4e47338c
--- /dev/null
+++ b/src/components/domain/LoginForm/LoginForm.tsx
@@ -0,0 +1,14 @@
+import Image from 'next/image'
+import Assets from '@/config/assets'
+import LoginButtons from './section/LoginButtons'
+
+const LoginForm = () => {
+ return (
+
+ )
+}
+
+export default LoginForm
diff --git a/src/components/domain/LoginForm/index.tsx b/src/components/domain/LoginForm/index.tsx
new file mode 100644
index 00000000..608996c9
--- /dev/null
+++ b/src/components/domain/LoginForm/index.tsx
@@ -0,0 +1,3 @@
+import LoginForm from './LoginForm'
+
+export default LoginForm
diff --git a/src/components/domain/LoginForm/section/LoginButtons.tsx b/src/components/domain/LoginForm/section/LoginButtons.tsx
new file mode 100644
index 00000000..5b80d764
--- /dev/null
+++ b/src/components/domain/LoginForm/section/LoginButtons.tsx
@@ -0,0 +1,15 @@
+'use client'
+
+import React from 'react'
+import { KakaoLoginButton, GoogleLoginButton } from '../../buttons/LoginButtons'
+
+const LoginButtons = () => {
+ return (
+
+ alert('k')} />
+ alert('g')} />
+
+ )
+}
+
+export default LoginButtons
diff --git a/src/components/domain/buttons/LoginButtons/LoginButtons.stories.tsx b/src/components/domain/buttons/LoginButtons/LoginButtons.stories.tsx
new file mode 100644
index 00000000..13380b8e
--- /dev/null
+++ b/src/components/domain/buttons/LoginButtons/LoginButtons.stories.tsx
@@ -0,0 +1,30 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import { KakaoLoginButton, GoogleLoginButton } from './LoginButtons'
+
+const meta = {
+ title: 'DOMAIN/LoginButtons',
+ component: KakaoLoginButton,
+ tags: ['autodocs'],
+ argTypes: {},
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Normal: Story = {
+ args: {},
+ render: () => (
+
+ alert('카카오 버튼')} />
+ alert('구글 버튼')} />
+
+ ),
+}
+
+export const Kakao: Story = {
+ render: () => alert('카카오 버튼')} />,
+}
+
+export const Google: Story = {
+ render: () => alert('구글 버튼')} />,
+}
diff --git a/src/components/domain/buttons/LoginButtons/LoginButtons.tsx b/src/components/domain/buttons/LoginButtons/LoginButtons.tsx
new file mode 100644
index 00000000..fc0f6fb8
--- /dev/null
+++ b/src/components/domain/buttons/LoginButtons/LoginButtons.tsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import Image from 'next/image'
+import Button from '@/components/ui/Button'
+import Assets from '@/config/assets'
+
+const KakaoLoginButton = ({ onClickButton }: { onClickButton: () => void }) => {
+ return (
+
+ )
+}
+
+const GoogleLoginButton = ({
+ onClickButton,
+}: {
+ onClickButton: () => void
+}) => {
+ return (
+
+ )
+}
+
+export { KakaoLoginButton, GoogleLoginButton }
diff --git a/src/components/domain/buttons/LoginButtons/index.tsx b/src/components/domain/buttons/LoginButtons/index.tsx
new file mode 100644
index 00000000..3ac89a00
--- /dev/null
+++ b/src/components/domain/buttons/LoginButtons/index.tsx
@@ -0,0 +1,3 @@
+import { KakaoLoginButton, GoogleLoginButton } from './LoginButtons'
+
+export { KakaoLoginButton, GoogleLoginButton }
diff --git a/src/components/ui/Avatar/Avatar.stories.tsx b/src/components/ui/Avatar/Avatar.stories.tsx
new file mode 100644
index 00000000..10fc1ab3
--- /dev/null
+++ b/src/components/ui/Avatar/Avatar.stories.tsx
@@ -0,0 +1,34 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import { Avatar, AvatarImage, AvatarFallback } from './Avatar'
+
+const meta = {
+ title: 'UI/Avatar',
+ component: Avatar,
+ tags: ['autodocs'],
+ argTypes: {},
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Normal: Story = {
+ args: {},
+ render: () => {
+ return (
+ <>
+
+
+ NB
+
+
+
+ NB
+
+
+
+ NB
+
+ >
+ )
+ },
+}
diff --git a/src/components/ui/Avatar/Avatar.tsx b/src/components/ui/Avatar/Avatar.tsx
new file mode 100644
index 00000000..bf9ed9e7
--- /dev/null
+++ b/src/components/ui/Avatar/Avatar.tsx
@@ -0,0 +1,67 @@
+'use client'
+
+import * as React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { cn } from '@/utils'
+
+const avatarVariants = cva(
+ 'relative flex shrink-0 overflow-hidden rounded-full',
+ {
+ variants: {
+ size: {
+ sm: 'w-6 h-6',
+ md: 'w-10 h-10',
+ lg: 'w-[100px] h-[100px]',
+ },
+ },
+ },
+)
+
+export interface AvatarProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ ref?: React.Ref
+}
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, size, ...props }: AvatarProps, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/Avatar/index.tsx b/src/components/ui/Avatar/index.tsx
new file mode 100644
index 00000000..6fe2f610
--- /dev/null
+++ b/src/components/ui/Avatar/index.tsx
@@ -0,0 +1,3 @@
+import { Avatar, AvatarImage, AvatarFallback } from './Avatar'
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/Badge/Badge.stories.tsx b/src/components/ui/Badge/Badge.stories.tsx
new file mode 100644
index 00000000..ed875344
--- /dev/null
+++ b/src/components/ui/Badge/Badge.stories.tsx
@@ -0,0 +1,20 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import { Badge } from './Badge'
+
+const meta = {
+ title: 'UI/Badge',
+ component: Badge,
+ tags: ['autodocs'],
+ argTypes: {},
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Normal: Story = {
+ args: {
+ variant: 'gradation',
+ size: 'sm',
+ children: '거래성사',
+ },
+}
diff --git a/src/components/ui/Badge/Badge.tsx b/src/components/ui/Badge/Badge.tsx
new file mode 100644
index 00000000..cce0aead
--- /dev/null
+++ b/src/components/ui/Badge/Badge.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { cn } from '@/utils'
+
+const badgeVariants = cva(
+ 'inline-flex items-center rounded-full px-2 font-medium',
+ {
+ variants: {
+ variant: {
+ primary: 'bg-primary-color text-white',
+ secondary: 'bg-secondary-color text-white',
+ gradation: 'bg-gradient-primary text-white',
+ information: 'bg-background-secondary-color text-black',
+ },
+ size: {
+ sm: 'h-[18px] text-xs',
+ lg: 'h-6 text-sm',
+ },
+ },
+ defaultVariants: {
+ variant: 'information',
+ size: 'sm',
+ },
+ },
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+const Badge = ({ className, variant, size, ...props }: BadgeProps) => {
+ return (
+
+ )
+}
+
+Badge.displayName = 'Badge'
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/Badge/index.tsx b/src/components/ui/Badge/index.tsx
new file mode 100644
index 00000000..97ca7067
--- /dev/null
+++ b/src/components/ui/Badge/index.tsx
@@ -0,0 +1,3 @@
+import { Badge } from './Badge'
+
+export default Badge
diff --git a/src/components/ui/Card/Card.stories.tsx b/src/components/ui/Card/Card.stories.tsx
new file mode 100644
index 00000000..0d81bf00
--- /dev/null
+++ b/src/components/ui/Card/Card.stories.tsx
@@ -0,0 +1,82 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import Button from '../Button'
+import { Card, CardFlex, CardImage, CardText } from './Card'
+
+const meta = {
+ title: 'UI/Card',
+ component: Card,
+ tags: ['autodocs'],
+ argTypes: {},
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Small: Story = {
+ args: {},
+ render: () => {
+ return (
+
+
+
+
+
+
+
+ 스위치 팜
+ 스위치
+ 10만원대
+ 25분전
+
+
+
+ )
+ },
+}
+
+export const Large: Story = {
+ args: {},
+ render: () => {
+ return (
+
+
+
+
+
+
+
+ 스위치 팜
+ 스위치
+ 10만원대
+
+ 25분전
+
+
+
+ )
+ },
+}
diff --git a/src/components/ui/Card/Card.tsx b/src/components/ui/Card/Card.tsx
new file mode 100644
index 00000000..82bbc8e4
--- /dev/null
+++ b/src/components/ui/Card/Card.tsx
@@ -0,0 +1,125 @@
+import * as React from 'react'
+import { cva, type VariantProps } from 'class-variance-authority'
+import Image from 'next/image'
+import COLORS from '@/styles/colors'
+import { TYPHOGRAPHY } from '@/styles/sizes'
+import { cn } from '@/utils'
+
+const cardVariants = cva(
+ 'rounded-card border border-background-secondary-color p-1.5',
+ {
+ variants: {
+ size: {
+ lg: 'h-card-lg',
+ md: 'h-card-md',
+ sm: 'h-card-sm',
+ },
+ },
+ defaultVariants: {
+ size: 'lg',
+ },
+ },
+)
+
+export type CardProps = React.HTMLAttributes &
+ VariantProps & {
+ asChild?: boolean
+ }
+
+const Card = React.forwardRef(
+ ({ size, className, ...props }, ref) => (
+
+ ),
+)
+Card.displayName = 'Card'
+
+const cardFlexVariants = cva('flex h-full', {
+ variants: {
+ direction: {
+ row: 'flex-row',
+ col: 'flex-col',
+ },
+ justify: {
+ start: 'justify-start',
+ center: 'justify-center',
+ end: 'justify-end',
+ between: 'justify-between',
+ around: 'justify-around',
+ },
+ align: {
+ start: 'items-start',
+ center: 'items-center',
+ end: 'items-end',
+ },
+ gap: {
+ space: 'gap-2',
+ none: 'gap-0',
+ },
+ },
+ defaultVariants: {
+ direction: 'row',
+ gap: 'none',
+ },
+})
+
+export type CardFlexProps = React.HTMLAttributes &
+ VariantProps & {
+ asChild?: boolean
+ }
+
+const CardFlex = React.forwardRef(
+ ({ direction, justify, align, gap, className, ...props }, ref) => (
+
+ ),
+)
+CardFlex.displayName = 'CardFlex'
+
+const CardImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, alt, ...props }, ref) => (
+
+))
+CardImage.displayName = 'CardImage'
+
+const cardTextVariants = cva('', {
+ variants: {
+ type: {
+ title: TYPHOGRAPHY.title,
+ description: TYPHOGRAPHY.description,
+ date: TYPHOGRAPHY.date,
+ icon: TYPHOGRAPHY.icon,
+ },
+ },
+ defaultVariants: {
+ type: 'description',
+ },
+})
+
+export type CardTextProps = React.HTMLAttributes &
+ VariantProps & {
+ asChild?: boolean
+ }
+
+const CardText = React.forwardRef(
+ ({ type, className, ...props }, ref) => (
+
+ ),
+)
+CardText.displayName = 'CardText'
+
+export { Card, CardFlex, CardImage, CardText }
diff --git a/src/components/ui/Card/index.tsx b/src/components/ui/Card/index.tsx
new file mode 100644
index 00000000..6183f4d6
--- /dev/null
+++ b/src/components/ui/Card/index.tsx
@@ -0,0 +1,3 @@
+import { Card } from './Card'
+
+export default Card
diff --git a/src/components/ui/DropdownMenu/DropdownMenu.tsx b/src/components/ui/DropdownMenu/DropdownMenu.tsx
index 909e4c98..8fd4f40b 100644
--- a/src/components/ui/DropdownMenu/DropdownMenu.tsx
+++ b/src/components/ui/DropdownMenu/DropdownMenu.tsx
@@ -68,7 +68,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
- 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-background-color p-1 text-text-color shadow-md',
+ 'z-50 absolute min-w-[8rem] overflow-hidden rounded-md border bg-background-color p-1 text-text-color shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
diff --git a/src/components/ui/Tabs/Tabs.stories.tsx b/src/components/ui/Tabs/Tabs.stories.tsx
new file mode 100644
index 00000000..c2859925
--- /dev/null
+++ b/src/components/ui/Tabs/Tabs.stories.tsx
@@ -0,0 +1,36 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs'
+
+const meta = {
+ title: 'UI/Tabs',
+ component: Tabs,
+ tags: ['autodocs'],
+ argTypes: {},
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Normal: Story = {
+ args: {},
+ render: () => {
+ return (
+
+
+ 오퍼하기
+ 찔러보기
+
+
+ 카드1
+ 카드2
+ 카드3
+
+
+ 카드4
+ 카드5
+ 카드6
+
+
+ )
+ },
+}
diff --git a/src/components/ui/Tabs/Tabs.tsx b/src/components/ui/Tabs/Tabs.tsx
new file mode 100644
index 00000000..51d0343d
--- /dev/null
+++ b/src/components/ui/Tabs/Tabs.tsx
@@ -0,0 +1,51 @@
+'use client'
+
+import * as React from 'react'
+import * as TabsPrimitive from '@radix-ui/react-tabs'
+import { cn } from '@/utils/cn'
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/components/ui/Tabs/index.tsx b/src/components/ui/Tabs/index.tsx
new file mode 100644
index 00000000..010f6d3a
--- /dev/null
+++ b/src/components/ui/Tabs/index.tsx
@@ -0,0 +1,3 @@
+import { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs'
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/config/assets.ts b/src/config/assets.ts
index c80e7d0c..8fd81228 100644
--- a/src/config/assets.ts
+++ b/src/config/assets.ts
@@ -1,14 +1,21 @@
import AlarmIcon from '/public/images/bell.svg'
+import GoogleIcon from '/public/images/google.png'
+import KakaoIcon from '/public/images/kakao.png'
+import Logo from '/public/images/logo.svg'
import MenuIcon from '/public/images/menu-icon.svg'
import LeftIcon from '/public/images/icon-left.svg'
import RightIcon from '/public/images/icon-right.svg'
-
+import XIcon from '/public/images/x-icon.svg'
const Assets = {
menuIcon: MenuIcon,
alarmIcon: AlarmIcon,
leftIcon: LeftIcon,
rightIcon: RightIcon
+ googleIcon: GoogleIcon,
+ kakaoIcon: KakaoIcon,
+ logo: Logo,
+ xIcon: XIcon,
} as const
export default Assets
diff --git a/src/styles/colors.ts b/src/styles/colors.ts
index 73613926..cd89cd05 100644
--- a/src/styles/colors.ts
+++ b/src/styles/colors.ts
@@ -8,6 +8,7 @@ const COLORS = {
gray: '#BFBFBF',
black: '#000000',
white: '#FFFFFF',
+ kakao: '#FEE103',
// dark: {
// primary_300: '#534CD0',
@@ -30,6 +31,7 @@ const LIGHT_THEMES = {
'text-color': COLORS.black,
'background-secondary-color': COLORS.gray,
'dialog-background-color': COLORS.black,
+ 'kakao-color': COLORS.kakao,
} as const
const DARK_THEMES = {
@@ -41,6 +43,7 @@ const DARK_THEMES = {
'text-color': COLORS.white,
'background-secondary-color': COLORS.gray,
'dialog-background-color': COLORS.black,
+ 'kakao-color': COLORS.kakao,
} as const
export default COLORS
diff --git a/src/styles/globals.css b/src/styles/globals.css
index b5c61c95..63103c99 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -1,3 +1,23 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+@layer base {
+ :root {
+ --nav-height: 3.5rem;
+ --page-min-width: 320px;
+ --page-max-width: 640px;
+ }
+}
+
+@layer base {
+ .centered-content {
+ @apply max-w-[640px] min-w-[320px] shadow-2xl bg-background-color text-text-color relative overflow-hidden mx-auto p-2 pt-16;
+ }
+
+ :root,
+ html,
+ body {
+ @apply h-full;
+ }
+}
diff --git a/src/styles/sizes.ts b/src/styles/sizes.ts
new file mode 100644
index 00000000..e79f92f7
--- /dev/null
+++ b/src/styles/sizes.ts
@@ -0,0 +1,16 @@
+const TYPHOGRAPHY = {
+ title: 'text-[14px] font-bold',
+ description: 'text-[12px]',
+ date: 'text-[10px] text-[#BFBFBF]',
+ icon: 'text-[10px]',
+}
+const HEIGHT = {
+ 'card-lg': '161px',
+ 'card-md': '118px',
+ 'card-sm': '106px',
+}
+const BORDER_RADIUS = {
+ card: '6px',
+}
+
+export { TYPHOGRAPHY, HEIGHT, BORDER_RADIUS }
diff --git a/tailwind.config.js b/tailwind.config.js
index 5db486eb..7e7e7b94 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,5 +1,6 @@
const { createThemes } = require('tw-colors')
const { LIGHT_THEMES, DARK_THEMES } = require('./src/styles/colors')
+const { HEIGHT, BORDER_RADIUS } = require('./src/styles/sizes')
/** @type {import('tailwindcss').Config} */
module.exports = {
@@ -20,6 +21,18 @@ module.exports = {
'gradient-secondary':
'linear-gradient(to right, #7C54D1 0%, #534CD0 100%)',
}),
+ height: {
+ ...HEIGHT,
+ nav: 'var(--nav-height)',
+ },
+ borderRadius: {
+ ...BORDER_RADIUS,
+ nav: 'var(--nav-height)',
+ },
+ width: {
+ page_min: 'var(--page-min-width)',
+ page_max: 'var(--page-max-width)',
+ },
},
},
plugins: [