104
common/resources/client/ui/overlays/floating-position.ts
Executable file
104
common/resources/client/ui/overlays/floating-position.ts
Executable file
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
arrow,
|
||||
autoUpdate,
|
||||
flip,
|
||||
offset as offsetMiddleware,
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
ReferenceType,
|
||||
shift,
|
||||
size,
|
||||
useFloating,
|
||||
} from '@floating-ui/react-dom';
|
||||
import {CSSProperties, Ref, useMemo, useRef} from 'react';
|
||||
import {mergeRefs} from 'react-merge-refs';
|
||||
import {UseFloatingOptions} from '@floating-ui/react-dom/src/types';
|
||||
|
||||
interface Props {
|
||||
floatingWidth?: 'auto' | 'matchTrigger';
|
||||
ref?: Ref<HTMLElement>;
|
||||
disablePositioning?: boolean;
|
||||
placement?: Placement;
|
||||
offset?: OffsetOptions;
|
||||
showArrow?: boolean;
|
||||
maxHeight?: number;
|
||||
shiftCrossAxis?: boolean;
|
||||
fallbackPlacements?: Placement[];
|
||||
}
|
||||
export function useFloatingPosition({
|
||||
floatingWidth,
|
||||
ref,
|
||||
disablePositioning = false,
|
||||
placement = 'bottom',
|
||||
offset = 2,
|
||||
showArrow = false,
|
||||
maxHeight,
|
||||
shiftCrossAxis = true,
|
||||
fallbackPlacements,
|
||||
}: Props) {
|
||||
const arrowRef = useRef<HTMLElement>(null);
|
||||
|
||||
const floatingConfig: UseFloatingOptions = {placement, strategy: 'fixed'};
|
||||
|
||||
if (!disablePositioning) {
|
||||
floatingConfig.whileElementsMounted = autoUpdate;
|
||||
floatingConfig.middleware = [
|
||||
offsetMiddleware(offset),
|
||||
shift({padding: 16, crossAxis: shiftCrossAxis, mainAxis: true}),
|
||||
flip({
|
||||
padding: 16,
|
||||
fallbackPlacements,
|
||||
}),
|
||||
size({
|
||||
apply({rects, availableHeight, availableWidth, elements}) {
|
||||
if (floatingWidth === 'matchTrigger' && maxHeight != null) {
|
||||
Object.assign(elements.floating.style, {
|
||||
width: `${rects.reference.width}px`,
|
||||
maxWidth: `${availableWidth}`,
|
||||
maxHeight: `${Math.min(availableHeight, maxHeight)}px`,
|
||||
});
|
||||
} else if (maxHeight != null) {
|
||||
Object.assign(elements.floating.style, {
|
||||
maxHeight: `${Math.min(availableHeight, maxHeight)}px`,
|
||||
});
|
||||
}
|
||||
},
|
||||
padding: 16,
|
||||
}),
|
||||
];
|
||||
if (showArrow) {
|
||||
floatingConfig.middleware.push(arrow({element: arrowRef}));
|
||||
}
|
||||
}
|
||||
|
||||
const floatingProps = useFloating(floatingConfig);
|
||||
|
||||
const mergedReferenceRef = useMemo(
|
||||
() => mergeRefs<ReferenceType>([ref!, floatingProps.refs.setReference]),
|
||||
[floatingProps.refs.setReference, ref]
|
||||
);
|
||||
|
||||
const {x: arrowX, y: arrowY} = floatingProps.middlewareData.arrow || {};
|
||||
|
||||
const staticSide = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[floatingProps.placement.split('-')[0]]!;
|
||||
|
||||
const arrowStyle: CSSProperties = {
|
||||
left: arrowX,
|
||||
top: arrowY,
|
||||
right: '',
|
||||
bottom: '',
|
||||
[staticSide]: '-4px',
|
||||
};
|
||||
|
||||
return {
|
||||
...floatingProps,
|
||||
reference: mergedReferenceRef,
|
||||
arrowRef,
|
||||
arrowStyle,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user