This update enhances the Tooltip component in the react/components package by adding the AsChild property. Now, users of the Tooltip component have flexibility to use it as a child of another component. A conditional logic is added to choose either Slot or "div" based on the asChild property. And rest of the properties are extracted into extractedRest for avoiding passing of asChild downstream.
149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
import React, { useState } from "react";
|
|
import { AsChild, Slot, VariantProps, vcn } from "@pswui-lib";
|
|
|
|
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>
|
|
);
|
|
});
|
|
|
|
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,
|
|
})}
|
|
{...rest}
|
|
/>
|
|
);
|
|
},
|
|
);
|
|
|
|
export { Tooltip, TooltipContent };
|