Skip to content

fix: eliminate hover lag in P3 color grid (zero rerender tooltip) #2295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@
}
}
}

/* Tooltip variant required by Zero-UI. Zero-UI's build step normally generates this. */
@custom-variant tooltip-on ([data-tooltip="on"] &);
30 changes: 7 additions & 23 deletions src/components/home/why-tailwind-css-section.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import colorValues from "@/docs/utils/colors";
import { Tooltip, TooltipPanel, TooltipTrigger } from "@headlessui/react";
import { StarIcon } from "@heroicons/react/16/solid";
import clsx from "clsx";
import { CSSProperties, Fragment, ReactNode } from "react";
Expand Down Expand Up @@ -43,6 +42,7 @@ import responsive2 from "./why-tailwind-css-section/responsive-2.png";
import responsive3 from "./why-tailwind-css-section/responsive-3.png";
import responsive4 from "./why-tailwind-css-section/responsive-4.png";
import responsive5 from "./why-tailwind-css-section/responsive-5.png";
import { ZeroTooltip } from "./zero-ui-tooltip";

export default function WhyTailwindCssSection() {
return (
Expand Down Expand Up @@ -454,28 +454,12 @@ export default function WhyTailwindCssSection() {
{colors.map((color) => {
let value = colorValues[`${color}-${shade}`];
return (
<Tooltip as="div" key={color} showDelayMs={100} hideDelayMs={0} className="relative">
{shadeIdx === 0 && (
<>
<div className="pointer-events-none absolute -top-1 -left-1 h-screen border-l border-gray-950/5 dark:border-white/10"></div>
<div className="pointer-events-none absolute -top-1 -left-1 h-16 origin-top-left translate-px rotate-225 border-l border-gray-950/5 sm:h-24 dark:border-white/10"></div>
</>
)}

<TooltipTrigger>
<div
style={{ "--color": `var(--color-${color}-${shade})` } as CSSProperties}
className="h-(--height) w-(--width) bg-(--color) inset-ring inset-ring-gray-950/10 transition-opacity group-hover:opacity-75 hover:opacity-100 dark:inset-ring-white/10"
/>
</TooltipTrigger>
<TooltipPanel
as="div"
anchor="top"
className="pointer-events-none z-10 translate-y-2 rounded-full border border-gray-950 bg-gray-950/90 py-0.5 pr-2 pb-1 pl-3 text-center font-mono text-xs/6 font-medium whitespace-nowrap text-white opacity-100 inset-ring inset-ring-white/10 transition-[opacity] starting:opacity-0"
>
{value}
</TooltipPanel>
</Tooltip>
<ZeroTooltip
key={color}
color={`${color}-${shade}`}
tooltip={value}
shadeIdx={shadeIdx}
/>
);
})}
</Fragment>
Expand Down
16 changes: 16 additions & 0 deletions src/components/home/zero-ui-custom-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Minimal single-use build of React Zero-UI.
* This handles UI-only state using `data-*` attributes with zero React rerenders and scoped state.
*
* React Zero-UI automates both the variant generation and state pattern used here,
* and supports global UI state as well.
*
* Full library documentation: https://github.com/react-zero-ui/core
*/
export function createZeroUI(key: string) {
return {
set(value: string, { scope }: { scope: HTMLElement }) {
scope.dataset[key] = value;
},
};
}
43 changes: 43 additions & 0 deletions src/components/home/zero-ui-tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";
import { createZeroUI } from "./zero-ui-custom-build";

/**
* ZeroTooltip
* -----------
* Uses a minimal, local build of React Zero-UI to eliminate React-based hover
* rerenders. The helper mutates a scoped `data-tooltip` attribute, which Tailwind
* v4 styles via the `@custom-variant tooltip-on` rule (defined in globals.css).
*
* Only this component relies on the helper; no global state or extra deps added.
*/
export function ZeroTooltip({ color, tooltip, shadeIdx }: { color: string; tooltip: string; shadeIdx: number }) {
// Local UI state: "on" | "off"
const { set } = createZeroUI("tooltip");

return (
<div
onMouseEnter={(e) => set("on", { scope: e.currentTarget })}
onMouseLeave={(e) => set("off", { scope: e.currentTarget })}
className="relative"
/* Initial attribute value. Zero-UI's build step normally injects this. */
data-tooltip="off"
>
{shadeIdx === 0 && (
<>
<div className="pointer-events-none absolute -top-1 -left-1 h-screen border-l border-gray-950/5 dark:border-white/10" />
<div className="pointer-events-none absolute -top-1 -left-1 h-16 origin-top-left translate-px rotate-225 border-l border-gray-950/5 sm:h-24 dark:border-white/10" />
</>
)}

<div
className="h-(--height) w-(--width) bg-(--color) inset-ring inset-ring-gray-950/10 transition-opacity group-hover:opacity-75 hover:opacity-100 dark:inset-ring-white/10"
style={{ "--color": `var(--color-${color})` } as React.CSSProperties}
/>

{/* Tooltip panel. Becomes visible when data-tooltip="on" */}
<div className="pointer-events-none absolute top-full left-1/2 z-10 mt-2 -translate-x-1/2 -translate-y-18 rounded-full border border-gray-950 bg-gray-950/90 pt-0.5 pr-2 pb-1 pl-3 text-center font-mono text-xs/6 font-medium whitespace-nowrap text-white opacity-0 inset-ring inset-ring-white/10 transition-opacity dark:border-white/10 tooltip-on:opacity-100 tooltip-on:delay-100">
{tooltip}
</div>
</div>
);
}