# Component/Modal/BasicModal > Props: component-modal-basicmodal.props.txt ## Examples ### Auto Focus Button 푸터 영역의 autoFocusButton 적용 기본 버튼을 변경할 수 있습니다. ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { title: 'Title', confirmText: 'Label', cancelText: 'Label', autoFocusButton: 'confirm' }, parameters: { docs: { description: { story: '푸터 영역의 autoFocusButton 적용 기본 버튼을 변경할 수 있습니다.' } } } } ``` ### Auto Max Height 콘텐츠 영역이 길어질 경우 상하좌우 안전여백내에서 maxHeight값이 자동 적용됩니다. ```tsx { parameters: { docs: { description: { story: '콘텐츠 영역이 길어질 경우 상하좌우 안전여백내에서 maxHeight값이 자동 적용됩니다.' } } }, render: () => { const [show, setShow] = useState(false); const handleOpen = () => setShow(true); const handleClose = () => setShow(false); return <> Heading Auto Max Height Long Content... ; } } ``` ### Base ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; } } ``` ### Controlled Cancel Event 취소사유(cancelClick | escapeKeyPress | overlayClick) 별로 특정 로직을 넣을 수 있습니다. ```tsx { render: () => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); return <> { console.log('reason is', reason); switch (reason) { case 'cancelClick': handleClose(); break; case 'escapeKeyPress': // custom logic... handleClose(); break; case 'overlayClick': // custom logic... handleClose(); } }} onConfirm={handleClose}> Heading Body ; }, parameters: { docs: { description: { story: '취소사유(cancelClick | escapeKeyPress | overlayClick) 별로 특정 로직을 넣을 수 있습니다.' } } } } ``` ### Custom Content 콘텐츠 영역을 커스텀할 수 있습니다. 기본 콘텐츠 영역은 간격을 가지고 있기 때문에 레이아웃 스타일을 커스텀할 경우 spaceProps를 이용, contentProps로 전달시 해당 스타일을 우선순위로 가집니다. ```tsx { render: () => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); return <> Custom Modal Content - SpaceProps ; }, parameters: { docs: { description: { story: '콘텐츠 영역을 커스텀할 수 있습니다. 기본 콘텐츠 영역은 간격을 가지고 있기 때문에 레이아웃 스타일을 커스텀할 경우 spaceProps를 이용, contentProps로 전달시 해당 스타일을 우선순위로 가집니다.' } } } } ``` ### Custom Footer 푸터 영역을 커스텀할 수 있습니다. 커스텀 사용시 dense 속성은 적용되지 않습니다. ```tsx { render: () => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); return <> Footer Addon 1 }> Heading Body ; }, parameters: { docs: { description: { story: '푸터 영역을 커스텀할 수 있습니다. 커스텀 사용시 dense 속성은 적용되지 않습니다.' } } } } ``` ### Custom Footer Full Screen ```tsx { render: () => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); return <> Footer Addon 1 }> Heading Custom Footer + FullScreen
Long Long Content
; } } ``` ### Custom Header 헤더 영역을 커스텀할 수 있습니다. 커스텀 사용시 기본 Header(title, backButton, closable)는 미노출됩니다. ```tsx { render: () => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); return <> Custom Header Title Status }> Heading Body ; }, parameters: { docs: { description: { story: '헤더 영역을 커스텀할 수 있습니다. 커스텀 사용시 기본 Header(title, backButton, closable)는 미노출됩니다.' } } } } ``` ### Custom Max Height 콘텐츠 영역 maxHeight을 커스텀할 수 있습니다. 지정하지 않을 경우 안전여백내에서 계산된 maxHeight값이 적용됩니다. ```tsx { parameters: { docs: { description: { story: '콘텐츠 영역 maxHeight을 커스텀할 수 있습니다. 지정하지 않을 경우 안전여백내에서 계산된 maxHeight값이 적용됩니다.' } } }, render: () => { const [show, setShow] = useState(false); const handleOpen = () => setShow(true); const handleClose = () => setShow(false); return <> Heading Custom Max Height - 500
Long Content...
; } } ``` ### Dense 여백이 `semantic_spacing.modal` 토큰으로 통일되어 현재는 여백에 영향을 주지 않습니다. ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { title: 'Title', confirmText: 'Label', cancelText: 'Label', dense: true, width: 340 }, parameters: { docs: { description: { story: '여백이 `semantic_spacing.modal` 토큰으로 통일되어 현재는 여백에 영향을 주지 않습니다.' } } } } ``` ### Disabled Overlay Escape 오버레이 클릭, esc 키이벤트로 인한 cancel 처리를 중지할 수 있습니다. ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { title: 'Title', confirmText: 'Label', cancelText: 'Label', shouldCloseOnOutsideClick: false, shouldCloseOnEscapeKey: false }, parameters: { docs: { description: { story: '오버레이 클릭, esc 키이벤트로 인한 cancel 처리를 중지할 수 있습니다.' } } } } ``` ### Fill 안전여백내에서 너비 100% 적용됩니다. ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { title: 'Title', confirmText: 'Label', cancelText: 'Label', fill: true }, parameters: { docs: { description: { story: '안전여백내에서 너비 100% 적용됩니다.' } } } } ``` ### Fill Button ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { confirmButtonProps: { fill: true }, confirmText: 'Label', cancelText: 'Label', cancelButtonProps: { fill: true } } } ``` ### Fit Content contentProps를 display: flex로 지정하면 하위 내용 컴포넌트를 모달 높이에 맞출 수 있습니다. ```tsx { parameters: { docs: { description: { story: 'contentProps를 display: flex로 지정하면 하위 내용 컴포넌트를 모달 높이에 맞출 수 있습니다.' } } }, render: () => { const [show, setShow] = useState(false); const handleOpen = () => setShow(true); const handleClose = () => setShow(false); return <> Heading
Content
; } } ``` ### Footer Extra ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { footerExtra: 조회 결과 내역이 모두 제외 처리됩니다. 제외하지 않을 대상은 ‘목록에서 빼기’를 이용해주세요. , width: 900, title: 'Title', confirmText: 'Label', cancelText: 'Label' } } ``` ### Footer Extra Full Screen 전체화면을 사용할 수 있습니다. 헤더/푸터 영역이 자동 고정됩니다. ```tsx { render: args => { const [show, setShow] = useState(false); const [selectValue, setSelectValue] = useState(); const options = [{ label: 'option 1', value: 'value-1' }, { label: 'option 2', value: 'value-2' }, { label: 'option 3', value: 'value-3' }, { label: 'option 4', value: 'value-4' }, { label: 'option 5', value: 'value-5' }]; return <> setShow(false)} onCancel={() => setShow(false)} autoFocusButton='confirm' {...args}> <> Text Example1 Description Text Example1 Text Example2 Description Text Example2
Long Long Content
; }, parameters: { docs: { description: { story: '전체화면을 사용할 수 있습니다. 헤더/푸터 영역이 자동 고정됩니다.' } } }, args: { footerExtra: 조회 결과 내역이 모두 제외 처리됩니다. 제외하지 않을 대상은 ‘목록에서 빼기’를 이용해주세요. } } ``` ### Full Screen 전체화면을 사용할 수 있습니다. 헤더/푸터 영역이 자동 고정됩니다. ```tsx { render: args => { const [show, setShow] = useState(false); const [selectValue, setSelectValue] = useState(); const options = [{ label: 'option 1', value: 'value-1' }, { label: 'option 2', value: 'value-2' }, { label: 'option 3', value: 'value-3' }, { label: 'option 4', value: 'value-4' }, { label: 'option 5', value: 'value-5' }]; return <> setShow(false)} onCancel={() => setShow(false)} autoFocusButton='confirm' {...args}> <> Text Example1 Description Text Example1 Text Example2 Description Text Example2
Long Long Content
; }, parameters: { docs: { description: { story: '전체화면을 사용할 수 있습니다. 헤더/푸터 영역이 자동 고정됩니다.' } } } } ``` ### Nesting Child Modal 자식 요소로써 중첩 모달을 호출 할 수 있습니다. ```tsx { render: () => { const [show, setShow] = useState(false); const [showSecond, setShowSecond] = useState(false); const handleOpenFirstModal = () => setShow(true); const handleCloseFirsModal = () => setShow(false); const handleOpenSecondModal = () => setShowSecond(true); const handleCloseSecondModal = () => setShowSecond(false); return <> Heading Body Second Modal Heading Second Modal Body ; }, parameters: { docs: { description: { story: '자식 요소로써 중첩 모달을 호출 할 수 있습니다.' } } } } ``` ### Nesting Confirm 다른 타입의 모달을 중첩으로 호출할 수 있습니다. ```tsx { render: () => { const [show, setShow] = useState(false); const handleOpen = () => setShow(true); const handleClose = () => setShow(false); const openConfirm = () => { return Confirm({ title: 'Confirm Title', confirmText: 'Confirm', cancelText: 'Cancel', onConfirm: () => { console.log('onConfirm'); handleClose(); }, onCancel: () => { console.log('onCancel'); }, onExited: () => { console.log('onExited'); } }); }; return <> Heading Body ; }, parameters: { docs: { description: { story: '다른 타입의 모달을 중첩으로 호출할 수 있습니다.' } } } } ``` ### Nesting Floating Elements 중첩된 플로팅 요소 테스트입니다. ```tsx { render: () => { const [modalShow, setModalShow] = useState(false); const [bottomSheetShow, setBottomSheetShow] = useState(false); const [nestedModalShow, setNestedModalShow] = useState(false); const [nestedChildModalShow, setNestedChildModalShow] = useState(false); const [selectValue, setSelectValue] = useState(); const [dates, setDates] = useState(); const options = [{ label: 'option 1', value: 'value-1' }, { label: 'option 2', value: 'value-2' }, { label: 'option 3', value: 'value-3' }, { label: 'option 4', value: 'value-4' }, { label: 'option 5', value: 'value-5' }]; return <> setModalShow(false)} onCancel={() => setModalShow(false)} autoFocusButton='confirm'> <> Text Example1 Description Text Example1 Text Example2 Description Text Example2 setNestedChildModalShow(false)} onCancel={() => setNestedChildModalShow(false)} autoFocusButton='confirm'> Text Example1 Description Text Example1 setDates({ from, to: end })} showTimePicker showRemoveButton />
Long Long Content
setBottomSheetShow(false)}> <> Body Content long content... { const [show, setShow] = useState(false); const order = useRef(0); const handleOpen = () => setShow(true); const handleClose = () => setShow(false); const openConfirm = (times: number = 1) => { const id = order.current++; return Confirm({ title: `Confirm Title (ID: ${id})`, content: new Array(times).fill(0).map((_, index) => index).join('\n'), confirmText: 'Confirm', cancelText: 'Cancel', onConfirm: () => { console.log('Confirm onConfirm', id); }, onCancel: () => { console.log('Confirm onCancel', id); }, onExited: () => { console.log('Confirm onExited', id); } }); }; const openAlert = (times: number = 1) => { const id = order.current++; return Alert({ title: `Alert Title (ID: ${id})`, content: new Array(times).fill(0).map((_, index) => index).join('\n'), confirmText: 'Close', onConfirm: () => { console.log('Alert onConfirm', id); }, onExited: () => { console.log('Alert onExited', id); } }); }; return <> { console.log('Modal onConfirm'); handleClose(); }} onCancel={() => { console.log('Modal onCancel'); handleClose(); }} onExited={() => { console.log('Modal onExited'); }} title='Title' confirmText='Confirm' cancelText='Cancel'> Heading Body ; }, parameters: { docs: { description: { story: '다른 타입의 모달을 중첩으로 호출할 수 있습니다.' } } } } ``` ### Nesting Modal 중첩 모달을 호출 할 수 있습니다. ```tsx { render: () => { const [show, setShow] = useState(false); const [showSecond, setShowSecond] = useState(false); const handleOpenFirstModal = () => setShow(true); const handleCloseFirsModal = () => setShow(false); const handleOpenSecondModal = () => setShowSecond(true); const handleCloseSecondModal = () => setShowSecond(false); return <> Heading Body Second Modal Heading Second Modal Body ; }, parameters: { docs: { description: { story: '중첩 모달을 호출 할 수 있습니다.' } } } } ``` ### Scrollable 콘텐츠 영역은 기본적으로 overflow auto가 적용되어, maxHeight를 지정하지 않거나 비동기적인 가변 높이를 가져도 스크롤됩니다. 모달 경계를 벗어나 그려져야 하는 요소가 있다면 contentProps scrollable을 false로 해제할 수 있으며, 이 경우에도 마운트 시점에 콘텐츠가 이미 넘쳐 있으면 overflow가 자동 적용됩니다. ```tsx { parameters: { docs: { description: { story: '콘텐츠 영역은 기본적으로 overflow auto가 적용되어, maxHeight를 지정하지 않거나 비동기적인 가변 높이를 가져도 스크롤됩니다. 모달 경계를 벗어나 그려져야 하는 요소가 있다면 contentProps scrollable을 false로 해제할 수 있으며, 이 경우에도 마운트 시점에 콘텐츠가 이미 넘쳐 있으면 overflow가 자동 적용됩니다.' } } }, render: () => { const [show, setShow] = useState(false); const handleOpen = () => setShow(true); const handleClose = () => setShow(false); return <> Heading contentProps.scrollable 기본값 true
content 영역 overflow auto가 기본 적용됩니다.
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
Long Content...
; } } ``` ### With Back Button ```tsx { args: { title: 'Title', confirmText: 'Label', cancelText: 'Label' }, render: args => { const [show, setShow] = useState(false); const spacing_value = args.dense ? semantic_spacing.modal.body_gap.mobile : semantic_spacing.modal.body_gap.desktop; const handleClose = () => setShow(false); return <> { console.log('cancel reason is', reason); handleClose(); }} onConfirm={handleClose} onExited={() => console.log('onExited')}> Heading Body ; }, args: { title: 'Title', confirmText: 'Label', cancelText: 'Label', backButton: true, onBack: () => alert('onBack') } } ``` ### Without Footer confirmText, cancelText, footer를 모두 전달하지 않은 경우 콘텐츠 영역 하단 border-radius가 적용되는 케이스입니다. ```tsx { args: { title: 'Title', confirmText: '', cancelText: '', contentProps: { p: 0 } }, render: props => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); return <> Heading Footer가 없는 BasicModal + Long Content 케이스
Long Content
Long Content
Long Content
Long Content
Long Content
Long Content
Long Content
Long Content
Long Content
; }, parameters: { docs: { description: { story: 'confirmText, cancelText, footer를 모두 전달하지 않은 경우 콘텐츠 영역 하단 border-radius가 적용되는 케이스입니다.' } } } } ```