mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #17736 from overleaf/ii-bs5-tooltip
[web] Create Btoostrap 5 tooltips GitOrigin-RevId: 28c7c389bda74765482049750fc0ae8a5995968e
This commit is contained in:
parent
a0da44358f
commit
9f38436f10
9 changed files with 210 additions and 7 deletions
|
@ -1,5 +1,4 @@
|
|||
import { useState } from 'react'
|
||||
import Tooltip from '../../../../../../shared/components/tooltip'
|
||||
import PrimaryButton from './primary-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
|
@ -15,6 +14,7 @@ import { UserEmailData } from '../../../../../../../../types/user-email'
|
|||
import { UseAsyncReturnType } from '../../../../../../shared/hooks/use-async'
|
||||
import { ssoAvailableForInstitution } from '../../../../utils/sso'
|
||||
import ConfirmationModal from './confirmation-modal'
|
||||
import TooltipWrapper from '@/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper'
|
||||
|
||||
const getDescription = (
|
||||
t: (s: string) => string,
|
||||
|
@ -86,7 +86,7 @@ function MakePrimary({ userEmailData, makePrimaryAsync }: MakePrimaryProps) {
|
|||
{t('processing_uppercase')}…
|
||||
</PrimaryButton>
|
||||
) : (
|
||||
<Tooltip
|
||||
<TooltipWrapper
|
||||
id={`make-primary-${userEmailData.email}`}
|
||||
description={getDescription(t, state, userEmailData)}
|
||||
>
|
||||
|
@ -102,7 +102,7 @@ function MakePrimary({ userEmailData, makePrimaryAsync }: MakePrimaryProps) {
|
|||
{t('make_primary')}
|
||||
</PrimaryButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
email={userEmailData.email}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import Icon from '../../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../../shared/components/tooltip'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { UserEmailData } from '../../../../../../../types/user-email'
|
||||
import { useUserEmailsContext } from '../../../context/user-email-context'
|
||||
import { postJSON } from '../../../../../infrastructure/fetch-json'
|
||||
import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async'
|
||||
import TooltipWrapper from '@/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper'
|
||||
|
||||
function DeleteButton({ children, disabled, onClick }: Button.ButtonProps) {
|
||||
function DeleteButton({ disabled, onClick }: Button.ButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
|
@ -53,7 +53,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<TooltipWrapper
|
||||
id={userEmailData.email}
|
||||
description={
|
||||
userEmailData.default
|
||||
|
@ -68,7 +68,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
|
|||
onClick={handleRemoveUserEmail}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</TooltipWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import { cloneElement, useEffect, forwardRef } from 'react'
|
||||
import { OverlayTrigger, Tooltip as BSTooltip } from 'react-bootstrap-5'
|
||||
import { callFnsInSequence } from '@/utils/functions'
|
||||
|
||||
type OverlayProps = Omit<
|
||||
React.ComponentProps<typeof OverlayTrigger>,
|
||||
'overlay' | 'children'
|
||||
>
|
||||
|
||||
type UpdatingTooltipProps = {
|
||||
popper: {
|
||||
scheduleUpdate: () => void
|
||||
}
|
||||
show: boolean
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
const UpdatingTooltip = forwardRef<HTMLDivElement, UpdatingTooltipProps>(
|
||||
({ popper, children, show: _, ...props }, ref) => {
|
||||
useEffect(() => {
|
||||
popper.scheduleUpdate()
|
||||
}, [children, popper])
|
||||
|
||||
return (
|
||||
<BSTooltip ref={ref} {...props}>
|
||||
{children}
|
||||
</BSTooltip>
|
||||
)
|
||||
}
|
||||
)
|
||||
UpdatingTooltip.displayName = 'UpdatingTooltip'
|
||||
|
||||
export type TooltipProps = {
|
||||
description: React.ReactNode
|
||||
id: string
|
||||
overlayProps?: OverlayProps
|
||||
tooltipProps?: React.ComponentProps<typeof BSTooltip>
|
||||
hidden?: boolean
|
||||
children: React.ReactElement
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
id,
|
||||
description,
|
||||
children,
|
||||
tooltipProps,
|
||||
overlayProps,
|
||||
hidden,
|
||||
}: TooltipProps) {
|
||||
const delay = overlayProps?.delay
|
||||
let delayShow = 300
|
||||
let delayHide = 300
|
||||
if (delay) {
|
||||
delayShow = typeof delay === 'number' ? delay : delay.show
|
||||
delayHide = typeof delay === 'number' ? delay : delay.hide
|
||||
}
|
||||
|
||||
const hideTooltip = (e: React.MouseEvent) => {
|
||||
if (e.currentTarget instanceof HTMLElement) {
|
||||
e.currentTarget.blur()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
overlay={
|
||||
<UpdatingTooltip
|
||||
id={`${id}-tooltip`}
|
||||
{...tooltipProps}
|
||||
style={{ display: hidden ? 'none' : 'block' }}
|
||||
>
|
||||
{description}
|
||||
</UpdatingTooltip>
|
||||
}
|
||||
{...overlayProps}
|
||||
delay={{ show: delayShow, hide: delayHide }}
|
||||
placement={overlayProps?.placement || 'top'}
|
||||
>
|
||||
{cloneElement(children, {
|
||||
onClick: callFnsInSequence(children.props.onClick, hideTooltip),
|
||||
})}
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tooltip
|
|
@ -0,0 +1,41 @@
|
|||
import Tooltip from '@/features/ui/components/bootstrap-5/tooltip'
|
||||
import BS3Tooltip from '@/shared/components/tooltip'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type TooltipWrapperProps = React.ComponentProps<typeof Tooltip> & {
|
||||
bs3Props?: Record<string, unknown>
|
||||
}
|
||||
|
||||
function TooltipWrapper(props: TooltipWrapperProps) {
|
||||
const { bs3Props, ...bs5Props } = props
|
||||
|
||||
const bs3TooltipProps: React.ComponentProps<typeof BS3Tooltip> = {
|
||||
children: bs5Props.children,
|
||||
id: bs5Props.id,
|
||||
description: bs5Props.description,
|
||||
overlayProps: {},
|
||||
...bs3Props,
|
||||
}
|
||||
|
||||
if ('hidden' in bs5Props) {
|
||||
bs3TooltipProps.hidden = bs5Props.hidden
|
||||
}
|
||||
|
||||
const delay = bs5Props.overlayProps?.delay
|
||||
if (delay && typeof delay !== 'number') {
|
||||
bs3TooltipProps.overlayProps = {
|
||||
...bs3TooltipProps.overlayProps,
|
||||
delayShow: delay.show,
|
||||
delayHide: delay.hide,
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<BS3Tooltip {...bs3TooltipProps} />}
|
||||
bs5={<Tooltip {...bs5Props} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TooltipWrapper
|
41
services/web/frontend/stories/ui/tooltip.stories.tsx
Normal file
41
services/web/frontend/stories/ui/tooltip.stories.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Button } from 'react-bootstrap-5'
|
||||
import Tooltip from '@/features/ui/components/bootstrap-5/tooltip'
|
||||
import { Meta } from '@storybook/react'
|
||||
|
||||
export const Tooltips = () => {
|
||||
const placements = ['top', 'right', 'bottom', 'left'] as const
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '200px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
margin: '0 auto',
|
||||
padding: '35px 0',
|
||||
gap: '35px',
|
||||
}}
|
||||
>
|
||||
{placements.map(placement => (
|
||||
<Tooltip
|
||||
key={placement}
|
||||
id={`tooltip-${placement}`}
|
||||
description={`Tooltip on ${placement}`}
|
||||
overlayProps={{ placement }}
|
||||
>
|
||||
<Button variant="secondary">Tooltip on {placement}</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta<typeof Tooltip> = {
|
||||
title: 'Shared / Components / Bootstrap 5 / Tooltip',
|
||||
component: Tooltip,
|
||||
parameters: {
|
||||
bootstrap5: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -21,3 +21,9 @@ $btn-border-radius-sm: $border-radius-full;
|
|||
// Colors
|
||||
$primary: $bg-accent-01;
|
||||
$secondary: $bg-light-primary;
|
||||
|
||||
// Tooltips
|
||||
$tooltip-max-width: 320px;
|
||||
$tooltip-border-radius: $border-radius-base;
|
||||
$tooltip-padding-y: $spacing-04;
|
||||
$tooltip-padding-x: $spacing-06;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
@import 'bootstrap-5/scss/buttons';
|
||||
@import 'bootstrap-5/scss/dropdown';
|
||||
@import 'bootstrap-5/scss/modal';
|
||||
@import 'bootstrap-5/scss/tooltip';
|
||||
@import 'bootstrap-5/scss/spinners';
|
||||
|
||||
// Helpers
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
@import 'dropdown-menu';
|
||||
@import 'split-button';
|
||||
@import 'notifications';
|
||||
@import 'tooltip';
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
.tooltip {
|
||||
line-height: 20px;
|
||||
@include shadow-md;
|
||||
|
||||
&.#{$prefix}tooltip-top {
|
||||
bottom: -1px !important;
|
||||
}
|
||||
&.#{$prefix}tooltip-end {
|
||||
top: 1px !important;
|
||||
left: -1px !important;
|
||||
}
|
||||
&.#{$prefix}tooltip-bottom {
|
||||
top: -1px !important;
|
||||
}
|
||||
&.#{$prefix}tooltip-start {
|
||||
top: 1px !important;
|
||||
right: -1px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
text-align: initial;
|
||||
|
||||
.tooltip-wide & {
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue