Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 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 prettier.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ module.exports = {
tailwindFunctions: ['clsx', 'twMerge'],
importOrder: [
'^next(.*)$', // Next 관련 import를 최상단으로
'^react(.*)$', // React 관련 import를 최상단으로
'<THIRD_PARTY_MODULES>', // 외부 모듈
'^@/app/api/(.*)$', // api 폴더
'^@/components/(.*)$', // components 폴더를
'^@/queries/(.*)$', // queries 폴더
'^@/hooks/(.*)$', // hooks 폴더
'^@/stores/(.*)$', // stores 폴더
'^@/services/(.*)$', // services 폴더
'^@/utils/(.*)$', // utils 폴더
'^[./]', // 상대 경로
Expand Down
14 changes: 9 additions & 5 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import Head from 'next/head'

import Providers from '@/app/providers'
import '@/styles/globals.css'

import { Modal } from '@/components/common/popup'

const RootLayout = ({
children,
}: Readonly<{
children: React.ReactNode
}>): JSX.Element => {
return (
<html lang='ko'>
<Head>
<head>
<link
rel='stylesheet'
as='style'
href='https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/static/pretendard.min.css'
crossOrigin='anonymous'
/>
</Head>
</head>
<body>
<Providers>{children}</Providers>
<Providers>
{children}
<div id={'portal-root'}></div>
<Modal />
</Providers>
</body>
</html>
)
Expand Down
28 changes: 28 additions & 0 deletions src/components/auth/SignUpSuccessModalContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ModalContent } from '@/components/shared/modalContent'

import celebrateImage from '/public/assets/images/img-celebration.png'

export const SignUpSuccessModalContent = (): JSX.Element => {
return (
<ModalContent>
<ModalContent.Image src={celebrateImage} alt={'축하 이미지'} />
<ModalContent.Header
title={'회원가입 완료!'}
subTitle={
<>
홍길동님의 회원가입이
<br />
성공적으로 완료되었습니다.
</>
}
/>
<ModalContent.InfoBox
firstLabel={'나의 정보 확인 및 수정은 '}
linkLabel={'마이페이지 > 프로필'}
lastLabel={'에서 가능합니다.'}
to={'/'}
/>
<ModalContent.Link href={'/'} label={'로그인 바로가기'} />
</ModalContent>
)
}
14 changes: 11 additions & 3 deletions src/components/common/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import clsx from 'clsx'

import { Clickable, ClickableProps } from './Clickable'

interface ButtonProps
export interface ButtonProps
extends ClickableProps,
React.ButtonHTMLAttributes<HTMLButtonElement> {}

export const Button = ({
onClick,
type = 'button',
disabled = false,
fullWidth,
...props
}: ButtonProps): JSX.Element => (
<button onClick={onClick} type={type} disabled={disabled}>
<Clickable {...props} disabled={disabled} />
<button
onClick={onClick}
type={type}
disabled={disabled}
className={clsx({ 'w-full': fullWidth })}
>
<Clickable {...props} disabled={disabled} fullWidth={fullWidth} />
</button>
)
7 changes: 3 additions & 4 deletions src/components/common/button/Clickable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

export interface ClickableProps {
label?: string
Expand Down Expand Up @@ -83,18 +82,18 @@ export const Clickable = ({
: ''
const textColorClass = textColor ? styleByTextColor[textColor] : ''

const clickableStyle = twMerge(
const clickableStyle = clsx(
baseStyle,
styleByVariant[variant],
styleBySize[size],
textColorClass,
clsx({
{
[borderColorClass]: variant === 'outlined',
[backgroundColorClass]: variant !== 'text',
[disabledStyle]: disabled,
'w-full': fullWidth,
'justify-start': leftAlign,
}),
},
className
)

Expand Down
11 changes: 9 additions & 2 deletions src/components/common/button/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import NextLink from 'next/link'

import clsx from 'clsx'

import { Clickable, ClickableProps } from './Clickable'

interface LinkProps
export interface LinkProps
extends ClickableProps,
React.AnchorHTMLAttributes<HTMLAnchorElement> {}

export const Link = ({
href = '#',
disabled,
onClick = () => {},
fullWidth,
...props
}: LinkProps): JSX.Element => (
<NextLink
Expand All @@ -18,9 +22,12 @@ export const Link = ({
onClick={e => {
if (disabled) {
e.preventDefault()
} else {
onClick(e)
}
}}
className={clsx({ 'w-full': fullWidth })}
>
<Clickable {...props} disabled={disabled} />
<Clickable {...props} disabled={disabled} fullWidth={fullWidth} />
</NextLink>
)
6 changes: 3 additions & 3 deletions src/components/common/button/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button } from './Button'
import { Link } from './Link'
import { Button, type ButtonProps } from './Button'
import { Link, type LinkProps } from './Link'

export { Button, Link }
export { Button, ButtonProps, Link, LinkProps }
11 changes: 5 additions & 6 deletions src/components/common/containers/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import { twMerge } from 'tailwind-merge'

type Variant = 'contained' | 'outlined'
type Rounded = 8 | 12
type Padding = 0 | 10 | 20 | 30 | 40
type Padding = 0 | 10 | 20 | 30 | 32 | 40
type Margin = 0 | 10 | 20 | 30 | 40
type Color = 'primary' | 'secondary' | 'tertiary'

interface BoxProps {
children: React.ReactNode
interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: Variant
rounded?: Rounded
padding?: Padding
margin?: Margin
color?: Color
className?: string
}

const BaseStyle = 'flex items-center justify-center w-full'
Expand All @@ -33,6 +31,7 @@ const styleByPadding: Record<Padding, string> = {
10: 'p-10',
20: 'p-20',
30: 'p-30',
32: 'p-32',
40: 'p-40',
}

Expand All @@ -54,8 +53,8 @@ export const Box = ({
children,
variant = 'outlined',
rounded = 12,
padding = 20,
margin = 10,
padding = 0,
margin = 0,
color = 'primary',
className = '',
}: BoxProps): JSX.Element => {
Expand Down
3 changes: 3 additions & 0 deletions src/components/common/logo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Logo } from './Logo'

export { Logo }
90 changes: 90 additions & 0 deletions src/components/common/popup/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client'

import { useEffect, useState } from 'react'

import clsx from 'clsx'

import { Box } from '@/components/common/containers'

import useModalStore from '@/stores/useModalStore'

import { Portal } from './Portal'

const baseOverlayStyle =
'fixed inset-0 z-50 flex h-screen w-screen items-center justify-center bg-common-white bg-opacity-80 transition-opacity duration-300 ease-out'

const baseBoxStyle =
' w-440 transform shadow-level4 transition-all duration-300 ease-out'

export const Modal = (): JSX.Element | null => {
const { isOpen, content, closeModal } = useModalStore()
const [isVisible, setIsVisible] = useState(false)

useEffect(() => {
let openTimer: NodeJS.Timeout | undefined
let closeTimer: NodeJS.Timeout | undefined

if (isOpen) {
openTimer = setTimeout(() => setIsVisible(true), 50)
document.body.style.overflow = 'hidden'

const handleEsc = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
closeModal()
}
}

const handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement
if (target.getAttribute('aria-label') === 'modal overlay') {
closeModal()
}
}

window.addEventListener('keydown', handleEsc)
window.addEventListener('mousedown', handleClickOutside)

return () => {
window.removeEventListener('keydown', handleEsc)
window.removeEventListener('mousedown', handleClickOutside)
}
} else {
document.body.style.overflow = ''
closeTimer = setTimeout(() => setIsVisible(false), 500)
}

return () => {
if (openTimer) clearTimeout(openTimer)
if (closeTimer) clearTimeout(closeTimer)
}
}, [isOpen, closeModal])

if (!isVisible) return null

const overlayStyle = clsx(baseOverlayStyle, {
'opacity-100': isOpen,
'opacity-0': !isOpen,
})

const boxStyle = clsx(baseBoxStyle, {
'translate-y-0 scale-100 opacity-100': isOpen,
'-translate-y-50 scale-95 opacity-0': !isOpen,
})

return (
<Portal>
<div className={overlayStyle} aria-label={'modal overlay'}>
<Box
variant={'contained'}
padding={32}
margin={0}
className={boxStyle}
aria-label={'modal box'}
onClick={e => e.stopPropagation()}
>
{content}
</Box>
</div>
</Portal>
)
}
22 changes: 22 additions & 0 deletions src/components/common/popup/Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import { ReactNode, useEffect, useState } from 'react'
import ReactDOM from 'react-dom'

interface PortalProps {
children: ReactNode
}

export const Portal = ({ children }: PortalProps): JSX.Element | null => {
const [mounted, setMounted] = useState(false)

useEffect(() => {
const portalRoot = document.getElementById('portal-root')
if (portalRoot) setMounted(true)
}, [])

const portalRoot = document.getElementById('portal-root')
if (!mounted || !portalRoot) return null

return ReactDOM.createPortal(children, portalRoot)
}
3 changes: 3 additions & 0 deletions src/components/common/popup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Modal } from './Modal'

export { Modal }
5 changes: 5 additions & 0 deletions src/components/common/text/Highlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const Highlight = ({
children,
}: React.PropsWithChildren): JSX.Element => {
return <span className={'text-primary-normal'}>{children}</span>
}
1 change: 0 additions & 1 deletion src/components/common/text/Text.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import clsx from 'clsx'
import React from 'react'

type Variant =
| 'heading1'
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/text/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Highlight } from './Highlight'
import { Text } from './Text'

export { Text }
export { Text, Highlight }
Loading