diff --git a/services/web/frontend/js/features/settings/components/emails/actions/make-primary/make-primary.tsx b/services/web/frontend/js/features/settings/components/emails/actions/make-primary/make-primary.tsx index 8f96b13b85..6ce1eb0fa7 100644 --- a/services/web/frontend/js/features/settings/components/emails/actions/make-primary/make-primary.tsx +++ b/services/web/frontend/js/features/settings/components/emails/actions/make-primary/make-primary.tsx @@ -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')}… ) : ( - @@ -102,7 +102,7 @@ function MakePrimary({ userEmailData, makePrimaryAsync }: MakePrimaryProps) { {t('make_primary')} - + )} - + ) } diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx new file mode 100644 index 0000000000..a4fd9e2bb9 --- /dev/null +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx @@ -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, + 'overlay' | 'children' +> + +type UpdatingTooltipProps = { + popper: { + scheduleUpdate: () => void + } + show: boolean + [x: string]: unknown +} + +const UpdatingTooltip = forwardRef( + ({ popper, children, show: _, ...props }, ref) => { + useEffect(() => { + popper.scheduleUpdate() + }, [children, popper]) + + return ( + + {children} + + ) + } +) +UpdatingTooltip.displayName = 'UpdatingTooltip' + +export type TooltipProps = { + description: React.ReactNode + id: string + overlayProps?: OverlayProps + tooltipProps?: React.ComponentProps + 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 ( + + ) +} + +export default Tooltip diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper.tsx new file mode 100644 index 0000000000..8a9dc92c9f --- /dev/null +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper.tsx @@ -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 & { + bs3Props?: Record +} + +function TooltipWrapper(props: TooltipWrapperProps) { + const { bs3Props, ...bs5Props } = props + + const bs3TooltipProps: React.ComponentProps = { + 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 ( + } + bs5={} + /> + ) +} + +export default TooltipWrapper diff --git a/services/web/frontend/stories/ui/tooltip.stories.tsx b/services/web/frontend/stories/ui/tooltip.stories.tsx new file mode 100644 index 0000000000..916ca6397c --- /dev/null +++ b/services/web/frontend/stories/ui/tooltip.stories.tsx @@ -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 ( +
+ {placements.map(placement => ( + + + + ))} +
+ ) +} + +const meta: Meta = { + title: 'Shared / Components / Bootstrap 5 / Tooltip', + component: Tooltip, + parameters: { + bootstrap5: true, + }, +} + +export default meta diff --git a/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss b/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss index 106d521f23..c70aa433d4 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss @@ -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; diff --git a/services/web/frontend/stylesheets/bootstrap-5/bootstrap.scss b/services/web/frontend/stylesheets/bootstrap-5/bootstrap.scss index 16a2b46ff7..1d78b7f7ec 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/bootstrap.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/bootstrap.scss @@ -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 diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/all.scss b/services/web/frontend/stylesheets/bootstrap-5/components/all.scss index 684cfac373..c8b62aa7a7 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/all.scss @@ -2,3 +2,4 @@ @import 'dropdown-menu'; @import 'split-button'; @import 'notifications'; +@import 'tooltip'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/tooltip.scss b/services/web/frontend/stylesheets/bootstrap-5/components/tooltip.scss new file mode 100644 index 0000000000..4a2916c85c --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/components/tooltip.scss @@ -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; + } +}