Merge pull request #16769 from overleaf/jel-scroll-to-notification

[web] Add scroll to notification component

GitOrigin-RevId: 096f9f42344729464e7fb38e4f6542cb2e891918
This commit is contained in:
Jessica Lawshe 2024-01-29 09:15:38 -06:00 committed by Copybot
parent 71ca6f05b7
commit f79f534d9f
4 changed files with 54 additions and 10 deletions

View file

@ -0,0 +1,46 @@
import Notification, {
NotificationProps,
} from '@/shared/components/notification'
import { useEffect } from 'react'
function elementIsInView(el: HTMLElement) {
const scroll = window.scrollY
const boundsTop = el.getBoundingClientRect().top + scroll
const viewport = {
top: scroll,
bottom: scroll + window.innerHeight,
}
const bounds = {
top: boundsTop,
bottom: boundsTop + el.clientHeight,
}
return (
(bounds.bottom >= viewport.top && bounds.bottom <= viewport.bottom) ||
(bounds.top <= viewport.bottom && bounds.top >= viewport.top)
)
}
function NotificationScrolledTo({ ...props }: NotificationProps) {
useEffect(() => {
if (props.id) {
const alert = document.getElementById(props.id)
if (alert && !elementIsInView(alert)) {
alert.scrollIntoView({ behavior: 'smooth' })
}
}
}, [props])
const notificationProps = { ...props }
if (!notificationProps.className) {
notificationProps.className = ''
}
notificationProps.className = `${notificationProps.className} notification-with-scroll-margin`
return <Notification {...notificationProps} />
}
export default NotificationScrolledTo

View file

@ -10,7 +10,7 @@ export type NotificationType =
| 'error' | 'error'
| 'offer' | 'offer'
type NotificationProps = { export type NotificationProps = {
action?: React.ReactElement action?: React.ReactElement
ariaLive?: 'polite' | 'off' | 'assertive' ariaLive?: 'polite' | 'off' | 'assertive'
className?: string className?: string
@ -21,6 +21,7 @@ type NotificationProps = {
onDismiss?: () => void onDismiss?: () => void
title?: string title?: string
type: NotificationType type: NotificationType
id?: string
} }
function NotificationIcon({ function NotificationIcon({
@ -57,6 +58,7 @@ function Notification({
onDismiss, onDismiss,
title, title,
type, type,
id,
}: NotificationProps) { }: NotificationProps) {
type = type || 'info' type = type || 'info'
const { t } = useTranslation() const { t } = useTranslation()
@ -83,6 +85,7 @@ function Notification({
className={notificationClassName} className={notificationClassName}
aria-live={ariaLive || 'off'} aria-live={ariaLive || 'off'}
role="alert" role="alert"
id={id}
> >
<NotificationIcon notificationType={type} customIcon={customIcon} /> <NotificationIcon notificationType={type} customIcon={customIcon} />

View file

@ -192,15 +192,6 @@ input[type='date'] {
margin-bottom: @form-group-margin-bottom; margin-bottom: @form-group-margin-bottom;
} }
// Form alert
//
// adds top padding/mergin so that element has spacing when scrolled into
// view, but no vertical spacing is applied to layout
.form-group#form-alert {
padding-top: @form-group-margin-bottom;
margin-top: -@form-group-margin-bottom;
}
// Checkboxes and radios // Checkboxes and radios
// //
// Indent the labels to position radios/checkboxes as hanging controls. // Indent the labels to position radios/checkboxes as hanging controls.

View file

@ -180,6 +180,10 @@
} }
} }
.notification-with-scroll-margin {
scroll-margin: 16px;
}
.notification-list { .notification-list {
.notification { .notification {
margin-bottom: @margin-md; margin-bottom: @margin-md;