Skeleton, Breadcrumbs, Link, Button, Image, Meter, NumberField Floating components: Tooltip (hover/focus), Popover (click, with focus trap and dismiss), HoverCard (hover with safe area). Simple components: Alert (role=alert), Badge (role=status), Skeleton (loading placeholder with data attributes). Navigation: Breadcrumbs (nav>ol>li with separators), Link (accessible anchor with disabled), Button (with disabled click suppression). Data/Form: Image (Img+Fallback with loading status), Meter (like Progress for known ranges), NumberField (spinbutton with inc/dec). 302 tests across 46 files, typecheck clean, build produces 176 files.
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import type { JSX } from "solid-js";
|
|
import { Show, createEffect, onCleanup, splitProps } from "solid-js";
|
|
import { useInternalHoverCardContext } from "./hover-card-context";
|
|
|
|
export interface HoverCardContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
/** Keep mounted when closed. @default false */
|
|
forceMount?: boolean | undefined;
|
|
children?: JSX.Element;
|
|
}
|
|
|
|
/**
|
|
* Floating content panel for HoverCard. Displayed when the trigger is hovered.
|
|
* No role is set since hover card content is supplementary (not announced).
|
|
* Handles Escape to close and pointer safe-area so the user can move
|
|
* from the trigger into the content without triggering a close.
|
|
*/
|
|
export function HoverCardContent(props: HoverCardContentProps): JSX.Element {
|
|
const [local, rest] = splitProps(props, ["children", "forceMount"]);
|
|
const ctx = useInternalHoverCardContext();
|
|
|
|
const handlePointerEnter: JSX.EventHandler<HTMLDivElement, PointerEvent> = () => {
|
|
ctx.cancelTimers();
|
|
};
|
|
|
|
const handlePointerLeave: JSX.EventHandler<HTMLDivElement, PointerEvent> = () => {
|
|
ctx.scheduleClose();
|
|
};
|
|
|
|
createEffect(() => {
|
|
if (!ctx.isOpen()) return;
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") {
|
|
e.preventDefault();
|
|
ctx.cancelTimers();
|
|
ctx.close();
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
onCleanup(() => document.removeEventListener("keydown", handleKeyDown));
|
|
});
|
|
|
|
return (
|
|
<Show when={local.forceMount || ctx.isOpen()}>
|
|
<div
|
|
ref={(el) => ctx.setContentRef(el)}
|
|
id={ctx.contentId}
|
|
data-state={ctx.isOpen() ? "open" : "closed"}
|
|
style={ctx.floatingStyle()}
|
|
onPointerEnter={handlePointerEnter}
|
|
onPointerLeave={handlePointerLeave}
|
|
{...rest}
|
|
>
|
|
{local.children}
|
|
</div>
|
|
</Show>
|
|
);
|
|
}
|