Skip to content
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

Tooltip: update api + add zIndex as optional prop #574

Merged
merged 3 commits into from
Oct 22, 2024
Merged
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
5 changes: 5 additions & 0 deletions .changeset/nervous-brooms-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cambly/syntax-floating-components": major
---

Tooltip: Reconfigure API for easier usage + add zIndex as a prop
75 changes: 29 additions & 46 deletions packages/syntax-floating-components/src/Tooltip/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useRef, useEffect, type ReactElement } from "react";
import type { StoryObj, Meta } from "@storybook/react";
import { Tooltip, TooltipContent, TooltipTrigger } from "./Tooltip";
import { Tooltip } from "./Tooltip";
import Button from "../../../syntax-core/src/Button/Button";
import IconButton from "../../../syntax-core/src/IconButton/IconButton";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
Expand Down Expand Up @@ -48,7 +48,7 @@ export default {
defaultValue: { summary: "absolute" },
},
},
children: {
content: {
control: { type: "text" },
description: "The string value to show on the tooltip content",
},
Expand All @@ -62,41 +62,39 @@ export const Default: StoryObj<typeof Tooltip> = {
placement: "right",
initialOpen: true,
strategy: "absolute",
children: "This is a tooltip",
content: "This is a tooltip",
},
render: ({ delay, placement, initialOpen, strategy, children }) => (
render: ({ delay, placement, initialOpen, strategy, content }) => (
<div style={{ margin: "240px" }}>
<Tooltip
delay={delay}
placement={placement}
initialOpen={initialOpen}
strategy={strategy}
content={content}
>
<TooltipTrigger>
<IconButton
accessibilityLabel="Info Icon Button"
icon={InfoOutlinedIcon}
onClick={() => alert("Default button pressed")}
color="tertiary"
size="lg"
/>
</TooltipTrigger>
<TooltipContent>{children}</TooltipContent>
<IconButton
accessibilityLabel="Info Icon Button"
icon={InfoOutlinedIcon}
onClick={() => alert("Default button pressed")}
color="tertiary"
size="lg"
/>
</Tooltip>
</div>
),
};

export const UncontrolledButtonTooltip: StoryObj<typeof Tooltip> = {
args: {
content: "This is a button",
},
render: () => (
<Tooltip>
<TooltipTrigger>
<Button
text="My trigger"
onClick={() => alert("UncontrolledButtonTooltip pressed")}
/>
</TooltipTrigger>
<TooltipContent>This is a button</TooltipContent>
<Tooltip content="This is a button">
<Button
text="My trigger"
onClick={() => alert("UncontrolledButtonTooltip pressed")}
/>
</Tooltip>
),
};
Expand Down Expand Up @@ -133,27 +131,15 @@ export const ControlledTooltip = (): ReactElement => {
>
<Tooltip
open={open}
content="This is really long text"
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
>
<TooltipTrigger>
<Button
ref={ref}
text="My trigger"
onClick={() => setOpen((v) => !v)}
/>
</TooltipTrigger>
<TooltipContent>
<span style={{ display: "block" }}>
This is a button and a really long sentence.
</span>
<a
href="http://localhost:6006/?path=/docs/floating-components-tooltip--docs"
style={{ textUnderlinePosition: "under", color: "white" }}
>
Learn more
</a>
</TooltipContent>
<Button
ref={ref}
text="My trigger"
onClick={() => setOpen((v) => !v)}
/>
</Tooltip>
</div>
</div>
Expand Down Expand Up @@ -187,7 +173,7 @@ export const RadioButtonGroupWithTooltips = (): ReactElement => {
return (
<Box paddingY={8} paddingX={4} backgroundColor="gray200">
<Box display="flex" alignItems="center" direction="column">
<Box padding={7} rounding="xl" backgroundColor="white" width="100%">
<Box padding={7} rounding="md" backgroundColor="white" width="100%">
<Box
display="flex"
direction="column"
Expand All @@ -205,11 +191,8 @@ export const RadioButtonGroupWithTooltips = (): ReactElement => {
onChange={(e) => setChoice(Number(e.target.value))}
checked={choice === value}
/>
<Tooltip placement="left">
<TooltipTrigger>
<InfoOutlinedIcon width={20} height={20} />
</TooltipTrigger>
<TooltipContent>{content}</TooltipContent>
<Tooltip placement="left" content={content}>
<InfoOutlinedIcon width={20} height={20} />
</Tooltip>
</Box>
))}
Expand Down
16 changes: 5 additions & 11 deletions packages/syntax-floating-components/src/Tooltip/Tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { screen, render } from "@testing-library/react";
import { Tooltip, TooltipContent, TooltipTrigger } from "./Tooltip";
import { Tooltip } from "./Tooltip";
import { expect } from "vitest";
import userEvent from "@testing-library/user-event";
class ResizeObserver {
Expand All @@ -19,11 +19,8 @@ window.ResizeObserver = ResizeObserver;
describe("tooltip", () => {
it("renders successfully", () => {
render(
<Tooltip>
<TooltipTrigger>
<button>My trigger</button>
</TooltipTrigger>
<TooltipContent>My tooltip</TooltipContent>
<Tooltip content="My tooltip">
<button>My trigger</button>
</Tooltip>,
);
expect(screen.getByRole("button")).toBeInTheDocument();
Expand All @@ -32,11 +29,8 @@ describe("tooltip", () => {

it("tooltip dialogue appears", async () => {
render(
<Tooltip>
<TooltipTrigger>
<button>My trigger</button>
</TooltipTrigger>
<TooltipContent>My tooltip</TooltipContent>
<Tooltip content="My tooltip">
<button>My trigger</button>
</Tooltip>,
);

Expand Down
111 changes: 71 additions & 40 deletions packages/syntax-floating-components/src/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,23 @@ type TooltipOptions = {
* @defaultValue "absolute"
*/
strategy?: Strategy;
/**
* The z-index of the tooltip
*
* @defaultValue 0
*/
zIndex?: number;
};

export function useTooltip({
function useTooltip({
delay = 0,
initialOpen = false,
open: controlledOpen,
placement = "right",
strategy = "absolute",
onOpen = undefined,
onClose = undefined,
zIndex = 0,
}: TooltipOptions): UseFloatingReturn & {
getReferenceProps: (
userProps?: React.HTMLProps<Element> | undefined,
Expand All @@ -80,6 +87,7 @@ export function useTooltip({
arrowRef: React.MutableRefObject<null>;
open: boolean;
setOpen: (open: boolean) => void;
zIndex: number;
} {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen);

Expand Down Expand Up @@ -136,13 +144,14 @@ export function useTooltip({

return React.useMemo(
() => ({
zIndex,
open,
setOpen,
...interactions,
...data,
arrowRef,
}),
[open, setOpen, interactions, data],
[zIndex, open, setOpen, interactions, data],
);
}

Expand All @@ -160,24 +169,36 @@ const useTooltipContext = () => {
return context;
};

/**
* [Tooltip](https://cambly-syntax.vercel.app/?path=/docs/floating-components-tooltip--docs) displays contextual information on hover or focus.
*/
export function Tooltip({
children,
...options
}: { children: React.ReactNode } & TooltipOptions): JSX.Element {
// This can accept any props as options, e.g. `placement`,
// or other positioning options.
const tooltip = useTooltip(options);
const TooltipContent = React.forwardRef<
HTMLDivElement,
React.HTMLProps<HTMLDivElement>
>(function TooltipContent(props, propRef) {
const { zIndex, context: floatingContext, ...context } = useTooltipContext();
const ref = useMergeRefs([context.refs.setFloating, propRef]);

if (!context.open) return null;

return (
<TooltipContext.Provider value={tooltip}>
{children}
</TooltipContext.Provider>
<FloatingPortal>
<div
{...context.getFloatingProps(props)}
ref={ref}
className={styles.tooltipContent}
style={{
...context.floatingStyles,
zIndex,
}}
>
<Typography size={100} color="white">
{props.children}
</Typography>
<FloatingArrow ref={context.arrowRef} context={floatingContext} />
</div>
</FloatingPortal>
);
}
});

export const TooltipTrigger = React.forwardRef<
const TooltipTrigger = React.forwardRef<
HTMLElement,
React.HTMLProps<HTMLElement>
>(function TooltipTrigger({ children, ...props }, propRef) {
Expand Down Expand Up @@ -207,30 +228,40 @@ export const TooltipTrigger = React.forwardRef<
}
});

export const TooltipContent = React.forwardRef<
HTMLDivElement,
React.HTMLProps<HTMLDivElement>
>(function TooltipContent(props, propRef) {
const { context: floatingContext, ...context } = useTooltipContext();
const ref = useMergeRefs([context.refs.setFloating, propRef]);
/**
* [Tooltip](https://cambly-syntax.vercel.app/?path=/docs/floating-components-tooltip--docs) displays contextual information on hover or focus.
*
*
* Usage:
* ```tsx
* <Tooltip content="This is a tooltip">
* <IconButton />
* </Tooltip>
* ```
*/
export function Tooltip({
children,
content,
...options
}: {
children: React.ReactNode;
zIndex?: number;
content: string;
} & TooltipOptions): JSX.Element {
// This can accept any props as options, e.g. `placement`,
// or other positioning options.
const tooltip = useTooltip(options);

if (!context.open) return null;
const value = React.useMemo(() => {
return {
...tooltip,
};
}, [tooltip]);

return (
<FloatingPortal>
<div
{...context.getFloatingProps(props)}
ref={ref}
className={styles.tooltipContent}
style={{
...context.floatingStyles,
}}
>
<Typography size={100} color="white">
{props.children}
</Typography>
<FloatingArrow ref={context.arrowRef} context={floatingContext} />
</div>
</FloatingPortal>
<TooltipContext.Provider value={value}>
<TooltipTrigger>{children}</TooltipTrigger>
<TooltipContent>{content}</TooltipContent>
</TooltipContext.Provider>
);
});
}
11 changes: 2 additions & 9 deletions packages/syntax-floating-components/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "./Tooltip/Tooltip";
import { Tooltip } from "./Tooltip/Tooltip";
import { Popover, PopoverContent, PopoverTrigger } from "./Popover/Popover";

export {
Popover,
PopoverContent,
PopoverTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
};
export { Popover, PopoverContent, PopoverTrigger, Tooltip };
Loading