/* * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. */ import type { Meta, StoryObj } from "@storybook/react-vite"; import { storybookLayoutDecorator } from "@storybook-common"; import { expect, screen, waitFor } from "storybook/test"; import { Flex } from "@blueprintjs/labs"; import { Intent, mergeRefs } from "../../common"; import { Button } from "../button/buttons"; import { Menu } from "../menu/menu"; import { MenuItem } from "../menu/menuItem"; import { PopoverNext } from "../popover-next/popoverNext"; import { Tooltip } from "./tooltip"; const TOOLTIP_POPOVER_MENU = ( ); const meta: Meta = { title: "Core/Overlays/Tooltip", component: Tooltip, decorators: [storybookLayoutDecorator], tags: ["autodocs"], args: { content: "This is a tooltip", intent: Intent.NONE, compact: false, minimal: false, disabled: false, }, argTypes: { intent: { control: "select", options: Object.values(Intent), }, compact: { control: "boolean", }, minimal: { control: "boolean", }, disabled: { control: "boolean", }, content: { control: "text", }, }, } satisfies Meta; export default meta; type Story = StoryObj; /** * A basic tooltip that appears on hover. */ export const Default: Story = { args: { content: "This is a tooltip", isOpen: true, children: , }, }; /** * Use the `intent` prop to apply a semantic color to the tooltip background. */ export const IntentExample: Story = { name: "Intent", argTypes: { intent: { table: { disable: true } }, }, render: args => { const intents = Object.values(Intent); const mid = Math.ceil(intents.length / 2); const columns = [intents.slice(0, mid), intents.slice(mid)]; return ( {columns.map((col, i) => ( {col.map(intent => (
))}
))}
); }, }; /** * The `compact` prop renders a more condensed tooltip, and the `minimal` prop removes the arrow. */ export const VariantExample: Story = { name: "Variant", argTypes: { compact: { table: { disable: true } }, minimal: { table: { disable: true } }, }, render: args => (
), }; /** * Interactive playground with all props togglable via Storybook controls. */ export const Playground: Story = { args: { content: "Tooltip content", children: , }, }; /** * A tooltip can share a single target with a click popover. Tooltip render-target * props are spread after PopoverNext's props here to verify that Tooltip does not * inject an `onClick` handler which would shadow the popover trigger. */ export const WithPopover: Story = { name: "With Popover", args: { content: "Tooltip: more actions", placement: "top", }, render: args => ( ( ( , }, play: async ({ canvas, userEvent, step }) => { await step("Hover target to open tooltip", async () => { const target = canvas.getByRole("button", { name: "Hover me" }); await userEvent.hover(target); await waitFor(() => expect(screen.getByText("I appeared on hover!")).toBeVisible()); }); }, }; /** * Demonstrates the tooltip closing when the user moves the cursor away. * After hovering to open, the cursor is moved off and the tooltip disappears. */ export const HoverClose: Story = { name: "Hover Close", args: { content: "Now you see me", hoverOpenDelay: 0, hoverCloseDelay: 0, transitionDuration: 0, children: , }, play: async ({ canvas, userEvent, step }) => { const target = canvas.getByRole("button", { name: "Hover then leave" }); await step("Hover to open tooltip", async () => { await userEvent.hover(target); await waitFor(() => expect(screen.getByText("Now you see me")).toBeVisible()); }); await step("Unhover to close tooltip", async () => { await userEvent.unhover(target); await waitFor(() => expect(screen.queryByText("Now you see me")).not.toBeInTheDocument()); }); }, }; /** * When `disabled` is true, hovering over the target does not open the tooltip. */ export const HoverDisabled: Story = { name: "Hover Disabled", args: { content: "You should not see this", disabled: true, hoverOpenDelay: 0, children: , }, play: async ({ canvas, userEvent, step }) => { await step("Hover disabled tooltip — nothing appears", async () => { const target = canvas.getByRole("button", { name: "Tooltip is disabled" }); await userEvent.hover(target); await expect(screen.queryByText("You should not see this")).not.toBeInTheDocument(); }); }, }; /** * Demonstrates `hoverOpenDelay` — the tooltip waits before appearing. * With a 500ms delay, the tooltip is not yet visible immediately after hover. */ export const HoverOpenDelay: Story = { name: "Hover Open Delay", args: { content: "Delayed tooltip", hoverOpenDelay: 500, children: , }, play: async ({ canvas, userEvent, step }) => { await step("Hover — tooltip not visible immediately due to delay", async () => { const target = canvas.getByRole("button", { name: "500ms delay" }); await userEvent.hover(target); await expect(screen.queryByText("Delayed tooltip")).not.toBeInTheDocument(); }); await step("Wait for delay — tooltip becomes visible", async () => { await waitFor(() => expect(screen.getByText("Delayed tooltip")).toBeVisible()); }); }, };