2026-03-29 20:39:28 +07:00

71 lines
2.3 KiB
TypeScript

// packages/core/src/components/popover/popover-root.tsx
import type { Middleware, Placement } from "@floating-ui/dom";
import { flip, offset, shift } from "@floating-ui/dom";
import type { Accessor, JSX } from "solid-js";
import { createSignal, createUniqueId } from "solid-js";
import {
type CreateDisclosureStateOptions,
createDisclosureState,
} from "../../primitives/create-disclosure-state";
import { createFloating } from "../../primitives/create-floating";
import {
InternalPopoverContextProvider,
PopoverPublicContextProvider,
type InternalPopoverContextValue,
} from "./popover-context";
import type { PopoverRootProps } from "./popover.props";
export type { PopoverRootProps } from "./popover.props";
/**
* Root component for Popover. Manages open state, floating positioning,
* and provides context to all Popover parts. Renders no DOM elements.
*/
export function PopoverRoot(props: PopoverRootProps): JSX.Element {
const disclosure = createDisclosureState({
get open() {
return props.open;
},
get defaultOpen() {
return props.defaultOpen;
},
get onOpenChange() {
return props.onOpenChange;
},
} as CreateDisclosureStateOptions);
const contentId = createUniqueId();
const [triggerRef, setTriggerRef] = createSignal<HTMLElement | null>(null);
const [contentRef, setContentRef] = createSignal<HTMLElement | null>(null);
const floating = createFloating({
anchor: triggerRef,
floating: contentRef,
placement: (() => props.placement ?? "bottom") as Accessor<Placement>,
middleware: (() => [offset(8), flip(), shift({ padding: 8 })]) as Accessor<Middleware[]>,
open: disclosure.isOpen,
});
const internalCtx: InternalPopoverContextValue = {
isOpen: disclosure.isOpen,
setOpen: (open) => (open ? disclosure.open() : disclosure.close()),
modal: () => props.modal ?? false,
contentId: () => contentId,
triggerRef,
setTriggerRef,
contentRef,
setContentRef,
floatingStyle: floating.style,
};
return (
<InternalPopoverContextProvider value={internalCtx}>
<PopoverPublicContextProvider
value={{ open: disclosure.isOpen, modal: () => props.modal ?? false }}
>
{props.children}
</PopoverPublicContextProvider>
</InternalPopoverContextProvider>
);
}