diff --git a/packages/console/src/components/ActionMenu/index.tsx b/packages/console/src/components/ActionMenu/index.tsx index c8937af61..93903b262 100644 --- a/packages/console/src/components/ActionMenu/index.tsx +++ b/packages/console/src/components/ActionMenu/index.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import type { ReactNode } from 'react'; import { useRef, useState } from 'react'; -import type { HorizontalAlignment } from '@/hooks/use-position'; +import type { HorizontalAlignment } from '@/types/positioning'; import type { Props as ButtonProps } from '../Button'; import Dropdown from '../Dropdown'; diff --git a/packages/console/src/components/Dropdown/index.tsx b/packages/console/src/components/Dropdown/index.tsx index 84f8770a1..6c952167e 100644 --- a/packages/console/src/components/Dropdown/index.tsx +++ b/packages/console/src/components/Dropdown/index.tsx @@ -3,8 +3,8 @@ import type { ReactNode, RefObject } from 'react'; import { useRef } from 'react'; import ReactModal from 'react-modal'; -import type { HorizontalAlignment } from '@/hooks/use-position'; import usePosition from '@/hooks/use-position'; +import type { HorizontalAlignment } from '@/types/positioning'; import { onKeyDownHandler } from '@/utilities/a11y'; import * as styles from './index.module.scss'; diff --git a/packages/console/src/components/FormField/index.tsx b/packages/console/src/components/FormField/index.tsx index f672d7ad4..f101db946 100644 --- a/packages/console/src/components/FormField/index.tsx +++ b/packages/console/src/components/FormField/index.tsx @@ -29,7 +29,7 @@ const FormField = ({ title, children, isRequired, className, tip, headlineClassN
{typeof title === 'string' ? t(title) : title}
{tip && ( - + diff --git a/packages/console/src/components/Tip/TipBubble/index.module.scss b/packages/console/src/components/Tip/TipBubble/index.module.scss index 5f8ab299b..4f3de85fc 100644 --- a/packages/console/src/components/Tip/TipBubble/index.module.scss +++ b/packages/console/src/components/Tip/TipBubble/index.module.scss @@ -1,15 +1,19 @@ @use '@/scss/underscore' as _; .tipBubble { - position: relative; + position: absolute; border-radius: 8px; background: var(--color-tooltip-background); color: var(--color-tooltip-text); - box-shadow: var(--shadow-1); + box-shadow: var(--shadow-2); padding: _.unit(2) _.unit(3); font: var(--font-body-medium); max-width: 300px; + &.invisible { + opacity: 0%; + } + a { color: #cabeff; @@ -18,8 +22,7 @@ } } - &::after { - content: ''; + .arrow { display: block; position: absolute; width: 10px; @@ -29,34 +32,29 @@ transform: translate(-50%, -50%) rotate(45deg); } - &.top::after { - top: 100%; + &.top { + .arrow { + top: 100%; + } } - &.right::after { - top: 50%; - left: 0%; + &.right { + .arrow { + top: 50%; + left: 0%; + } } - &.bottom::after { - top: 0%; + &.bottom { + .arrow { + top: 0%; + } } - &.left::after { - top: 50%; - left: 100%; - } - - &.start::after { - left: _.unit(10); - } - - - &.center::after { - left: 50%; - } - - &.end::after { - right: _.unit(7.5); + &.left { + .arrow { + top: 50%; + left: 100%; + } } } diff --git a/packages/console/src/components/Tip/TipBubble/index.tsx b/packages/console/src/components/Tip/TipBubble/index.tsx index c2c2cd0af..2540527df 100644 --- a/packages/console/src/components/Tip/TipBubble/index.tsx +++ b/packages/console/src/components/Tip/TipBubble/index.tsx @@ -1,41 +1,64 @@ import { conditional } from '@silverhand/essentials'; import classNames from 'classnames'; import { forwardRef } from 'react'; -import type { ForwardedRef, ReactNode, HTMLProps } from 'react'; +import type { ForwardedRef, ReactNode, HTMLProps, RefObject } from 'react'; -import type { HorizontalAlignment } from '@/hooks/use-position'; +import type { HorizontalAlignment, Position } from '@/types/positioning'; import * as styles from './index.module.scss'; -export type TipBubblePosition = 'top' | 'right' | 'bottom' | 'left'; +export type TipBubblePlacement = 'top' | 'right' | 'bottom' | 'left'; type Props = HTMLProps & { children: ReactNode; - position?: TipBubblePosition; + position?: Position; + anchorRef: RefObject; + placement?: TipBubblePlacement; horizontalAlignment?: HorizontalAlignment; className?: string; }; -const supportHorizontalAlignmentPositions = new Set(['top', 'bottom']); +const supportHorizontalAlignmentPlacements = new Set(['top', 'bottom']); const TipBubble = ( - { children, position = 'bottom', horizontalAlignment = 'center', className, ...rest }: Props, + { + children, + position, + placement = 'bottom', + horizontalAlignment = 'center', + className, + anchorRef, + ...rest + }: Props, reference: ForwardedRef ) => { + if (!anchorRef.current) { + return null; + } + + const anchorRect = anchorRef.current.getBoundingClientRect(); + + const arrowPosition = conditional( + supportHorizontalAlignmentPlacements.has(placement) && + position && { + left: anchorRect.x + anchorRect.width / 2 - Number(position.left), + } + ); + return (
{children} +
); }; diff --git a/packages/console/src/components/Tip/TipBubble/utils.ts b/packages/console/src/components/Tip/TipBubble/utils.ts index fd416d9a2..9c0ad6aa4 100644 --- a/packages/console/src/components/Tip/TipBubble/utils.ts +++ b/packages/console/src/components/Tip/TipBubble/utils.ts @@ -1,9 +1,9 @@ -import type { HorizontalAlignment, VerticalAlignment } from '@/hooks/use-position'; +import type { HorizontalAlignment, VerticalAlignment } from '@/types/positioning'; -import type { TipBubblePosition } from '.'; +import type { TipBubblePlacement } from '.'; -export const getVerticalOffset = (position: TipBubblePosition) => { - switch (position) { +export const getVerticalOffset = (placement: TipBubblePlacement) => { + switch (placement) { case 'top': return -16; case 'bottom': @@ -14,10 +14,10 @@ export const getVerticalOffset = (position: TipBubblePosition) => { }; export const getHorizontalOffset = ( - tooltipPosition: TipBubblePosition, + placement: TipBubblePlacement, horizontalAlignment: HorizontalAlignment ): number => { - if (tooltipPosition === 'top' || tooltipPosition === 'bottom') { + if (placement === 'top' || placement === 'bottom') { switch (horizontalAlignment) { case 'start': return -32; @@ -27,12 +27,12 @@ export const getHorizontalOffset = ( return 0; } } else { - return tooltipPosition === 'left' ? -32 : 32; + return placement === 'left' ? -32 : 32; } }; -export const getVerticalAlignment = (position: TipBubblePosition): VerticalAlignment => { - switch (position) { +export const getVerticalAlignment = (placement: TipBubblePlacement): VerticalAlignment => { + switch (placement) { case 'top': return 'top'; case 'bottom': @@ -43,10 +43,10 @@ export const getVerticalAlignment = (position: TipBubblePosition): VerticalAlign }; export const getHorizontalAlignment = ( - position: TipBubblePosition, + placement: TipBubblePlacement, fallback: HorizontalAlignment ): HorizontalAlignment => { - switch (position) { + switch (placement) { case 'right': return 'start'; case 'left': diff --git a/packages/console/src/components/Tip/ToggleTip/index.module.scss b/packages/console/src/components/Tip/ToggleTip/index.module.scss index 0c0816adb..d9a91ab64 100644 --- a/packages/console/src/components/Tip/ToggleTip/index.module.scss +++ b/packages/console/src/components/Tip/ToggleTip/index.module.scss @@ -1,16 +1,15 @@ @use '@/scss/underscore' as _; -.content { - box-shadow: var(--shadow-2); - position: absolute; - - &:focus { - outline: none; - } -} - .overlay { background: transparent; position: fixed; inset: 0; + + .content { + position: relative; + + &:focus { + outline: none; + } + } } diff --git a/packages/console/src/components/Tip/ToggleTip/index.tsx b/packages/console/src/components/Tip/ToggleTip/index.tsx index da46eab14..42bce18df 100644 --- a/packages/console/src/components/Tip/ToggleTip/index.tsx +++ b/packages/console/src/components/Tip/ToggleTip/index.tsx @@ -2,11 +2,11 @@ import type { ReactNode } from 'react'; import { useCallback, useState, useRef } from 'react'; import ReactModal from 'react-modal'; -import type { HorizontalAlignment } from '@/hooks/use-position'; import usePosition from '@/hooks/use-position'; +import type { HorizontalAlignment } from '@/types/positioning'; import { onKeyDownHandler } from '@/utilities/a11y'; -import type { TipBubblePosition } from '../TipBubble'; +import type { TipBubblePlacement } from '../TipBubble'; import TipBubble from '../TipBubble'; import { getVerticalAlignment, @@ -20,7 +20,7 @@ export type Props = { children: ReactNode; className?: string; anchorClassName?: string; - position?: TipBubblePosition; + placement?: TipBubblePlacement; horizontalAlign?: HorizontalAlignment; content?: ((closeTip: () => void) => ReactNode) | ReactNode; }; @@ -29,11 +29,11 @@ const ToggleTip = ({ children, className, anchorClassName, - position = 'top', + placement = 'top', horizontalAlign = 'center', content, }: Props) => { - const overlayRef = useRef(null); + const tipBubbleRef = useRef(null); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -47,14 +47,14 @@ const ToggleTip = ({ positionState, mutate, } = usePosition({ - verticalAlign: getVerticalAlignment(position), - horizontalAlign: getHorizontalAlignment(position, horizontalAlign), + verticalAlign: getVerticalAlignment(placement), + horizontalAlign: getHorizontalAlignment(placement, horizontalAlign), offset: { - vertical: getVerticalOffset(position), - horizontal: getHorizontalOffset(position, horizontalAlign), + vertical: getVerticalOffset(placement), + horizontal: getHorizontalOffset(placement, horizontalAlign), }, anchorRef, - overlayRef, + overlayRef: tipBubbleRef, }); return ( @@ -77,20 +77,16 @@ const ToggleTip = ({ shouldCloseOnOverlayClick shouldCloseOnEsc isOpen={isOpen} - style={{ - content: { - ...(!layoutPosition && { opacity: 0 }), - ...layoutPosition, - }, - }} className={styles.content} overlayClassName={styles.overlay} onRequestClose={onClose} onAfterOpen={mutate} > diff --git a/packages/console/src/components/Tip/Tooltip/index.module.scss b/packages/console/src/components/Tip/Tooltip/index.module.scss index bc2af8bca..2e1ddb9ff 100644 --- a/packages/console/src/components/Tip/Tooltip/index.module.scss +++ b/packages/console/src/components/Tip/Tooltip/index.module.scss @@ -1,9 +1,5 @@ @use '@/scss/underscore' as _; -.tooltip { - position: absolute; - - .content { - @include _.multi-line-ellipsis(6); - } +.content { + @include _.multi-line-ellipsis(6); } diff --git a/packages/console/src/components/Tip/Tooltip/index.tsx b/packages/console/src/components/Tip/Tooltip/index.tsx index c9aa037dc..4d6be04c5 100644 --- a/packages/console/src/components/Tip/Tooltip/index.tsx +++ b/packages/console/src/components/Tip/Tooltip/index.tsx @@ -2,11 +2,11 @@ import type { ReactNode } from 'react'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import type { HorizontalAlignment } from '@/hooks/use-position'; import usePosition from '@/hooks/use-position'; +import type { HorizontalAlignment } from '@/types/positioning'; import TipBubble from '../TipBubble'; -import type { TipBubblePosition } from '../TipBubble'; +import type { TipBubblePlacement } from '../TipBubble'; import { getVerticalAlignment, getHorizontalAlignment, @@ -18,7 +18,7 @@ import * as styles from './index.module.scss'; type Props = { className?: string; isKeepOpen?: boolean; - position?: TipBubblePosition; + placement?: TipBubblePlacement; horizontalAlign?: HorizontalAlignment; anchorClassName?: string; children?: ReactNode; @@ -28,7 +28,7 @@ type Props = { const Tooltip = ({ className, isKeepOpen = false, - position = 'top', + placement = 'top', horizontalAlign = 'center', anchorClassName, children, @@ -38,16 +38,12 @@ const Tooltip = ({ const anchorRef = useRef(null); const tooltipRef = useRef(null); - const { - position: layoutPosition, - positionState, - mutate, - } = usePosition({ - verticalAlign: getVerticalAlignment(position), - horizontalAlign: getHorizontalAlignment(position, horizontalAlign), + const { position, positionState, mutate } = usePosition({ + verticalAlign: getVerticalAlignment(placement), + horizontalAlign: getHorizontalAlignment(placement, horizontalAlign), offset: { - vertical: getVerticalOffset(position), - horizontal: getHorizontalOffset(position, horizontalAlign), + vertical: getVerticalOffset(placement), + horizontal: getHorizontalOffset(placement, horizontalAlign), }, anchorRef, overlayRef: tooltipRef, @@ -132,17 +128,16 @@ const Tooltip = ({ {tooltipDom && content && createPortal( -
- -
{content}
-
-
, + +
{content}
+
, tooltipDom )} diff --git a/packages/console/src/hooks/use-position.ts b/packages/console/src/hooks/use-position.ts index d7d5d0885..a4910aea9 100644 --- a/packages/console/src/hooks/use-position.ts +++ b/packages/console/src/hooks/use-position.ts @@ -1,14 +1,7 @@ import type { RefObject } from 'react'; import { useCallback, useEffect, useState } from 'react'; -export type VerticalAlignment = 'top' | 'middle' | 'bottom'; - -export type HorizontalAlignment = 'start' | 'center' | 'end'; - -type Offset = { - vertical: number; - horizontal: number; -}; +import type { HorizontalAlignment, Offset, Position, VerticalAlignment } from '@/types/positioning'; type Props = { verticalAlign: VerticalAlignment; @@ -18,11 +11,6 @@ type Props = { overlayRef: RefObject; }; -type Position = { - top: number; - left: number; -}; - // Leave space for box-shadow effect. const windowSafePadding = 12; diff --git a/packages/console/src/pages/Applications/components/GuideHeader/index.tsx b/packages/console/src/pages/Applications/components/GuideHeader/index.tsx index 3ec1a9b32..f59163cd3 100644 --- a/packages/console/src/pages/Applications/components/GuideHeader/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideHeader/index.tsx @@ -63,7 +63,7 @@ const GuideHeader = ({ appName, selectedSdk, isCompact = false, onClose }: Props /> diff --git a/packages/console/src/types/positioning.ts b/packages/console/src/types/positioning.ts new file mode 100644 index 000000000..a56b8d52c --- /dev/null +++ b/packages/console/src/types/positioning.ts @@ -0,0 +1,13 @@ +export type Position = { + top: number; + left: number; +}; + +export type Offset = { + horizontal: number; + vertical: number; +}; + +export type HorizontalAlignment = 'start' | 'center' | 'end'; + +export type VerticalAlignment = 'top' | 'middle' | 'bottom';