152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib";
|
|
import React, { useState } from "react";
|
|
|
|
interface TooltipContextBody {
|
|
position: "top" | "bottom" | "left" | "right";
|
|
}
|
|
|
|
const tooltipContextInitial: TooltipContextBody = {
|
|
position: "top",
|
|
};
|
|
const TooltipContext = React.createContext<
|
|
[TooltipContextBody, React.Dispatch<React.SetStateAction<TooltipContextBody>>]
|
|
>([
|
|
tooltipContextInitial,
|
|
() => {
|
|
if (process.env.NODE_ENV && process.env.NODE_ENV === "development") {
|
|
console.warn(
|
|
"It seems like you're using TooltipContext outside of a provider.",
|
|
);
|
|
}
|
|
},
|
|
]);
|
|
|
|
const [tooltipVariant, resolveTooltipVariantProps] = vcn({
|
|
base: "w-fit h-fit relative group/tooltip",
|
|
variants: {
|
|
position: {
|
|
top: "",
|
|
bottom: "",
|
|
left: "",
|
|
right: "",
|
|
},
|
|
controlled: {
|
|
true: "controlled",
|
|
false: "",
|
|
},
|
|
opened: {
|
|
true: "opened",
|
|
false: "",
|
|
},
|
|
},
|
|
defaults: {
|
|
position: "top",
|
|
controlled: false,
|
|
opened: false,
|
|
},
|
|
});
|
|
|
|
interface TooltipProps
|
|
extends React.HTMLAttributes<HTMLDivElement>,
|
|
VariantProps<typeof tooltipVariant>,
|
|
AsChild {}
|
|
|
|
const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>((props, ref) => {
|
|
const [variantProps, rest] = resolveTooltipVariantProps(props);
|
|
const { asChild, ...extractedRest } = rest;
|
|
const contextState = useState<TooltipContextBody>({
|
|
...tooltipContextInitial,
|
|
...variantProps,
|
|
});
|
|
|
|
const Comp = asChild ? Slot : "div";
|
|
|
|
return (
|
|
<TooltipContext.Provider value={contextState}>
|
|
<Comp
|
|
ref={ref}
|
|
className={tooltipVariant(variantProps)}
|
|
{...extractedRest}
|
|
/>
|
|
</TooltipContext.Provider>
|
|
);
|
|
});
|
|
Tooltip.displayName = "Tooltip";
|
|
|
|
const tooltipContentColors = {
|
|
variants: {
|
|
default:
|
|
"bg-white dark:bg-black border-neutral-200 dark:border-neutral-700",
|
|
error: "bg-red-400 dark:bg-red-800 border-red-500 text-white",
|
|
success: "bg-green-400 dark:bg-green-800 border-green-500 text-white",
|
|
warning: "bg-yellow-400 dark:bg-yellow-800 border-yellow-500",
|
|
},
|
|
};
|
|
|
|
const [tooltipContentVariant, resolveTooltipContentVariantProps] = vcn({
|
|
base: `absolute py-1 px-3 rounded-md border opacity-0 transition-all
|
|
group-[:not(.controlled):hover]/tooltip:opacity-100 group-[.opened]/tooltip:opacity-100
|
|
select-none pointer-events-none
|
|
group-[:not(.controlled):hover]/tooltip:select-auto group-[.opened]/tooltip:select-auto group-[:not(.controlled):hover]/tooltip:pointer-events-auto group-[.opened]/tooltip:pointer-events-auto
|
|
group-[:not(.controlled):hover]/tooltip:[transition:transform_150ms_ease-out_var(--delay),opacity_150ms_ease-out_var(--delay),background-color_150ms_ease-in-out,color_150ms_ease-in-out,border-color_150ms_ease-in-out]`,
|
|
variants: {
|
|
position: {
|
|
top: "bottom-[calc(100%+var(--tooltip-offset))] left-1/2 -translate-x-1/2 group-[:not(.controlled):hover]/tooltip:translate-y-0 group-[.opened]/tooltip:translate-y-0 translate-y-[10px]",
|
|
bottom:
|
|
"top-[calc(100%+var(--tooltip-offset))] left-1/2 -translate-x-1/2 group-[:not(.controlled):hover]/tooltip:translate-y-0 group-[.opened]/tooltip:translate-y-0 translate-y-[-10px]",
|
|
left: "right-[calc(100%+var(--tooltip-offset))] top-1/2 -translate-y-1/2 group-[:not(.controlled):hover]/tooltip:translate-x-0 group-[.opened]/tooltip:translate-x-0 translate-x-[10px]",
|
|
right:
|
|
"left-[calc(100%+var(--tooltip-offset))] top-1/2 -translate-y-1/2 group-[:not(.controlled):hover]/tooltip:translate-x-0 group-[.opened]/tooltip:translate-x-0 translate-x-[-10px]",
|
|
},
|
|
delay: {
|
|
none: "[--delay:0ms]",
|
|
early: "[--delay:150ms]",
|
|
normal: "[--delay:500ms]",
|
|
late: "[--delay:1000ms]",
|
|
},
|
|
offset: {
|
|
sm: "[--tooltip-offset:2px]",
|
|
md: "[--tooltip-offset:4px]",
|
|
lg: "[--tooltip-offset:8px]",
|
|
},
|
|
status: {
|
|
normal: tooltipContentColors.variants.default,
|
|
error: tooltipContentColors.variants.error,
|
|
success: tooltipContentColors.variants.success,
|
|
warning: tooltipContentColors.variants.warning,
|
|
},
|
|
},
|
|
defaults: {
|
|
position: "top",
|
|
offset: "md",
|
|
delay: "normal",
|
|
status: "normal",
|
|
},
|
|
});
|
|
|
|
interface TooltipContentProps
|
|
extends React.HTMLAttributes<HTMLDivElement>,
|
|
Omit<VariantProps<typeof tooltipContentVariant>, "position"> {}
|
|
|
|
const TooltipContent = React.forwardRef<HTMLDivElement, TooltipContentProps>(
|
|
(props, ref) => {
|
|
const [variantProps, rest] = resolveTooltipContentVariantProps(props);
|
|
const [contextState] = React.useContext(TooltipContext);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={tooltipContentVariant({
|
|
...variantProps,
|
|
position: contextState.position,
|
|
})}
|
|
role="tooltip"
|
|
{...rest}
|
|
/>
|
|
);
|
|
},
|
|
);
|
|
TooltipContent.displayName = "TooltipContent";
|
|
|
|
export { Tooltip, TooltipContent };
|