Created
November 13, 2025 20:57
-
-
Save mlaopane/61d805b7dba3be3512800bf6f8d855e0 to your computer and use it in GitHub Desktop.
Mantine Modal with scrollable body
This file contains hidden or 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
| import { Modal, Box, Flex, Text, Paper, ScrollArea } from "@mantine/core"; | |
| import { PropsWithChildren } from "react"; | |
| interface Props { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| children: React.ReactNode; | |
| } | |
| export const CustomModal = ({ isOpen, onClose, children }: Props) => { | |
| return ( | |
| <Modal | |
| opened={isOpen} | |
| onClose={onClose} | |
| withCloseButton | |
| size="xl" | |
| styles={{ | |
| root: { | |
| "--modal-size-xl": "960px", | |
| }, | |
| body: { | |
| padding: 0, // remove default padding so our layout is clean | |
| }, | |
| header: { | |
| height: 12, | |
| position: "absolute", | |
| padding: 0, | |
| right: "1rem", | |
| }, | |
| }} | |
| > | |
| {/* Container that fills the modal */} | |
| <Flex | |
| style={{ | |
| flexDirection: "column", | |
| height: "70vh", | |
| minHeight: "300px", | |
| overflow: "hidden", | |
| }} | |
| > | |
| {children} | |
| </Flex> | |
| </Modal> | |
| ); | |
| }; | |
| const Body = ({ children }: PropsWithChildren) => { | |
| return ( | |
| <Box | |
| style={{ | |
| flex: 1, | |
| overflowY: "auto", | |
| padding: "1rem", | |
| }} | |
| > | |
| <Paper bg="lightgrey" h="100%" mih={0}> | |
| <ScrollArea h="100%" p={16} scrollbarSize={8}> | |
| {children} | |
| </ScrollArea> | |
| </Paper> | |
| </Box> | |
| ); | |
| }; | |
| const Header = ({ children }: PropsWithChildren) => { | |
| return ( | |
| <Flex | |
| style={{ | |
| padding: "1rem", | |
| borderBottom: "1px solid var(--mantine-color-gray-3)", | |
| flexShrink: 0, | |
| }} | |
| > | |
| {children} | |
| </Flex> | |
| ); | |
| }; | |
| const Footer = ({ children }: PropsWithChildren) => { | |
| return ( | |
| <Flex | |
| style={{ | |
| padding: "1rem", | |
| borderTop: "1px solid var(--mantine-color-gray-3)", | |
| flexShrink: 0, | |
| }} | |
| > | |
| {children} | |
| </Flex> | |
| ); | |
| }; | |
| CustomModal.Body = Body; | |
| CustomModal.Header = Header; | |
| CustomModal.Footer = Footer; |
This file contains hidden or 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
| import { MantineProvider, Text } from "@mantine/core"; | |
| import { useMemo, useState } from "react"; | |
| import { CustomModal } from "./CustomModal"; | |
| import "./styles.css"; | |
| import "@mantine/core/styles.css"; | |
| export const Parent = () => { | |
| const modalManager = useModalManager(); | |
| return ( | |
| <MantineProvider> | |
| <div> | |
| <h2>Modal gist</h2> | |
| <div> | |
| <button onClick={modalManager.open}>Open the modal</button> | |
| </div> | |
| <CustomModal isOpen={modalManager.isOpen} onClose={modalManager.close}> | |
| <CustomModal.Header> | |
| <Text fz={18} fw={700} component="span"> | |
| My Modal Header | |
| </Text> | |
| </CustomModal.Header> | |
| <CustomModal.Body> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| Lorem ipsum Lorem ipsum Lorem ipsum <br /> | |
| </CustomModal.Body> | |
| <CustomModal.Footer> | |
| <button onClick={modalManager.close}>Close</button> | |
| </CustomModal.Footer> | |
| </CustomModal> | |
| </div> | |
| </MantineProvider> | |
| ); | |
| } | |
| function useModalManager(isInitiallyOpen = false) { | |
| const [isOpen, setIsOpen] = useState(isInitiallyOpen); | |
| const open = () => { | |
| setIsOpen(true); | |
| }; | |
| const close = () => { | |
| setIsOpen(false); | |
| }; | |
| return useMemo(() => ({ isOpen, open, close }), [isOpen, open, close]); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment