Snackbar
Snackbar
Snackbar(스낵바, toast라고도 불려요)는 수행되었거나 수행될 프로세스에 대한 짧은 알림에 사용돼요.
출처: 문서
본문
소개 (Introduction)
Snackbar 컴포넌트는 일시적으로 나타나서 UI 위에 떠 있으며, 앱의 프로세스에 대한 (중요하지 않은) 업데이트를 사용자에게 제공해요. Google Keep에서 영감을 받은 아래 데모는 텍스트 요소와 두 개의 동작이 있는 기본 Snackbar를 보여줘요:
import * as React from 'react';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
export default function SimpleSnackbar() {
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
const action = (
<React.Fragment>
<Button color="secondary" size="small" onClick={handleClose}>
UNDO
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
return (
<div>
<Button onClick={handleClick}>Open Snackbar</Button>
<Snackbar
open={open}
autoHideDuration={6000}
onClose={handleClose}
message="Note archived"
action={action}
/>
</div>
);
}
용도 (Usage)
Snackbar는 Alerts와 다른데, Snackbar는 고정된 위치와 높은 z-index를 가져서 문서 흐름(documents flow)을 벗어나려고 설계되었기 때문이에요. 반면 Alerts는 보통 흐름의 일부예요 — Snackbar의 children으로 사용될 때를 제외하고요.
Snackbar는 Dialogs와도 다른데, Snackbar는 중요한 정보를 전달하거나 사용자가 앱의 나머지 부분과 상호작용하는 것을 막기 위한 것이 아니기 때문이에요. 반면 Dialogs는 닫기 위해 사용자 입력을 필요로 해요.
기본 (Basics)
Import
import Snackbar from '@mui/material/Snackbar';
위치 (Position)
anchorOrigin prop을 사용해 화면에서 Snackbar의 위치를 제어해요.
import * as React from 'react';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarOrigin } from '@mui/material/Snackbar';
interface State extends SnackbarOrigin {
open: boolean;
}
export default function PositionedSnackbar() {
const [state, setState] = React.useState<State>({
open: false,
vertical: 'top',
horizontal: 'center',
});
const { vertical, horizontal, open } = state;
const handleClick = (newState: SnackbarOrigin) => () => {
setState({ ...newState, open: true });
};
const handleClose = () => {
setState({ ...state, open: false });
};
const buttons = (
<React.Fragment>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleClick({ vertical: 'top', horizontal: 'center' })}>
Top-Center
</Button>
</Box>
<Grid container sx={{ justifyContent: 'center' }}>
<Grid size={6}>
<Button onClick={handleClick({ vertical: 'top', horizontal: 'left' })}>
Top-Left
</Button>
</Grid>
<Grid sx={{ textAlign: 'right' }} size={6}>
<Button onClick={handleClick({ vertical: 'top', horizontal: 'right' })}>
Top-Right
</Button>
</Grid>
<Grid size={6}>
<Button onClick={handleClick({ vertical: 'bottom', horizontal: 'left' })}>
Bottom-Left
</Button>
</Grid>
<Grid sx={{ textAlign: 'right' }} size={6}>
<Button onClick={handleClick({ vertical: 'bottom', horizontal: 'right' })}>
Bottom-Right
</Button>
</Grid>
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleClick({ vertical: 'bottom', horizontal: 'center' })}>
Bottom-Center
</Button>
</Box>
</React.Fragment>
);
return (
<Box sx={{ width: 500 }}>
{buttons}
<Snackbar
anchorOrigin={{ vertical, horizontal }}
open={open}
onClose={handleClose}
message="I love snacks"
key={vertical + horizontal}
/>
</Box>
);
}
콘텐츠 (Content)
import SnackbarContent from '@mui/material/SnackbarContent';
Snackbar Content 컴포넌트를 사용해 Snackbar에 텍스트와 동작을 추가해요.
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import SnackbarContent from '@mui/material/SnackbarContent';
const action = (
<Button color="secondary" size="small">
lorem ipsum dolorem
</Button>
);
export default function LongTextSnackbar() {
return (
<Stack spacing={2} sx={{ maxWidth: 600 }}>
<SnackbarContent message="I love snacks." action={action} />
<SnackbarContent
message={
'I love candy. I love cookies. I love cupcakes. \
I love cheesecake. I love chocolate.'
}
/>
<SnackbarContent
message="I love candy. I love cookies. I love cupcakes."
action={action}
/>
<SnackbarContent
message={
'I love candy. I love cookies. I love cupcakes. \
I love cheesecake. I love chocolate.'
}
action={action}
/>
</Stack>
);
}
자동 닫기 (Automatic dismiss)
autoHideDuration prop을 사용해 설정된 시간(밀리초) 후에 Snackbar의 onClose 함수를 자동으로 트리거해요.
사용자가 표시된 정보를 처리할 시간을 충분히 제공해야 해요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
export default function AutohideSnackbar() {
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<div>
<Button onClick={handleClick}>Open Snackbar</Button>
<Snackbar
open={open}
autoHideDuration={5000}
onClose={handleClose}
message="This Snackbar will be dismissed in 5 seconds."
/>
</div>
);
}
전환 (Transitions)
slots.transition과 slotProps.transition을 사용해 Snackbar의 전환을 Grow(기본값)에서 Slide와 같은 다른 전환으로 바꿔요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Snackbar from '@mui/material/Snackbar';
import Fade from '@mui/material/Fade';
import Slide, { SlideProps } from '@mui/material/Slide';
import Grow, { GrowProps } from '@mui/material/Grow';
import { TransitionProps } from '@mui/material/transitions';
function SlideTransition(props: SlideProps) {
return <Slide {...props} direction="up" />;
}
function GrowTransition(props: GrowProps) {
return <Grow {...props} />;
}
export default function TransitionsSnackbar() {
const [state, setState] = React.useState<{
open: boolean;
Transition: React.ComponentType<
TransitionProps & {
children: React.ReactElement<any, any>;
}
>;
}>({
open: false,
Transition: Fade,
});
const handleClick =
(
Transition: React.ComponentType<
TransitionProps & {
children: React.ReactElement<any, any>;
}
>,
) =>
() => {
setState({
open: true,
Transition,
});
};
const handleClose = () => {
setState({
...state,
open: false,
});
};
return (
<div>
<Button onClick={handleClick(GrowTransition)}>Grow Transition</Button>
<Button onClick={handleClick(Fade)}>Fade Transition</Button>
<Button onClick={handleClick(SlideTransition)}>Slide Transition</Button>
<Snackbar
open={state.open}
onClose={handleClose}
slots={{ transition: state.Transition }}
message="I love snacks"
key={state.Transition.name}
autoHideDuration={1200}
/>
</div>
);
}
커스터마이즈 (Customization)
기본 클릭-어웨이 이벤트 방지
기본 onClickAway 동작을 방지하고 싶다면 이벤트의 defaultMuiPrevented 속성을 true로 설정할 수 있어요:
<Snackbar
slotProps={{
clickAwayListener: {
onClickAway: (event) => {
// Prevent's default 'onClickAway' behavior.
event.defaultMuiPrevented = true;
},
},
}}
/>
Alerts와 함께 사용하기
특정 심각도(severity)를 전달하는 메시지에는 Snackbar 안에 Alert를 사용해요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
export default function CustomizedSnackbars() {
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (
event?: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<div>
<Button onClick={handleClick}>Open Snackbar</Button>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert
onClose={handleClose}
severity="success"
variant="filled"
sx={{ width: '100%' }}
>
This is a success Alert inside a Snackbar!
</Alert>
</Snackbar>
</div>
);
}
플로팅 액션 버튼과 함께 사용하기
모바일에서 Floating Action Button을 사용 중이라면, Material Design은 아래 데모처럼 snackbar를 버튼 바로 위에 배치할 것을 권장해요:
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import CssBaseline from '@mui/material/CssBaseline';
import GlobalStyles from '@mui/material/GlobalStyles';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import Fab from '@mui/material/Fab';
import AddIcon from '@mui/icons-material/Add';
import Snackbar from '@mui/material/Snackbar';
export default function FabIntegrationSnackbar() {
return (
<React.Fragment>
<CssBaseline />
<GlobalStyles
styles={(theme) => ({
body: { backgroundColor: theme.palette.background.paper },
})}
/>
<div>
<AppBar position="static" color="primary">
<Toolbar>
<IconButton
edge="start"
sx={{ mr: 2 }}
color="inherit"
aria-label="menu"
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
component="div"
sx={{
color: 'inherit',
}}
>
App bar
</Typography>
</Toolbar>
</AppBar>
<Fab
color="secondary"
sx={(theme) => ({
position: 'absolute',
bottom: theme.spacing(2),
right: theme.spacing(2),
})}
>
<AddIcon />
</Fab>
<Snackbar
open
autoHideDuration={6000}
message="Archived"
action={
<Button color="inherit" size="small">
Undo
</Button>
}
sx={{ bottom: { xs: 90, sm: 0 } }}
/>
</div>
</React.Fragment>
);
}
일반적인 예시 (Common examples)
연속적인 Snackbar (Consecutive Snackbars)
이 데모는 연속 애니메이션을 사용해 Snackbar를 쌓지 않고 여러 개를 표시하는 방법을 보여줘요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
export interface SnackbarMessage {
message: string;
key: number;
}
export default function ConsecutiveSnackbars() {
const [snackPack, setSnackPack] = React.useState<readonly SnackbarMessage[]>([]);
const [open, setOpen] = React.useState(false);
const [messageInfo, setMessageInfo] = React.useState<SnackbarMessage | undefined>(
undefined,
);
React.useEffect(() => {
if (snackPack.length && !messageInfo) {
// Set a new snack when we don't have an active one
setMessageInfo({ ...snackPack[0] });
setSnackPack((prev) => prev.slice(1));
setOpen(true);
} else if (snackPack.length && messageInfo && open) {
// Close an active snack when a new one is added
setOpen(false);
}
}, [snackPack, messageInfo, open]);
const handleClick = (message: string) => () => {
setSnackPack((prev) => [...prev, { message, key: new Date().getTime() }]);
};
const handleClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
const handleExited = () => {
setMessageInfo(undefined);
};
return (
<div>
<Button onClick={handleClick('Message A')}>Show message A</Button>
<Button onClick={handleClick('Message B')}>Show message B</Button>
<Snackbar
key={messageInfo ? messageInfo.key : undefined}
open={open}
autoHideDuration={6000}
onClose={handleClose}
slotProps={{ transition: { onExited: handleExited } }}
message={messageInfo ? messageInfo.message : undefined}
action={
<React.Fragment>
<Button color="secondary" size="small" onClick={handleClose}>
UNDO
</Button>
<IconButton
aria-label="close"
color="inherit"
sx={{ p: 0.5 }}
onClick={handleClose}
>
<CloseIcon />
</IconButton>
</React.Fragment>
}
/>
</div>
);
}
보조 컴포넌트 (Supplementary components)
notistack
명령형(imperative) API를 사용하는 notistack은 열고 닫는 상태를 직접 다룰 필요 없이 여러 Snackbar를 세로로 쌓을 수 있게 해줘요. Material Design 가이드라인에서 권장되지는 않지만, 여전히 흔한 패턴이에요.
import * as React from 'react';
import Button from '@mui/material/Button';
import { SnackbarProvider, VariantType, useSnackbar } from 'notistack';
function MyApp() {
const { enqueueSnackbar } = useSnackbar();
const handleClick = () => {
enqueueSnackbar('I love snacks.');
};
const handleClickVariant = (variant: VariantType) => () => {
// variant could be success, error, warning, info, or default
enqueueSnackbar('This is a success message!', { variant });
};
return (
<React.Fragment>
<Button onClick={handleClick}>Show snackbar</Button>
<Button onClick={handleClickVariant('success')}>Show success snackbar</Button>
</React.Fragment>
);
}
export default function IntegrationNotistack() {
return (
<SnackbarProvider maxSnack={3}>
<MyApp />
</SnackbarProvider>
);
}
:::warning notistack는 Snackbar가 Escape로 닫히는 것을 방지한다는 점에 유의하세요. :::
접근성 (Accessibility)
사용자는 Escape를 눌러 Snackbar를 닫을 수 있어야 해요. 한 번에 여러 인스턴스가 나타나고 Escape로 현재 열려있는 것 중 가장 오래된 것만 닫고 싶다면, onClose prop에서 event.preventDefault를 호출해요.
export default function MyComponent() {
const [open, setOpen] = React.useState(true);
return (
<React.Fragment>
<Snackbar
open={open}
onClose={(event, reason) => {
// `reason === 'escapeKeyDown'` if `Escape` was pressed
setOpen(false);
// call `event.preventDefault` to only close one Snackbar at a time.
}}
/>
<Snackbar open={open} onClose={() => setOpen(false)} />
</React.Fragment>
);
}
구조 (Anatomy)
Snackbar 컴포넌트는 루트 <div>로 구성되며, 그 안에 Snackbar Content와 같은 내부 요소와 다른 선택적 컴포넌트(버튼이나 데코레이터 등)를 담아요.
<div role="presentation" class="MuiSnackbar-root">
<div class="MuiPaper-root MuiSnackbarContent-root" role="alert">
<div class="MuiSnackbarContent-message">
<!-- Snackbar content goes here -->
</div>
</div>
</div>
Snackbar API
데모 (Demos)
이 React 컴포넌트의 사용에 관한 예제와 자세한 내용은 컴포넌트 데모 페이지를 방문하세요:
Import
import Snackbar from '@mui/material/Snackbar';
// or
import { Snackbar } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| action | node |
- | No | |
| anchorOrigin | { horizontal: 'center' | 'left' | 'right', vertical: 'bottom' | 'top' } |
{ vertical: 'bottom', horizontal: 'left' } |
No | |
| autoHideDuration | number |
null |
No | |
| children | element |
- | No | |
| classes | object |
- | No | 컴포넌트에 적용되는 스타일을 오버라이드하거나 확장해요. |
| disableWindowBlurListener | bool |
false |
No | |
| key | any |
- | No | |
| message | node |
- | No | |
| onClose | function(event: React.SyntheticEvent<any> | Event, reason: string) => void |
- | No | |
| open | bool |
- | No | |
| resumeHideDuration | number |
- | No | |
| slotProps | { clickAwayListener?: func | object, content?: func | object, root?: func | object, transition?: func | object } |
{} |
No | |
| slots | { clickAwayListener?: elementType, content?: elementType, root?: elementType, transition?: elementType } |
{} |
No | |
| sx | Array<func | object | bool> | func | object |
- | No | 시스템 오버라이드 및 추가 CSS 스타일을 정의할 수 있게 해주는 시스템 prop이에요. |
| transitionDuration | number | { appear?: number, enter?: number, exit?: number } |
`{ | ||
| enter: theme.transitions.duration.enteringScreen, | ||||
| exit: theme.transitions.duration.leavingScreen, | ||||
| }` | No |
Note:
ref는 루트 요소(HTMLDivElement)로 전달돼요.
제공된 다른 모든 props는 루트 요소(네이티브 요소)로 전달돼요.
테마 기본 props (Theme default props)
MuiSnackbar을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
Slots
| Name | Default | Class | Description |
|---|---|---|---|
| root | 'div' |
.MuiSnackbar-root |
루트 슬롯을 렌더링하는 컴포넌트예요. |
| content | SnackbarContent |
- | 콘텐츠 슬롯을 렌더링하는 컴포넌트예요. |
| clickAwayListener | ClickAwayListener |
- | clickAwayListener 슬롯을 렌더링하는 컴포넌트예요. |
| transition | Grow |
- | 전환을 렌더링하는 컴포넌트예요. |
| 이 가이드를 따라 이 컴포넌트의 요구사항에 대해 자세히 알아보세요. |
CSS
규칙 이름 (Rule name)
| Global class | Rule name | Description |
|---|---|---|
| - | anchorOriginBottomCenter | anchorOrigin={{ 'bottom', 'center' }}일 때 루트 요소에 적용되는 스타일이에요. |
| - | anchorOriginBottomLeft | anchorOrigin={{ 'bottom', 'left' }}일 때 루트 요소에 적용되는 스타일이에요. |
| - | anchorOriginBottomRight | anchorOrigin={{ 'bottom', 'right' }}일 때 루트 요소에 적용되는 스타일이에요. |
| - | anchorOriginTopCenter | anchorOrigin={{ 'top', 'center' }}일 때 루트 요소에 적용되는 스타일이에요. |
| - | anchorOriginTopLeft | anchorOrigin={{ 'top', 'left' }}일 때 루트 요소에 적용되는 스타일이에요. |
| - | anchorOriginTopRight | anchorOrigin={{ 'top', 'right' }}일 때 루트 요소에 적용되는 스타일이에요. |
소스 코드 (Source code)
이 페이지에서 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보세요.
SnackbarContent API
데모 (Demos)
이 React 컴포넌트의 사용에 관한 예제와 자세한 내용은 컴포넌트 데모 페이지를 방문하세요:
Import
import SnackbarContent from '@mui/material/SnackbarContent';
// or
import { SnackbarContent } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| action | node |
- | No | |
| classes | object |
- | No | 컴포넌트에 적용되는 스타일을 오버라이드하거나 확장해요. |
| message | node |
- | No | |
| role | string |
'alert' |
No | |
| sx | Array<func | object | bool> | func | object |
- | No | 시스템 오버라이드 및 추가 CSS 스타일을 정의할 수 있게 해주는 시스템 prop이에요. |
Note:
ref는 루트 요소(HTMLDivElement)로 전달돼요.
제공된 다른 모든 props는 루트 요소(Paper)로 전달돼요.
상속 (Inheritance)
위에 명시적으로 문서화되지는 않았지만, Paper 컴포넌트의 props는 SnackbarContent에서도 사용할 수 있어요.
테마 기본 props (Theme default props)
MuiSnackbarContent을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
CSS
규칙 이름 (Rule name)
| Global class | Rule name | Description |
|---|---|---|
| - | action | action이 제공될 때 동작 래퍼 요소에 적용되는 스타일이에요. |
| - | message | 메시지 래퍼 요소에 적용되는 스타일이에요. |
| - | root | 루트 요소에 적용되는 스타일이에요. |
소스 코드 (Source code)
이 페이지에서 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보세요.