mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix: use popover core in react popover (#208)
This commit is contained in:
@@ -5,12 +5,12 @@ import { map } from 'nanostores';
|
||||
type Placement = 'top' | 'top-start' | 'top-end';
|
||||
|
||||
export interface PopoverState {
|
||||
open: boolean;
|
||||
openOnHover: boolean;
|
||||
delay: number;
|
||||
closeDelay: number;
|
||||
placement: Placement;
|
||||
sideOffset: number;
|
||||
_open: boolean;
|
||||
_setTriggerElement: (element: HTMLElement | null) => void;
|
||||
_triggerElement: HTMLElement | null;
|
||||
_setPopoverElement: (element: HTMLElement | null) => void;
|
||||
@@ -21,7 +21,7 @@ export interface PopoverState {
|
||||
export class Popover {
|
||||
#hoverTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
#state = map<PopoverState>({
|
||||
open: false,
|
||||
_open: false,
|
||||
openOnHover: false,
|
||||
delay: 0,
|
||||
closeDelay: 0,
|
||||
@@ -113,13 +113,13 @@ export class Popover {
|
||||
}
|
||||
|
||||
get #open(): boolean {
|
||||
return this.getState().open;
|
||||
return this.getState()._open;
|
||||
}
|
||||
|
||||
#setOpen(open: boolean): void {
|
||||
if (this.#open === open) return;
|
||||
|
||||
this.setState({ open });
|
||||
this.setState({ _open: open });
|
||||
|
||||
if (open) {
|
||||
this.setState({ _transitionStatus: 'initial' });
|
||||
@@ -132,14 +132,17 @@ export class Popover {
|
||||
} else {
|
||||
this.setState({ _transitionStatus: 'close' });
|
||||
|
||||
const transitions = this.#popoverElement?.getAnimations().filter(anim => anim instanceof CSSTransition);
|
||||
if (transitions && transitions.length > 0) {
|
||||
Promise.all(transitions.map(t => t.finished))
|
||||
.then(() => this.#popoverElement?.hidePopover())
|
||||
.catch(() => this.#popoverElement?.hidePopover());
|
||||
} else {
|
||||
this.#popoverElement?.hidePopover();
|
||||
}
|
||||
// This requestAnimationFrame is required for React because the data- attributes are not updated immediately.
|
||||
requestAnimationFrame(() => {
|
||||
const transitions = this.#popoverElement?.getAnimations().filter(anim => anim instanceof CSSTransition);
|
||||
if (transitions && transitions.length > 0) {
|
||||
Promise.all(transitions.map(t => t.finished))
|
||||
.then(() => this.#popoverElement?.hidePopover())
|
||||
.catch(() => this.#popoverElement?.hidePopover());
|
||||
} else {
|
||||
this.#popoverElement?.hidePopover();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +162,7 @@ export class Popover {
|
||||
this.#addPointerMoveListener();
|
||||
}
|
||||
|
||||
if (this.getState().open) {
|
||||
if (this.getState()._open) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { ConnectedComponentConstructor, PropsHook, StateHook } from '../utils/component-factory';
|
||||
import type { Prettify } from '../types';
|
||||
import type { ConnectedComponentConstructor, PropsHook, StateHook } from '../utils/component-factory';
|
||||
|
||||
import { Popover as CorePopover } from '@videojs/core';
|
||||
|
||||
import { getDocumentOrShadowRoot } from '@videojs/utils/dom';
|
||||
import { getCoreState, getPropsFromAttrs, toConnectedHTMLComponent } from '../utils/component-factory';
|
||||
|
||||
type Placement = 'top' | 'top-start' | 'top-end';
|
||||
|
||||
type PopoverState = Prettify<ReturnType<CorePopover['getState']>>;
|
||||
export type PopoverState = Prettify<ReturnType<CorePopover['getState']>>;
|
||||
|
||||
export const getPopoverState: StateHook<Popover, PopoverState> = (element, _mediaStore) => {
|
||||
const coreState = getCoreState(CorePopover, getPropsFromAttrs(element));
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
import type { PopoverState as CorePopoverState } from '@videojs/core';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Prettify } from '../types';
|
||||
import type { ConnectedComponent } from '../utils/component-factory';
|
||||
|
||||
import { contains, safePolygon } from '@videojs/utils/dom';
|
||||
import { Popover as CorePopover } from '@videojs/core';
|
||||
import { Children, cloneElement, useCallback, useEffect, useId, useState } from 'react';
|
||||
import { toConnectedComponent, toContextComponent, useCore } from '../utils/component-factory';
|
||||
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useId,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
type Placement = CorePopoverState['placement'];
|
||||
|
||||
type Placement = 'top' | 'top-start' | 'top-end';
|
||||
export type PopoverState = Prettify<
|
||||
CorePopoverState & {
|
||||
popupId: string | undefined;
|
||||
updatePositioning: (placement: Placement, sideOffset: number) => void;
|
||||
}
|
||||
>;
|
||||
|
||||
type TransitionStatus = 'initial' | 'open' | 'close' | 'unmounted';
|
||||
|
||||
interface PopoverContextType {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
popupRef: React.RefObject<HTMLElement | null>;
|
||||
triggerRef: React.RefObject<HTMLElement | null>;
|
||||
updatePositioning: (placement: Placement, sideOffset: number) => void;
|
||||
transitionStatus: TransitionStatus;
|
||||
placement: Placement;
|
||||
sideOffset: number;
|
||||
popupId: string | undefined;
|
||||
}
|
||||
// ============================================================================
|
||||
// ROOT COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
interface PopoverRootProps {
|
||||
openOnHover?: boolean;
|
||||
@@ -38,16 +27,128 @@ interface PopoverRootProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function usePopoverRootState(props: PopoverRootProps): PopoverState {
|
||||
const { openOnHover = false, delay = 0, closeDelay = 0 } = props;
|
||||
const [placement, setPlacement] = useState<Placement>('top');
|
||||
const [sideOffset, setSideOffset] = useState(5);
|
||||
const uniqueId = useId();
|
||||
const popupId = uniqueId.replace(/^:([^:]+):$/, '«$1»');
|
||||
|
||||
const coreState = useCore<CorePopover>(CorePopover, {
|
||||
openOnHover,
|
||||
delay,
|
||||
closeDelay,
|
||||
placement,
|
||||
sideOffset,
|
||||
});
|
||||
|
||||
const updatePositioning = useCallback((newPlacement: Placement, newSideOffset: number) => {
|
||||
setPlacement(newPlacement);
|
||||
setSideOffset(newSideOffset);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...coreState,
|
||||
popupId,
|
||||
updatePositioning,
|
||||
};
|
||||
}
|
||||
|
||||
export function usePopoverRootProps(props: PopoverRootProps, _state: PopoverState): { children: ReactNode } {
|
||||
return {
|
||||
children: props.children,
|
||||
};
|
||||
}
|
||||
|
||||
export function renderPopoverRoot(props: { children: ReactNode }): JSX.Element {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
const PopoverRoot: ConnectedComponent<PopoverRootProps, typeof renderPopoverRoot> = toConnectedComponent(
|
||||
usePopoverRootState,
|
||||
usePopoverRootProps,
|
||||
renderPopoverRoot,
|
||||
'Popover.Root',
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// TRIGGER COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
interface PopoverTriggerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function usePopoverTriggerProps(
|
||||
props: PopoverTriggerProps,
|
||||
context: PopoverState,
|
||||
): { child: JSX.Element; triggerProps: Record<string, any> } {
|
||||
const { children } = props;
|
||||
const { _setTriggerElement, _open, popupId } = context;
|
||||
|
||||
const child = Children.only(children) as JSX.Element;
|
||||
const existingStyle = (child.props as { style?: React.CSSProperties })?.style || {};
|
||||
|
||||
return {
|
||||
child,
|
||||
triggerProps: {
|
||||
ref: _setTriggerElement,
|
||||
'data-popup-open': _open ? '' : undefined,
|
||||
commandfor: popupId ?? undefined,
|
||||
style: {
|
||||
...existingStyle,
|
||||
...(popupId ? { anchorName: `--${popupId}` as any } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function renderPopoverTrigger(props: { child: JSX.Element; triggerProps: Record<string, any> }): JSX.Element {
|
||||
// eslint-disable-next-line react/no-clone-element
|
||||
return cloneElement(props.child, props.triggerProps);
|
||||
}
|
||||
|
||||
const PopoverTrigger: ConnectedComponent<PopoverTriggerProps, typeof renderPopoverTrigger> = toContextComponent(
|
||||
usePopoverTriggerProps,
|
||||
renderPopoverTrigger,
|
||||
'Popover.Trigger',
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// POSITIONER COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
interface PopoverPositionerProps {
|
||||
side?: Placement;
|
||||
sideOffset?: number;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function usePopoverPositionerProps(
|
||||
props: PopoverPositionerProps,
|
||||
context: PopoverState,
|
||||
): { children: ReactNode } {
|
||||
const { side = 'top', sideOffset = 5, children } = props;
|
||||
const { updatePositioning } = context;
|
||||
|
||||
useEffect(() => {
|
||||
updatePositioning(side, sideOffset);
|
||||
}, [side, sideOffset, updatePositioning]);
|
||||
|
||||
return { children };
|
||||
}
|
||||
|
||||
export function renderPopoverPositioner(props: { children: ReactNode }): JSX.Element {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
const PopoverPositioner: ConnectedComponent<PopoverPositionerProps, typeof renderPopoverPositioner>
|
||||
= toContextComponent(usePopoverPositionerProps, renderPopoverPositioner, 'Popover.Positioner');
|
||||
|
||||
// ============================================================================
|
||||
// POPUP COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
interface PopoverPopupProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
@@ -55,269 +156,68 @@ interface PopoverPopupProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const PopoverContext = createContext<PopoverContextType | null>(null);
|
||||
|
||||
function usePopoverContext(): PopoverContextType {
|
||||
const context = useContext(PopoverContext);
|
||||
if (!context) {
|
||||
throw new Error('Popover components must be used within PopoverRoot');
|
||||
}
|
||||
return context;
|
||||
interface PopoverPopupRenderProps extends React.ComponentProps<'div'> {
|
||||
children: ReactNode;
|
||||
'data-side': Placement;
|
||||
'data-starting-style': string | undefined;
|
||||
'data-open': string | undefined;
|
||||
'data-ending-style': string | undefined;
|
||||
'data-closed': string | undefined;
|
||||
}
|
||||
|
||||
function PopoverRoot({ openOnHover = false, delay = 0, closeDelay = 0, children }: PopoverRootProps): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [placement, setPlacement] = useState<Placement>('top');
|
||||
const [sideOffset, setSideOffset] = useState(5);
|
||||
const [transitionStatus, setTransitionStatus] = useState<TransitionStatus>('initial');
|
||||
const popupRef = useRef<HTMLElement | null>(null);
|
||||
const triggerRef = useRef<HTMLElement | null>(null);
|
||||
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const pointerMoveHandlerRef = useRef<((event: MouseEvent) => void) | null>(null);
|
||||
const uniqueId = useId();
|
||||
const popupId = uniqueId.replace(/^:([^:]+):$/, '«$1»');
|
||||
export function usePopoverPopupProps(props: PopoverPopupProps, context: PopoverState): PopoverPopupRenderProps {
|
||||
const { className, style, children, id } = props;
|
||||
const { _setPopoverElement, _transitionStatus, placement, popupId, sideOffset } = context;
|
||||
const [side, alignment] = placement.split('-');
|
||||
|
||||
const clearHoverTimeout = useCallback(() => {
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
hoverTimeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setOpenState = useCallback((newOpen: boolean) => {
|
||||
if (open === newOpen) return;
|
||||
|
||||
setOpen(newOpen);
|
||||
|
||||
if (newOpen) {
|
||||
setTransitionStatus('initial');
|
||||
if (popupRef.current) {
|
||||
popupRef.current.showPopover();
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
setTransitionStatus('open');
|
||||
});
|
||||
} else {
|
||||
setTransitionStatus('close');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!popupRef.current || open) return;
|
||||
|
||||
const transitions = popupRef.current.getAnimations().filter(anim => anim instanceof CSSTransition);
|
||||
if (transitions.length > 0) {
|
||||
Promise.all(transitions.map(t => t.finished))
|
||||
.then(() => popupRef.current?.hidePopover())
|
||||
.catch(() => popupRef.current?.hidePopover());
|
||||
} else {
|
||||
popupRef.current.hidePopover();
|
||||
}
|
||||
}, [open, transitionStatus]);
|
||||
|
||||
const updatePositioning = useCallback((newPlacement: Placement, newSideOffset: number) => {
|
||||
setPlacement(newPlacement);
|
||||
setSideOffset(newSideOffset);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const trigger = triggerRef.current;
|
||||
const popup = popupRef.current;
|
||||
if (!trigger || !popup) return;
|
||||
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
|
||||
const addPointerMoveListener = () => {
|
||||
if (!globalThis.matchMedia?.('(hover: hover)')?.matches) return;
|
||||
|
||||
if (!pointerMoveHandlerRef.current) {
|
||||
pointerMoveHandlerRef.current = safePolygon({ blockPointerEvents: true })({
|
||||
placement,
|
||||
elements: {
|
||||
domReference: trigger,
|
||||
floating: popup,
|
||||
},
|
||||
x: 0,
|
||||
y: 0,
|
||||
onClose: () => {
|
||||
if (pointerMoveHandlerRef.current) {
|
||||
document.documentElement.removeEventListener('pointermove', pointerMoveHandlerRef.current);
|
||||
}
|
||||
clearHoverTimeout();
|
||||
|
||||
hoverTimeoutRef.current = setTimeout(() => {
|
||||
setOpenState(false);
|
||||
}, closeDelay);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Event listener is automatically removed when AbortController is aborted
|
||||
document.documentElement.addEventListener('pointermove', pointerMoveHandlerRef.current, { signal });
|
||||
};
|
||||
|
||||
const handlePointerEnter = (event: PointerEvent) => {
|
||||
if (!openOnHover) return;
|
||||
|
||||
clearHoverTimeout();
|
||||
|
||||
if (event.currentTarget === popup) {
|
||||
addPointerMoveListener();
|
||||
}
|
||||
|
||||
if (open) {
|
||||
return;
|
||||
}
|
||||
|
||||
hoverTimeoutRef.current = setTimeout(() => {
|
||||
setOpenState(true);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const handlePointerLeave = () => {
|
||||
if (!openOnHover) return;
|
||||
addPointerMoveListener();
|
||||
};
|
||||
|
||||
if (globalThis.matchMedia?.('(hover: hover)')?.matches) {
|
||||
// Event listeners are automatically removed when AbortController is aborted
|
||||
trigger.addEventListener('pointerenter', handlePointerEnter, { signal });
|
||||
trigger.addEventListener('pointerleave', handlePointerLeave, { signal });
|
||||
popup.addEventListener('pointerenter', handlePointerEnter, { signal });
|
||||
}
|
||||
|
||||
const handleFocusIn = () => {
|
||||
setOpenState(true);
|
||||
};
|
||||
|
||||
const handleFocusOut = (event: FocusEvent) => {
|
||||
const relatedTarget = event.relatedTarget as HTMLElement;
|
||||
if (relatedTarget && popup && contains(popup, relatedTarget)) return;
|
||||
setOpenState(false);
|
||||
};
|
||||
|
||||
// Event listeners are automatically removed when AbortController is aborted
|
||||
trigger.addEventListener('focusin', handleFocusIn, { signal });
|
||||
trigger.addEventListener('focusout', handleFocusOut, { signal });
|
||||
popup.addEventListener('focusout', handleFocusOut, { signal });
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
clearHoverTimeout();
|
||||
|
||||
if (pointerMoveHandlerRef.current) {
|
||||
document.documentElement.removeEventListener('pointermove', pointerMoveHandlerRef.current);
|
||||
}
|
||||
|
||||
pointerMoveHandlerRef.current = null;
|
||||
};
|
||||
}, [openOnHover, delay, closeDelay, open, placement, setOpenState, clearHoverTimeout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!popupRef.current || !triggerRef.current) return;
|
||||
|
||||
const popup = popupRef.current;
|
||||
const [side, alignment] = placement.split('-');
|
||||
popup.style.setProperty('top', `calc(anchor(${side}) - ${sideOffset}px)`);
|
||||
popup.style.setProperty('translate', `0 -100%`);
|
||||
popup.style.setProperty('justify-self', alignment === 'start'
|
||||
? 'anchor-start'
|
||||
: alignment === 'end'
|
||||
? 'anchor-end'
|
||||
: 'anchor-center');
|
||||
}, [placement, sideOffset]);
|
||||
|
||||
const value: PopoverContextType = useMemo(() => ({
|
||||
open,
|
||||
setOpen: setOpenState,
|
||||
popupRef,
|
||||
triggerRef,
|
||||
updatePositioning,
|
||||
transitionStatus,
|
||||
placement,
|
||||
sideOffset,
|
||||
popupId,
|
||||
}), [open, setOpenState, updatePositioning, transitionStatus, placement, sideOffset, popupId]);
|
||||
|
||||
return <PopoverContext.Provider value={value}>{children}</PopoverContext.Provider>;
|
||||
}
|
||||
|
||||
function PopoverTrigger({ children }: PopoverTriggerProps): JSX.Element {
|
||||
const { triggerRef, open, popupId } = usePopoverContext();
|
||||
|
||||
const child = Children.only(children) as JSX.Element;
|
||||
const existingStyle = (child.props as { style?: React.CSSProperties })?.style || {};
|
||||
|
||||
// eslint-disable-next-line react/no-clone-element
|
||||
return cloneElement(child, {
|
||||
ref: triggerRef,
|
||||
'data-popup-open': open ? '' : undefined,
|
||||
commandfor: popupId ?? undefined,
|
||||
return {
|
||||
ref: _setPopoverElement,
|
||||
id: id ?? popupId ?? undefined,
|
||||
className,
|
||||
popover: 'manual' as const,
|
||||
style: {
|
||||
...existingStyle,
|
||||
...(popupId ? { anchorName: `--${popupId}` as any } : {}),
|
||||
},
|
||||
});
|
||||
...(popupId ? { positionAnchor: `--${popupId}` as any } : {}),
|
||||
top: `calc(anchor(${side}) - ${sideOffset}px)`,
|
||||
translate: '0 -100%',
|
||||
justifySelf: alignment === 'start' ? 'anchor-start' : alignment === 'end' ? 'anchor-end' : 'anchor-center',
|
||||
...style,
|
||||
} as React.CSSProperties,
|
||||
'data-side': placement,
|
||||
'data-starting-style': _transitionStatus === 'initial' ? '' : undefined,
|
||||
'data-open': _transitionStatus === 'initial' || _transitionStatus === 'open' ? '' : undefined,
|
||||
'data-ending-style': _transitionStatus === 'close' || _transitionStatus === 'unmounted' ? '' : undefined,
|
||||
'data-closed': _transitionStatus === 'close' || _transitionStatus === 'unmounted' ? '' : undefined,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
function PopoverPositioner({ side = 'top', sideOffset = 5, children }: PopoverPositionerProps): JSX.Element | null {
|
||||
const { updatePositioning } = usePopoverContext();
|
||||
|
||||
useEffect(() => {
|
||||
updatePositioning(side, sideOffset);
|
||||
}, [side, sideOffset, updatePositioning]);
|
||||
|
||||
return <>{children}</>;
|
||||
export function renderPopoverPopup(props: PopoverPopupRenderProps): JSX.Element {
|
||||
return <div {...props} />;
|
||||
}
|
||||
|
||||
function PopoverPopup({ className, style, children }: PopoverPopupProps): JSX.Element {
|
||||
const { popupRef, triggerRef, transitionStatus, placement, popupId } = usePopoverContext();
|
||||
const triggerElement = triggerRef.current;
|
||||
const PopoverPopup: ConnectedComponent<PopoverPopupProps, typeof renderPopoverPopup> = toContextComponent(
|
||||
usePopoverPopupProps,
|
||||
renderPopoverPopup,
|
||||
'Popover.Popup',
|
||||
);
|
||||
|
||||
// Copy data attributes from trigger element
|
||||
const dataAttributes = useMemo(() => {
|
||||
if (!triggerElement?.attributes) return {};
|
||||
return Object.fromEntries(
|
||||
Array.from(triggerElement.attributes)
|
||||
.filter(attr => attr.name.startsWith('data-'))
|
||||
.map(attr => [attr.name, attr.value]),
|
||||
);
|
||||
}, [triggerElement]);
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={popupRef as React.RefObject<HTMLDivElement>}
|
||||
id={popupId ?? undefined}
|
||||
className={className}
|
||||
popover="manual"
|
||||
style={{
|
||||
...(popupId ? { positionAnchor: `--${popupId}` as any } : {}),
|
||||
...style,
|
||||
}}
|
||||
{...dataAttributes}
|
||||
data-side={placement}
|
||||
data-starting-style={transitionStatus === 'initial' ? '' : undefined}
|
||||
data-open={transitionStatus === 'initial' || transitionStatus === 'open' ? '' : undefined}
|
||||
data-ending-style={transitionStatus === 'close' || transitionStatus === 'unmounted' ? '' : undefined}
|
||||
data-closed={transitionStatus === 'close' || transitionStatus === 'unmounted' ? '' : undefined}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const Popover: {
|
||||
export const Popover = Object.assign(
|
||||
{},
|
||||
{
|
||||
Root: PopoverRoot,
|
||||
Trigger: PopoverTrigger,
|
||||
Positioner: PopoverPositioner,
|
||||
Popup: PopoverPopup,
|
||||
},
|
||||
) as {
|
||||
Root: typeof PopoverRoot;
|
||||
Trigger: typeof PopoverTrigger;
|
||||
Positioner: typeof PopoverPositioner;
|
||||
Popup: typeof PopoverPopup;
|
||||
} = {
|
||||
Root: PopoverRoot,
|
||||
Trigger: PopoverTrigger,
|
||||
Positioner: PopoverPositioner,
|
||||
Popup: PopoverPopup,
|
||||
};
|
||||
|
||||
export default Popover;
|
||||
|
||||
Reference in New Issue
Block a user