From a94260eb4235e1436012b1fb7bd3fbd9aa20c3bf Mon Sep 17 00:00:00 2001 From: mkrause Date: Thu, 2 Jan 2025 20:38:59 +0100 Subject: [PATCH] Work on DialogModal. --- .../DialogModal/DialogModal.module.scss | 127 +++++++++++++----- .../DialogModal/DialogModal.stories.tsx | 15 ++- .../overlays/DialogModal/DialogModal.tsx | 6 +- .../overlays/ModalProvider/ModalProvider.tsx | 25 +++- .../Dialog}/useControlledDialog.ts | 0 src/util/hooks/useDebounce.ts | 36 +++++ 6 files changed, 171 insertions(+), 38 deletions(-) rename src/components/{overlays/ModalProvider => util/Dialog}/useControlledDialog.ts (100%) create mode 100644 src/util/hooks/useDebounce.ts diff --git a/src/components/overlays/DialogModal/DialogModal.module.scss b/src/components/overlays/DialogModal/DialogModal.module.scss index 67ecb85..8faefb4 100644 --- a/src/components/overlays/DialogModal/DialogModal.module.scss +++ b/src/components/overlays/DialogModal/DialogModal.module.scss @@ -4,68 +4,99 @@ @use '../../../styling/defs.scss' as bk; -@mixin dialog-entry-exit($entry-dur: 200ms, $exit-dur: 180ms) { +// Note: `$blur` should be in px, not rem (blur effect should be independent of any font size scaling) +@mixin dialog-backdrop($entry-dur: 200ms, $exit-dur: 180ms, $blur: 2px) { + &::backdrop { + transition-property: overlay, display, background-color, backdrop-filter; + transition-timing-function: linear; + transition-behavior: allow-discrete; + + // Open state styling + background-color: light-dark(rgb(0 0 0 / 10%), rgb(0 0 0 / 20%)); + backdrop-filter: blur($blur) opacity(1); + + // Entry transition styling + transition-duration: $entry-dur; + @starting-style { + background-color: transparent; + backdrop-filter: blur($blur) opacity(0); + } + } + // Exit transition styling + // Note: cannot nest the following inside `::backdrop`, since `:not()` needs to apply to the dialog not `::backdrop` + &:not([open])::backdrop { + transition-duration: $exit-dur; + background-color: transparent; + backdrop-filter: blur($blur) opacity(0); + } +} + +@mixin dialog-fade-in($entry-dur: 200ms, $exit-dur: 180ms, $scale: 1) { transition-property: overlay, display, opacity, scale; + transition-timing-function: ease-out; transition-behavior: allow-discrete; - transition-duration: $exit-dur; // Exit - transition-timing-function: ease-out; // Exit // Open state styling opacity: 1; scale: 1; // Entry transition styling + transition-duration: $entry-dur; @starting-style { - transition-duration: $entry-dur; - transition-timing-function: ease-in; opacity: 0; - scale: 1.1; + scale: $scale; } // Exit transition styling &:not([open]) { + transition-duration: $exit-dur; + transition-timing-function: ease-in; opacity: 0; - scale: 0.98; + scale: calc(1 - (($scale - 1) * 0.2)); // Multiply by 0.2 to make the scale out effect a bit more subtle } +} + +@mixin dialog-slide-over($entry-dur: 200ms, $exit-dur: 180ms) { + transition-property: overlay, display, translate; + transition-timing-function: ease-out; + transition-behavior: allow-discrete; + // Open state styling + translate: 0; - $blur: 2px; // Should be in px, not rem (blur effect should be constant) - &::backdrop { - transition-property: overlay, display, background-color, backdrop-filter; - transition-behavior: allow-discrete; - transition-duration: $exit-dur; // Exit - transition-timing-function: ease-out; // Exit - - // Open state styling - background-color: light-dark(rgb(0 0 0 / 10%), rgb(0 0 0 / 20%)); - backdrop-filter: blur($blur) opacity(1); - - // Entry transition styling - @starting-style { - transition-duration: $entry-dur; - transition-timing-function: ease-in; - background-color: transparent; - backdrop-filter: blur($blur) opacity(0); - } + // Entry transition styling + transition-duration: $entry-dur; + @starting-style { + translate: 80vw 0; } + // Exit transition styling - // Note: cannot nest the following inside `::backdrop`, since `:not()` needs to apply to the dialog not `::backdrop` - &:not([open])::backdrop { - background-color: transparent; - backdrop-filter: blur($blur) opacity(0); + &:not([open]) { + transition-duration: $exit-dur; + transition-timing-function: ease-in; + translate: 80vw 0; } } + @layer baklava.components { .bk-dialog-modal { @include bk.component-base(bk-dialog-modal); + --bk-dialog-modal-entry-dur: 200ms; + --bk-dialog-modal-exit-dur: 180ms; + --bk-dialog-modal-fade-scale: 1; + // Basic modal positioning position: fixed; inset: 0; + max-width: 100vw; + max-height: 100vh; - // Entry and exit animations - @include dialog-entry-exit; + @include dialog-backdrop( + $entry-dur: var(--bk-dialog-modal-entry-dur), + $exit-dur: var(--bk-dialog-modal-exit-dur), + ); // @@ -76,6 +107,26 @@ margin: auto; width: fit-content; height: fit-content; + + @include dialog-fade-in( + $entry-dur: var(--bk-dialog-modal-entry-dur), + $exit-dur: var(--bk-dialog-modal-exit-dur), + $scale: var(--bk-dialog-modal-fade-scale), + ); + + max-height: calc(100vh - 5vw); // The larger the viewport width, the more breathing room in height + &.bk-dialog-modal--small { + --bk-dialog-modal-fade-scale: 1.05; + max-width: 30vw; + } + &.bk-dialog-modal--medium { + --bk-dialog-modal-fade-scale: 1.04; + max-width: 50vw; + } + &.bk-dialog-modal--large { + --bk-dialog-modal-fade-scale: 1.03; + max-width: 60vw; + } } &.bk-dialog-modal--full-screen { @@ -84,6 +135,13 @@ margin: 0; width: auto; height: auto; + + --bk-dialog-modal-entry-dur: 120ms; + --bk-dialog-modal-exit-dur: 120ms; + @include dialog-fade-in( + $entry-dur: var(--bk-dialog-modal-entry-dur), + $exit-dur: var(--bk-dialog-modal-exit-dur), + ); } &.bk-dialog-modal--slide-over { @@ -93,6 +151,13 @@ height: auto; margin-inline-start: 20vw; + + --bk-dialog-modal-entry-dur: 300ms; + --bk-dialog-modal-exit-dur: 250ms; + @include dialog-slide-over( + $entry-dur: var(--bk-dialog-modal-entry-dur), + $exit-dur: var(--bk-dialog-modal-exit-dur), + ); } } } diff --git a/src/components/overlays/DialogModal/DialogModal.stories.tsx b/src/components/overlays/DialogModal/DialogModal.stories.tsx index 6fed7c7..f7fa17f 100644 --- a/src/components/overlays/DialogModal/DialogModal.stories.tsx +++ b/src/components/overlays/DialogModal/DialogModal.stories.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { LoremIpsum } from '../../../util/storybook/LoremIpsum.tsx'; import { Button } from '../../actions/Button/Button.tsx'; @@ -24,12 +25,24 @@ export default { args: { title: 'Modal dialog', trigger: ({ active, activate }) =>