Skip to content

Commit

Permalink
feat: toast 디자인 시스템 actionType & 애니메이션 추가 (#72)
Browse files Browse the repository at this point in the history
* feat: toast 디자인 시스템 actionType & 애니메이션 추가

* fix: build, lint error 수정

* fix: toast remove time 수정
  • Loading branch information
SEOKKAMONI authored Feb 10, 2024
1 parent 429dc66 commit 7627e7d
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 41 deletions.
4 changes: 2 additions & 2 deletions src/components/common/ChattingInput/ChattingInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { css } from '@emotion/react';
import styled from '@emotion/styled';
import type { InputHTMLAttributes, MouseEventHandler } from 'react';

import { IconCamera } from '../Icons/IconCamera';
import { CameraIcon } from '../Icons';
import { Text } from '../Text';

type ChattingInputProps = {
Expand All @@ -23,7 +23,7 @@ export const ChattingInput = ({
return (
<StyledChattingInputWrapper>
<StyledCameraButton isChatEnd={isChatEnd}>
<IconCamera width={20} height={20} color={colors.black} />
<CameraIcon width={20} height={20} color={colors.black} />
</StyledCameraButton>
<StyledChattingInput
onChange={onChange}
Expand Down
12 changes: 0 additions & 12 deletions src/components/common/Icons/IconCamera.tsx

This file was deleted.

23 changes: 20 additions & 3 deletions src/components/common/Icons/Icons.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { type SVGProps } from 'react';
import type { Meta, StoryObj } from '@storybook/react';

import { AddIcon, ArrowBackIcon, CameraIcon, CheckIcon, CloseIcon, MenuIcon, PeopleIcon } from '.';
import {
AddIcon,
ArrowBackIcon,
CameraIcon,
CloseIcon,
MenuIcon,
PeopleIcon,
ToastErrorIcon,
ToastSuccessIcon,
} from '.';

type Icon = SVGProps<SVGSVGElement>;

Expand Down Expand Up @@ -43,12 +52,20 @@ export const Camera: StoryObj<Icon> = {
render: args => <CameraIcon {...args} />,
};

export const Check: StoryObj<Icon> = {
export const ToastError: StoryObj<Icon> = {
args: {
width: 24,
height: 24,
},
render: args => <CheckIcon {...args} />,
render: args => <ToastErrorIcon {...args} />,
};

export const ToastSuccess: StoryObj<Icon> = {
args: {
width: 24,
height: 24,
},
render: args => <ToastSuccessIcon {...args} />,
};

export const Close: StoryObj<Icon> = {
Expand Down
12 changes: 12 additions & 0 deletions src/components/common/Icons/ToastError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type SVGProps } from 'react';

export const ToastErrorIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" {...props}>
<path
d="M12 13.4L7.09999 18.3C6.91665 18.4833 6.68332 18.575 6.39999 18.575C6.11665 18.575 5.88332 18.4833 5.69999 18.3C5.51665 18.1167 5.42499 17.8833 5.42499 17.6C5.42499 17.3167 5.51665 17.0833 5.69999 16.9L10.6 12L5.69999 7.1C5.51665 6.91667 5.42499 6.68334 5.42499 6.4C5.42499 6.11667 5.51665 5.88334 5.69999 5.7C5.88332 5.51667 6.11665 5.425 6.39999 5.425C6.68332 5.425 6.91665 5.51667 7.09999 5.7L12 10.6L16.9 5.7C17.0833 5.51667 17.3167 5.425 17.6 5.425C17.8833 5.425 18.1167 5.51667 18.3 5.7C18.4833 5.88334 18.575 6.11667 18.575 6.4C18.575 6.68334 18.4833 6.91667 18.3 7.1L13.4 12L18.3 16.9C18.4833 17.0833 18.575 17.3167 18.575 17.6C18.575 17.8833 18.4833 18.1167 18.3 18.3C18.1167 18.4833 17.8833 18.575 17.6 18.575C17.3167 18.575 17.0833 18.4833 16.9 18.3L12 13.4Z"
fill="currentColor"
/>
</svg>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type SVGProps } from 'react';

export const CheckIcon = (props: SVGProps<SVGSVGElement>) => {
export const ToastSuccessIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" {...props}>
<path
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/Icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from './Add';
export * from './ArrowBack';
export * from './Camera';
export * from './Check';
export * from './Close';
export * from './Menu';
export * from './People';
export * from './ToastError';
export * from './ToastSuccess';
4 changes: 2 additions & 2 deletions src/components/common/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export default meta;
export const Default: StoryObj<Toast> = {
render: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { addToast } = useToast();
const toast = useToast();

return (
// eslint-disable-next-line react/button-has-type
<button onClick={() => addToast('제발')} style={{ color: 'white' }}>
<button onClick={() => toast.error('제발')} style={{ color: 'white' }}>
클릭
</button>
);
Expand Down
36 changes: 29 additions & 7 deletions src/components/common/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,60 @@ import { colors } from '@/styles';
import { css } from '@emotion/react';
import styled from '@emotion/styled';

import { CheckIcon } from '../Icons';
import { ToastErrorIcon, ToastSuccessIcon } from '../Icons';
import { Text } from '..';

export type ToastProps = {
message: string;
actionType?: 'success' | 'error';
id: number;
};

export const Toast = ({ id, message }: ToastProps) => {
const { removeToast } = useToast();
export const Toast = ({ id, message, actionType = 'success' }: ToastProps) => {
const toast = useToast();

useEffect(() => {
const timer = setTimeout(() => {
removeToast(id);
}, 3000);
toast.remove(id);
}, 2000);

return () => {
clearTimeout(timer);
};
}, [id, removeToast]);
}, [id, toast]);

return (
<StyledToast>
<CheckIcon color={colors.secondary} width={24} height={24} />
{actionType === 'success' ? (
<ToastSuccessIcon color={colors.secondary} width={24} height={24} />
) : (
<ToastErrorIcon color={colors.red} width={24} height={24} />
)}
<Text styleType="body1" color="white">
{message}
</Text>
</StyledToast>
);
};

const slideDownAnimation = css`
@keyframes slideDown {
0% {
transform: translateY(-100%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
animation: slideDown 0.18s ease-out;
`;

const StyledToast = styled.div`
${slideDownAnimation}
display: inline-flex;
justify-content: center;
align-items: center;
Expand Down
7 changes: 6 additions & 1 deletion src/components/common/Toast/ToastContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export const ToastContainer = ({ toasts }: ToastContainerProps) => {
return createPortal(
<StyledToastList>
{toasts.map(toast => (
<Toast key={toast.id} id={toast.id} message={toast.message} />
<Toast
key={toast.id}
id={toast.id}
actionType={toast.actionType}
message={toast.message}
/>
))}
</StyledToastList>,
document.body
Expand Down
10 changes: 6 additions & 4 deletions src/contexts/ToastContext.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { createContext } from 'react';

type ToastContextProps = {
addToast: (message: string) => void;
removeToast: (toastId: number) => void;
success: (message: string) => void;
error: (message: string) => void;
remove: (toastId: number) => void;
};

export const ToastContext = createContext<ToastContextProps>({
addToast: () => {},
removeToast: () => {},
success: () => {},
error: () => {},
remove: () => {},
});
21 changes: 13 additions & 8 deletions src/providers/ToastProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,32 @@ import { ToastContainer, type ToastProps } from '../components/common/Toast';

let id = 1;

type ToastProviderProps = PropsWithChildren;

export const ToastProvider = ({ children }: ToastProviderProps) => {
export const ToastProvider = ({ children }: PropsWithChildren) => {
const [toasts, setToasts] = useState<ToastProps[]>([]);

const addToast = useCallback(
const success = useCallback(
(message: string) => {
setToasts(prevToasts => [...prevToasts, { id: id++, message, actionType: 'success' }]);
},
[setToasts]
);

const error = useCallback(
(message: string) => {
setToasts(prevToasts => [...prevToasts, { id: id++, message }]);
setToasts(prevToasts => [...prevToasts, { id: id++, message, actionType: 'error' }]);
},
[setToasts]
);

const removeToast = useCallback(
const remove = useCallback(
(toastId: number) => {
setToasts(prevToasts => prevToasts.filter(toast => toast.id !== toastId));
setToasts(prevToasts => prevToasts.filter(prevToast => prevToast.id !== toastId));
},
[setToasts]
);

return (
<ToastContext.Provider value={{ addToast, removeToast }}>
<ToastContext.Provider value={{ success, error, remove }}>
<ToastContainer toasts={toasts} />
{children}
</ToastContext.Provider>
Expand Down
1 change: 1 addition & 0 deletions src/styles/themes/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const colors = {
black: '#1F2128',
white: '#ffffff',
yellow: '#FEE500',
red: '#FF6060',

primary: '#6557FF',
primaryHover: '#7366FF',
Expand Down

0 comments on commit 7627e7d

Please sign in to comment.