Dialog
Dialog (다이얼로그)
사용자에게 중요한 정보를 전달하거나 결정을 요구하거나 여러 작업을 묶어 수행하게 하는 다이얼로그 컴포넌트를 알아볼게요. 모달 창 위에 떠서 앱을 잠시 멈추는 방식이라, 꼭 필요할 때만 사용해야 하는 컴포넌트예요.
출처: 문서
본문
다이얼로그는 사용자에게 작업에 대해 알리고, 중요한 정보를 포함하거나, 결정을 요구하거나, 여러 작업을 수반할 수 있어요.
다이얼로그는 앱 콘텐츠 앞에 나타나 중요한 정보를 제공하거나 결정을 요청하는 modal 창의 한 유형이에요. 다이얼로그는 나타나면 모든 앱 기능을 비활성화하고, 확인되거나 닫히거나 필요한 작업이 수행될 때까지 화면에 남아 있어요.
다이얼로그는 의도적으로 방해적(interruptive)이기 때문에, 드물게 사용해야 해요.
소개 (Introduction)
다이얼로그는 관련 컴포넌트 모음으로 구현돼요:
- Dialog: 모달을 렌더링하는 부모 컴포넌트.
- Dialog Title: 다이얼로그의 제목에 사용되는 래퍼.
- Dialog Actions: 다이얼로그의 버튼을 위한 선택적 컨테이너.
- Dialog Content: 다이얼로그의 콘텐츠를 표시하기 위한 선택적 컨테이너.
- Dialog Content Text:
<DialogContent />안의 텍스트용 래퍼. - Slide: 선택적 Transition으로, 화면 가장자리에서 다이얼로그를 슬라이드 인되게 해요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Avatar from '@mui/material/Avatar';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import DialogTitle from '@mui/material/DialogTitle';
import Dialog from '@mui/material/Dialog';
import PersonIcon from '@mui/icons-material/Person';
import AddIcon from '@mui/icons-material/Add';
import Typography from '@mui/material/Typography';
import { blue } from '@mui/material/colors';
const emails = ['[email protected]', '[email protected]'];
export interface SimpleDialogProps {
open: boolean;
selectedValue: string;
onClose: (value: string) => void;
}
function SimpleDialog(props: SimpleDialogProps) {
const { onClose, selectedValue, open } = props;
const handleClose = () => {
onClose(selectedValue);
};
const handleListItemClick = (value: string) => {
onClose(value);
};
return (
<Dialog onClose={handleClose} open={open}>
<DialogTitle>Set backup account</DialogTitle>
<List sx={{ pt: 0 }}>
{emails.map((email) => (
<ListItem disablePadding key={email}>
<ListItemButton onClick={() => handleListItemClick(email)}>
<ListItemAvatar>
<Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItemButton>
</ListItem>
))}
<ListItem disablePadding>
<ListItemButton
autoFocus
onClick={() => handleListItemClick('addAccount')}
>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Add account" />
</ListItemButton>
</ListItem>
</List>
</Dialog>
);
}
export default function SimpleDialogDemo() {
const [open, setOpen] = React.useState(false);
const [selectedValue, setSelectedValue] = React.useState(emails[1]);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = (value: string) => {
setOpen(false);
setSelectedValue(value);
};
return (
<div>
<Typography variant="subtitle1" component="div">
Selected: {selectedValue}
</Typography>
<br />
<Button variant="outlined" onClick={handleClickOpen}>
Open simple dialog
</Button>
<SimpleDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
/>
</div>
);
}
기본 (Basics)
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
알림 (Alerts)
알림은 사용자에게 상황을 알려주는, 확인이 필요한 긴급한 중단(interruption)이에요.
role="alertdialog"를 사용해서 Alert Dialog를 만들어요. 이렇게 하면 보조 기술(assistive technologies)에 다이얼로그의 올바른 목적을 제공해요.
대부분의 알림은 제목이 필요하지 않아요. 다음 중 하나로 결정을 한두 문장으로 요약해요:
- 질문하기(예: "이 대화를 삭제할까요?")
- 동작 버튼과 관련된 진술하기
제목 표시줄 알림은 잠재적인 연결 손실 같은 고위험 상황에서만 사용하세요. 사용자는 제목과 버튼 텍스트만으로 선택을 이해할 수 있어야 해요.
제목이 필요한 경우:
- 콘텐츠 영역에 설명이 포함된 명확한 질문이나 진술을 사용하세요, 예를 들면 "USB 저장소를 지울까요?".
- "경고!"나 "정말 확실한가요?" 같은 사과, 모호함, 질문은 피하세요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open alert dialog
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
role="alertdialog"
>
<DialogTitle id="alert-dialog-title">
{"Use Google's location service?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Let Google help apps determine location. This means sending anonymous
location data to Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} autoFocus>
Disagree
</Button>
<Button onClick={handleClose}>Agree</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
전환 (Transitions)
slots.transition과 slotProps.transition prop으로 기본 전환을 교체할 수 있어요. 다음 예제는 Slide를 사용해요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import Slide from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions';
const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement<any, any>;
},
ref: React.Ref<unknown>,
) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function AlertDialogSlide() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Slide in alert dialog
</Button>
<Dialog
open={open}
slots={{
transition: Transition,
}}
keepMounted
onClose={handleClose}
aria-describedby="alert-dialog-slide-description"
role="alertdialog"
>
<DialogTitle>{"Use Google's location service?"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-slide-description">
Let Google help apps determine location. This means sending anonymous
location data to Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} autoFocus>
Disagree
</Button>
<Button onClick={handleClose}>Agree</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
폼 다이얼로그 (Form dialogs)
폼 다이얼로그는 사용자가 다이얼로그 안에서 폼 필드를 작성할 수 있게 해줘요. 예를 들어, 사이트가 잠재 구독자에게 이메일 주소를 입력하라는 안내를 한다면, 사용자는 이메일 필드를 작성하고 'Submit'을 누를 수 있어요.
import * as React from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
export default function FormDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const formJson = Object.fromEntries((formData as any).entries());
const email = formJson.email;
console.log(email);
handleClose();
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open form dialog
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We
will send updates occasionally.
</DialogContentText>
<form onSubmit={handleSubmit} id="subscription-form">
<TextField
autoFocus
required
margin="dense"
id="name"
name="email"
label="Email Address"
type="email"
fullWidth
variant="standard"
/>
</form>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit" form="subscription-form">
Subscribe
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
커스터마이즈 (Customization)
컴포넌트를 커스터마이즈하는 예제예요. 이에 대해 더 자세히 알아보려면 overrides 문서 페이지를 참고하세요.
다이얼로그에는 사용성을 돕기 위해 닫기 버튼이 추가되어 있어요.
import * as React from 'react';
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import Typography from '@mui/material/Typography';
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
}));
export default function CustomizedDialogs() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open dialog
</Button>
<BootstrapDialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle sx={{ m: 0, p: 2 }} id="customized-dialog-title">
Modal title
</DialogTitle>
<IconButton
aria-label="close"
onClick={handleClose}
sx={(theme) => ({
position: 'absolute',
right: 8,
top: 8,
color: theme.palette.grey[500],
})}
>
<CloseIcon />
</IconButton>
<DialogContent dividers>
<Typography gutterBottom>
Cras mattis consectetur purus sit amet fermentum. Cras justo odio,
dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
consectetur ac, vestibulum at eros.
</Typography>
<Typography gutterBottom>
Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
</Typography>
<Typography gutterBottom>
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus
magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec
ullamcorper nulla non metus auctor fringilla.
</Typography>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose}>
Save changes
</Button>
</DialogActions>
</BootstrapDialog>
</React.Fragment>
);
}
전체 화면 다이얼로그 (Full-screen dialogs)
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import ListItemText from '@mui/material/ListItemText';
import ListItemButton from '@mui/material/ListItemButton';
import List from '@mui/material/List';
import Divider from '@mui/material/Divider';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import CloseIcon from '@mui/icons-material/Close';
import Slide from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions';
const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement<unknown>;
},
ref: React.Ref<unknown>,
) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function FullScreenDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open full-screen dialog
</Button>
<Dialog
fullScreen
open={open}
onClose={handleClose}
slots={{
transition: Transition,
}}
>
<AppBar sx={{ position: 'relative' }}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<List>
<ListItemButton>
<ListItemText primary="Phone ringtone" secondary="Titania" />
</ListItemButton>
<Divider />
<ListItemButton>
<ListItemText
primary="Default notification ringtone"
secondary="Tethys"
/>
</ListItemButton>
</List>
</Dialog>
</React.Fragment>
);
}
선택적 크기 (Optional sizes)
fullWidth 불리언과 함께 maxWidth 열거형을 사용해서 다이얼로그의 최대 너비를 설정할 수 있어요. fullWidth prop이 true이면 다이얼로그는 maxWidth 값에 따라 적응해요.
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Dialog, { DialogProps } from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Switch from '@mui/material/Switch';
export default function MaxWidthDialog() {
const [open, setOpen] = React.useState(false);
const [fullWidth, setFullWidth] = React.useState(true);
const [maxWidth, setMaxWidth] = React.useState<DialogProps['maxWidth']>('sm');
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleMaxWidthChange = (event: SelectChangeEvent<typeof maxWidth>) => {
setMaxWidth(
// @ts-expect-error autofill of arbitrary value is not handled.
event.target.value,
);
};
const handleFullWidthChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setFullWidth(event.target.checked);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open max-width dialog
</Button>
<Dialog
fullWidth={fullWidth}
maxWidth={maxWidth}
open={open}
onClose={handleClose}
>
<DialogTitle>Optional sizes</DialogTitle>
<DialogContent>
<DialogContentText>
You can set my maximum width and whether to adapt or not.
</DialogContentText>
<Box
noValidate
component="form"
sx={{
display: 'flex',
flexDirection: 'column',
m: 'auto',
width: 'fit-content',
}}
>
<FormControl sx={{ mt: 2, minWidth: 120 }}>
<InputLabel htmlFor="max-width">maxWidth</InputLabel>
<Select
autoFocus
value={maxWidth}
onChange={handleMaxWidthChange}
label="maxWidth"
inputProps={{
name: 'max-width',
id: 'max-width',
}}
>
<MenuItem value={false as any}>false</MenuItem>
<MenuItem value="xs">xs</MenuItem>
<MenuItem value="sm">sm</MenuItem>
<MenuItem value="md">md</MenuItem>
<MenuItem value="lg">lg</MenuItem>
<MenuItem value="xl">xl</MenuItem>
</Select>
</FormControl>
<FormControlLabel
sx={{ mt: 1 }}
control={
<Switch checked={fullWidth} onChange={handleFullWidthChange} />
}
label="Full width"
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
반응형 전체 화면 (Responsive full-screen)
useMediaQuery를 사용해서 다이얼로그를 반응형으로 전체 화면으로 만들 수 있어요.
import useMediaQuery from '@mui/material/useMediaQuery';
function MyComponent() {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
return <Dialog fullScreen={fullScreen} />;
}
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
export default function ResponsiveDialog() {
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open responsive dialog
</Button>
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">
{"Use Google's location service?"}
</DialogTitle>
<DialogContent>
<DialogContentText>
Let Google help apps determine location. This means sending anonymous
location data to Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose}>
Disagree
</Button>
<Button onClick={handleClose} autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
확인 다이얼로그 (Confirmation dialogs)
확인 다이얼로그는 옵션이 확정되기 전에 사용자가 선택을 명시적으로 확인하도록 요구해요. 예를 들어, 사용자가 여러 벨소리를 들을 수 있지만 "OK"를 눌러야만 최종 선택을 할 수 있어요.
확인 다이얼로그에서 "Cancel"을 누르면 동작이 취소되고, 변경 사항이 버려지며, 다이얼로그가 닫혀요.
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Dialog from '@mui/material/Dialog';
import RadioGroup from '@mui/material/RadioGroup';
import Radio from '@mui/material/Radio';
import FormControlLabel from '@mui/material/FormControlLabel';
const options = [
'None',
'Atria',
'Callisto',
'Dione',
'Ganymede',
'Hangouts Call',
'Luna',
'Oberon',
'Phobos',
'Pyxis',
'Sedna',
'Titania',
'Triton',
'Umbriel',
];
export interface ConfirmationDialogRawProps {
id: string;
keepMounted: boolean;
value: string;
open: boolean;
onClose: (value?: string) => void;
}
function ConfirmationDialogRaw(props: ConfirmationDialogRawProps) {
const { onClose, value: valueProp, open, ...other } = props;
const [value, setValue] = React.useState(valueProp);
const radioGroupRef = React.useRef<HTMLElement>(null);
React.useEffect(() => {
if (!open) {
setValue(valueProp);
}
}, [valueProp, open]);
const handleEntering = () => {
if (radioGroupRef.current != null) {
radioGroupRef.current.focus();
}
};
const handleCancel = () => {
onClose();
};
const handleOk = () => {
onClose(value);
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue((event.target as HTMLInputElement).value);
};
return (
<Dialog
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
maxWidth="xs"
slotProps={{
transition: {
onEntering: handleEntering,
},
}}
open={open}
{...other}
>
<DialogTitle>Phone Ringtone</DialogTitle>
<DialogContent dividers>
<RadioGroup
ref={radioGroupRef}
aria-label="ringtone"
name="ringtone"
value={value}
onChange={handleChange}
>
{options.map((option) => (
<FormControlLabel
value={option}
key={option}
control={<Radio />}
label={option}
/>
))}
</RadioGroup>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel}>
Cancel
</Button>
<Button onClick={handleOk}>Ok</Button>
</DialogActions>
</Dialog>
);
}
export default function ConfirmationDialog() {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('Dione');
const handleClickListItem = () => {
setOpen(true);
};
const handleClose = (newValue?: string) => {
setOpen(false);
if (newValue) {
setValue(newValue);
}
};
return (
<Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
<List component="div" role="group">
<ListItemButton divider disabled>
<ListItemText primary="Interruptions" />
</ListItemButton>
<ListItemButton
divider
aria-haspopup="true"
aria-controls="ringtone-menu"
aria-label="phone ringtone"
onClick={handleClickListItem}
>
<ListItemText primary="Phone ringtone" secondary={value} />
</ListItemButton>
<ListItemButton divider disabled>
<ListItemText primary="Default notification ringtone" secondary="Tethys" />
</ListItemButton>
<ConfirmationDialogRaw
id="ringtone-menu"
keepMounted
open={open}
onClose={handleClose}
value={value}
/>
</List>
</Box>
);
}
비모달 다이얼로그 (Non-modal dialog)
다이얼로그는 비모달(non-modal)일 수도 있는데, 뒤에 있는 사용자 상호작용을 방해하지 않는다는 뜻이에요. 모달 대 비모달 다이얼로그 사용에 대한 더 심층적인 지침은 the Nielsen Norman Group article을 방문하세요.
아래 데모는 일반적인 비모달 다이얼로그 사용 사례인 지속형 쿠키 배너를 보여줘요.
import * as React from 'react';
import Stack from '@mui/material/Stack';
import TrapFocus from '@mui/material/Unstable_TrapFocus';
import CssBaseline from '@mui/material/CssBaseline';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Container from '@mui/material/Container';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Paper from '@mui/material/Paper';
import Fade from '@mui/material/Fade';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
export default function CookiesBanner() {
const [bannerOpen, setBannerOpen] = React.useState(true);
const closeBanner = () => {
setBannerOpen(false);
};
return (
<React.Fragment>
<CssBaseline />
<AppBar position="fixed" component="nav">
<Toolbar>
<IconButton size="large" edge="start" color="inherit" aria-label="menu">
<MenuIcon />
</IconButton>
</Toolbar>
</AppBar>
<Container component="main" sx={{ pt: 3 }}>
<Toolbar />
<Typography sx={{ marginBottom: 2 }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
imperdiet.
</Typography>
<Typography sx={{ marginBottom: 2 }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
imperdiet.
</Typography>
</Container>
<TrapFocus open disableAutoFocus disableEnforceFocus>
<Fade appear={false} in={bannerOpen}>
<Paper
role="dialog"
aria-modal="false"
aria-label="Cookie banner"
square
variant="outlined"
tabIndex={-1}
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
m: 0,
p: 2,
borderWidth: 0,
borderTopWidth: 1,
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
sx={{ justifyContent: 'space-between', gap: 2 }}
>
<Box
sx={{ flexShrink: 1, alignSelf: { xs: 'flex-start', sm: 'center' } }}
>
<Typography sx={{ fontWeight: 'bold' }}>
This website uses cookies
</Typography>
<Typography variant="body2">
example.com relies on cookies to improve your experience.
</Typography>
</Box>
<Stack
direction={{
xs: 'row-reverse',
sm: 'row',
}}
sx={{
gap: 2,
flexShrink: 0,
alignSelf: { xs: 'flex-end', sm: 'center' },
}}
>
<Button size="small" onClick={closeBanner} variant="contained">
Allow all
</Button>
<Button size="small" onClick={closeBanner}>
Reject all
</Button>
</Stack>
</Stack>
</Paper>
</Fade>
</TrapFocus>
</React.Fragment>
);
}
드래그 가능한 다이얼로그 (Draggable dialog)
react-draggable을 사용해서 드래그 가능한 다이얼로그를 만들 수 있어요. 이렇게 하려면 import한 Draggable 컴포넌트를 Dialog 컴포넌트의 PaperComponent로 전달할 수 있어요. 이렇게 하면 다이얼로그 전체가 드래그 가능해져요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import Paper, { PaperProps } from '@mui/material/Paper';
import Draggable from 'react-draggable';
function PaperComponent(props: PaperProps) {
const nodeRef = React.useRef<HTMLDivElement>(null);
return (
<Draggable
nodeRef={nodeRef as React.RefObject<HTMLDivElement>}
handle="#draggable-dialog-title"
cancel={'[class*="MuiDialogContent-root"]'}
>
<Paper {...props} ref={nodeRef} />
</Draggable>
);
}
export default function DraggableDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button variant="outlined" onClick={handleClickOpen}>
Open draggable dialog
</Button>
<Dialog
open={open}
onClose={handleClose}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog-title"
>
<DialogTitle style={{ cursor: 'move' }} id="draggable-dialog-title">
Subscribe
</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We
will send updates occasionally.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose}>
Cancel
</Button>
<Button onClick={handleClose}>Subscribe</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
긴 콘텐츠 스크롤 (Scrolling long content)
다이얼로그가 사용자의 뷰포트나 기기보다 너무 길어지면 스크롤돼요.
scroll=paper다이얼로그의 콘텐츠는 paper 요소 안에서 스크롤돼요.scroll=body다이얼로그의 콘텐츠는 body 요소 안에서 스크롤돼요.
무슨 말인지 아래 데모로 확인해 보세요:
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog, { DialogProps } from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
export default function ScrollDialog() {
const [open, setOpen] = React.useState(false);
const [scroll, setScroll] = React.useState<DialogProps['scroll']>('paper');
const handleClickOpen = (scrollType: DialogProps['scroll']) => () => {
setOpen(true);
setScroll(scrollType);
};
const handleClose = () => {
setOpen(false);
};
const descriptionElementRef = React.useRef<HTMLElement>(null);
React.useEffect(() => {
if (open) {
const { current: descriptionElement } = descriptionElementRef;
if (descriptionElement !== null) {
descriptionElement.focus();
}
}
}, [open]);
return (
<React.Fragment>
<Button onClick={handleClickOpen('paper')}>scroll=paper</Button>
<Button onClick={handleClickOpen('body')}>scroll=body</Button>
<Dialog
open={open}
onClose={handleClose}
scroll={scroll}
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
>
<DialogTitle id="scroll-dialog-title">Subscribe</DialogTitle>
<DialogContent dividers={scroll === 'paper'}>
<DialogContentText
id="scroll-dialog-description"
ref={descriptionElementRef}
tabIndex={-1}
>
{[...new Array(50)]
.map(
() => `Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et.`,
)
.join('\n')}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose}>Subscribe</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
성능 (Performance)
Modal performance 섹션을 따르세요.
제한 사항 (Limitations)
Modal limitations 섹션을 따르세요.
보조 프로젝트 (Supplementary projects)
더 고급 사용 사례를 위해 다음을 활용할 수 있어요:
material-ui-confirm
material-ui-confirm 패키지는 보일러플레이트 코드 없이 사용자 동작을 확인하는 다이얼로그를 제공해요.
접근성 (Accessibility)
Modal accessibility 섹션을 따르세요.
Dialog API
Demos (데모)
이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:
Import (임포트)
import Dialog from '@mui/material/Dialog';
// or
import { Dialog } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| open | bool |
- | Yes | |
| aria-describedby | string |
- | No | |
| aria-labelledby | string |
- | No | |
| aria-modal | 'false' | 'true' | bool |
true |
No | |
| children | node |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| fullScreen | bool |
false |
No | |
| fullWidth | bool |
false |
No | |
| maxWidth | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false | string |
'sm' |
No | |
| onClose | function(event: object, reason: string) => void |
- | No | |
| PaperComponent | elementType |
Paper |
No | |
| role | 'alertdialog' | 'dialog' |
'dialog' |
No | |
| scroll | 'body' | 'paper' |
'paper' |
No | |
| slotProps | { backdrop?: func | object, container?: func | object, paper?: func | object, root?: func | object, transition?: func | object } |
{} |
No | |
| slots | { backdrop?: elementType, container?: elementType, paper?: elementType, root?: elementType, transition?: elementType } |
{} |
No | |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
| transitionDuration | number | { appear?: number, enter?: number, exit?: number } |
`{ | ||
| enter: theme.transitions.duration.enteringScreen, | ||||
| exit: theme.transitions.duration.leavingScreen, | ||||
| }` | No |
Note: The
refis forwarded to the root element (HTMLDivElement).
Any other props supplied will be provided to the root element (Modal).
Inheritance (상속)
위에 명시적으로 문서화되지는 않았지만, Modal 컴포넌트의 props도 Dialog에서 사용할 수 있어요.
Theme default props (테마 기본 props)
MuiDialog를 사용해서 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
Slots
| Name | Default | Class | Description |
|---|---|---|---|
| transition | Collapse |
- | The component that renders the transition. |
| Follow this guide to learn more about the requirements for this component. | |||
| paper | Paper |
.MuiDialog-paper |
The component that renders the paper. |
| container | undefined |
.MuiDialog-container |
The component that renders the container. |
| backdrop | undefined |
.MuiDialog-backdrop |
The component that renders the backdrop. |
| root | undefined |
.MuiDialog-root |
The component that renders the root. |
CSS
Rule name (규칙 이름)
| Global class | Rule name | Description |
|---|---|---|
| - | paperFullScreen | Styles applied to the Paper component if fullScreen={true}. |
| - | paperFullWidth | Styles applied to the Paper component if fullWidth={true}. |
| - | paperWidthFalse | Styles applied to the Paper component if maxWidth=false. |
| - | paperWidthLg | Styles applied to the Paper component if maxWidth="lg". |
| - | paperWidthMd | Styles applied to the Paper component if maxWidth="md". |
| - | paperWidthSm | Styles applied to the Paper component if maxWidth="sm". |
| - | paperWidthXl | Styles applied to the Paper component if maxWidth="xl". |
| - | paperWidthXs | Styles applied to the Paper component if maxWidth="xs". |
| - | scrollBody | Styles applied to the container element if scroll="body". |
| - | scrollPaper | Styles applied to the container element if scroll="paper". |
Source code (소스 코드)
이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트의 구현을 살펴보는 것이 좋아요.
DialogActions API
Demos (데모)
이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:
Import (임포트)
import DialogActions from '@mui/material/DialogActions';
// or
import { DialogActions } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| children | node |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| disableSpacing | bool |
false |
No | |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
Note: The
refis forwarded to the root element (HTMLDivElement).
Any other props supplied will be provided to the root element (native element).
Theme default props (테마 기본 props)
MuiDialogActions를 사용해서 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
CSS
Rule name (규칙 이름)
| Global class | Rule name | Description |
|---|---|---|
| - | root | Styles applied to the root element. |
| - | spacing | Styles applied to the root element unless disableSpacing={true}. |
Source code (소스 코드)
이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트의 구현을 살펴보는 것이 좋아요.
DialogContent API
Demos (데모)
이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:
Import (임포트)
import DialogContent from '@mui/material/DialogContent';
// or
import { DialogContent } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| children | node |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| dividers | bool |
false |
No | |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
Note: The
refis forwarded to the root element (HTMLDivElement).
Any other props supplied will be provided to the root element (native element).
Theme default props (테마 기본 props)
MuiDialogContent를 사용해서 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
CSS
Rule name (규칙 이름)
| Global class | Rule name | Description |
|---|---|---|
| - | dividers | Styles applied to the root element if dividers={true}. |
| - | root | Styles applied to the root element. |
Source code (소스 코드)
이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트의 구현을 살펴보는 것이 좋아요.
DialogContentText API
Demos (데모)
이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:
Import (임포트)
import DialogContentText from '@mui/material/DialogContentText';
// or
import { DialogContentText } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| children | node |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
Note: The
refis forwarded to the root element (HTMLParagraphElement).
Any other props supplied will be provided to the root element (Typography).
Inheritance (상속)
위에 명시적으로 문서화되지는 않았지만, Typography 컴포넌트의 props도 DialogContentText에서 사용할 수 있어요.
Theme default props (테마 기본 props)
MuiDialogContentText를 사용해서 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
CSS
Rule name (규칙 이름)
| Global class | Rule name | Description |
|---|---|---|
| - | root | Styles applied to the root element. |
Source code (소스 코드)
이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트의 구현을 살펴보는 것이 좋아요.
DialogTitle API
Demos (데모)
이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:
Import (임포트)
import DialogTitle from '@mui/material/DialogTitle';
// or
import { DialogTitle } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| children | node |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
Note: The
refis forwarded to the root element (HTMLHeadingElement).
Any other props supplied will be provided to the root element (Typography).
Inheritance (상속)
위에 명시적으로 문서화되지는 않았지만, Typography 컴포넌트의 props도 DialogTitle에서 사용할 수 있어요.
Theme default props (테마 기본 props)
MuiDialogTitle를 사용해서 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
CSS
Rule name (규칙 이름)
| Global class | Rule name | Description |
|---|---|---|
| - | root | Styles applied to the root element. |
Source code (소스 코드)
이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트의 구현을 살펴보는 것이 좋아요.
Slide API
Demos (데모)
이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:
Import (임포트)
import Slide from '@mui/material/Slide';
// or
import { Slide } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| children | element |
- | Yes | |
| addEndListener | function(node: HTMLElement, done: Function) => void |
- | No | |
| appear | bool |
true |
No | |
| container | HTML element | func |
- | No | |
| direction | 'down' | 'left' | 'right' | 'up' |
'down' |
No | |
| disablePrefersReducedMotion | bool |
false |
No | |
| easing | { enter?: string, exit?: string } | string |
`{ | ||
| enter: theme.transitions.easing.easeOut, | ||||
| exit: theme.transitions.easing.sharp, | ||||
| }` | No | |||
| in | bool |
- | No | |
| timeout | number | { appear?: number, enter?: number, exit?: number } |
`{ | ||
| enter: theme.transitions.duration.enteringScreen, | ||||
| exit: theme.transitions.duration.leavingScreen, | ||||
| }` | No |
Note: The
refis forwarded to the root element (HTMLDivElement).
Any other props supplied will be provided to the root element (Transition).
Inheritance (상속)
위에 명시적으로 문서화되지는 않았지만, Transition 컴포넌트의 props도 Slide에서 사용할 수 있어요. 일부 컴포넌트 하위 집합은 기본적으로 react-transition-group을 지원해요.
Source code (소스 코드)
이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트의 구현을 살펴보는 것이 좋아요.
더 알아보기 (Learn more)
- Modal — 다이얼로그가 기반으로 하는 모달
- Transitions — 전환 컴포넌트