diff --git a/packages/react/src/components/modal/modal-dialog.test.tsx b/packages/react/src/components/modal/dialog/modal-dialog.test.tsx similarity index 91% rename from packages/react/src/components/modal/modal-dialog.test.tsx rename to packages/react/src/components/modal/dialog/modal-dialog.test.tsx index 8ab4b6fffa..e42b4cb89b 100644 --- a/packages/react/src/components/modal/modal-dialog.test.tsx +++ b/packages/react/src/components/modal/dialog/modal-dialog.test.tsx @@ -1,10 +1,11 @@ import { fireEvent, RenderResult } from '@testing-library/react'; -import { doNothing } from '../../test-utils/callbacks'; -import { getByTestId as enzymeGetByTestId } from '../../test-utils/enzyme-selectors'; -import { mountWithProviders, renderPortalWithProviders } from '../../test-utils/renderer'; -import { DeviceType } from '../device-context-provider/device-context-provider'; -import { ModalDialog, ModalDialogProps, DialogType } from './modal-dialog'; -import { IconName } from '../icon/icon'; +import { doNothing } from '../../../test-utils/callbacks'; +import { getByTestId as enzymeGetByTestId } from '../../../test-utils/enzyme-selectors'; +import { mountWithProviders, renderPortalWithProviders } from '../../../test-utils/renderer'; +import { DeviceType } from '../../device-context-provider/device-context-provider'; +import { ModalDialog } from './modal-dialog'; +import { IconName } from '../../icon/icon'; +import { DialogType, ModalDialogProps } from './types'; type ModalDialogPropsLite = Omit; @@ -123,7 +124,7 @@ describe('Modal-Dialog', () => { test.each([ ['information', 'alertFilled', 'primary', false], ['action', 'home', 'primary', true], - ['alert', 'alertFilled', 'destructive-primary', true], + ['alert', 'alertOctagon', 'destructive-primary', true], ])( 'should respect %s dialogType with proper titleIcon and buttons', (modalType, expectedIcon, expectedButtonType, hasCancelButton) => { diff --git a/packages/react/src/components/modal/modal-dialog.test.tsx.snap b/packages/react/src/components/modal/dialog/modal-dialog.test.tsx.snap similarity index 80% rename from packages/react/src/components/modal/modal-dialog.test.tsx.snap rename to packages/react/src/components/modal/dialog/modal-dialog.test.tsx.snap index d2acbd1652..77e31cf020 100644 --- a/packages/react/src/components/modal/modal-dialog.test.tsx.snap +++ b/packages/react/src/components/modal/dialog/modal-dialog.test.tsx.snap @@ -25,7 +25,7 @@ exports[`Modal-Dialog Matches snapshot (closed) 1`] = ` `; exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` -.c9 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -54,10 +54,10 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` -ms-letter-spacing: 0.025rem; letter-spacing: 0.025rem; line-height: 1rem; - min-height: var(--size-2x); + min-height: var(--size-1halfx); min-width: 2rem; outline: none; - padding: 0 var(--spacing-2x); + padding: 0 var(--spacing-1halfx); text-transform: uppercase; -webkit-user-select: none; -moz-user-select: none; @@ -65,23 +65,23 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` user-select: none; } -.c9 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c9:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c9 > svg { +.c2 > svg { height: var(--size-1x); width: var(--size-1x); } -.c13 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -110,10 +110,10 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` -ms-letter-spacing: 0.025rem; letter-spacing: 0.025rem; line-height: 1rem; - min-height: var(--size-1halfx); + min-height: var(--size-2x); min-width: 2rem; outline: none; - padding: 0 var(--spacing-1halfx); + padding: 0 var(--spacing-2x); text-transform: uppercase; -webkit-user-select: none; -moz-user-select: none; @@ -121,23 +121,23 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` user-select: none; } -.c13 { +.c11 { outline: 2px solid transparent; outline-offset: -2px; } -.c13:focus { +.c11:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c13 > svg { +.c11 > svg { height: var(--size-1x); width: var(--size-1x); } -.c14 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -146,32 +146,32 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` width: var(--size-1halfx); } -.c14 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c14:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c14:hover, -.c14[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c14[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c14 > svg { +.c3 > svg { height: var(--size-1x); width: var(--size-1x); } @@ -184,76 +184,84 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` right: var(--spacing-2x); } -.c10 { +.c12 { background-color: transparent; border-color: transparent; color: #60666E; } -.c10 { +.c12 { outline: 2px solid transparent; outline-offset: -2px; } -.c10:focus { +.c12:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c10:hover, -.c10[aria-expanded='true'] { +.c12:hover, +.c12[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c10[aria-disabled='true'] { +.c12[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c11 { +.c13 { background-color: #006296; border-color: #006296; color: #FFFFFF; } -.c11 { +.c13 { outline: 2px solid transparent; outline-offset: -2px; } -.c11:focus { +.c13:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c11:hover, -.c11[aria-expanded='true'] { +.c13:hover, +.c13[aria-expanded='true'] { background-color: #003A5A; border-color: #003A5A; color: #FFFFFF; } -.c11[aria-disabled='true'] { +.c13[aria-disabled='true'] { background-color: #84C6EA; border-color: #84C6EA; color: #FFFFFF; cursor: not-allowed; } -.c3 { +.c6 { color: #1B1C1E; font-size: 1.25rem; - font-weight: var(--font-normal); + font-weight: var(--font-semi-bold); line-height: 2rem; margin: 0; } +.c8 { + color: #1B1C1E; + font-size: 1rem; + font-weight: var(--font-semi-bold); + line-height: 1.5rem; + margin: 0; +} + .c1 { background-color: #FFFFFF; border-radius: var(--border-radius-2x); @@ -266,8 +274,10 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: calc(480px - var(--spacing-4x)); position: relative; width: 60vw; @@ -276,43 +286,59 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-4x); } -.c6 { +.c7 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-3x) var(--spacing-4x) 0; + padding: var(--spacing-3x) var(--spacing-4x); + padding-top: 0; + padding-bottom: 0; } -.c2 { +.c5 { border-bottom: 1px solid transparent; - padding: var(--spacing-3x) var(--spacing-4x) var(--spacing-2x); + padding: var(--spacing-3x) var(--spacing-4x); + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + padding-right: calc(var(--spacing-4x) + var(--spacing-3x)); } -.c2 + .c5 { - padding-top: 0; +.c9 { + border-top: 1px solid transparent; + padding: var(--spacing-4x) var(--spacing-4x); } -.c15 { +.c4 { + pointer-events: none; position: absolute; right: var(--spacing-4x); - top: 1.75rem; -} - -.c7 { - border-top: 1px solid transparent; - padding: var(--spacing-4x) var(--spacing-4x) 0; + top: var(--spacing-3x); } -.c4 { - font-size: 1rem; - font-weight: var(--font-normal); - line-height: 1.375rem; - margin: var(--spacing-3x) 0 0; +.c4 > * { + pointer-events: auto; } -.c8 { +.c10 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -320,15 +346,11 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` -webkit-flex-direction: unset; -ms-flex-direction: unset; flex-direction: unset; + gap: var(--spacing-1x); -webkit-box-pack: end; - -webkit-justify-content: end; + -webkit-justify-content: flex-end; -ms-flex-pack: end; - justify-content: end; -} - -.c12 { - margin-left: var(--spacing-1x); - margin-top: 0; + justify-content: flex-end; } +

Title

+
+

Subtitle

- -
- @@ -432,7 +454,7 @@ exports[`Modal-Dialog Matches snapshot (custom button labels) 1`] = ` `; exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` -.c7 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -472,23 +494,23 @@ exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` user-select: none; } -.c7 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c7:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c7 > svg { +.c2 > svg { height: var(--size-1x); width: var(--size-1x); } -.c8 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -497,32 +519,32 @@ exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` width: var(--size-1halfx); } -.c8 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c8:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c8:hover, -.c8[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c8[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c8 > svg { +.c3 > svg { height: var(--size-1x); width: var(--size-1x); } @@ -535,10 +557,10 @@ exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` right: var(--spacing-2x); } -.c3 { +.c6 { color: #1B1C1E; font-size: 1.25rem; - font-weight: var(--font-normal); + font-weight: var(--font-semi-bold); line-height: 2rem; margin: 0; } @@ -555,8 +577,10 @@ exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: calc(480px - var(--spacing-4x)); position: relative; width: 60vw; @@ -565,33 +589,56 @@ exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-4x); } -.c5 { +.c7 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-3x) var(--spacing-4x) 0; + padding: var(--spacing-3x) var(--spacing-4x); + padding-top: 0; + padding-bottom: 0; } -.c2 { +.c5 { border-bottom: 1px solid transparent; - padding: var(--spacing-3x) var(--spacing-4x) var(--spacing-2x); + padding: var(--spacing-3x) var(--spacing-4x); + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + padding-right: calc(var(--spacing-4x) + var(--spacing-3x)); } -.c2 + .c4 { - padding-top: 0; +.c8 { + border-top: 1px solid transparent; + padding: var(--spacing-4x) var(--spacing-4x); } -.c9 { +.c4 { + pointer-events: none; position: absolute; right: var(--spacing-4x); - top: 1.75rem; + top: var(--spacing-3x); } -.c6 { - border-top: 1px solid transparent; - padding: var(--spacing-4x) var(--spacing-4x) 0; +.c4 > * { + pointer-events: auto; } +

Title

Custom content

- @@ -675,7 +722,7 @@ exports[`Modal-Dialog Matches snapshot (custom footer content) 1`] = ` `; exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` -.c9 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -704,10 +751,10 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` -ms-letter-spacing: 0.025rem; letter-spacing: 0.025rem; line-height: 1rem; - min-height: var(--size-2x); + min-height: var(--size-1halfx); min-width: 2rem; outline: none; - padding: 0 var(--spacing-2x); + padding: 0 var(--spacing-1halfx); text-transform: uppercase; -webkit-user-select: none; -moz-user-select: none; @@ -715,23 +762,23 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` user-select: none; } -.c9 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c9:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c9 > svg { +.c2 > svg { height: var(--size-1x); width: var(--size-1x); } -.c13 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -760,10 +807,10 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` -ms-letter-spacing: 0.025rem; letter-spacing: 0.025rem; line-height: 1rem; - min-height: var(--size-1halfx); + min-height: var(--size-2x); min-width: 2rem; outline: none; - padding: 0 var(--spacing-1halfx); + padding: 0 var(--spacing-2x); text-transform: uppercase; -webkit-user-select: none; -moz-user-select: none; @@ -771,23 +818,23 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` user-select: none; } -.c13 { +.c11 { outline: 2px solid transparent; outline-offset: -2px; } -.c13:focus { +.c11:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c13 > svg { +.c11 > svg { height: var(--size-1x); width: var(--size-1x); } -.c14 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -796,32 +843,32 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` width: var(--size-1halfx); } -.c14 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c14:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c14:hover, -.c14[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c14[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c14 > svg { +.c3 > svg { height: var(--size-1x); width: var(--size-1x); } @@ -834,76 +881,84 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` right: var(--spacing-2x); } -.c10 { +.c12 { background-color: transparent; border-color: transparent; color: #60666E; } -.c10 { +.c12 { outline: 2px solid transparent; outline-offset: -2px; } -.c10:focus { +.c12:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c10:hover, -.c10[aria-expanded='true'] { +.c12:hover, +.c12[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c10[aria-disabled='true'] { +.c12[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c11 { +.c13 { background-color: #006296; border-color: #006296; color: #FFFFFF; } -.c11 { +.c13 { outline: 2px solid transparent; outline-offset: -2px; } -.c11:focus { +.c13:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c11:hover, -.c11[aria-expanded='true'] { +.c13:hover, +.c13[aria-expanded='true'] { background-color: #003A5A; border-color: #003A5A; color: #FFFFFF; } -.c11[aria-disabled='true'] { +.c13[aria-disabled='true'] { background-color: #84C6EA; border-color: #84C6EA; color: #FFFFFF; cursor: not-allowed; } -.c3 { +.c6 { color: #1B1C1E; font-size: 1.25rem; - font-weight: var(--font-normal); + font-weight: var(--font-semi-bold); line-height: 2rem; margin: 0; } +.c8 { + color: #1B1C1E; + font-size: 1rem; + font-weight: var(--font-semi-bold); + line-height: 1.5rem; + margin: 0; +} + .c1 { background-color: #FFFFFF; border-radius: var(--border-radius-2x); @@ -916,8 +971,10 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: calc(480px - var(--spacing-4x)); position: relative; width: 60vw; @@ -926,43 +983,59 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-4x); } -.c6 { +.c7 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-3x) var(--spacing-4x) 0; + padding: var(--spacing-3x) var(--spacing-4x); + padding-top: 0; + padding-bottom: 0; } -.c2 { +.c5 { border-bottom: 1px solid transparent; - padding: var(--spacing-3x) var(--spacing-4x) var(--spacing-2x); + padding: var(--spacing-3x) var(--spacing-4x); + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + padding-right: calc(var(--spacing-4x) + var(--spacing-3x)); } -.c2 + .c5 { - padding-top: 0; +.c9 { + border-top: 1px solid transparent; + padding: var(--spacing-4x) var(--spacing-4x); } -.c15 { +.c4 { + pointer-events: none; position: absolute; right: var(--spacing-4x); - top: 1.75rem; -} - -.c7 { - border-top: 1px solid transparent; - padding: var(--spacing-4x) var(--spacing-4x) 0; + top: var(--spacing-3x); } -.c4 { - font-size: 1rem; - font-weight: var(--font-normal); - line-height: 1.375rem; - margin: var(--spacing-3x) 0 0; +.c4 > * { + pointer-events: auto; } -.c8 { +.c10 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -970,15 +1043,11 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` -webkit-flex-direction: unset; -ms-flex-direction: unset; flex-direction: unset; + gap: var(--spacing-1x); -webkit-box-pack: end; - -webkit-justify-content: end; + -webkit-justify-content: flex-end; -ms-flex-pack: end; - justify-content: end; -} - -.c12 { - margin-left: var(--spacing-1x); - margin-top: 0; + justify-content: flex-end; } +

Title

+
+

Subtitle

- -
- @@ -1082,7 +1151,7 @@ exports[`Modal-Dialog Matches snapshot (only subtitle) 1`] = ` `; exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` -.c9 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -1111,10 +1180,10 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` -ms-letter-spacing: 0.025rem; letter-spacing: 0.025rem; line-height: 1rem; - min-height: var(--size-2x); + min-height: var(--size-1halfx); min-width: 2rem; outline: none; - padding: 0 var(--spacing-2x); + padding: 0 var(--spacing-1halfx); text-transform: uppercase; -webkit-user-select: none; -moz-user-select: none; @@ -1122,23 +1191,23 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` user-select: none; } -.c9 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c9:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c9 > svg { +.c2 > svg { height: var(--size-1x); width: var(--size-1x); } -.c13 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -1167,10 +1236,10 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` -ms-letter-spacing: 0.025rem; letter-spacing: 0.025rem; line-height: 1rem; - min-height: var(--size-1halfx); + min-height: var(--size-2x); min-width: 2rem; outline: none; - padding: 0 var(--spacing-1halfx); + padding: 0 var(--spacing-2x); text-transform: uppercase; -webkit-user-select: none; -moz-user-select: none; @@ -1178,23 +1247,23 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` user-select: none; } -.c13 { +.c11 { outline: 2px solid transparent; outline-offset: -2px; } -.c13:focus { +.c11:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c13 > svg { +.c11 > svg { height: var(--size-1x); width: var(--size-1x); } -.c14 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -1203,32 +1272,32 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` width: var(--size-1halfx); } -.c14 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c14:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c14:hover, -.c14[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c14[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c14 > svg { +.c3 > svg { height: var(--size-1x); width: var(--size-1x); } @@ -1241,76 +1310,84 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` right: var(--spacing-2x); } -.c10 { +.c12 { background-color: transparent; border-color: transparent; color: #60666E; } -.c10 { +.c12 { outline: 2px solid transparent; outline-offset: -2px; } -.c10:focus { +.c12:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c10:hover, -.c10[aria-expanded='true'] { +.c12:hover, +.c12[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c10[aria-disabled='true'] { +.c12[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c11 { +.c13 { background-color: #006296; border-color: #006296; color: #FFFFFF; } -.c11 { +.c13 { outline: 2px solid transparent; outline-offset: -2px; } -.c11:focus { +.c13:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c11:hover, -.c11[aria-expanded='true'] { +.c13:hover, +.c13[aria-expanded='true'] { background-color: #003A5A; border-color: #003A5A; color: #FFFFFF; } -.c11[aria-disabled='true'] { +.c13[aria-disabled='true'] { background-color: #84C6EA; border-color: #84C6EA; color: #FFFFFF; cursor: not-allowed; } -.c3 { +.c6 { color: #1B1C1E; font-size: 1.25rem; - font-weight: var(--font-normal); + font-weight: var(--font-semi-bold); line-height: 2rem; margin: 0; } +.c8 { + color: #1B1C1E; + font-size: 1rem; + font-weight: var(--font-semi-bold); + line-height: 1.5rem; + margin: 0; +} + .c1 { background-color: #FFFFFF; border-radius: var(--border-radius-2x); @@ -1323,8 +1400,10 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: calc(480px - var(--spacing-4x)); position: relative; width: 60vw; @@ -1333,43 +1412,59 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-4x); } -.c6 { +.c7 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-3x) var(--spacing-4x) 0; + padding: var(--spacing-3x) var(--spacing-4x); + padding-top: 0; + padding-bottom: 0; } -.c2 { +.c5 { border-bottom: 1px solid transparent; - padding: var(--spacing-3x) var(--spacing-4x) var(--spacing-2x); + padding: var(--spacing-3x) var(--spacing-4x); + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + padding-right: calc(var(--spacing-4x) + var(--spacing-3x)); } -.c2 + .c5 { - padding-top: 0; +.c9 { + border-top: 1px solid transparent; + padding: var(--spacing-4x) var(--spacing-4x); } -.c15 { +.c4 { + pointer-events: none; position: absolute; right: var(--spacing-4x); - top: 1.75rem; -} - -.c7 { - border-top: 1px solid transparent; - padding: var(--spacing-4x) var(--spacing-4x) 0; + top: var(--spacing-3x); } -.c4 { - font-size: 1rem; - font-weight: var(--font-normal); - line-height: 1.375rem; - margin: var(--spacing-3x) 0 0; +.c4 > * { + pointer-events: auto; } -.c8 { +.c10 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1377,15 +1472,11 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` -webkit-flex-direction: unset; -ms-flex-direction: unset; flex-direction: unset; + gap: var(--spacing-1x); -webkit-box-pack: end; - -webkit-justify-content: end; + -webkit-justify-content: flex-end; -ms-flex-pack: end; - justify-content: end; -} - -.c12 { - margin-left: var(--spacing-1x); - margin-top: 0; + justify-content: flex-end; } +

Title

+
+

Subtitle

- -
- @@ -1489,7 +1580,7 @@ exports[`Modal-Dialog Matches snapshot (opened, desktop) 1`] = ` `; exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` -.c9 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -1529,23 +1620,23 @@ exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` user-select: none; } -.c9 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c9:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c9 > svg { +.c2 > svg { height: var(--size-1halfx); width: var(--size-1halfx); } -.c13 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -1553,32 +1644,32 @@ exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` width: var(--size-3x); } -.c13 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c13:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c13:hover, -.c13[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c13[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c13 > svg { +.c3 > svg { height: var(--size-1halfx); width: var(--size-1halfx); } @@ -1591,76 +1682,84 @@ exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` right: 0; } -.c10 { +.c11 { background-color: transparent; border-color: transparent; color: #60666E; } -.c10 { +.c11 { outline: 2px solid transparent; outline-offset: -2px; } -.c10:focus { +.c11:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c10:hover, -.c10[aria-expanded='true'] { +.c11:hover, +.c11[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c10[aria-disabled='true'] { +.c11[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c11 { +.c12 { background-color: #006296; border-color: #006296; color: #FFFFFF; } -.c11 { +.c12 { outline: 2px solid transparent; outline-offset: -2px; } -.c11:focus { +.c12:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c11:hover, -.c11[aria-expanded='true'] { +.c12:hover, +.c12[aria-expanded='true'] { background-color: #003A5A; border-color: #003A5A; color: #FFFFFF; } -.c11[aria-disabled='true'] { +.c12[aria-disabled='true'] { background-color: #84C6EA; border-color: #84C6EA; color: #FFFFFF; cursor: not-allowed; } -.c3 { +.c6 { color: #1B1C1E; font-size: 1.25rem; - font-weight: var(--font-normal); + font-weight: var(--font-semi-bold); line-height: 2rem; margin: 0; } +.c8 { + color: #1B1C1E; + font-size: 1rem; + font-weight: var(--font-semi-bold); + line-height: 1.5rem; + margin: 0; +} + .c1 { background-color: #FFFFFF; border-radius: var(--border-radius-2x); @@ -1673,8 +1772,10 @@ exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: initial; position: relative; width: calc(100vw - var(--spacing-2x)); @@ -1683,43 +1784,59 @@ exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-2x); } -.c6 { +.c7 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-2x) var(--spacing-2x) 0; + padding: var(--spacing-2x) var(--spacing-2x); + padding-top: 0; + padding-bottom: 0; } -.c2 { +.c5 { border-bottom: 1px solid transparent; - padding: var(--spacing-2x) var(--spacing-2x) var(--spacing-2x); + padding: var(--spacing-2x) var(--spacing-2x); + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + padding-right: calc(var(--spacing-2x) + var(--spacing-3x)); } -.c2 + .c5 { - padding-top: 0; +.c9 { + border-top: 1px solid transparent; + padding: var(--spacing-4x) var(--spacing-2x); } -.c14 { +.c4 { + pointer-events: none; position: absolute; - right: var(--spacing-half); - top: 1.75rem; -} - -.c7 { - border-top: 1px solid transparent; - padding: var(--spacing-4x) var(--spacing-2x) 0; + right: var(--spacing-2x); + top: var(--spacing-2x); } -.c4 { - font-size: 1.125rem; - font-weight: var(--font-normal); - line-height: 1.75rem; - margin: var(--spacing-3x) 0 0; +.c4 > * { + pointer-events: auto; } -.c8 { +.c10 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1727,15 +1844,11 @@ exports[`Modal-Dialog Matches snapshot (opened, mobile) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + gap: var(--spacing-1x); -webkit-box-pack: end; - -webkit-justify-content: end; + -webkit-justify-content: flex-end; -ms-flex-pack: end; - justify-content: end; -} - -.c12 { - margin-left: 0; - margin-top: var(--spacing-1x); + justify-content: flex-end; } +

Title

+
+

Subtitle

- -
- diff --git a/packages/react/src/components/modal/dialog/modal-dialog.tsx b/packages/react/src/components/modal/dialog/modal-dialog.tsx new file mode 100644 index 0000000000..3d91699c86 --- /dev/null +++ b/packages/react/src/components/modal/dialog/modal-dialog.tsx @@ -0,0 +1,146 @@ +import { Fragment, ReactElement, Ref, useRef, VoidFunctionComponent } from 'react'; +import { useId } from '../../../hooks/use-id'; +import { useTranslation } from '../../../i18n/use-translation'; +import { Button } from '../../buttons/button'; +import { useDeviceContext } from '../../device-context-provider/device-context-provider'; +import { Heading } from '../../heading/heading'; +import { Modal } from '../modal'; +import { + ButtonContainer, + StyledHeadingWrapperComponent, + TitleIcon, +} from './styled'; +import { DialogType, ModalDialogProps } from './types'; + +const modalRoles: Record = { + information: 'dialog', + action: 'dialog', + alert: 'alertdialog', +}; + +export const ModalDialog: VoidFunctionComponent = ({ + appElement, + ariaDescribedby, + ariaHideApp, + dialogType = 'action', + cancelButton, + children, + className, + confirmButton, + footerContent, + hasCloseButton, + isOpen, + width, + parentSelector, + shouldCloseOnOverlayClick = true, + subtitle, + title, + titleIcon, + onRequestClose, +}) => { + const { isMobile } = useDeviceContext(); + const { t } = useTranslation('modal-dialog'); + const titleId = useId(); + const titleRef: Ref = useRef(null); + const titleIconName = dialogType === 'alert' ? 'alertOctagon' : titleIcon; + const hasTitleIcon = !!titleIconName; + + function handleConfirm(): void { + confirmButton?.onConfirm?.(); + } + + function handleCancel(): void { + if (cancelButton?.onCancel) { + cancelButton.onCancel(); + } else { + onRequestClose(); + } + } + + function getHeader(): ReactElement | undefined { + const HeadingWrapperComponent = hasTitleIcon ? StyledHeadingWrapperComponent : Fragment; + + return ( + + {titleIconName && ( + + ); + } + + function getFooter(): ReactElement { + const confirmButtonType = dialogType === 'alert' ? 'destructive-primary' : 'primary'; + + return ( + + {dialogType !== 'information' && ( + +
+ +
@@ -407,7 +440,7 @@ exports[`Modal Matches snapshot (noPadding) 1`] = ` `; exports[`Modal Matches snapshot (opened, desktop) 1`] = ` -.c3 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -447,23 +480,23 @@ exports[`Modal Matches snapshot (opened, desktop) 1`] = ` user-select: none; } -.c3 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c3:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c3 > svg { +.c2 > svg { height: var(--size-1x); width: var(--size-1x); } -.c4 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -472,32 +505,32 @@ exports[`Modal Matches snapshot (opened, desktop) 1`] = ` width: var(--size-1halfx); } -.c4 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c4:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c4:hover, -.c4[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c4[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c4 > svg { +.c3 > svg { height: var(--size-1x); width: var(--size-1x); } @@ -522,8 +555,10 @@ exports[`Modal Matches snapshot (opened, desktop) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: calc(480px - var(--spacing-4x)); position: relative; width: 60vw; @@ -532,19 +567,32 @@ exports[`Modal Matches snapshot (opened, desktop) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-4x); } -.c2 { +.c5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-3x) var(--spacing-4x) 0; + padding: var(--spacing-3x) var(--spacing-4x); + padding-right: calc(var(--spacing-4x) + var(--spacing-3x)); } -.c5 { +.c4 { + pointer-events: none; position: absolute; right: var(--spacing-4x); - top: 1.75rem; + top: var(--spacing-3x); +} + +.c4 > * { + pointer-events: auto; } -
- -
+
+ +
@@ -611,7 +659,7 @@ exports[`Modal Matches snapshot (opened, desktop) 1`] = ` `; exports[`Modal Matches snapshot (opened, mobile) 1`] = ` -.c3 { +.c2 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -651,23 +699,23 @@ exports[`Modal Matches snapshot (opened, mobile) 1`] = ` user-select: none; } -.c3 { +.c2 { outline: 2px solid transparent; outline-offset: -2px; } -.c3:focus { +.c2:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c3 > svg { +.c2 > svg { height: var(--size-1halfx); width: var(--size-1halfx); } -.c4 { +.c3 { background-color: transparent; border-color: transparent; color: #60666E; @@ -675,32 +723,32 @@ exports[`Modal Matches snapshot (opened, mobile) 1`] = ` width: var(--size-3x); } -.c4 { +.c3 { outline: 2px solid transparent; outline-offset: -2px; } -.c4:focus { +.c3:focus { box-shadow: 0 0 0 2px #006296; outline: 2px solid #84C6EA; outline-offset: -2px; } -.c4:hover, -.c4[aria-expanded='true'] { +.c3:hover, +.c3[aria-expanded='true'] { background-color: rgb(0 0 0 / 0.15); border-color: transparent; color: #000000; } -.c4[aria-disabled='true'] { +.c3[aria-disabled='true'] { background-color: transparent; border-color: transparent; color: #B7BBC2; cursor: not-allowed; } -.c4 > svg { +.c3 > svg { height: var(--size-1halfx); width: var(--size-1halfx); } @@ -725,8 +773,10 @@ exports[`Modal Matches snapshot (opened, mobile) 1`] = ` -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + height: auto; max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; + max-width: 95vw; + min-height: 1vh; min-width: initial; position: relative; width: calc(100vw - var(--spacing-2x)); @@ -735,19 +785,32 @@ exports[`Modal Matches snapshot (opened, mobile) 1`] = ` .c1::after { content: ''; display: block; - padding-bottom: var(--spacing-2x); } -.c2 { +.c5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + gap: var(--spacing-2x); max-height: 100%; overflow-y: auto; - padding: var(--spacing-2x) var(--spacing-2x) 0; + padding: var(--spacing-2x) var(--spacing-2x); + padding-right: calc(var(--spacing-2x) + var(--spacing-3x)); } -.c5 { +.c4 { + pointer-events: none; position: absolute; - right: var(--spacing-half); - top: 1.75rem; + right: var(--spacing-2x); + top: var(--spacing-2x); +} + +.c4 > * { + pointer-events: auto; } -
- -
+
+ +
diff --git a/packages/react/src/components/modal/modal.tsx b/packages/react/src/components/modal/modal.tsx index 8d588319b9..e020aea6ac 100644 --- a/packages/react/src/components/modal/modal.tsx +++ b/packages/react/src/components/modal/modal.tsx @@ -1,145 +1,16 @@ -import { FunctionComponent, PropsWithChildren, ReactNode, useCallback, useEffect, useState } from 'react'; +import { FunctionComponent, PropsWithChildren, ReactElement, useCallback, useEffect, useState } from 'react'; import ReactModal from 'react-modal'; -import styled, { useTheme } from 'styled-components'; +import { useTheme } from 'styled-components'; import { useTranslation } from '../../i18n/use-translation'; -import { IconButton } from '../buttons/icon-button'; -import { DeviceContextProps, useDeviceContext } from '../device-context-provider/device-context-provider'; - -interface StyledModalProps extends Pick { - noPadding: boolean; - hasCloseButton: boolean; -} - -interface ContentProps extends Pick { - noPadding: boolean; - hasCloseButton: boolean; -} - -function getPadding({ noPadding, isMobile }: ContentProps): string { - if (noPadding) { - return '0'; - } - if (isMobile) { - return 'var(--spacing-2x)'; - } - return 'var(--spacing-4x)'; -} - -function getTopPadding({ hasCloseButton, noPadding, isMobile }: ContentProps): string { - if (noPadding) { - return '0'; - } - if (isMobile) { - if (hasCloseButton) { - return 'var(--spacing-2x)'; - } - } - return 'var(--spacing-3x)'; -} - -function getModalMinWidth({ breakpoints, isMobile }: StyledModalProps): string { - return isMobile ? 'initial' : `calc(${breakpoints.mobile}px - var(--spacing-4x))`; -} - -const StyledModal = styled(ReactModal)` - background-color: ${({ theme }) => theme.component['modal-background-color']}; - border-radius: var(--border-radius-2x); - box-shadow: 0 6px 10px 0 rgb(0 0 0 / 10%); - box-sizing: border-box; - display: flex; - flex-direction: column; - max-height: calc(100vh - var(--spacing-2x)); - max-width: 700px; - min-width: ${getModalMinWidth}; - position: relative; - width: ${({ isMobile }) => (isMobile ? 'calc(100vw - var(--spacing-2x))' : '60vw')}; - - /* Firefox overflow-y: scroll problem fix (skipped bottom padding) - https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */ - - &::after { - content: ''; - display: block; - padding-bottom: ${getPadding}; - } -`; - -const Main = styled.main` - max-height: 100%; - overflow-y: auto; - padding: ${getTopPadding} ${getPadding} 0; -`; - -interface HeaderProps extends ContentProps { - isTopScrolled?: boolean; -} -const Header = styled.header` - border-bottom: 1px solid ${({ isTopScrolled, theme }) => (isTopScrolled ? theme.component['modal-border-color'] : 'transparent')}; - padding: ${getTopPadding} ${getPadding} var(--spacing-2x); - - & + ${Main} { - padding-top: 0; - } -`; - -const CloseIconButton = styled(IconButton)>` - position: absolute; - right: ${({ isMobile }) => (isMobile ? 'var(--spacing-half)' : 'var(--spacing-4x)')}; - top: 1.75rem; -`; - -interface FooterProps extends ContentProps { - isBottomScrolled?: boolean; -} -const Footer = styled.footer` - border-top: 1px solid ${({ isBottomScrolled, theme }) => (isBottomScrolled ? theme.component['modal-border-color'] : 'transparent')}; - padding: var(--spacing-4x) ${getPadding} 0; -`; - -export interface ModalProps { - /** Takes a query selector targeting the app Element. */ - appElement?: string; - ariaDescribedby?: string; - /** Boolean indicating if the appElement should be hidden. Defaults to true. - * Should only be used for test purposes. */ - ariaHideApp?: boolean; - ariaLabel?: string; - ariaLabelledBy?: string; - children?: ReactNode; - className?: string; - /** - * Removes padding to give you a blank modal to work with. - * @default false - */ - noPadding?: boolean; - /** - * Adds "x" iconButton to close modal - * @default true - */ - hasCloseButton?: boolean; - isOpen: boolean; - modalFooter?: ReactNode; - modalHeader?: ReactNode; - parentSelector?: () => HTMLElement; - /** - * Sets modal role tag - * @default dialog - */ - role?: string; - /** - * Defines if the overlay click should close the modal - * @default true - */ - shouldCloseOnOverlayClick?: boolean; - - /** Function that will run after the modal has opened */ - onAfterOpen?(): void; - - /** Function that will run after the modal has closed */ - onAfterClose?(): void; - - onRequestClose(): void; -} +import { useDeviceContext } from '../device-context-provider/device-context-provider'; +import { + Footer, + Header, + Main, + StyledCloseButton, + StyledModal, +} from './styled'; +import { ModalProps } from './types'; export const Modal: FunctionComponent> = ({ appElement, @@ -151,6 +22,7 @@ export const Modal: FunctionComponent> = ({ className, noPadding = false, hasCloseButton = true, + width = '60vw', isOpen, modalFooter, modalHeader, @@ -209,6 +81,25 @@ export const Modal: FunctionComponent> = ({ ReactModal.setAppElement(appElement); } + function renderCloseButton(): ReactElement | null { + if (!hasCloseButton) { + return null; + } + return ( + + ); + } + return ( > = ({ }} ariaHideApp={ariaHideApp} className={className} - noPadding={noPadding} - hasCloseButton={hasCloseButton} isOpen={isOpen} onAfterOpen={onAfterOpen} onAfterClose={onAfterClose} @@ -231,52 +120,41 @@ export const Modal: FunctionComponent> = ({ shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} breakpoints={breakpoints} isMobile={isMobile} + $width={width} > + {renderCloseButton()} {modalHeader && (
0} - noPadding={noPadding} + $hasCloseButton={hasCloseButton} + $isTopScrolled={topScroll > 0} + $noPadding={noPadding} > {modalHeader}
)} - {children && (
{children} +
)} - {modalFooter && (
0} isMobile={isMobile} - noPadding={noPadding} + $isBottomScrolled={bottomScroll > 0} + $noPadding={noPadding} > {modalFooter}
)} - - {hasCloseButton && ( - - )}
); }; diff --git a/packages/react/src/components/modal/styled.ts b/packages/react/src/components/modal/styled.ts new file mode 100644 index 0000000000..6a1fe04a30 --- /dev/null +++ b/packages/react/src/components/modal/styled.ts @@ -0,0 +1,97 @@ +import { CSSProperties } from 'react'; +import ReactModal from 'react-modal'; +import styled from 'styled-components'; +import { IconButton } from '../buttons/icon-button'; +import { CommonStyledProps, FooterProps, HeaderProps, MainProps, StyledModalProps } from './types'; + +function getWidthPadding({ $noPadding, isMobile }: CommonStyledProps): string { + if ($noPadding) { + return '0'; + } + if (isMobile) { + return 'var(--spacing-2x)'; + } + return 'var(--spacing-4x)'; +} + +function getHeightPadding({ $noPadding, isMobile }: CommonStyledProps): string { + if ($noPadding) { + return '0'; + } + if (isMobile) { + return 'var(--spacing-2x)'; + } + return 'var(--spacing-3x)'; +} + +function getModalMinWidth({ breakpoints, isMobile }: StyledModalProps): string { + return isMobile ? 'initial' : `calc(${breakpoints.mobile}px - var(--spacing-4x))`; +} + +function getModalWidth({ $width, isMobile }: StyledModalProps): CSSProperties['width'] { + return isMobile ? 'calc(100vw - var(--spacing-2x))' : $width; +} + +export const StyledModal = styled(ReactModal)` + background-color: ${({ theme }) => theme.component['modal-background-color']}; + border-radius: var(--border-radius-2x); + box-shadow: 0 6px 10px 0 rgb(0 0 0 / 10%); + box-sizing: border-box; + display: flex; + flex-direction: column; + height: auto; + max-height: calc(100vh - var(--spacing-2x)); + max-width: 95vw; + min-height: 1vh; + min-width: ${getModalMinWidth}; + position: relative; + width: ${getModalWidth}; + + /* Firefox overflow-y: scroll problem fix (skipped bottom padding) + https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */ + + &::after { + content: ''; + display: block; + } +`; + +export const Main = styled.main` + display: flex; + flex-direction: column; + gap: var(--spacing-2x); + max-height: 100%; + overflow-y: auto; + padding: ${getHeightPadding} ${getWidthPadding}; + ${({ $hasHeader }) => $hasHeader && 'padding-top: 0'}; + ${({ $hasFooter }) => $hasFooter && 'padding-bottom: 0'}; + ${({ $hasHeader, $hasCloseButton, ...props }) => !$hasHeader && $hasCloseButton + && `padding-right: calc(${getWidthPadding(props)} + var(--spacing-3x))`};`; + +export const Header = styled.header` + border-bottom: 1px solid ${({ $isTopScrolled, theme }) => ($isTopScrolled ? theme.component['modal-border-color'] : 'transparent')}; + padding: ${getHeightPadding} ${getWidthPadding}; + + ${({ $hasCloseButton, ...props }) => $hasCloseButton && ` + align-items: center; + display: flex; + justify-content: space-between; + padding-right: calc(${getWidthPadding(props)} + var(--spacing-3x)); + `}; +`; + +export const Footer = styled.footer` + border-top: 1px solid ${({ $isBottomScrolled, theme }) => ($isBottomScrolled ? theme.component['modal-border-color'] : 'transparent')}; + padding: var(--spacing-4x) ${getWidthPadding}; +`; + +export const StyledCloseButton = styled(IconButton)` + pointer-events: none; + position: absolute; + right: ${getWidthPadding}; + top: ${getHeightPadding}; + + & > * { + pointer-events: auto; + } +`; diff --git a/packages/react/src/components/modal/types.ts b/packages/react/src/components/modal/types.ts new file mode 100644 index 0000000000..b5df18f238 --- /dev/null +++ b/packages/react/src/components/modal/types.ts @@ -0,0 +1,77 @@ +import { CSSProperties, ReactNode } from 'react'; +import { DeviceContextProps } from '../device-context-provider/device-context-provider'; + +export interface StyledModalProps extends Pick { + $width: CSSProperties['width']; +} + +export type MobileDeviceContextProps = Pick + +export interface CommonStyledProps extends MobileDeviceContextProps { + $noPadding: boolean; +} + +export interface MainProps extends CommonStyledProps { + $hasCloseButton: boolean; + $hasHeader?: boolean; + $hasFooter?: boolean; +} + +export interface HeaderProps extends CommonStyledProps { + $hasCloseButton: boolean; + $isTopScrolled?: boolean; +} + +export interface FooterProps extends CommonStyledProps { + $isBottomScrolled?: boolean; +} + +export interface BaseModalProps { + /** Takes a query selector targeting the app Element. */ + appElement?: string; + ariaDescribedby?: string; + /** Boolean indicating if the appElement should be hidden. Defaults to true. + * Should only be used for test purposes. */ + ariaHideApp?: boolean; + children?: ReactNode; + className?: string; + /** + * Modify the modal width + * @default 60vw + */ + width?: CSSProperties['width']; + /** + * Adds "x" iconButton to close modal + * @default true + */ + hasCloseButton?: boolean; + isOpen: boolean; + /** + * Defines if the overlay click should close the modal + * @default true + */ + shouldCloseOnOverlayClick?: boolean; + parentSelector?: () => HTMLElement; + onRequestClose(): void; +} + +export interface ModalProps extends BaseModalProps { + ariaLabel?: string; + ariaLabelledBy?: string; + /** + * Removes padding to give you a blank modal to work with. + * @default false + */ + noPadding?: boolean; + modalHeader?: ReactNode; + modalFooter?: ReactNode; + /** + * Sets modal role tag + * @default dialog + */ + role?: string; + /** Function that will run after the modal has opened */ + onAfterOpen?(): void; + /** Function that will run after the modal has closed */ + onAfterClose?(): void; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 9e6c331da1..3ad6f92633 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -71,8 +71,7 @@ export * from './components/tooltip/tooltip'; export * from './components/toggletip/toggletip'; export { Table } from './components/table/table'; export { TableColumn, TableData } from './components/table/types'; -export { Modal } from './components/modal/modal'; -export { ModalDialog } from './components/modal/modal-dialog'; +export { Modal, ModalDialog } from './components/modal'; export { Tag, TagValue, TagColor, TagSize, TagProps, } from './components/tag/tag'; diff --git a/packages/react/src/test-utils/renderer.tsx b/packages/react/src/test-utils/renderer.tsx index 146b49a6c1..acdfaed50d 100644 --- a/packages/react/src/test-utils/renderer.tsx +++ b/packages/react/src/test-utils/renderer.tsx @@ -88,3 +88,11 @@ export function renderPortalWithProviders( ): RenderResult { return testingLibRender({component}); } + +export function rerenderPortalWithProviders( + component: ReactElement, + rerender: RenderResult['rerender'], + device?: DeviceType, +): void { + rerender({component}); +} diff --git a/packages/react/src/themes/tokens/component/modal-tokens.ts b/packages/react/src/themes/tokens/component/modal-tokens.ts index 29965542f4..4666b797b8 100644 --- a/packages/react/src/themes/tokens/component/modal-tokens.ts +++ b/packages/react/src/themes/tokens/component/modal-tokens.ts @@ -4,7 +4,8 @@ import { RefTokens } from '../ref-tokens'; export type ModalTokens = | 'modal-background-color' | 'modal-border-color' - | 'modal-backdrop-background-color'; + | 'modal-backdrop-background-color' + | 'modal-dialog-alert-icon-color'; export type ModalTokenValue = AliasTokens | RefTokens; @@ -16,4 +17,5 @@ export const defaultModalTokens: ModalTokenMap = { 'modal-background-color': 'color-background-overlay', 'modal-border-color': 'color-border-overlay', 'modal-backdrop-background-color': 'color-backdrop-background', + 'modal-dialog-alert-icon-color': 'color-feedback-content-alert', }; diff --git a/packages/storybook/stories/modal.stories.tsx b/packages/storybook/stories/modal.stories.tsx index 8687d27628..b1469bcdc3 100644 --- a/packages/storybook/stories/modal.stories.tsx +++ b/packages/storybook/stories/modal.stories.tsx @@ -3,6 +3,9 @@ import { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { title: 'Components/Modal', + argTypes: { + width: { control: { type: 'text' } }, + }, component: ModalDialog, };