useOpenState
Usage
"use client";
import { XIcon } from "@/icons/*";
import { cvx, cvxProps } from "cretex";
import { Button, UnstyledButton } from "@/ui/button";
import { useOpenState } from "@/hooks/use-open-state";
import { Stack } from "@/ui/stack";
import { Group } from "@/ui/group";
import { Kbd } from "@/ui/kbd";
export function UseOpenStateDemo() {
const state = useOpenState({ modal: true, hotKeys: "ctrl+D" });
return (
<>
<Button ref={state.triggerRef} onClick={state.toggle} className="m-auto h-auto">
<Group align="center" gap="4">
Dialog |
<Kbd items={["ctrl", "D"]} />
</Group>
</Button>
<state.Portal render={state.render}>
<div onClick={state.toggle} {...state.attr()} {...classes("overlay")} />
<div role="dialog" ref={state.contentRef as React.RefObject<HTMLDivElement>} {...state.attr()} {...classes("content")}>
<Stack align="center" className="size-full">
Click overlay to close
<br />
<Group align="center" gap="4">
or use <Kbd items={["ctrl", "D"]} />
</Group>
</Stack>
<UnstyledButton onClick={state.toggle} {...classes("close")}>
<XIcon />
<span className="sr-only">Close Trigger</span>
</UnstyledButton>
</div>
</state.Portal>
</>
);
}
function classes(selector: NonNullable<cvxProps<typeof dialog>["selector"]>) {
return { className: dialog({ selector }) };
}
const dialog = cvx({
variants: {
selector: {
overlay:
"fixed inset-0 z-[100] bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0",
content:
"fixed left-[50%] top-[50%] z-[111] w-80 h-80 translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-100 data-[state=closed]:zoom-out-100 data-[state=open]:slide-in-from-left-1/2 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-top-[60%] data-[state=closed]:slide-out-to-top-[60%] rounded-lg",
close: "size-4 absolute right-4 top-4 text-muted-foreground hover:text-color rounded-sm disabled:opacity-50"
}
}
});
Create Tooltip
"use client";
import { cvx } from "cretex";
import { Button } from "@/ui/button";
import { useOpenState } from "@/hooks/use-open-state";
type DataSide = "top" | "right" | "bottom" | "left";
type DataAlign = "start" | "center" | "end";
export function UseOpenStateTooltipDemo({ side }: { side?: DataSide; align?: DataAlign }) {
const state = useOpenState({
trigger: "hover",
sideOffset: 8,
side: side as DataSide,
observe: {
touch: true,
align: true,
side: true,
sideswipe: true,
offset: true,
contentRect: true
}
});
return (
<>
<Button
ref={state.triggerRef}
{...state.attr()}
onMouseEnter={state.handleOnMouseEnter}
onMouseLeave={state.handleOnMouseLeave}
onMouseMove={state.handleOnMouseMove}
onTouchStart={state.handleOnTouchStart}
>
Tooltip
</Button>
<state.Portal render={state.render}>
<p role="tooltip" ref={state.contentRef} className={tooltip({ side })} {...{ ...state.attr(), style: state.styleVars("content") }}>
allows use as a tooltip
</p>
</state.Portal>
</>
);
}
const tooltip = cvx({
assign:
"group absolute min-w-max z-20 text-[13px] rounded-md border bg-background text-popover-foreground shadow-md outline-none focus-visible:ring-0 flex items-center justify-center py-1 px-2 w-max max-w-max transition-opacity [transition-duration:200ms] data-[state=open]:animate-in data-[state=closed]:duration-200 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-100 data-[state=closed]:zoom-out-95 top-[--top] left-[--left]",
variants: {
side: {
top: "data-[side=top]:slide-in-from-bottom-0 data-[side=top]:data-[state=closed]:slide-out-to-bottom-0",
right: "data-[side=right]:slide-in-from-left-0 data-[side=right]:data-[state=closed]:slide-out-to-left-0",
bottom: "data-[side=bottom]:slide-in-from-top-0 data-[side=bottom]:data-[state=closed]:slide-out-to-top-0",
left: "data-[side=left]:slide-in-from-right-0 data-[side=left]:data-[state=closed]:slide-out-to-right-0"
}
}
});