diff --git a/packages/web/src/scss/components/Toast/README.md b/packages/web/src/scss/components/Toast/README.md new file mode 100644 index 0000000000..e3b27367e5 --- /dev/null +++ b/packages/web/src/scss/components/Toast/README.md @@ -0,0 +1,12 @@ +# Toast + +Toast displays a brief, temporary notification that appears at a prescribed location of an application window. + +TODO + +### Accessibility + +The wrapping container has the [`role="status"`][mdn-role-status] to announce the toast to screen readers. It has an +implicit `aria-live` value of `polite` and an implicit `aria-atomic` value of `true`. + +[mdn-role-status]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/status_role diff --git a/packages/web/src/scss/components/Toast/_Toast.scss b/packages/web/src/scss/components/Toast/_Toast.scss new file mode 100644 index 0000000000..849e2525bb --- /dev/null +++ b/packages/web/src/scss/components/Toast/_Toast.scss @@ -0,0 +1,73 @@ +// 1. Spread the `Toast` container over the viewport. +// 2. On devices with rounded displays (like iPhone X and later), prefer the safe area value over our spacing, if +// bigger. The `viewport-fit="cover"` meta must be present in HTML for this to work. +// 3. Allow scrolling when the messages do not fit the screen. +// 4. Because the `Toast` container is present on the page all the time, we must allow interaction with the page content +// beneath it. +// 5. The toast queue element is in place so we can reverse the visual order of child toast messages when alignment-y is +// set to `bottom`. + +@use 'sass:map'; +@use '../../tools/breakpoint'; +@use 'theme'; + +.Toast { + --toast-padding-x: #{theme.$padding-x}; + --toast-padding-y: #{theme.$padding-y}; + + position: fixed; // 1. + inset: 0; // 1. + display: grid; + grid-auto-flow: row; + align-content: var(--toast-alignment-y); + justify-content: var(--toast-alignment-x); + + // 2. + padding: max(var(--toast-padding-y), env(safe-area-inset-top)) + max(var(--toast-padding-x), env(safe-area-inset-right)) max(var(--toast-padding-y), env(safe-area-inset-bottom)) + max(var(--toast-padding-x), env(safe-area-inset-left)); + overflow-y: auto; // 3. TODO + pointer-events: none; // 4. + + @include breakpoint.up(map.get(theme.$breakpoints, tablet)) { + --toast-padding-x: #{theme.$padding-x-tablet}; + } + + @include breakpoint.up(map.get(theme.$breakpoints, desktop)) { + --toast-padding-x: #{theme.$padding-x-desktop}; + } +} + +// 5. +.Toast__queue { + display: flex; + flex-direction: column; + row-gap: theme.$gap; + align-items: var(--toast-alignment-x); +} + +// TODO responsive +.Toast--top { + --toast-alignment-y: start; +} + +.Toast--bottom { + --toast-alignment-y: end; +} + +.Toast--left { + --toast-alignment-x: start; +} + +.Toast--center { + --toast-alignment-x: center; +} + +.Toast--right { + --toast-alignment-x: end; +} + +// 5. +.Toast--bottom > .Toast__queue { + flex-direction: column-reverse; +} diff --git a/packages/web/src/scss/components/Toast/_ToastBar.scss b/packages/web/src/scss/components/Toast/_ToastBar.scss new file mode 100644 index 0000000000..0568e60b17 --- /dev/null +++ b/packages/web/src/scss/components/Toast/_ToastBar.scss @@ -0,0 +1,47 @@ +// 1. Restore pointer events that have been disabled by parent `Toast` component. + +@use '../../tools/dictionaries'; +@use '../../tools/typography'; +@use 'theme'; + +.ToastBar { + display: grid; + column-gap: theme.$gap; + align-items: start; + max-width: theme.$bar-max-width; + padding: theme.$bar-padding; + border-radius: theme.$bar-border-radius; + box-shadow: theme.$bar-box-shadow; + pointer-events: initial; // 1. +} + +.ToastBar--dismissible { + grid-template-columns: 1fr auto; +} + +.ToastBar__content { + display: grid; + grid-template-columns: auto 1fr; + column-gap: theme.$bar-content-gap; + align-self: center; +} + +.ToastBar__message { + @include typography.generate(theme.$bar-typography); + + display: flex; + flex-wrap: wrap; + gap: theme.$bar-message-gap-y theme.$bar-message-gap-x; +} + +.ToastBar--dismissible .ToastBar__message > :is(a, button):last-child { + margin-inline-end: 8px; + font-weight: 400; +} + +.ToastBar--inverted { + color: theme.$bar-inverted-color; + background-color: theme.$bar-inverted-background; +} + +@include dictionaries.generate-colors('ToastBar', theme.$color-dictionary, theme.$color-dictionary-config); diff --git a/packages/web/src/scss/components/Toast/_theme.scss b/packages/web/src/scss/components/Toast/_theme.scss new file mode 100644 index 0000000000..71e3ec5226 --- /dev/null +++ b/packages/web/src/scss/components/Toast/_theme.scss @@ -0,0 +1,30 @@ +@use 'sass:list'; +@use '@tokens' as tokens; +@use '../../settings/dictionaries'; + +$breakpoints: tokens.$breakpoints; + +$gap: tokens.$space-600; +$padding-x: tokens.$space-700; +$padding-x-tablet: tokens.$space-800; +$padding-x-desktop: tokens.$space-900; +$padding-y: tokens.$space-700; + +$bar-typography: tokens.$body-small-text-bold; +$bar-max-width: 460px; +$bar-gap: tokens.$space-600; +$bar-padding: tokens.$space-600; +$bar-border-radius: tokens.$radius-200; +$bar-box-shadow: tokens.$shadow-400; +$bar-content-gap: tokens.$space-500; +$bar-message-gap-x: tokens.$space-700; +$bar-message-gap-y: tokens.$space-500; + +$bar-inverted-color: tokens.$text-primary-inverted-default; +$bar-inverted-background: tokens.$background-inverted; + +$color-dictionary: dictionaries.$emotion-colors; +$color-dictionary-config: ( + color: tokens.$text-primary-inverted-default, + background-color: 'default', +); diff --git a/packages/web/src/scss/components/Toast/index.html b/packages/web/src/scss/components/Toast/index.html new file mode 100644 index 0000000000..e999e7dc21 --- /dev/null +++ b/packages/web/src/scss/components/Toast/index.html @@ -0,0 +1,342 @@ +{{#> layout/plain }} + + + + Alignment + + + + + + + + + + + Top + + + + Bottom + + + + + + Left + + + + Center + + + + Right + + + + + + + + + On mobile, tap into this text field to see how Toast position updates: + + + + + + + + + + + + + + + I was first! + Action + + + + + + + Close + + + + + + + + + + I appeared later. This toast has a long message that wraps automatically. + Action + + + + + + + Close + + + + + + + + + + + + + Content Variations + + + + + + + Message only + + + + + + + + Message with action + Action + + + + + + + + When the text is longer, the action automatically wraps to the next line + Action + + + + + + + + + + + Message with action and icon + Action + + + + + + + + Dismissible message + + + + + + + Close + + + + + + + + + + Dismissible message with icon and action + Action + + + + + + + Close + + + + + + + + + Colors + + + + + + + + + + Inverted + Action + + + + + + + Close + + + + + + + + + + Informative + Action + + + + + + + Close + + + + + + + + + + Success + Action + + + + + + + Close + + + + + + + + + + Warning + Action + + + + + + + Close + + + + + + + + + + Danger + Action + + + + + + + Close + + + + + + + +{{/layout/plain}} diff --git a/packages/web/src/scss/components/Toast/index.scss b/packages/web/src/scss/components/Toast/index.scss new file mode 100644 index 0000000000..cb2d09399d --- /dev/null +++ b/packages/web/src/scss/components/Toast/index.scss @@ -0,0 +1,2 @@ +@forward 'Toast'; +@forward 'ToastBar'; diff --git a/packages/web/src/scss/components/index.scss b/packages/web/src/scss/components/index.scss index 8e4644a793..b6ac714359 100644 --- a/packages/web/src/scss/components/index.scss +++ b/packages/web/src/scss/components/index.scss @@ -22,4 +22,5 @@ @forward 'Tag'; @forward 'TextArea'; @forward 'TextField'; +@forward 'Toast'; @forward 'Tooltip';