Skip to content

Commit

Permalink
Feat(web): Introduce option to disable scrolling inside Modal #DS-732
Browse files Browse the repository at this point in the history
Scrolling inside `ModalDialog` can now be turned off by adding the
`ModalDialog--nonScrolling` modifier class.
  • Loading branch information
adamkudrna authored and crishpeen committed Feb 5, 2024
1 parent 2a0fa28 commit 31f9964
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 127 deletions.
28 changes: 27 additions & 1 deletion packages/web/src/scss/components/Modal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ This is useful for Modals with dynamic content, e.g. a list of items that can be
</dialog>
```

👉 Please note the preferred height options are ignored when scrolling inside ModalDialog is
[turned off](#disable-scrolling-inside-modaldialog).

👉 Please note the custom height values are considered **preferred:** Modal will not expand beyond the viewport height.

### Custom Max Height
Expand All @@ -82,6 +85,8 @@ You can use the custom property `--modal-max-height-tablet` to override the max
</dialog>
```

👉 Please note the max height is ignored when scrolling inside ModalDialog is [turned off](#disable-scrolling-inside-modaldialog).

👉 Please note the max height on mobile screens is currently not customizable. Let us know if you need this feature! 🙏

## ModalDialog
Expand Down Expand Up @@ -246,7 +251,7 @@ Use our JavaScript plugin to open your Modal, e.g.:
</button>
```

## Disable Modal closing on backdrop click
## Disable Modal Closing on Backdrop Click

Disable modal close when clicking on the backdrop.
You can still close modal with close buttons or ESC key.
Expand Down Expand Up @@ -280,6 +285,27 @@ scrolling, e.g.:
</div>
```

### Disable Scrolling Inside ModalDialog

Scrolling inside ModalDialog can be turned off by adding the `ModalDialog--nonScrollable` modifier class:

```html
<article class="ModalDialog ModalDialog--nonScrollable">
<!---->
</article>
```

This way, the ModalBody will expand to fit the height of its content and the whole ModalDialog will scroll in case the
content is longer than user's viewport.

👉 Please note that this modifier class can produce unexpected results when used in combination with ScrollView.

#### ⚠️ DEPRECATION NOTICE

The `.ModalDialog--nonScrollable` modifier will be removed in the next major release and the ModalDialog will be made
non-scrollable by default. It will be possible to re-enable the inside scrolling by adding the
`.ModalDialog--scrollable` modifier class (which will remain the recommended default usage).

## Stacking Modals

Multiple Modals can be open at the same time. That means, you can open a Modal from another Modal, and they will display
Expand Down
43 changes: 13 additions & 30 deletions packages/web/src/scss/components/Modal/_Modal.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// 1. In order to be transitioned, the visibility of the dialog is controlled through `opacity` and `visibility`
// properties instead of the default `display`.
// 2. Use a `::before` pseudo-element instead of `::backdrop` to make fading possible.
// 3. Allow scrolling through the backdrop.
// 4. Restore text selection after `all: unset`.
// 5. For alignments other than `top`, prefer the `bottom` CSS property to enable smooth transitions between the uniform
// and docked variants (see `_ModalDialog.scss`).
// 2. Simply use Modal background color instead of `::backdrop` to make fading possible. (A pseudo-element cannot be
// used because it covers the scrollbar of the ModalDialog with the inside scrolling turned off.)
// 3. Use background gradient to prevent background from flickering in Safari.
// 4. Clip overflow during transition of the docked variant on mobile screens.
// 5. Allow scrolling through the modal. Has no effect unless the modal is taller than the viewport.
// 6. Restore text selection after `all: unset`.

@use 'sass:map';
@use '../../tools/breakpoint';
Expand All @@ -19,6 +20,8 @@
z-index: 1;
display: flex; // 1.
padding-block: theme.$padding-y;
overflow: hidden; // 4.
background: linear-gradient(#{theme.$backdrop-background-color}, #{theme.$backdrop-background-color}); // 8.
visibility: hidden; // 1.
opacity: 0; // 1.
pointer-events: none; // 1.
Expand All @@ -29,29 +32,16 @@
background-color: transparent;
}

// 2.
&::before {
content: '';
position: fixed;
inset: 0;
z-index: -2;
background-color: theme.$backdrop-background-color;
visibility: hidden;
opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
transition-property: visibility, opacity;
transition-duration: theme.$transition-duration;

// 2.
&::before {
transition: inherit;
}
}
}

// 5.
.Modal:has(.ModalDialog--nonScrollable) {
overflow-y: auto; // 5.
}

.Modal--center,
.Modal:not(.Modal--top, .Modal--bottom) {
--modal-top: auto;
Expand Down Expand Up @@ -80,13 +70,6 @@

visibility: visible;
opacity: 1;
user-select: text; // 4.
user-select: text; // 6.
pointer-events: auto;

// 2.
&::before {
visibility: visible;
opacity: 1;
pointer-events: none; // 3.
}
}
53 changes: 46 additions & 7 deletions packages/web/src/scss/components/Modal/_ModalDialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// 5. Unfortunately, the open state cannot be part of the parent `-docked-modal-dialog()` mixin because it's not
// possible to generate selector for the feature class scenario.
// 6. Override bottom padding of parent `.Modal` in the docked variant.
// 7. Override the min-height of the expanded docked variant.

@use 'sass:map';
@use '../../settings/feature-flags';
Expand Down Expand Up @@ -53,20 +54,29 @@
width: theme.$dialog-uniform-width;
max-width: calc(100% - #{theme.$padding-x});
height: theme.$dialog-uniform-height;
max-height: theme.$dialog-uniform-max-height;
min-height: unset; // 7. @deprecated Can be removed once the uniform modal dialog has been made default.
border-radius: theme.$dialog-border-radius;
}

@include breakpoint.up(map.get(theme.$breakpoints, tablet)) {
height: theme.$dialog-height-tablet;
max-height: theme.$dialog-uniform-max-height-tablet;
height: theme.$dialog-uniform-height-tablet;
}

@include breakpoint.up(map.get(theme.$breakpoints, desktop)) {
width: theme.$dialog-width-desktop;
}
}

@mixin -scrollable-uniform-modal-dialog($from-breakpoint) {
@if $from-breakpoint == 0 {
max-height: theme.$dialog-uniform-max-height;
}

@include breakpoint.up(map.get(theme.$breakpoints, tablet)) {
max-height: theme.$dialog-uniform-max-height-tablet;
}
}

@mixin -docked-modal-dialog() {
@include breakpoint.down(map.get(theme.$breakpoints, tablet)) {
--modal-top: auto;
Expand All @@ -76,8 +86,7 @@

width: theme.$dialog-width;
max-width: none;
height: theme.$dialog-height;
max-height: theme.$dialog-max-height;
height: theme.$dialog-docked-height;
border-radius: theme.$dialog-border-radius theme.$dialog-border-radius 0 0;
}
}
Expand All @@ -91,20 +100,36 @@

@mixin -expand-docked-modal-dialog() {
@include breakpoint.down(map.get(theme.$breakpoints, tablet)) {
height: theme.$dialog-max-height;
min-height: theme.$dialog-docked-expanded-height;
}
}

@mixin -scrollable-docked-modal-dialog() {
@include breakpoint.down(map.get(theme.$breakpoints, tablet)) {
max-height: theme.$dialog-docked-expanded-height;
}
}

// @deprecated The non-scrollable modal dialog is deprecated and will be removed in the next major release.
// Migration: In CSS, make the modal dialog non-scrollable by default so scrolling inside is turned on by a modifier class.
@mixin -non-scrollable-modal-dialog() {
height: auto;
max-height: none;
}

.ModalDialog {
@include -modal-dialog();

// @deprecated The "uniform" dialog variant is deprecated and will be removed in the next major release.
// Migration: Remove the feature flag and make the uniform dialog the default.
@if feature-flags.$modal-enable-uniform-dialog {
@include -uniform-modal-dialog($from-breakpoint: map.get(theme.$breakpoints, mobile));
@include -scrollable-uniform-modal-dialog($from-breakpoint: map.get(theme.$breakpoints, mobile));
} @else {
@include -docked-modal-dialog();
@include -scrollable-docked-modal-dialog();
@include -uniform-modal-dialog($from-breakpoint: map.get(theme.$breakpoints, tablet));
@include -scrollable-uniform-modal-dialog($from-breakpoint: map.get(theme.$breakpoints, tablet));

[open] > & {
@include -open-docked-modal-dialog();
Expand All @@ -126,22 +151,36 @@
.ModalDialog--dockOnMobile.ModalDialog--expandOnMobile {
@include -expand-docked-modal-dialog();
}

.ModalDialog--nonScrollable {
@include -non-scrollable-modal-dialog();
}
} @else {
.spirit-feature-modal-enable-uniform-dialog .ModalDialog {
@include -uniform-modal-dialog($from-breakpoint: map.get(theme.$breakpoints, mobile));
@include -scrollable-uniform-modal-dialog($from-breakpoint: map.get(theme.$breakpoints, mobile));
}

.spirit-feature-modal-enable-uniform-dialog .ModalDialog--dockOnMobile {
@include -docked-modal-dialog();
@include -scrollable-docked-modal-dialog();
}

.spirit-feature-modal-enable-uniform-dialog [open] > .ModalDialog--dockOnMobile {
@include -open-docked-modal-dialog();
}

// stylelint-disable-next-line selector-max-class -- Only target the docked variant.
// stylelint-disable selector-max-class, no-descending-specificity -- Only target the docked variant.
.ModalDialog--expandOnMobile,
.spirit-feature-modal-enable-uniform-dialog .ModalDialog--dockOnMobile.ModalDialog--expandOnMobile {
@include -expand-docked-modal-dialog();
}
// stylelint-enable selector-max-class

// @deprecated The non-scrollable modal dialog is deprecated and will be removed in the next major release.
// Migration: In CSS, make the modal dialog non-scrollable by default so scrolling inside is turned on by a modifier class.
.ModalDialog--nonScrollable,
.spirit-feature-modal-enable-uniform-dialog .ModalDialog--nonScrollable {
@include -non-scrollable-modal-dialog();
}
}
15 changes: 8 additions & 7 deletions packages/web/src/scss/components/Modal/_theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,22 @@ $common-padding-x-tablet: tokens.$space-800;
$dialog-width: 100%;
$dialog-width-tablet: 640px;
$dialog-width-desktop: 680px;
$dialog-height: var(--modal-preferred-height-mobile, min-content);
$dialog-height-tablet: var(--modal-preferred-height-tablet, min-content);
$dialog-max-height: calc(100dvh - #{tokens.$space-1100});
$dialog-max-height-tablet: min(var(--modal-max-height-tablet, 600px), calc(100dvh - #{2 * tokens.$space-600}));
$dialog-text-color: tokens.$text-primary-default;
$dialog-border-radius: tokens.$radius-200;
$dialog-background-color: tokens.$background-basic;
$dialog-shadow: tokens.$shadow-300;

// @deprecated The "uniform" Header padding is deprecated and will be removed in the next major release.
// @deprecated The "uniform" dialog dimensions are deprecated and will be removed in the next major release.
// Migration: Rename the variables containing the `uniform` keyword to remove it.
$dialog-uniform-width: 640px;
$dialog-uniform-height: var(--modal-preferred-height-mobile, min-content);
$dialog-uniform-max-height: min(var(--modal-max-height-mobile, 600px), calc(100dvh - #{2 * tokens.$space-600}));
$dialog-uniform-max-height-tablet: min(var(--modal-max-height-tablet, 600px), calc(100dvh - #{2 * tokens.$space-600}));
$dialog-uniform-height-tablet: var(--modal-preferred-height-tablet, min-content);
$dialog-uniform-max-height: min(var(--modal-max-height-mobile, 600px), calc(100dvh - #{2 * $padding-y}));
$dialog-uniform-max-height-tablet: min(var(--modal-max-height-tablet, 600px), calc(100dvh - #{2 * $padding-y}));

$dialog-docked-height: var(--modal-preferred-height-mobile, min-content);
$dialog-docked-margin-top: tokens.$space-1100;
$dialog-docked-expanded-height: calc(100dvh - #{$dialog-docked-margin-top});

// ModalHeader
$header-gap: tokens.$space-400;
Expand Down
Loading

0 comments on commit 31f9964

Please sign in to comment.