-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ui-kit): 스낵바 컴포넌트 추가 #41
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
61bd9d2
feat(ui-kit): 스낵바 컴포넌트 추가
evan-moon e13a1c6
feat(ui-kit): 스낵바 타입 업데이트
evan-moon 70b7a9f
feat(ui-kit): Snackbar 컴포넌트 타입에서 children 제거
evan-moon 322b0c5
feat(ui-kit): 스낵바에 버튼이 연속으로 등장할 때 간격 설정
evan-moon fbce4f1
feat(ui-kit): 의미없는 모듈 임포트 제거
evan-moon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import React, { ReactNode, isValidElement } from 'react'; | ||
import classnames from 'classnames'; | ||
import Text from 'components/Text'; | ||
import {} from 'react'; | ||
import Button from '../Button'; | ||
|
||
interface Props { | ||
message: string; | ||
button: ReactNode; | ||
onClick?: () => void; | ||
} | ||
|
||
const SnackbarBody = ({ message, button, onClick }: Props) => { | ||
return ( | ||
<div className={classnames('lubycon-snackbar__body', 'lubycon-shadow--3')}> | ||
<Text typography="p2" className="lubycon-snackbar__text"> | ||
{message} | ||
</Text> | ||
<div className="lubycon-snackbar__body__buttons"> | ||
{isValidElement(button) ? button : <Button onClick={onClick}>{button}</Button>} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default SnackbarBody; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import React, { HTMLAttributes, useEffect, useState, ReactNode } from 'react'; | ||
import { animated, useTransition } from 'react-spring'; | ||
import classnames from 'classnames'; | ||
import SnackbarBody from './SnackbarBody'; | ||
import { Combine } from 'src/types/utils'; | ||
|
||
export type SnackbarProps = Combine< | ||
{ | ||
show: boolean; | ||
message: string; | ||
button: ReactNode; | ||
autoHideDuration?: number; | ||
onShow?: () => void; | ||
onHide?: () => void; | ||
onClick?: () => void; | ||
}, | ||
Omit<HTMLAttributes<HTMLDivElement>, 'children'> | ||
>; | ||
|
||
const Snackbar = ({ | ||
show, | ||
message, | ||
button, | ||
autoHideDuration, | ||
onShow, | ||
onHide, | ||
onClick, | ||
className, | ||
style, | ||
...rest | ||
}: SnackbarProps) => { | ||
const [isOpen, setOpen] = useState(show); | ||
const transition = useTransition(isOpen, null, { | ||
from: { | ||
opacity: 0, | ||
transform: 'translateX(-100%)', | ||
height: 60, | ||
}, | ||
enter: [ | ||
{ height: 60 }, | ||
{ | ||
opacity: 1, | ||
transform: 'translateX(0)', | ||
}, | ||
], | ||
leave: [ | ||
{ | ||
opacity: 0, | ||
transform: 'translateX(-100%)', | ||
}, | ||
{ height: 0 }, | ||
], | ||
onStart: () => { | ||
onShow?.(); | ||
}, | ||
onDestroyed: () => { | ||
onHide?.(); | ||
}, | ||
}); | ||
|
||
useEffect(() => { | ||
let timer: NodeJS.Timeout; | ||
if (autoHideDuration != null && isOpen === true) { | ||
timer = setTimeout(() => { | ||
setOpen(false); | ||
}, autoHideDuration); | ||
} | ||
|
||
return () => clearTimeout(timer); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
{transition.map(({ item, key, props }) => { | ||
return item ? ( | ||
<animated.div | ||
key={key} | ||
className={classnames('lubycon-snackbar', className)} | ||
style={{ | ||
...style, | ||
...props, | ||
}} | ||
{...rest} | ||
> | ||
<SnackbarBody message={message} button={button} onClick={onClick} /> | ||
</animated.div> | ||
) : null; | ||
})} | ||
</> | ||
); | ||
}; | ||
|
||
export default Snackbar; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React, { ReactNode, createContext, useState, useCallback, useContext } from 'react'; | ||
import classnames from 'classnames'; | ||
import Snackbar, { SnackbarProps } from 'components/Snackbar'; | ||
import { generateID } from 'src/utils'; | ||
import { Portal } from './Portal'; | ||
interface SnackbarOptions extends Omit<SnackbarProps, 'show'> { | ||
duration?: number; | ||
} | ||
|
||
interface SnackbarGlobalState { | ||
openSnackbar: (option: SnackbarOptions) => void; | ||
closeSnackbar: (toastId: string) => void; | ||
} | ||
const SnackbarContext = createContext<SnackbarGlobalState>({ | ||
openSnackbar: () => {}, | ||
closeSnackbar: () => {}, | ||
}); | ||
|
||
interface SnackbarProviderProps { | ||
children: ReactNode; | ||
maxStack?: number; | ||
} | ||
export function SnackbarProvider({ children, maxStack = 1 }: SnackbarProviderProps) { | ||
const [openedSnackbarQueue, setOpenedSnackbarQueue] = useState<SnackbarOptions[]>([]); | ||
|
||
const openSnackbar = useCallback( | ||
({ id = generateID('lubycon-snackbar'), ...option }: SnackbarOptions) => { | ||
const snackbar = { id, ...option }; | ||
const [, ...rest] = openedSnackbarQueue; | ||
|
||
if (openedSnackbarQueue.length >= maxStack) { | ||
setOpenedSnackbarQueue([...rest, snackbar]); | ||
} else { | ||
setOpenedSnackbarQueue([...openedSnackbarQueue, snackbar]); | ||
} | ||
}, | ||
[openedSnackbarQueue] | ||
); | ||
|
||
const closeSnackbar = useCallback( | ||
(closedSnackbarId: string) => { | ||
setOpenedSnackbarQueue( | ||
openedSnackbarQueue.filter((snackbar) => snackbar.id !== closedSnackbarId) | ||
); | ||
}, | ||
[openedSnackbarQueue] | ||
); | ||
|
||
return ( | ||
<SnackbarContext.Provider | ||
value={{ | ||
openSnackbar, | ||
closeSnackbar, | ||
}} | ||
> | ||
{children} | ||
<Portal> | ||
<div className={classnames('lubycon-snackbar__context-container')}> | ||
{openedSnackbarQueue.map(({ id, onHide, duration = 3000, ...snackbarProps }) => ( | ||
<Snackbar | ||
key={id} | ||
show={true} | ||
autoHideDuration={duration} | ||
onHide={() => { | ||
closeSnackbar(id ?? ''); | ||
onHide?.(); | ||
}} | ||
{...snackbarProps} | ||
/> | ||
))} | ||
</div> | ||
</Portal> | ||
</SnackbarContext.Provider> | ||
); | ||
} | ||
|
||
export function useSnackbar() { | ||
return useContext(SnackbarContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
.lubycon-snackbar { | ||
overflow: visible; | ||
|
||
.lubycon-snackbar__body { | ||
display: inline-flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
padding: 8px 16px; | ||
min-width: 400px; | ||
border-radius: 4px; | ||
background-color: white; | ||
margin: 12px 0; | ||
} | ||
|
||
.lubycon-snackbar__text { | ||
white-space: pre; | ||
} | ||
|
||
.lubycon-snackbar__body__buttons { | ||
margin-left: 16px; | ||
.lubycon-button + .lubycon-button { | ||
margin-left: 8px; | ||
} | ||
} | ||
|
||
&:first-of-type { | ||
.lubycon-snackbar__body { | ||
margin-bottom: 0; | ||
} | ||
} | ||
} | ||
|
||
.lubycon-snackbar__context-container { | ||
position: fixed; | ||
display: flex; | ||
flex-direction: column-reverse; | ||
top: auto; | ||
bottom: 40px; | ||
left: 40px; | ||
right: auto; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,4 @@ | |
@import './Toast'; | ||
@import './Tooltip'; | ||
@import './Tabs'; | ||
@import './Snackbar'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React, { useState } from 'react'; | ||
import { Meta } from '@storybook/react/types-6-0'; | ||
import Snackbar from 'components/Snackbar'; | ||
import Button from 'components/Button'; | ||
import { useSnackbar } from 'contexts/Snackbar'; | ||
|
||
export default { | ||
title: 'Lubycon UI Kit/Snackbar', | ||
component: Snackbar, | ||
} as Meta; | ||
|
||
export const Default = () => { | ||
return ( | ||
<div> | ||
<Snackbar show={true} message="데이터 전송이 완료되었습니다." button="실행취소" /> | ||
<Snackbar | ||
show={true} | ||
message={`16개의 이미지가\n“동물" 폴더에 추가되었습니다.`} | ||
button="실행취소" | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
export const AutoHide = () => { | ||
const [show, setShow] = useState(true); | ||
return ( | ||
<div> | ||
<Snackbar show={true} message="데이터 전송이 완료되었습니다." button="실행취소" /> | ||
<Snackbar | ||
show={show} | ||
autoHideDuration={3000} | ||
onHide={() => setShow(true)} | ||
message={`16개의 이미지가\n“동물" 폴더에 추가되었습니다.`} | ||
button="실행취소" | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
export const SnackbarHooks = () => { | ||
const { openSnackbar } = useSnackbar(); | ||
return ( | ||
<div> | ||
<Button | ||
onClick={() => | ||
openSnackbar({ | ||
message: `파일이 휴지통으로 이동되었습니다.`, | ||
button: '실행취소', | ||
}) | ||
} | ||
> | ||
스낵바 열기 | ||
</Button> | ||
</div> | ||
); | ||
}; | ||
|
||
export const onClick = () => { | ||
const { openSnackbar } = useSnackbar(); | ||
return ( | ||
<div> | ||
<Button | ||
onClick={() => | ||
openSnackbar({ | ||
message: `파일이 휴지통으로 이동되었습니다.`, | ||
button: '실행취소', | ||
onClick: () => alert('실행 취소 완료'), | ||
}) | ||
} | ||
> | ||
스낵바 열기 | ||
</Button> | ||
</div> | ||
); | ||
}; | ||
|
||
export const multipleButton = () => { | ||
const { openSnackbar } = useSnackbar(); | ||
return ( | ||
<div> | ||
<Button | ||
onClick={() => | ||
openSnackbar({ | ||
message: '메세지가 전송되었습니다.', | ||
button: ( | ||
<> | ||
<Button onClick={() => alert('실행 취소 완료')}>실행취소</Button> | ||
<Button onClick={() => alert('메세지 보기 클릭')}>메세지 보기</Button> | ||
</> | ||
), | ||
}) | ||
} | ||
> | ||
스낵바 열기 | ||
</Button> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요거 삭제되면 좋을것 같습니당...!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fbce4f1 에서 반영되었습니다!