Skip to content

Commit fbf6af2

Browse files
committed
Introduce color property to the Modal component (#468)
`Modal` can now be colored via the `color` prop, using a value from the Feedback Colors collection.
1 parent ff626cc commit fbf6af2

File tree

9 files changed

+177
-3
lines changed

9 files changed

+177
-3
lines changed

src/components/Modal/Modal.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { createPortal } from 'react-dom';
99
import { classNames } from '../../helpers/classNames';
1010
import { transferProps } from '../../helpers/transferProps';
1111
import { withGlobalProps } from '../../providers/globalProps';
12+
import { getRootColorClassName } from '../_helpers/getRootColorClassName';
1213
import { dialogOnCancelHandler } from './_helpers/dialogOnCancelHandler';
1314
import { dialogOnClickHandler } from './_helpers/dialogOnClickHandler';
1415
import { dialogOnCloseHandler } from './_helpers/dialogOnCloseHandler';
@@ -21,6 +22,7 @@ import styles from './Modal.module.scss';
2122

2223
const preRender = (
2324
children,
25+
color,
2426
dialogRef,
2527
position,
2628
size,
@@ -32,6 +34,7 @@ const preRender = (
3234
{...transferProps(events)}
3335
className={classNames(
3436
styles.root,
37+
color && getRootColorClassName(color, styles),
3538
getSizeClassName(size, styles),
3639
getPositionClassName(position, styles),
3740
)}
@@ -48,6 +51,7 @@ export const Modal = ({
4851
autoFocus,
4952
children,
5053
closeButtonRef,
54+
color,
5155
dialogRef,
5256
portalId,
5357
position,
@@ -107,6 +111,7 @@ export const Modal = ({
107111
if (portalId === null) {
108112
return preRender(
109113
children,
114+
color,
110115
internalDialogRef,
111116
position,
112117
size,
@@ -118,6 +123,7 @@ export const Modal = ({
118123
return createPortal(
119124
preRender(
120125
children,
126+
color,
121127
internalDialogRef,
122128
position,
123129
size,
@@ -135,6 +141,7 @@ Modal.defaultProps = {
135141
autoFocus: true,
136142
children: null,
137143
closeButtonRef: null,
144+
color: undefined,
138145
dialogRef: null,
139146
portalId: null,
140147
position: 'center',
@@ -180,6 +187,11 @@ Modal.propTypes = {
180187
// eslint-disable-next-line react/forbid-prop-types
181188
current: PropTypes.any,
182189
}),
190+
/**
191+
* Color to clarify importance and meaning of the modal. Implements
192+
* [Feedback color collection](/docs/foundation/collections#colors).
193+
*/
194+
color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note']),
183195
/**
184196
* Reference to dialog element
185197
*/

src/components/Modal/Modal.module.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@use "../../styles/theme/typography";
88
@use "../../styles/tools/accessibility";
99
@use "../../styles/tools/breakpoint";
10+
@use "../../styles/tools/collections";
1011
@use "../../styles/tools/reset";
1112
@use "../../styles/tools/spacing";
1213
@use "animations";
@@ -82,4 +83,14 @@
8283
top: var(--rui-local-outer-spacing);
8384
bottom: auto;
8485
}
86+
87+
@each $color in settings.$colors {
88+
@include collections.generate-class(
89+
$prefix: "rui-",
90+
$component-name: "Modal",
91+
$variant-name: "color",
92+
$variant-value: $color,
93+
$properties: settings.$themeable-properties,
94+
);
95+
}
8596
}

src/components/Modal/ModalBody.module.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
@use "settings";
2+
13
@layer components.modal {
24
.root {
35
flex: 1 1 auto;
6+
border-inline: settings.$border-width solid var(--rui-local-border-color);
7+
8+
&:first-child {
9+
border-top: settings.$border-width solid var(--rui-local-border-color);
10+
border-top-left-radius: settings.$border-radius;
11+
border-top-right-radius: settings.$border-radius;
12+
}
13+
14+
&:last-child {
15+
border-bottom: settings.$border-width solid var(--rui-local-border-color);
16+
border-bottom-right-radius: settings.$border-radius;
17+
border-bottom-left-radius: settings.$border-radius;
18+
}
419
}
520

621
.isRootScrollingAuto,

src/components/Modal/ModalFooter.module.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
gap: theme.$footer-gap;
1010
align-items: center;
1111
padding: theme.$padding-y theme.$padding-x;
12-
border-top: theme.$separator-width solid theme.$separator-color;
12+
border: settings.$border-width solid var(--rui-local-border-color);
13+
border-top: theme.$separator-width solid var(--rui-local-border-color, #{theme.$separator-color});
1314
border-bottom-right-radius: settings.$border-radius;
1415
border-bottom-left-radius: settings.$border-radius;
15-
background: theme.$footer-background;
16+
background: var(--rui-local-background-color, #{theme.$footer-background});
1617
}
1718

1819
.isRootJustifiedToStart {

src/components/Modal/ModalHeader.module.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@use "settings";
12
@use "theme";
23

34
@layer components.modal {
@@ -7,7 +8,10 @@
78
gap: theme.$header-gap;
89
align-items: baseline;
910
padding: theme.$padding-y theme.$padding-x;
10-
border-bottom: theme.$separator-width solid theme.$separator-color;
11+
border: settings.$border-width solid var(--rui-local-border-color);
12+
border-bottom: theme.$separator-width solid var(--rui-local-border-color, #{theme.$separator-color});
13+
border-top-left-radius: settings.$border-radius;
14+
border-top-right-radius: settings.$border-radius;
1115
}
1216

1317
.isRootJustifiedToStart {

src/components/Modal/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ React.createElement(() => {
313313
<ModalBody>
314314
<ModalContent>
315315
<Radio
316+
id="modal-footer-justify"
316317
label="Footer justification"
317318
onChange={(e) => setModalJustify(e.target.value)}
318319
options={[
@@ -690,6 +691,91 @@ React.createElement(() => {
690691
});
691692
```
692693

694+
## Color Variants
695+
696+
Modal can be colored using the `color` prop. The `color` prop implements the
697+
[Feedback color collection](/docs/foundation/collections#colors)
698+
and is applied to the border of the modal and the modal footer.
699+
700+
```docoff-react-preview
701+
React.createElement(() => {
702+
const [modalOpen, setModalOpen] = React.useState(false);
703+
const [modalColor, setModalColor] = React.useState('success');
704+
const modalCloseButtonRef = React.useRef();
705+
{/*
706+
The `preventScrollUnderneath` feature is necessary for Modals to work in
707+
React UI docs. You may not need it in your application.
708+
*/}
709+
return (
710+
<GlobalPropsProvider globalProps={{
711+
Modal: { preventScrollUnderneath: window.document.documentElement }
712+
}}>
713+
<Button
714+
label="Launch modal with color options"
715+
onClick={() => setModalOpen(true)}
716+
/>
717+
<div>
718+
{modalOpen && (
719+
<Modal
720+
closeButtonRef={modalCloseButtonRef}
721+
color={modalColor}
722+
>
723+
<ModalHeader>
724+
<ModalTitle>Modal color</ModalTitle>
725+
<ModalCloseButton onClick={() => setModalOpen(false)} />
726+
</ModalHeader>
727+
<ModalBody>
728+
<ModalContent>
729+
<Radio
730+
id="modal-color"
731+
label="Modal color"
732+
onChange={(e) => setModalColor(e.target.value)}
733+
options={[
734+
{
735+
label: 'success',
736+
value: 'success',
737+
},
738+
{
739+
label: 'warning',
740+
value: 'warning',
741+
},
742+
{
743+
label: 'danger',
744+
value: 'danger',
745+
},
746+
{
747+
label: 'info',
748+
value: 'info',
749+
},
750+
{
751+
label: 'help',
752+
value: 'help',
753+
},
754+
{
755+
label: 'note',
756+
value: 'note',
757+
},
758+
]}
759+
value={modalColor}
760+
/>
761+
</ModalContent>
762+
</ModalBody>
763+
<ModalFooter>
764+
<Button
765+
color={modalColor}
766+
label="Close"
767+
onClick={() => setModalOpen(false)}
768+
ref={modalCloseButtonRef}
769+
/>
770+
</ModalFooter>
771+
</Modal>
772+
)}
773+
</div>
774+
</GlobalPropsProvider>
775+
);
776+
});
777+
```
778+
693779
## Mouse and Keyboard Control
694780

695781
Modal can be controlled either by mouse or keyboard. To enhance user
@@ -1207,6 +1293,21 @@ accessibility.
12071293
| `--rui-Modal--fullscreen__height` | Height of fullscreen modal |
12081294
| `--rui-Modal__animation__duration` | Duration of animation used (when opening modal) |
12091295

1296+
### Theming Variants
1297+
1298+
It's possible to adjust the theme of specific color variant. Naming convention
1299+
looks as follows:
1300+
1301+
`--rui-Modal--<COLOR>__<PROPERTY>`
1302+
1303+
Where:
1304+
1305+
- `<COLOR>` is a value from supported
1306+
[color collections](/docs/foundation/collections#colors)
1307+
(check [color variants](#color-variants) and [API](#api) to see which
1308+
collections are supported),
1309+
- `<PROPERTY>` is one of `border-color` or `background-color`.
1310+
12101311
[button-attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes
12111312
[controlled-components]: /docs/getting-started/usage#foundation-css
12121313
[dialog-attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#attributes

src/components/Modal/__tests__/Modal.test.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
within,
77
} from '@testing-library/react';
88
import userEvent from '@testing-library/user-event';
9+
import { feedbackColorPropTest } from '../../../../tests/propTests/feedbackColorPropTest';
910
import { Button } from '../../..';
1011
import { Modal } from '../Modal';
1112
import { ModalBody } from '../ModalBody';
@@ -31,6 +32,7 @@ describe('rendering', () => {
3132
});
3233

3334
it.each([
35+
...feedbackColorPropTest,
3436
[
3537
{ children: <div>content text</div> },
3638
(rootElement) => expect(within(rootElement).getByText('content text')),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
@use "sass:map";
2+
@use "../../styles/settings/collections";
23
@use "../../styles/theme/borders";
34
@use "../../styles/theme/typography";
45

6+
$border-width: borders.$width;
57
$border-radius: borders.$radius-2;
68
$title-font-size: map.get(typography.$font-size-values, 2);
9+
$colors: collections.$feedback-colors;
10+
$themeable-properties: border-color, background-color;

src/theme.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,30 @@
10141014
--rui-Modal--fullscreen__height: 100%;
10151015
--rui-Modal__animation__duration: 0.25s;
10161016

1017+
// Modal: success variant
1018+
--rui-Modal--success__border-color: var(--rui-color-feedback-success);
1019+
--rui-Modal--success__background-color: var(--rui-color-background-success);
1020+
1021+
// Modal: warning variant
1022+
--rui-Modal--warning__border-color: var(--rui-color-feedback-warning);
1023+
--rui-Modal--warning__background-color: var(--rui-color-background-warning);
1024+
1025+
// Modal: danger variant
1026+
--rui-Modal--danger__border-color: var(--rui-color-feedback-danger);
1027+
--rui-Modal--danger__background-color: var(--rui-color-background-danger);
1028+
1029+
// Modal: info variant
1030+
--rui-Modal--info__border-color: var(--rui-color-feedback-info);
1031+
--rui-Modal--info__background-color: var(--rui-color-background-info);
1032+
1033+
// Modal: help variant
1034+
--rui-Modal--help__border-color: var(--rui-color-feedback-help);
1035+
--rui-Modal--help__background-color: var(--rui-color-background-help);
1036+
1037+
// Modal: note variant
1038+
--rui-Modal--note__border-color: var(--rui-color-feedback-note);
1039+
--rui-Modal--note__background-color: var(--rui-color-background-note);
1040+
10171041
//
10181042
// Paper
10191043
// =====

0 commit comments

Comments
 (0)