From 2b690617578320300d0e6f03f84c27ed3fff8891 Mon Sep 17 00:00:00 2001
From: Christina Yang <ychristina011@gmail.com>
Date: Mon, 6 Jan 2025 21:49:15 -0800
Subject: [PATCH] circled close button for alert

---
 src/commands/__tests__/delete.ts |  4 +-
 src/components/Alert.tsx         | 30 ++++++-------
 src/components/CloseButton.tsx   | 76 ++++++++++++++++++++++++++------
 src/components/PopupBase.tsx     |  6 +--
 4 files changed, 83 insertions(+), 33 deletions(-)

diff --git a/src/commands/__tests__/delete.ts b/src/commands/__tests__/delete.ts
index 82ff312c251..f70031028d1 100644
--- a/src/commands/__tests__/delete.ts
+++ b/src/commands/__tests__/delete.ts
@@ -35,8 +35,8 @@ describe('delete', () => {
 
       await act(vi.runAllTimersAsync)
 
-      const popupValue = await screen.findByTestId('alert')!
-      expect(popupValue.textContent).toBe('Permanently deleted test✕')
+      const popupValue = await screen.findByTestId('alert-content')!
+      expect(popupValue.textContent).toBe('Permanently deleted test')
     })
   })
 
diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx
index 6ed4b490ee4..580c781ed81 100644
--- a/src/components/Alert.tsx
+++ b/src/components/Alert.tsx
@@ -27,8 +27,6 @@ const Alert: FC = () => {
   const value = strip(alertStoreValue ?? alert?.value ?? '')
   const iconSize = useSelector(state => 0.78 * state.fontSize)
   const dispatch = useDispatch()
-  const paddingInline = useSelector(state => (state.fontSize * 16) / 14)
-  const paddingBlock = paddingInline * 0.75
 
   /** Dismiss the alert on close. */
   const onClose = useCallback(() => {
@@ -55,14 +53,7 @@ const Alert: FC = () => {
           <PopupBase
             disableTop
             cssRaw={css.raw({
-              position: 'fixed',
               boxSizing: 'border-box',
-              display: 'flex',
-              flexDirection: 'row',
-              justifyContent: 'center',
-              alignItems: 'flex-start',
-              padding: '12px 16px',
-              gap: '8px',
               bottom: '36px',
               left: '50%',
               transform: 'translateX(-50%)',
@@ -73,14 +64,23 @@ const Alert: FC = () => {
             })}
             ref={popupRef}
             key={value}
-            data-testid='alert-content'
-            onClose={onClose}
-            closeButtonSize='sm'
+            circledCloseButton
             showXOnHover
-            style={{ padding: `${paddingBlock}px ${paddingInline}px` }}
           >
-            {renderedIcon}
-            {value}
+            <div
+              data-testid='alert-content'
+              className={css({
+                gap: '12px',
+                display: 'flex',
+                flexDirection: 'row',
+                justifyContent: 'center',
+                alignItems: 'flex-start',
+                padding: '0.85em 1.1em',
+              })}
+            >
+              {renderedIcon}
+              {value}
+            </div>
           </PopupBase>
         </FadeTransition>
       ) : null}
diff --git a/src/components/CloseButton.tsx b/src/components/CloseButton.tsx
index 1c98812f133..d9b0ad4ca34 100644
--- a/src/components/CloseButton.tsx
+++ b/src/components/CloseButton.tsx
@@ -1,23 +1,25 @@
+import { PropsWithChildren } from 'react'
 import { useSelector } from 'react-redux'
 import { css, cx } from '../../styled-system/css'
 import { upperRightRecipe } from '../../styled-system/recipes'
 import { SystemStyleObject } from '../../styled-system/types'
 import fastClick from '../util/fastClick'
 
-/** A close button with an ✕. */
-const CloseButton = ({
-  onClose,
-  disableSwipeToDismiss,
-  size = 'md',
-  cssRaw,
-}: {
+type CloseButtonProps = {
   onClose: () => void
   disableSwipeToDismiss?: boolean
-  size?: 'sm' | 'md'
   cssRaw?: SystemStyleObject
-}) => {
-  const fontSize = useSelector(state => state.fontSize)
-  const padding = (fontSize / 2 + 2) / (size === 'sm' ? 2 : 1)
+  style?: React.CSSProperties
+}
+
+/** Base for a close button. */
+const BaseCloseButton = ({
+  onClose,
+  disableSwipeToDismiss,
+  cssRaw,
+  style,
+  children,
+}: PropsWithChildren<CloseButtonProps>) => {
   return (
     <a
       {...fastClick(onClose)}
@@ -33,14 +35,62 @@ const CloseButton = ({
           cssRaw,
         ),
       )}
-      style={{ fontSize: size === 'sm' ? fontSize / 2 : fontSize, padding: `${padding}px ${padding * 1.25}px` }}
+      style={style}
       aria-label={disableSwipeToDismiss ? 'no-swipe-to-dismiss' : undefined}
       data-testid='close-button'
       data-close-button
     >
-      ✕
+      {children}
     </a>
   )
 }
 
+/** A circled close button with an ✕. Sized using parent fontsize. */
+const CircledCloseButton = ({ cssRaw, ...props }: CloseButtonProps) => {
+  return (
+    <BaseCloseButton
+      {...props}
+      cssRaw={css.raw(
+        {
+          display: 'flex',
+          // inherit not yet supported by plugin
+          // eslint-disable-next-line @pandacss/no-hardcoded-color
+          color: 'inherit',
+          textDecoration: 'none',
+          borderRadius: '50%',
+          transform: 'translate(50%, -50%)',
+          // inherit not yet supported by plugin
+          // eslint-disable-next-line @pandacss/no-hardcoded-color
+          background: 'inherit',
+          border: 'inherit',
+        },
+        cssRaw,
+      )}
+    >
+      <svg width='1em' height='1em' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+        <path
+          d='M4.49642 12.1284L8.06394 8.56023L11.5039 12.0166L12.0802 11.4566L8.62388 8.00023L12.0802 4.54391L11.5039 3.98391L8.06394 7.44023L4.49642 3.87207L3.92017 4.43207L7.48833 8.00023L3.92017 11.5684L4.49642 12.1284Z'
+          fill='white'
+        />
+      </svg>
+    </BaseCloseButton>
+  )
+}
+
+/** A close button with an ✕. */
+const LetterCloseButton = (props: CloseButtonProps) => {
+  const fontSize = useSelector(state => state.fontSize)
+  const padding = fontSize / 2 + 2
+  return (
+    <BaseCloseButton {...props} style={{ fontSize, padding: `${padding}px ${padding * 1.25}px` }}>
+      ✕
+    </BaseCloseButton>
+  )
+}
+
+/** A close button with an ✕. */
+const CloseButton = ({ circled, ...props }: CloseButtonProps & { circled?: boolean }) => {
+  return circled ? <CircledCloseButton {...props} /> : <LetterCloseButton {...props} />
+}
+
 export default CloseButton
diff --git a/src/components/PopupBase.tsx b/src/components/PopupBase.tsx
index 9a75cd9a0d2..294c0727cbe 100644
--- a/src/components/PopupBase.tsx
+++ b/src/components/PopupBase.tsx
@@ -22,7 +22,7 @@ export type PopupBaseProps = PropsWithChildren<
     onClose?: () => void
     disableTop?: boolean
     cssRaw?: SystemStyleObject
-    closeButtonSize?: 'sm' | 'md'
+    circledCloseButton?: boolean
     showXOnHover?: boolean
   } & Omit<React.HTMLAttributes<HTMLDivElement>, 'className'>
 >
@@ -30,7 +30,7 @@ export type PopupBaseProps = PropsWithChildren<
 /** A popup component that can be dismissed. */
 const PopupBase = React.forwardRef<HTMLDivElement, PopupBaseProps>(
   (
-    { children, importFileId, onClose, cssRaw, style, disableTop = false, closeButtonSize, showXOnHover, ...props },
+    { children, importFileId, onClose, cssRaw, style, disableTop = false, circledCloseButton, showXOnHover, ...props },
     ref,
   ) => {
     const dispatch = useDispatch()
@@ -94,8 +94,8 @@ const PopupBase = React.forwardRef<HTMLDivElement, PopupBaseProps>(
         )}
         {onClose ? (
           <CloseButton
+            circled={circledCloseButton}
             cssRaw={showXOnHover ? { visibility: 'hidden' } : undefined}
-            size={closeButtonSize}
             onClose={onClose}
             disableSwipeToDismiss
           />