Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1.5 Alert #2746

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2a0446c
new alert styling + fix tests
yangchristina Dec 29, 2024
2040b71
rename alert color tokens
yangchristina Jan 2, 2025
08813fc
extract out PopupBase and use it in Alert + Popup
yangchristina Jan 2, 2025
10adfa3
show x on hover
yangchristina Jan 2, 2025
ed9cac4
scale padding with fontsize + update snapshot
yangchristina Jan 3, 2025
84c7405
clean up Icon
yangchristina Jan 7, 2025
9d7c925
set icon cursor to default
yangchristina Jan 7, 2025
a688a75
circled close button for alert
yangchristina Jan 7, 2025
6553273
only show x on alert.showCloseLink
yangchristina Jan 11, 2025
2cd803d
allow position from bottom with usePositionFixed
yangchristina Jan 14, 2025
84ae112
remove unnecessary check
yangchristina Jan 14, 2025
a0cb0e3
pass in height for usePositionFixed position calculations
yangchristina Jan 17, 2025
56dd466
remove use `transform` for centering alert, using margin instead.
yangchristina Jan 17, 2025
e55b3c8
swipe down to dismiss alert
yangchristina Jan 19, 2025
b8565d5
Update src/components/PopupBase.tsx
yangchristina Jan 23, 2025
4691e26
Correct comment capitalization in PopupBaseProps type definition
yangchristina Jan 27, 2025
91c36cb
handle mobile keyboard covering alert
yangchristina Jan 27, 2025
32028e0
change close button dimensions + svg
yangchristina Jan 27, 2025
c937dae
refactor usePositionFixed to utilize viewport store for keyboard heig…
yangchristina Jan 28, 2025
7e3cb6a
increase padding on close button
yangchristina Jan 30, 2025
1954da0
add currentKeyboardHeight
yangchristina Jan 30, 2025
1ca6d5d
fix: alert position calculation for position absolute
yangchristina Feb 8, 2025
e06af0a
fix: extract a safariKeyboardStore and use closing state to hide the …
trevinhofmann Feb 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,8 @@ export default defineConfig({
},
},
fontSizes: {
sm: {
value: '80%',
},
md: {
value: '90%',
},
sm: { value: '80%' },
md: { value: '90%' },
},
spacing: {
modalPadding: { value: '8%' },
Expand Down
4 changes: 4 additions & 0 deletions src/colors.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ const colors = {
pinkAgainstFg: 'rgba(233, 12, 89, 1)',
brightBlue: 'rgba(70, 223, 240, 1)', // #46dff0
exportTextareaColor: 'rgba(170, 170, 170, 1)', // #aaa, also used in anchorButton
panelBorder: 'rgba(36, 36, 36, 1)',
panelBg: 'rgba(23, 23, 23, 1)', // #171717
},
light: {
// Background colors in capacitor app needs to be in hexadecimal codes
Expand Down Expand Up @@ -128,6 +130,8 @@ const colors = {
pinkAgainstFg: 'rgba(227, 179, 196, 1)',
brightBlue: 'rgba(70, 223, 240, 1)', // #46dff0
exportTextareaColor: 'rgba(85, 85, 85, 1)',
panelBorder: 'rgba(219, 219, 219, 1)',
panelBg: 'rgba(232, 232, 232, 1)', // #171717
},
} as const

Expand Down
2 changes: 1 addition & 1 deletion src/commands/__tests__/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('delete', () => {

await act(vi.runAllTimersAsync)

const popupValue = await screen.findByTestId('popup-value')!
const popupValue = await screen.findByTestId('alert-content')!
expect(popupValue.textContent).toBe('Permanently deleted test')
})
})
Expand Down
99 changes: 52 additions & 47 deletions src/components/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
import React, { FC, useCallback, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'
import { TransitionGroup } from 'react-transition-group'
import { css, cx } from '../../styled-system/css'
import { anchorButtonRecipe } from '../../styled-system/recipes'
import { css } from '../../styled-system/css'
import { token } from '../../styled-system/tokens'
import { alertActionCreator } from '../actions/alert'
import { redoActionCreator as redo } from '../actions/redo'
import { undoActionCreator as undo } from '../actions/undo'
import { AlertType } from '../constants'
import isUndoEnabled from '../selectors/isUndoEnabled'
import alertStore from '../stores/alert'
import fastClick from '../util/fastClick'
import strip from '../util/strip'
import FadeTransition from './FadeTransition'
import Popup from './Popup'
import PopupBase from './PopupBase'
import RedoIcon from './RedoIcon'
import UndoIcon from './UndoIcon'

const alertToIcon: { [key in AlertType]?: typeof UndoIcon } = {
[AlertType.Undo]: UndoIcon,
[AlertType.Redo]: RedoIcon,
}

/** An alert component that fades in and out. */
const Alert: FC = () => {
const popupRef = useRef<HTMLDivElement>(null)
const [isDismissed, setDismiss] = useState(false)
const dispatch = useDispatch()
const alert = useSelector(state => state.alert)
const alertStoreValue = alertStore.useState()
const value = strip(alertStoreValue ?? alert?.value ?? '')
const undoEnabled = useSelector(isUndoEnabled)
const redoEnabled = useSelector(state => state.redoPatches.length > 0)
const fontSize = useSelector(state => state.fontSize)
const iconSize = 0.78 * fontSize
const iconSize = useSelector(state => 0.78 * state.fontSize)
const dispatch = useDispatch()

/** Dismiss the alert on close. */
const onClose = useCallback(() => {
Expand All @@ -37,36 +35,10 @@ const Alert: FC = () => {
dispatch(alertActionCreator(null))
}, [alert, dispatch])

const undoOrRedo = alert?.alertType === AlertType.Undo || alert?.alertType === AlertType.Redo
const buttons = undoOrRedo ? (
<div className={css({ marginTop: '0.5em' })}>
<a
className={cx(anchorButtonRecipe({ small: true, isDisabled: !undoEnabled }), css({ margin: '0.25em' }))}
{...fastClick(() => {
dispatch(undo())
})}
>
<UndoIcon
size={iconSize}
fill={token('colors.bg')}
cssRaw={css.raw({ position: 'relative', top: '0.25em', right: '0.25em' })}
/>
Undo
</a>
<a
className={cx(anchorButtonRecipe({ small: true, isDisabled: !redoEnabled }), css({ margin: '0.25em' }))}
{...fastClick(() => {
dispatch(redo())
})}
>
Redo
<RedoIcon
size={iconSize}
fill={token('colors.bg')}
cssRaw={css.raw({ position: 'relative', top: '0.25em', left: '0.25em' })}
/>
</a>
</div>
const alertType = alert?.alertType
const Icon = alertType ? alertToIcon[alertType] : null
const renderedIcon = Icon ? (
<Icon cssRaw={css.raw({ cursor: 'default' })} size={iconSize} fill={token('colors.fg')} />
) : null

// if dismissed, set timeout to 0 to remove alert component immediately. Otherwise it will block toolbar interactions until the timeout completes.
Expand All @@ -78,10 +50,43 @@ const Alert: FC = () => {
{alert ? (
<FadeTransition duration='slow' nodeRef={popupRef} onEntering={() => setDismiss(false)}>
{/* Specify a key to force the component to re-render and thus recalculate useSwipeToDismissProps when the alert changes. Otherwise the alert gets stuck off screen in the dismiss state. */}
<Popup {...alert} ref={popupRef} onClose={onClose} key={value}>
{value}
{buttons}
</Popup>
<PopupBase
anchorFromBottom
anchorOffset={36}
cssRaw={css.raw({
boxSizing: 'border-box',
background: 'panelBg',
border: '1px solid {colors.panelBorder}',
borderRadius: '8px',
zIndex: 'popup',
marginInline: 'auto',
left: 0,
right: 0,
width: 'max-content',
})}
ref={popupRef}
key={value}
circledCloseButton
showXOnHover
onClose={alert.showCloseLink ? onClose : undefined}
calculatedHeight={popupRef.current?.getBoundingClientRect().height || 50}
swipeDownToDismiss
>
<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}
</TransitionGroup>
Expand Down
88 changes: 75 additions & 13 deletions src/components/CloseButton.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,96 @@
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 }: { onClose: () => void; disableSwipeToDismiss?: boolean }) => {
const fontSize = useSelector(state => state.fontSize)
const padding = fontSize / 2 + 2
type CloseButtonProps = {
onClose: () => void
disableSwipeToDismiss?: boolean
cssRaw?: SystemStyleObject
style?: React.CSSProperties
}

/** Base for a close button. */
const BaseCloseButton = ({
onClose,
disableSwipeToDismiss,
cssRaw,
style,
children,
}: PropsWithChildren<CloseButtonProps>) => {
return (
<a
{...fastClick(onClose)}
className={cx(
upperRightRecipe(),
css({
fontSize: 'sm',
css(
{
// inherit not yet supported by plugin
// eslint-disable-next-line @pandacss/no-hardcoded-color
color: 'inherit',
textDecoration: 'none',
},
cssRaw,
),
)}
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',
justifyContent: 'center',
alignItems: 'center',
// inherit not yet supported by plugin
// eslint-disable-next-line @pandacss/no-hardcoded-color
color: 'inherit',
right: '0',
textDecoration: 'none',
top: '0',
}),
borderRadius: '50%',
transform: 'translate(50%, -50%)',
// inherit not yet supported by plugin
// eslint-disable-next-line @pandacss/no-hardcoded-color
background: 'inherit',
border: 'inherit',
padding: '0.5em',
},
cssRaw,
)}
style={{ fontSize, padding: `${padding}px ${padding * 1.25}px` }}
aria-label={disableSwipeToDismiss ? 'no-swipe-to-dismiss' : undefined}
data-testid='close-button'
>
<svg fill='currentColor' width='0.5em' height='0.5em' xmlns='http://www.w3.org/2000/svg' viewBox='1 0.5 7 7'>
<path d='M1.64877 0.515015C1.48064 0.515015 1.31939 0.58189 1.20064 0.700647C0.953139 0.948151 0.953139 1.3494 1.20064 1.5969L3.60384 4.00058L1.20064 6.40378C0.953139 6.65129 0.953139 7.05253 1.20064 7.30004C1.44815 7.54692 1.84939 7.54692 2.0969 7.30004L4.50058 4.89636L6.90378 7.30004C7.15128 7.54692 7.55191 7.54692 7.79941 7.30004C8.04692 7.05253 8.04692 6.65129 7.79941 6.40378L5.39621 4.00058L7.79941 1.5969C8.04692 1.3494 8.04692 0.948151 7.79941 0.700647C7.68066 0.581896 7.51941 0.515015 7.35191 0.515015C7.18378 0.515015 7.02253 0.58189 6.90378 0.700647L4.50058 3.10433L2.0969 0.700647C1.97752 0.581896 1.8169 0.515015 1.64877 0.515015Z'></path>
</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` }}>
</a>
</BaseCloseButton>
)
}

/** A close button with an ✕. */
const CloseButton = ({ circled, ...props }: CloseButtonProps & { circled?: boolean }) => {
return circled ? <CircledCloseButton {...props} /> : <LetterCloseButton {...props} />
}

export default CloseButton
76 changes: 12 additions & 64 deletions src/components/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,24 @@
import React, { PropsWithChildren } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import React from 'react'
import { useSelector } from 'react-redux'
import { css } from '../../styled-system/css'
import { SystemStyleObject } from '../../styled-system/types'
import { alertActionCreator as alert } from '../actions/alert'
import { clearMulticursorsActionCreator as clearMulticursors } from '../actions/clearMulticursors'
import { deleteResumableFile } from '../actions/importFiles'
import { isTouch } from '../browser'
import { AlertType } from '../constants'
import useCombinedRefs from '../hooks/useCombinedRefs'
import usePositionFixed from '../hooks/usePositionFixed'
import useSwipeToDismiss from '../hooks/useSwipeToDismiss'
import syncStatusStore from '../stores/syncStatus'
import fastClick from '../util/fastClick'
import CloseButton from './CloseButton'
import PopupBase, { PopupBaseProps } from './PopupBase'

/** A popup component that can be dismissed. */
const Popup = React.forwardRef<
HTMLDivElement,
PropsWithChildren<{
// used to cancel imports
importFileId?: string
/** If defined, will show a small x in the upper right corner. */
onClose?: () => void
{
textAlign?: 'center' | 'left' | 'right'
value?: string | null
cssRaw?: SystemStyleObject
}>
>(({ children, importFileId, onClose, textAlign = 'center', cssRaw }, ref) => {
const dispatch = useDispatch()

const fontSize = useSelector(state => state.fontSize)
} & Omit<PopupBaseProps, 'className'>
>(({ children, textAlign = 'center', cssRaw, style, ...props }, ref) => {
const padding = useSelector(state => state.fontSize / 2 + 2)
const multicursor = useSelector(state => state.alert?.alertType === AlertType.MulticursorActive)
const positionFixedStyles = usePositionFixed()
const useSwipeToDismissProps = useSwipeToDismiss({
// dismiss after animation is complete to avoid touch events going to the Toolbar
onDismissEnd: () => {
dispatch(alert(null))
},
})

const combinedRefs = useCombinedRefs(isTouch ? [useSwipeToDismissProps.ref, ref] : [ref])

return (
<div
className={css(
<PopupBase
ref={ref}
cssRaw={css.raw(
{
boxShadow: 'none',
border: 'none',
Expand All @@ -61,44 +35,18 @@ const Popup = React.forwardRef<
},
cssRaw,
)}
{...(isTouch ? useSwipeToDismissProps : null)}
ref={combinedRefs}
// merge style with useSwipeToDismissProps.style (transform, transition, and touchAction for sticking to user's touch)
style={{
...positionFixedStyles,
fontSize,
// scale with font size to stay vertically centered over toolbar
padding: `${padding}px 0 ${padding}px`,
textAlign,
...(isTouch ? useSwipeToDismissProps.style : null),
...style,
}}
{...props}
>
<div data-testid='popup-value' className={css({ padding: '0.25em', backgroundColor: 'bgOverlay80' })}>
{children}
</div>
{importFileId && (
<a
onClick={() => {
deleteResumableFile(importFileId!)
syncStatusStore.update({ importProgress: 1 })
onClose?.()
}}
>
cancel
</a>
)}
{multicursor && (
<a
{...fastClick(() => {
dispatch(clearMulticursors())
onClose?.()
})}
>
cancel
</a>
)}
{onClose ? <CloseButton onClose={onClose} disableSwipeToDismiss /> : null}
</div>
</PopupBase>
)
})

Expand Down
Loading