Skip to content

Commit 9d13c9f

Browse files
committed
add modal footer component
1 parent 14d4df8 commit 9d13c9f

7 files changed

Lines changed: 114 additions & 1 deletion

File tree

packages/@mantine/core/src/components/Modal/Modal.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
border-start-end-radius: var(--modal-radius, var(--mantine-radius-default));
3737
}
3838

39+
.footer {
40+
border-end-start-radius: var(--modal-radius, var(--mantine-radius-default));
41+
border-end-end-radius: var(--modal-radius, var(--mantine-radius-default));
42+
}
43+
3944
.content {
4045
flex: var(--modal-content-flex, 0 0 var(--modal-size));
4146
max-width: 100%;

packages/@mantine/core/src/components/Modal/Modal.story.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useDisclosure } from '@mantine/hooks';
22
import { Button } from '../Button';
33
import { Card } from '../Card';
4+
import { Group } from '../Group';
45
import { Menu } from '../Menu';
56
import { ScrollArea } from '../ScrollArea';
67
import { Select } from '../Select';
@@ -323,3 +324,32 @@ export function WithScrollArea() {
323324
</>
324325
);
325326
}
327+
328+
export const Compound = () => {
329+
const [opened, { open, close }] = useDisclosure(false);
330+
331+
return (
332+
<>
333+
<Button onClick={open}>Open modal</Button>
334+
335+
<Modal.Root opened={opened} onClose={close}>
336+
<Modal.Overlay />
337+
<Modal.Content>
338+
<Modal.Header>
339+
<Modal.Title>Modal title</Modal.Title>
340+
<Modal.CloseButton />
341+
</Modal.Header>
342+
<Modal.Body>{content}</Modal.Body>
343+
<Modal.Footer>
344+
<Group>
345+
<Button variant="transparent" onClick={close}>
346+
Close
347+
</Button>
348+
<Button>Button</Button>
349+
</Group>
350+
</Modal.Footer>
351+
</Modal.Content>
352+
</Modal.Root>
353+
</>
354+
);
355+
};

packages/@mantine/core/src/components/Modal/Modal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ModalBaseCloseButtonProps, ModalBaseOverlayProps } from '../ModalBase';
44
import { ModalBody } from './ModalBody';
55
import { ModalCloseButton } from './ModalCloseButton';
66
import { ModalContent } from './ModalContent';
7+
import { ModalFooter } from './ModalFooter';
78
import { ModalHeader } from './ModalHeader';
89
import { ModalOverlay } from './ModalOverlay';
910
import {
@@ -55,6 +56,7 @@ export type ModalFactory = Factory<{
5556
Content: typeof ModalContent;
5657
Body: typeof ModalBody;
5758
Header: typeof ModalHeader;
59+
Footer: typeof ModalFooter;
5860
Title: typeof ModalTitle;
5961
CloseButton: typeof ModalCloseButton;
6062
Stack: typeof ModalStack;
@@ -151,6 +153,7 @@ Modal.Overlay = ModalOverlay;
151153
Modal.Content = ModalContent;
152154
Modal.Body = ModalBody;
153155
Modal.Header = ModalHeader;
156+
Modal.Footer = ModalFooter;
154157
Modal.Title = ModalTitle;
155158
Modal.CloseButton = ModalCloseButton;
156159
Modal.Stack = ModalStack;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { CompoundStylesApiProps, factory, Factory, useProps } from '../../core';
2+
import { ModalBaseFooter, ModalBaseFooterProps } from '../ModalBase';
3+
import { useModalContext } from './Modal.context';
4+
import classes from './Modal.module.css';
5+
6+
export type ModalFooterStylesNames = 'footer';
7+
8+
export interface ModalFooterProps
9+
extends ModalBaseFooterProps,
10+
CompoundStylesApiProps<ModalFooterFactory> {}
11+
12+
export type ModalFooterFactory = Factory<{
13+
props: ModalFooterProps;
14+
ref: HTMLElement;
15+
stylesNames: ModalFooterStylesNames;
16+
compound: true;
17+
}>;
18+
19+
export const ModalFooter = factory<ModalFooterFactory>((_props, ref) => {
20+
const props = useProps('ModalFooter', null, _props);
21+
const { classNames, className, style, styles, vars, ...others } = props;
22+
23+
const ctx = useModalContext();
24+
25+
return (
26+
<ModalBaseFooter
27+
ref={ref}
28+
{...ctx.getStyles('footer', { classNames, style, styles, className })}
29+
{...others}
30+
/>
31+
);
32+
});
33+
34+
ModalFooter.classes = classes;
35+
ModalFooter.displayName = '@mantine/core/ModalFooter';

packages/@mantine/core/src/components/ModalBase/ModalBase.module.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@
2020
transition: padding-inline-end 100ms;
2121
}
2222

23+
.footer {
24+
display: flex;
25+
justify-content: space-between;
26+
align-items: center;
27+
padding: var(--mb-padding, var(--mantine-spacing-md));
28+
padding-inline-end: calc(var(--mb-padding, var(--mantine-spacing-md)) - rem(5px));
29+
position: sticky;
30+
bottom: 0;
31+
background-color: var(--mantine-color-body);
32+
z-index: 1000;
33+
min-height: 60px;
34+
transition: padding-inline-end 100ms;
35+
}
36+
2337
.inner {
2438
position: fixed;
2539
width: 100%;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { forwardRef } from 'react';
2+
import cx from 'clsx';
3+
import { Box, BoxProps, ElementProps } from '../../core';
4+
import { useModalBaseContext } from './ModalBase.context';
5+
import classes from './ModalBase.module.css';
6+
7+
export interface ModalBaseFooterProps extends BoxProps, ElementProps<'footer'> {}
8+
9+
export const ModalBaseFooter = forwardRef<HTMLElement, ModalBaseFooterProps>(
10+
({ className, ...others }, ref) => {
11+
const ctx = useModalBaseContext();
12+
return (
13+
<Box
14+
component="footer"
15+
ref={ref}
16+
className={cx({ [classes.footer]: !ctx.unstyled }, className)}
17+
{...others}
18+
/>
19+
);
20+
}
21+
);
22+
23+
ModalBaseFooter.displayName = '@mantine/core/ModalBaseFooter';

packages/@mantine/core/src/components/ModalBase/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export { ModalBaseContent } from './ModalBaseContent';
55
export { ModalBaseHeader } from './ModalBaseHeader';
66
export { ModalBaseOverlay } from './ModalBaseOverlay';
77
export { ModalBaseTitle } from './ModalBaseTitle';
8+
export { ModalBaseFooter } from './ModalBaseFooter';
89
export { NativeScrollArea } from './NativeScrollArea';
910

1011
export type { ModalBaseProps } from './ModalBase';
@@ -14,6 +15,7 @@ export type { ModalBaseContentProps } from './ModalBaseContent';
1415
export type { ModalBaseHeaderProps } from './ModalBaseHeader';
1516
export type { ModalBaseOverlayProps } from './ModalBaseOverlay';
1617
export type { ModalBaseTitleProps } from './ModalBaseTitle';
18+
export type { ModalBaseFooterProps } from './ModalBaseFooter.tsx';
1719

1820
export type ModalBaseStylesNames =
1921
| 'body'
@@ -23,4 +25,5 @@ export type ModalBaseStylesNames =
2325
| 'root'
2426
| 'content'
2527
| 'close'
26-
| 'inner';
28+
| 'inner'
29+
| 'footer';

0 commit comments

Comments
 (0)