From 5c631882009cd89af1b5d14fe40548ee7feb9c0f Mon Sep 17 00:00:00 2001 From: Jessica Lawshe <5312836+lawshe@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:31:24 -0600 Subject: [PATCH] Merge pull request #21832 from overleaf/jel-search-badges [web] Add tooltips when needed on badge links in gallery search results GitOrigin-RevId: edcdcf8134698f17f607e003627a8b4123519b0b --- .../frontend/js/features/tooltip/index-bs5.ts | 6 +- .../bootstrap-5/badge-link-with-tooltip.tsx | 93 +++++++++++++++++++ .../ui/components/bootstrap-5/badge-link.tsx | 13 ++- .../ui/components/bootstrap-5/badge.tsx | 7 +- .../bootstrap-5/components/badge.scss | 6 ++ 5 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx diff --git a/services/web/frontend/js/features/tooltip/index-bs5.ts b/services/web/frontend/js/features/tooltip/index-bs5.ts index 5251948bb2..f748504e4d 100644 --- a/services/web/frontend/js/features/tooltip/index-bs5.ts +++ b/services/web/frontend/js/features/tooltip/index-bs5.ts @@ -17,9 +17,7 @@ const footerLanguageElement = document.querySelector( const allTooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]') -const possibleBadgeTooltips = document.querySelectorAll( - '[data-bs-toggle="tooltip-if-needed"]' -) +const possibleBadgeTooltips = document.querySelectorAll('[data-badge-tooltip]') // eslint-disable-next-line no-unused-vars const footLangTooltip = new Tooltip(footerLanguageElement) @@ -30,7 +28,7 @@ allTooltips.forEach(element => { }) possibleBadgeTooltips.forEach(element => { - // Put data-bs-toggle="tooltip-if-needed" on .badge-content + // Put data-badge-tooltip on .badge-content // then tooltip is only shown if content is clipped due to max-width on .badge // Due to font loading, the width calculated on page load might change, so we might // incorrectly determine a tooltip is not needed. This is why max-width will always be set to none diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx new file mode 100644 index 0000000000..aea97ad6f2 --- /dev/null +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx @@ -0,0 +1,93 @@ +import { OverlayTrigger, Tooltip } from 'react-bootstrap-5' +import type { MergeAndOverride } from '../../../../../../types/utils' +import BadgeLink, { type BadgeLinkProps } from './badge-link' +import { useEffect, useRef, useState } from 'react' +import classNames from 'classnames' + +type BadgeLinkWithTooltipProps = MergeAndOverride< + BadgeLinkProps, + { + placement?: 'top' | 'right' | 'bottom' | 'left' + tooltipTitle: string + } +> + +function getElementWidth(el: Element) { + const elComputedStyle = window.getComputedStyle(el) + const elPaddingX = + parseFloat(elComputedStyle.paddingLeft) + + parseFloat(elComputedStyle.paddingRight) + const elBorderX = + parseFloat(elComputedStyle.borderLeftWidth) + + parseFloat(elComputedStyle.borderRightWidth) + return el.scrollWidth - elPaddingX - elBorderX +} + +function BadgeLinkWithTooltip({ + children, + tooltipTitle, + placement, + ...rest +}: BadgeLinkWithTooltipProps) { + const badgeContentRef = useRef(null) + const [showTooltip, setShowTooltip] = useState(true) + const [noMaxWidth, setNoMaxWidth] = useState(false) + + const badgeLinkClasses = classNames({ 'badge-link-no-max-width': noMaxWidth }) + + const renderTooltip = (props: any) => { + if (showTooltip) { + return ( + + {tooltipTitle} + + ) + } else { + return <> + } + } + + useEffect(() => { + if (badgeContentRef.current) { + // Check if tooltip needed. + // If .badge-content does not extend beyond max-width limit on + // .badge then tooltip is not needed. max-width is always + // removed when withTooltip exists and tooltip is not needed + // to avoid any differences in width calculation after font + // loaded (for example, Noto sans). Othwerise, badge might get + // clipped due to font loaded causing .badge-content to be + // greater than .badge max-width and no tooltip was determined + // to be needed with default font (for example, sans-serif) + const badgeContentWidth = badgeContentRef.current.scrollWidth + if (badgeContentRef.current?.parentElement) { + const badgeWidth = getElementWidth( + badgeContentRef.current?.parentElement + ) + if (badgeContentWidth <= badgeWidth) { + // no tooltip and remove max-width + setNoMaxWidth(true) + setShowTooltip(false) + } + } + } + }, []) + + return ( + + + + {children} + + + + ) +} + +export default BadgeLinkWithTooltip diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link.tsx index b4fd20d5c6..bf99689110 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link.tsx @@ -2,15 +2,22 @@ import classNames from 'classnames' import type { MergeAndOverride } from '../../../../../../types/utils' import Badge, { type BadgeProps } from './badge' -type BadgeLinkProps = MergeAndOverride< +export type BadgeLinkProps = MergeAndOverride< BadgeProps, { href: string + badgeContentRef?: React.RefObject + badgeLinkClasses?: string } > -function BadgeLink({ href, children, ...badgeProps }: BadgeLinkProps) { - const containerClass = classNames('badge-link', { +function BadgeLink({ + href, + badgeLinkClasses, + children, + ...badgeProps +}: BadgeLinkProps) { + const containerClass = classNames(badgeLinkClasses, 'badge-link', { [`badge-link-${badgeProps.bg}`]: badgeProps.bg, }) diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx index f3d1ee20fa..0e66500bc0 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx @@ -5,14 +5,17 @@ export type BadgeProps = MergeAndOverride< BSBadgeProps, { prepend?: React.ReactNode + badgeContentRef?: React.RefObject } > -function Badge({ prepend, children, ...rest }: BadgeProps) { +function Badge({ prepend, children, badgeContentRef, ...rest }: BadgeProps) { return ( {prepend && {prepend}} - {children} + + {children} + ) } diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss b/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss index 34bcf0cde2..424ac978cd 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/badge.scss @@ -105,6 +105,12 @@ $max-width: 160px; background-color: var(--neutral-30) !important; } + &.badge-link-no-max-width { + .badge { + max-width: none; + } + } + .badge { @include body-sm;