116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
import { createContext, JSXElement, useContext } from "solid-js";
|
|
import { FloatingRoot, FloatingTrigger, FloatingContent, useFloatingHover, Placement } from "./Floating.tsx";
|
|
|
|
const POPOVER_CLS = "bg-white rounded-default shadow-lg border border-neutral-200";
|
|
|
|
interface PopoverProps {
|
|
open?: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
placement?: Placement;
|
|
offset?: number;
|
|
// Nested floating that shouldn't close (or be closed by) an ancestor popover.
|
|
standalone?: boolean;
|
|
children?: JSXElement;
|
|
}
|
|
|
|
interface HoverPopoverProps extends PopoverProps {
|
|
hoverDelay?: number;
|
|
hoverCloseDelay?: number;
|
|
}
|
|
|
|
export function Popover(props: PopoverProps) {
|
|
return (
|
|
<FloatingRoot open={props.open} onOpenChange={(v: boolean) => props.onOpenChange?.(v)} placement={props.placement ?? "bottom-start"} offset={props.offset ?? 8} flip={true} shift={true} standalone={props.standalone}>
|
|
{props.children}
|
|
</FloatingRoot>
|
|
);
|
|
}
|
|
|
|
interface PopoverTriggerProps {
|
|
class?: string;
|
|
title?: string;
|
|
children?: JSXElement;
|
|
}
|
|
|
|
export function PopoverTrigger(props: PopoverTriggerProps) {
|
|
return (
|
|
<FloatingTrigger openOnHover={false} class={props.class || ""} title={props.title}>
|
|
{props.children}
|
|
</FloatingTrigger>
|
|
);
|
|
}
|
|
|
|
interface PopoverContentProps {
|
|
class?: string;
|
|
style?: Record<string, string | number>;
|
|
children?: JSXElement;
|
|
}
|
|
|
|
export function PopoverContent(props: PopoverContentProps) {
|
|
return (
|
|
<FloatingContent class={POPOVER_CLS + " " + (props.class || "")} style={props.style}>
|
|
{props.children}
|
|
</FloatingContent>
|
|
);
|
|
}
|
|
|
|
export function HoverPopover(props: HoverPopoverProps) {
|
|
return (
|
|
<FloatingRoot open={props.open} onOpenChange={(v: boolean) => props.onOpenChange?.(v)} placement={props.placement ?? "bottom-start"} offset={props.offset ?? 8} flip={true} shift={true} standalone={props.standalone}>
|
|
<HoverPopoverInner hoverDelay={props.hoverDelay ?? 0} hoverCloseDelay={props.hoverCloseDelay ?? 150}>
|
|
{props.children}
|
|
</HoverPopoverInner>
|
|
</FloatingRoot>
|
|
);
|
|
}
|
|
|
|
interface HoverPopoverContextValue {
|
|
hoverDelay: number;
|
|
hoverCloseDelay: number;
|
|
}
|
|
|
|
const HoverPopoverContext = createContext<HoverPopoverContextValue | null>(null);
|
|
|
|
interface HoverPopoverInnerProps {
|
|
hoverDelay: number;
|
|
hoverCloseDelay: number;
|
|
children?: JSXElement;
|
|
}
|
|
|
|
function HoverPopoverInner(props: HoverPopoverInnerProps) {
|
|
return (
|
|
<HoverPopoverContext.Provider value={{
|
|
get hoverDelay() { return props.hoverDelay; },
|
|
get hoverCloseDelay() { return props.hoverCloseDelay; },
|
|
}}>
|
|
{props.children}
|
|
</HoverPopoverContext.Provider>
|
|
);
|
|
}
|
|
|
|
interface HoverPopoverTriggerProps {
|
|
asChild?: boolean;
|
|
class?: string;
|
|
children?: JSXElement;
|
|
}
|
|
|
|
export function HoverPopoverTrigger(props: HoverPopoverTriggerProps) {
|
|
const ctx = useContext(HoverPopoverContext);
|
|
return (
|
|
<FloatingTrigger openOnHover={true} class={props.class || ""} hoverDelay={ctx?.hoverDelay ?? 0} hoverCloseDelay={ctx?.hoverCloseDelay ?? 150}>
|
|
{props.children}
|
|
</FloatingTrigger>
|
|
);
|
|
}
|
|
|
|
export function HoverPopoverContent(props: PopoverContentProps) {
|
|
const ctx = useContext(HoverPopoverContext);
|
|
const hoverProps = useFloatingHover(true, ctx?.hoverCloseDelay ?? 150);
|
|
|
|
return (
|
|
<FloatingContent class={POPOVER_CLS + " " + (props.class || "")} style={props.style} onMouseEnter={hoverProps.onMouseEnter} onMouseLeave={hoverProps.onMouseLeave}>
|
|
{props.children}
|
|
</FloatingContent>
|
|
);
|
|
}
|