Popup
사용자의 즉각적인 주의와 응답이 필요한 상황에서 사용하는 모달 요소입니다. 사용자의 현재 작업을 중단 시키고 팝업 내용에 집중하도록 하며, 필수적인 결정이나 확인이 필요한 경우에 사용합니다.
Popup overlay를 표시합니다.
breakpoint에 따라 variant를 override 하여 Bottom sheet 와 쉽게 통합할 수 있습니다.
import {
Button,
Modal,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalDescription,
ModalHeading,
ModalNavigation,
ModalSummary,
ModalTrigger,
} from '@montage-ui/core';
const Demo = () => {
return (
<Modal>
<ModalTrigger>
<Button>Open</Button>
</ModalTrigger>
<ModalContainer variant="popup">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
)
}
export default Demo;Popup 은 여러 컴포넌트를 조합해서 사용합니다.
ModalTrigger 는 선택적으로 사용할 수 있습니다.
기본 구성은 아래와 같습니다.
3가지 사이즈를 사용할 수 있습니다.
- medium (default)
- large
- xlarge
import {
FlexBox,
Button,
Modal,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalDescription,
ModalHeading,
ModalNavigation,
ModalSummary,
ModalTrigger,
} from '@montage-ui/core';
const Demo = () => {
return (
<FlexBox gap="12px" flexWrap="wrap">
<Modal>
<ModalTrigger>
<Button>Medium</Button>
</ModalTrigger>
<ModalContainer variant="popup" size="medium">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
<Modal>
<ModalTrigger>
<Button>Large</Button>
</ModalTrigger>
<ModalContainer variant="popup" size="large">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
<Modal>
<ModalTrigger>
<Button>Xlarge</Button>
</ModalTrigger>
<ModalContainer variant="popup" size="xlarge">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
</FlexBox>
)
}
export default Demo;resize 옵션을 사용하면 Popup이 차지하는 높이를 조정할 수 있습니다.
- hug (default)
- fixed
import {
FlexBox,
Button,
Modal,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalDescription,
ModalHeading,
ModalNavigation,
ModalSummary,
ModalTrigger,
} from '@montage-ui/core';
const Demo = () => {
return (
<FlexBox gap="12px" flexWrap="wrap">
<Modal>
<ModalTrigger>
<Button>Hug</Button>
</ModalTrigger>
<ModalContainer variant="popup" resize="hug">
<ModalNavigation>
Hug
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
<Modal>
<ModalTrigger>
<Button>Fixed</Button>
</ModalTrigger>
<ModalContainer variant="popup" resize="fixed">
<ModalNavigation>
Fixed
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
</FlexBox>
)
}
export default Demo;3가지 variant의 Navigation을 사용할 수 있습니다.
- emphasized (default)
- floating
- search
또한 ModalNavigation 의 leadingContent, trailingContent로 콘텐츠를 추가할 수 있으며 ModalNavigationButton 으로 사용합니다.
import {
FlexBox,
Box,
Button,
Modal,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalNavigation,
ModalNavigationButton,
ModalTrigger,
SearchField,
SegmentedControl,
SegmentedControlItem,
Typography,
} from '@montage-ui/core';
import { useState } from 'react';
const Control = ({ label, value, onValueChange, options }) => (
<FlexBox flexDirection="column" gap="8px" sx={{ width: '100%', maxWidth: 400 }}>
<Typography variant="label1" weight="medium" color="semantic.foreground.neutral.secondary">
{label}
</Typography>
<SegmentedControl size="small" value={value} onValueChange={onValueChange}>
{options.map((option) => (
<SegmentedControlItem key={option} value={option}>
{option}
</SegmentedControlItem>
))}
</SegmentedControl>
</FlexBox>
);
const Demo = () => {
const [navigationVariant, setNavigationVariant] = useState('emphasized');
const [buttonVariant, setButtonVariant] = useState('icon-button');
const [leading, setLeading] = useState('on');
const [trailing, setTrailing] = useState('on');
const [background, setBackground] = useState('off');
// background only applies to icon buttons in a floating navigation
const supportsBackground = navigationVariant === 'floating' && buttonVariant === 'icon-button';
const iconButtonBackground = supportsBackground ? background === 'on' : undefined;
const leadingContent = leading === 'on' ? (
<ModalNavigationButton variant={buttonVariant === 'icon-button' ? 'back-button' : buttonVariant} background={iconButtonBackground}>
{buttonVariant === 'text-button' ? '뒤로' : undefined}
</ModalNavigationButton>
) : null;
const trailingContent = trailing === 'on' ? (
<ModalNavigationButton variant={buttonVariant === 'icon-button' ? 'close-button' : buttonVariant} background={iconButtonBackground}>
{buttonVariant === 'text-button' ? '닫기' : undefined}
</ModalNavigationButton>
) : null;
const getNavigationContent = () => {
switch (navigationVariant) {
case 'search':
return <SearchField placeholder="Enter search keyword." />;
case 'floating':
return null;
case 'emphasized':
return 'Emphasized';
}
};
return (
<FlexBox flexDirection="column" alignItems="center" gap="24px" sx={{ width: '100%' }}>
<Control
label="variant"
value={navigationVariant}
onValueChange={setNavigationVariant}
options={['emphasized', 'floating', 'search']}
/>
<Control
label="button variant"
value={buttonVariant}
onValueChange={setButtonVariant}
options={['icon-button', 'text-button']}
/>
<Control label="leadingContent" value={leading} onValueChange={setLeading} options={['on', 'off']} />
<Control label="trailingContent" value={trailing} onValueChange={setTrailing} options={['on', 'off']} />
{supportsBackground && (
<Control
label="button background"
value={background}
onValueChange={setBackground}
options={['on', 'off']}
/>
)}
<Modal>
<ModalTrigger>
<Button>Open</Button>
</ModalTrigger>
<ModalContainer variant="popup">
<ModalNavigation
variant={navigationVariant}
leadingContent={leadingContent}
trailingContent={trailingContent}
>
{getNavigationContent()}
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<Box
sx={theme => ({
width: '100%',
height: '800px',
background: theme.semantic.surface.accent.violetOpaque,
opacity: 0.08
})}
/>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
</FlexBox>
)
}
export default Demo;닫기 버튼을 없애거나 좌측에 배치할 수도 있습니다.
import {
Button,
Modal,
ModalTrigger,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalDescription,
ModalHeading,
ModalNavigation,
ModalNavigationButton,
ModalSummary,
} from '@montage-ui/core';
const Demo = () => {
return (
<Modal>
<ModalTrigger>
<Button>Open</Button>
</ModalTrigger>
<ModalContainer variant="popup">
<ModalNavigation trailingContent={null} leadingContent={<ModalNavigationButton variant="close-button" />}>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
)
}
export default Demo;내부 콘텐츠는 ModalContent 로 감싸서 사용하는 것을 권장하며, verticalPadding, horizontalPadding, gap 옵션을 사용하여 내부 여백을 조정할 수 있습니다.
verticalPadding
- none (default)
- top-only
- bottom-only
- both
horizontalPadding
- none
- both (default)
import {
FlexBox,
Button,
Modal,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalDescription,
ModalHeading,
ModalNavigation,
ModalSummary,
ModalTrigger,
Typography,
SegmentedControl,
SegmentedControlItem,
addOpacity,
} from '@montage-ui/core';
import { useState } from 'react';
const Control = ({ label, value, onValueChange, options }) => (
<FlexBox flexDirection="column" gap="8px" sx={{ width: '100%', maxWidth: 400 }}>
<Typography variant="label1" weight="medium" color="semantic.foreground.neutral.secondary">
{label}
</Typography>
<SegmentedControl size="small" value={value} onValueChange={onValueChange}>
{options.map((option) => (
<SegmentedControlItem key={option} value={option}>
{option}
</SegmentedControlItem>
))}
</SegmentedControl>
</FlexBox>
);
const Demo = () => {
const [verticalPadding, setVerticalPadding] = useState('none');
const [horizontalPadding, setHorizontalPadding] = useState('both');
return (
<FlexBox gap="12px" flexWrap="wrap">
<Modal>
<ModalTrigger>
<Button>Medium</Button>
</ModalTrigger>
<ModalContainer variant="popup" size="medium">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent verticalPadding={verticalPadding} horizontalPadding={horizontalPadding}>
<ModalContentItem>
<FlexBox
flexDirection="column"
gap="12px"
alignItems="center"
justifyContent="center"
sx={theme => ({
width: '100%',
height: '400px',
background: addOpacity(theme.semantic.surface.accent.violetOpaque, 0.08),
})}
>
<Control
label="verticalPadding"
value={verticalPadding}
onValueChange={setVerticalPadding}
options={['none', 'top-only', 'bottom-only', 'both']}
/>
<Control
label="horizontalPadding"
value={horizontalPadding}
onValueChange={setHorizontalPadding}
options={['none', 'both']}
/>
</FlexBox>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
<Modal>
<ModalTrigger>
<Button>Large</Button>
</ModalTrigger>
<ModalContainer variant="popup" size="large">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent verticalPadding={verticalPadding} horizontalPadding={horizontalPadding}>
<ModalContentItem>
<FlexBox
flexDirection="column"
gap="12px"
alignItems="center"
justifyContent="center"
sx={theme => ({
width: '100%',
height: '400px',
background: addOpacity(theme.semantic.surface.accent.violetOpaque, 0.08),
})}
>
<Control
label="verticalPadding"
value={verticalPadding}
onValueChange={setVerticalPadding}
options={['none', 'top-only', 'bottom-only', 'both']}
/>
<Control
label="horizontalPadding"
value={horizontalPadding}
onValueChange={setHorizontalPadding}
options={['none', 'both']}
/>
</FlexBox>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
<Modal>
<ModalTrigger>
<Button>Xlarge</Button>
</ModalTrigger>
<ModalContainer variant="popup" size="xlarge">
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent verticalPadding={verticalPadding} horizontalPadding={horizontalPadding}>
<ModalContentItem>
<FlexBox
flexDirection="column"
gap="12px"
alignItems="center"
justifyContent="center"
sx={theme => ({
width: '100%',
height: '400px',
background: addOpacity(theme.semantic.surface.accent.violetOpaque, 0.08),
})}
>
<Control
label="verticalPadding"
value={verticalPadding}
onValueChange={setVerticalPadding}
options={['none', 'top-only', 'bottom-only', 'both']}
/>
<Control
label="horizontalPadding"
value={horizontalPadding}
onValueChange={setHorizontalPadding}
options={['none', 'both']}
/>
</FlexBox>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
</FlexBox>
)
}
export default Demo;ModalContainer의 wrapperProps 를 사용하면 모달이 나타나는 위치를 조정할 수 있습니다.
import {
Button,
Modal,
ModalTrigger,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalDescription,
ModalHeading,
ModalNavigation,
ModalSummary,
} from '@montage-ui/core';
const Demo = () => {
return (
<Modal>
<ModalTrigger>
<Button>Open</Button>
</ModalTrigger>
<ModalContainer
variant="popup"
wrapperProps={{
sx: {
justifyContent: 'flex-end',
alignItems: 'flex-end'
}
}}>
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<ModalHeading>Heading</ModalHeading>
<ModalSummary>Summary</ModalSummary>
<ModalDescription>Description</ModalDescription>
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton>
Action
</ActionAreaButton>
</ActionArea>
</ModalContainer>
</Modal>
)
}
export default Demo;Form 구조로 사용하려면 ModalContainer 하위에 <FlexBox as="form" flex="1" flexDirection="column"> 구조를 사용합니다.
import {
Button,
Modal,
ActionArea,
ActionAreaButton,
ModalContainer,
ModalContent,
ModalContentItem,
ModalNavigation,
ModalTrigger,
TextField,
FlexBox,
FormControl,
FormControlField,
FormControlLabel,
FormControlNegativeMessage,
FormControlMessage,
} from '@montage-ui/core';
import { useForm, Controller } from 'react-hook-form';
const Demo = () => {
const form = useForm({ defaultValues: { value: '' } });
return (
<Modal>
<ModalTrigger>
<Button>Open</Button>
</ModalTrigger>
<ModalContainer variant="popup">
<FlexBox
as="form"
flex="1"
flexDirection="column"
onSubmit={form.handleSubmit((v) => alert(JSON.stringify(v)))}
>
<ModalNavigation>
Title
</ModalNavigation>
<ModalContent>
<ModalContentItem>
<Controller
control={form.control}
name="value"
rules={{
required: {
value: true,
message: '필수 값입니다.'
}
}}
render={({ field, formState }) => (
<FormControl>
<FormControlLabel>라벨</FormControlLabel>
<FormControlField>
<TextField {...field} width="100%" placeholder="값을 입력하세요." status={Boolean(formState.errors.value) ? 'negative' : 'normal'} />
</FormControlField>
{Boolean(formState.errors.value) ? (
<FormControlNegativeMessage>{formState.errors.value?.message}</FormControlNegativeMessage>
): (
<FormControlMessage>Description</FormControlMessage>
)}
</FormControl>
)} />
</ModalContentItem>
</ModalContent>
<ActionArea>
<ActionAreaButton type="submit">
Submit
</ActionAreaButton>
</ActionArea>
</FlexBox>
</ModalContainer>
</Modal>
)
}
export default Demo;FlexBox 와 동일한 Props를 사용합니다.
Typography 와 동일한 Props를 사용합니다.
Typography 와 동일한 Props를 사용합니다.
Typography 와 동일한 Props를 사용합니다.
