Repository: equinor/design-system (ts)

Issue:
💄 Snackbar: use [popover]
If a snackbar is triggered while a dialog is open, it will render below the scrim and be hard to read. We should use the `popover` attribute to render it in the top-layer

Pull request description:
💄 Snackbar: implement [popover]
resolves #3576 

Patch:
diff --git a/packages/eds-core-react/src/components/Dialog/Dialog.stories.tsx b/packages/eds-core-react/src/components/Dialog/Dialog.stories.tsx
index 98242227..8558f28e 100644
--- a/packages/eds-core-react/src/components/Dialog/Dialog.stories.tsx
+++ b/packages/eds-core-react/src/components/Dialog/Dialog.stories.tsx
@@ -1,6 +1,14 @@
 import { useState } from 'react'
 import { useArgs } from '@storybook/preview-api'
-import { Dialog, DialogProps, Button, Radio, Typography, Tooltip } from '../..'
+import {
+  Dialog,
+  DialogProps,
+  Button,
+  Radio,
+  Typography,
+  Tooltip,
+  Snackbar,
+} from '../..'
 import styled from 'styled-components'
 import { StoryFn, Meta } from '@storybook/react'
 import { Stack } from './../../../.storybook/components'
@@ -99,6 +107,7 @@ export const Introduction: StoryFn<DialogProps> = (args) => {
 
 export const Dismissable: StoryFn<DialogProps> = () => {
   const [isOpen, setIsOpen] = useState(false)
+  const [snackbar, setSnackbar] = useState(false)
   const handleOpen = () => {
     setIsOpen(true)
   }
@@ -110,6 +119,13 @@ export const Dismissable: StoryFn<DialogProps> = () => {
       <Button aria-haspopup="dialog" onClick={handleOpen}>
         Trigger Dialog
       </Button>
+      <Snackbar
+        open={snackbar}
+        onClose={() => setSnackbar(false)}
+        autoHideDuration={2000}
+      >
+        Snackbar in front of scrim!
+      </Snackbar>
       <Dialog open={isOpen} isDismissable onClose={handleClose}>
         <Dialog.Header>
           <Dialog.Title>Dismissable dialog</Dialog.Title>
@@ -118,6 +134,9 @@ export const Dismissable: StoryFn<DialogProps> = () => {
           <Typography variant="body_short">
             Closes dialog on click outside and escape key.
           </Typography>
+          <Button variant="outlined" onClick={() => setSnackbar(true)}>
+            Show a snackbar
+          </Button>
         </Dialog.CustomContent>
         <Dialog.Actions>
           <Wrapper>
diff --git a/packages/eds-core-react/src/components/Snackbar/Snackbar.tsx b/packages/eds-core-react/src/components/Snackbar/Snackbar.tsx
index c935489e..dfa26f75 100644
--- a/packages/eds-core-react/src/components/Snackbar/Snackbar.tsx
+++ b/packages/eds-core-react/src/components/Snackbar/Snackbar.tsx
@@ -1,5 +1,4 @@
-import { useState, useEffect, HTMLAttributes, forwardRef, useRef } from 'react'
-import * as ReactDom from 'react-dom'
+import { useEffect, HTMLAttributes, forwardRef, useRef, useState } from 'react'
 import styled, { css, ThemeProvider } from 'styled-components'
 import { snackbar as SnackbarToken } from './Snackbar.tokens'
 import {
@@ -12,6 +11,7 @@ import { Paper } from '../Paper'
 import { useEds } from '../EdsProvider'
 
 type StyledProps = {
+  popover: string
   $placement?:
     | 'left'
     | 'right'
@@ -23,17 +23,16 @@ type StyledProps = {
     | 'bottom-right'
 }
 
-const StyledSnackbar = styled(Paper)<StyledProps>(({ theme, $placement }) => {
+const PopoverDiv = styled('div').withConfig({
+  shouldForwardProp: () => true, //workaround to avoid warning until popover gets added to react types
+})<StyledProps>(({ theme, $placement }) => {
   return css`
+    inset: unset;
+    border: 0;
+    overflow: visible;
     position: fixed;
-    background-color: ${theme.background};
-    ${spacingsTemplate(theme.spacings)}
-    ${bordersTemplate(theme.border)}
-    ${typographyTemplate(theme.typography)}
-    min-height: ${theme.minHeight};
-    box-sizing: border-box;
-    z-index: 1400;
-
+    padding: 0;
+    background-color: transparent;
     ${{
       top: $placement.includes('top')
         ? theme.spacings.top
@@ -54,7 +53,20 @@ const StyledSnackbar = styled(Paper)<StyledProps>(({ theme, $placement }) => {
             ? 'translateX(-50%)'
             : undefined,
     }}
+    &::backdrop {
+      background-color: transparent;
+    }
+  `
+})
 
+const StyledSnackbar = styled(Paper)(({ theme }) => {
+  return css`
+    background-color: ${theme.background};
+    ${spacingsTemplate(theme.spacings)}
+    ${bordersTemplate(theme.border)}
+    ${typographyTemplate(theme.typography)}
+    min-height: ${theme.minHeight};
+    box-sizing: border-box;
     a,
     button {
       color: ${theme.entities.button.typography.color};
@@ -107,11 +119,10 @@ export const Snackbar = forwardRef<HTMLDivElement, SnackbarProps>(
         }, autoHideDuration)
       }
       return () => clearTimeout(timer.current)
-    }, [open, autoHideDuration, setVisible, onClose])
+    }, [open, setVisible, autoHideDuration, onClose])
 
     const props = {
       ref,
-      $placement: placement,
       ...rest,
     }
     const { density } = useEds()
@@ -119,13 +130,17 @@ export const Snackbar = forwardRef<HTMLDivElement, SnackbarProps>(
 
     return (
       <ThemeProvider theme={token}>
-        {visible &&
-          ReactDom.createPortal(
+        {visible && (
+          <PopoverDiv
+            popover="manual"
+            $placement={placement}
+            ref={(el) => el?.showPopover()}
+          >
             <StyledSnackbar role="alert" elevation="overlay" {...props}>
               {children}
-            </StyledSnackbar>,
-            document.body,
-          )}
+            </StyledSnackbar>
+          </PopoverDiv>
+        )}
       </ThemeProvider>
     )
   },


Test patch:
diff --git a/packages/eds-core-react/src/components/Snackbar/Snackbar.test.tsx b/packages/eds-core-react/src/components/Snackbar/Snackbar.test.tsx
index 8690fd77..b52017f9 100644
--- a/packages/eds-core-react/src/components/Snackbar/Snackbar.test.tsx
+++ b/packages/eds-core-react/src/components/Snackbar/Snackbar.test.tsx
@@ -84,10 +84,15 @@ describe('Snackbar', () => {
         {message}
       </Snackbar>,
     )
-    expect(screen.getByTestId('test-snackbar-3')).toHaveStyleRule(
+    // eslint-disable-next-line testing-library/no-node-access
+    expect(screen.getByTestId('test-snackbar-3').parentElement).toHaveStyleRule(
       'top',
       tokens.spacings.top,
     )
-    expect(screen.getByTestId('test-snackbar-3')).toHaveStyleRule('left', '50%')
+    // eslint-disable-next-line testing-library/no-node-access
+    expect(screen.getByTestId('test-snackbar-3').parentElement).toHaveStyleRule(
+      'left',
+      '50%',
+    )
   })
 })
diff --git a/packages/eds-core-react/src/components/Snackbar/__snapshots__/Snackbar.test.tsx.snap b/packages/eds-core-react/src/components/Snackbar/__snapshots__/Snackbar.test.tsx.snap
index 4583dee5..6580d032 100644
--- a/packages/eds-core-react/src/components/Snackbar/__snapshots__/Snackbar.test.tsx.snap
+++ b/packages/eds-core-react/src/components/Snackbar/__snapshots__/Snackbar.test.tsx.snap
@@ -7,7 +7,6 @@ exports[`Snackbar Matches snapshot 1`] = `
 }
 
 .c1 {
-  position: fixed;
   background-color: var(--eds_ui_background__overlay, rgba(0, 0, 0, 0.8));
   padding-left: 16px;
   padding-top: 16px;
@@ -23,10 +22,6 @@ exports[`Snackbar Matches snapshot 1`] = `
   text-align: left;
   min-height: 48px;
   box-sizing: border-box;
-  z-index: 1400;
-  bottom: 16px;
-  left: 50%;
-  transform: translateX(-50%);
 }
 
 .c1 a,