Don't be afraid of challenges
[nextjs] contextAPI로 alert, modal창 만들기 본문
1. alert
import { ToastType } from '@/types/toast.type';
import { cva } from 'class-variance-authority';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import CloseIcon from '../../../public/icons/ic-close.png';
import InfoIcon from '../../../public/icons/ic-info.png';
interface AlertProps {
toast: ToastType;
}
const alertVariants = cva(
'bg-gray-200 border border-l-4 border-l-gray-500 w-[300px] translation rounded-md px-4 py-3 shadow-sm flex flex-row justify-between items-center duration-500',
{
variants: {
isDisplayed: {
true: 'translate-y-[calc(20px)]',
false: 'translate-y-[calc(-100%)]'
}
},
defaultVariants: {
isDisplayed: false
}
}
);
function Alert({ toast }: AlertProps) {
const [isDisplayed, setIsDisplayed] = useState<boolean>(false);
useEffect(() => {
setIsDisplayed(true);
setTimeout(() => setIsDisplayed(false), 2000 - 500);
}, []);
const handleDelete = () => {
setIsDisplayed(false);
};
return (
<div className={alertVariants({ isDisplayed })}>
<div className="flex flex-row gap-3">
<Image src={InfoIcon} width={25} height={25} alt={'info icon'} />
<span>{toast.label}</span>
</div>
<div>
<Image
src={CloseIcon}
width={15}
height={15}
alt={'close button'}
className="hover:cursor-pointer"
onClick={handleDelete}
/>
</div>
</div>
);
}
export default Alert;
'use client';
import Alert from '@/components/Alert';
import { ToastProps, ToastType } from '@/types/toast.type';
import { createContext, PropsWithChildren, useContext, useState } from 'react';
const initialValue: ToastProps = {
on: () => {},
off: () => {}
};
export const ToastContext = createContext(initialValue);
export const useToast = () => useContext(ToastContext);
export function ToastProvider({ children }: PropsWithChildren) {
const [toasts, setToasts] = useState<ToastType[]>([]);
const value: ToastProps = {
on: (toast) => {
const id = crypto.randomUUID();
if (toasts.some((t) => t.label === toast.label)) return;
setToasts((prev) => [...prev, { ...toast, id }]);
setTimeout(() => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
}, 2000);
},
off: (id) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
}
};
return (
<ToastContext.Provider value={value}>
{toasts.length > 0 && (
<ul className="fixed right-6 z-20 grid grid-cols-1 gap-y-3">
{toasts.map((toast: ToastType) => (
<li key={toast.id}>
<Alert toast={toast} />
</li>
))}
</ul>
)}
{children}
</ToastContext.Provider>
);
}
import Page from '@/components/Page';
import PokemonList from '@/components/PokemonList';
import SearchBar from '@/components/SearchBar';
import { ConfirmProvider } from '@/contexts/confirm.context';
import { ToastProvider } from '@/contexts/toast.context';
export default function HomePage() {
return (
<ToastProvider>
<ConfirmProvider>
<Page title="포켓몬 도감">
<SearchBar />
<PokemonList />
</Page>
</ConfirmProvider>
</ToastProvider>
);
}
2.모달
'use client';
import { useConfirm } from '@/contexts/confirm.context';
import { ModalType } from '@/types/toast.type';
import Image from 'next/image';
import InfoIcon from '../../../public/icons/ic-info.png';
import BackDrop from '../BackDrop';
interface ModalProps {
modalOptions: ModalType | null;
handleClick: () => void;
}
function ConfirmModal({ modalOptions, handleClick }: ModalProps) {
const modal = useConfirm();
return (
<BackDrop>
<div className="bg-white border rounded-md p-4 w-[320px] shadow-sm" onClick={(e) => e.stopPropagation()}>
<div className="flex flex-row gap-2 justify-center">
<Image src={InfoIcon} width={25} height={25} alt={'info icon'} />
<span>시스템알림</span>
</div>
<h1 className="font-semibold text-xl my-8 text-center">{modalOptions!.label}</h1>
<div className="flex flex-row gap-3 justify-between">
<button onClick={handleClick}>확인</button>
<button onClick={() => modal.off()}>취소</button>
</div>
</div>
</BackDrop>
);
}
export default ConfirmModal;
'use client';
import { ModalProps } from '@/types/toast.type';
import { createContext, PropsWithChildren, useContext, useState } from 'react';
const initialValue: ModalProps = {
modalOptions: null,
on: () => {},
off: () => {}
};
export const ConfirmContext = createContext(initialValue);
export const useConfirm = () => useContext(ConfirmContext);
export function ConfirmProvider({ children }: PropsWithChildren) {
const [modalOptions, setModalOptions] = useState<ModalProps['modalOptions']>(null);
const value: ModalProps = {
modalOptions,
on: (toast) => {
setModalOptions(toast);
},
off: () => {
setModalOptions(null);
}
};
return <ConfirmContext.Provider value={value}>{children}</ConfirmContext.Provider>;
}
'nextjs' 카테고리의 다른 글
| 쿼리 파라미터를 이용한 검색로직 구현 (0) | 2025.01.12 |
|---|---|
| [nextjs] 페이지네이션 (1) | 2024.07.11 |
| [nextjs] 좋아요 기능 구현 + 낙관적 업데이트 (0) | 2024.07.10 |
| [nextjs] Rendering (0) | 2024.07.03 |
| [nextjs] routing (1) | 2024.07.03 |