mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: add vertical center support (#2032)
Co-authored-by: wangsijie <wangsijie@silverhand.io>
This commit is contained in:
parent
4b0970b6d8
commit
5eeb06e301
3 changed files with 62 additions and 9 deletions
|
@ -27,6 +27,16 @@
|
|||
top: 0%;
|
||||
}
|
||||
|
||||
&.arrowRight::after {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
&.arrowLeft::after {
|
||||
top: 50%;
|
||||
left: 0%;
|
||||
}
|
||||
|
||||
&.start::after {
|
||||
left: _.unit(10);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|||
import { ReactNode, RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import usePosition, { HorizontalAlignment } from '@/hooks/use-position';
|
||||
import usePosition, { VerticalAlignment, HorizontalAlignment } from '@/hooks/use-position';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -11,15 +11,17 @@ type Props = {
|
|||
anchorRef: RefObject<Element>;
|
||||
className?: string;
|
||||
isKeepOpen?: boolean;
|
||||
verticalAlign?: VerticalAlignment;
|
||||
horizontalAlign?: HorizontalAlignment;
|
||||
flip?: 'right' | 'left';
|
||||
};
|
||||
|
||||
const getHorizontalOffset = (alignment: HorizontalAlignment): number => {
|
||||
const getHorizontalOffset = (alignment: HorizontalAlignment, flipped: string): number => {
|
||||
switch (alignment) {
|
||||
case 'start':
|
||||
return -32;
|
||||
return flipped === 'right' ? 32 : -32;
|
||||
case 'end':
|
||||
return 32;
|
||||
return flipped === 'left' ? -32 : 32;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
@ -30,15 +32,17 @@ const Tooltip = ({
|
|||
anchorRef,
|
||||
className,
|
||||
isKeepOpen = false,
|
||||
verticalAlign = 'top',
|
||||
horizontalAlign = 'start',
|
||||
flip,
|
||||
}: Props) => {
|
||||
const [tooltipDom, setTooltipDom] = useState<HTMLDivElement>();
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { position, positionState, mutate } = usePosition({
|
||||
verticalAlign: 'top',
|
||||
verticalAlign,
|
||||
horizontalAlign,
|
||||
offset: { vertical: 16, horizontal: getHorizontalOffset(horizontalAlign) },
|
||||
offset: { vertical: 16, horizontal: getHorizontalOffset(horizontalAlign, flip ?? '') },
|
||||
anchorRef,
|
||||
overlayRef: tooltipRef,
|
||||
});
|
||||
|
@ -119,6 +123,8 @@ const Tooltip = ({
|
|||
}
|
||||
|
||||
const isArrowUp = positionState.verticalAlign === 'bottom';
|
||||
const isArrowRight = flip === 'left' && positionState.horizontalAlign === 'end';
|
||||
const isArrowLeft = flip === 'right' && positionState.horizontalAlign === 'start';
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
|
@ -126,7 +132,9 @@ const Tooltip = ({
|
|||
className={classNames(
|
||||
styles.tooltip,
|
||||
isArrowUp && styles.arrowUp,
|
||||
styles[horizontalAlign],
|
||||
isArrowRight && styles.arrowRight,
|
||||
isArrowLeft && styles.arrowLeft,
|
||||
!flip && styles[horizontalAlign],
|
||||
className
|
||||
)}
|
||||
style={{ ...position }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { RefObject, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export type VerticalAlignment = 'top' | 'bottom';
|
||||
export type VerticalAlignment = 'top' | 'center' | 'bottom';
|
||||
|
||||
export type HorizontalAlignment = 'start' | 'center' | 'end';
|
||||
|
||||
|
@ -28,11 +28,13 @@ const windowSafePadding = 12;
|
|||
const selectVerticalAlignment = ({
|
||||
verticalAlign,
|
||||
verticalTop,
|
||||
verticalCenter,
|
||||
verticalBottom,
|
||||
overlayHeight,
|
||||
}: {
|
||||
verticalAlign: VerticalAlignment;
|
||||
verticalTop: number;
|
||||
verticalCenter: number;
|
||||
verticalBottom: number;
|
||||
overlayHeight: number;
|
||||
}) => {
|
||||
|
@ -40,6 +42,8 @@ const selectVerticalAlignment = ({
|
|||
const maxY = window.innerHeight - windowSafePadding;
|
||||
|
||||
const isTopAllowed = verticalTop >= minY;
|
||||
const isCenterAllowed =
|
||||
verticalCenter - overlayHeight / 2 >= minY && verticalCenter + overlayHeight / 2 <= maxY;
|
||||
const isBottomAllowed = verticalBottom + overlayHeight <= maxY;
|
||||
|
||||
switch (verticalAlign) {
|
||||
|
@ -52,6 +56,26 @@ const selectVerticalAlignment = ({
|
|||
return 'bottom';
|
||||
}
|
||||
|
||||
if (isCenterAllowed) {
|
||||
return 'center';
|
||||
}
|
||||
|
||||
return verticalAlign;
|
||||
}
|
||||
|
||||
case 'center': {
|
||||
if (isCenterAllowed) {
|
||||
return 'center';
|
||||
}
|
||||
|
||||
if (isTopAllowed) {
|
||||
return 'top';
|
||||
}
|
||||
|
||||
if (isBottomAllowed) {
|
||||
return 'bottom';
|
||||
}
|
||||
|
||||
return verticalAlign;
|
||||
}
|
||||
|
||||
|
@ -64,6 +88,10 @@ const selectVerticalAlignment = ({
|
|||
return 'top';
|
||||
}
|
||||
|
||||
if (isCenterAllowed) {
|
||||
return 'center';
|
||||
}
|
||||
|
||||
return verticalAlign;
|
||||
}
|
||||
|
||||
|
@ -170,9 +198,15 @@ export default function usePosition({
|
|||
const { scrollTop, scrollLeft } = document.documentElement;
|
||||
|
||||
const verticalTop = anchorRect.y - overlayRect.height + scrollTop - offset.vertical;
|
||||
const verticalCenter =
|
||||
anchorRect.y - anchorRect.height / 2 - overlayRect.height / 2 + scrollTop + offset.vertical;
|
||||
const verticalBottom = anchorRect.y + anchorRect.height + scrollTop + offset.vertical;
|
||||
|
||||
const verticalPositionMap = { top: verticalTop, bottom: verticalBottom };
|
||||
const verticalPositionMap = {
|
||||
top: verticalTop,
|
||||
center: verticalCenter,
|
||||
bottom: verticalBottom,
|
||||
};
|
||||
|
||||
const horizontalStart = anchorRect.x + scrollLeft + offset.horizontal;
|
||||
const horizontalCenter =
|
||||
|
@ -189,6 +223,7 @@ export default function usePosition({
|
|||
const selectedVerticalAlign = selectVerticalAlignment({
|
||||
verticalAlign,
|
||||
verticalTop,
|
||||
verticalCenter,
|
||||
verticalBottom,
|
||||
overlayHeight: overlayRect.height,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue