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

Introduce tooltip component #1

Merged
merged 9 commits into from
Sep 4, 2024
Merged
8 changes: 5 additions & 3 deletions examples/storybook/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const plugins = {
tailwindcss: {},
autoprefixer: {},
export default {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
112 changes: 112 additions & 0 deletions examples/storybook/src/stories/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { Meta, StoryObj } from "@storybook/react";
import * as React from "react";
import { Tooltip, type TooltipProps } from "@synopsisapp/symbiosis-ui";

const meta: Meta<typeof Tooltip.Root> = {
title: "Components/Tooltip",
component: Tooltip.Root,
tags: ["autodocs"],
decorators: [
(Story) => (
<div className="flex flex-col gap-4 items-start">
<Story />
</div>
),
],
};

export default meta;

type Story = StoryObj<typeof Tooltip.Root>;

export const Uncontrolled: Story = {
render: (args: TooltipProps["Root"]) => (
<Tooltip.Root {...args}>
<Tooltip.Trigger>
<span>Hover me</span>
</Tooltip.Trigger>
<Tooltip.Content label="Tooltip label" />
</Tooltip.Root>
),
argTypes: {
defaultOpen: {
control: false,
table: {
defaultValue: { summary: "false" },
type: { summary: "boolean" },
},
description:
"The open state of the tooltip when it is initially rendered. Use when you do not need to control its open state.",
required: false,
},
open: {
control: {
type: "boolean",
},
table: {
defaultValue: { summary: "false" },
},
type: "boolean",
description: "The controlled open state of the tooltip. Must be used in conjunction with onOpenChange.",
defaultValue: false,
required: false,
},
onOpenChange: {
table: {
type: { summary: "(open: boolean) => void" },
},
description: "Event handler called when the open state of the tooltip changes.",
control: false,
required: false,
},
},
};

export const Controlled: Story = {
render: (args) => {
const [open, setOpen] = React.useState(false);
return (
<Tooltip.Root {...args} open={open} onOpenChange={setOpen}>
<Tooltip.Trigger>
<span>Hover me</span>
</Tooltip.Trigger>
<Tooltip.Content label="Controlled Tooltip" />
</Tooltip.Root>
);
},
parameters: {
docs: {
source: {
code: `
const ControlledTooltip = () => {
const [open, setOpen] = React.useState(false);

return (
<Tooltip.Root open={open} onOpenChange={setOpen}>
<Tooltip.Trigger>
<span>Hover me</span>
</Tooltip.Trigger>
<Tooltip.Content label="Controlled Tooltip" />
</Tooltip.Root>
);
};
`,
language: "jsx",
type: "code",
},
},
},
};

export const CustomContent: Story = {
render: (args: TooltipProps["Root"]) => (
<Tooltip.Root {...args}>
<Tooltip.Trigger>
<span>Hover me</span>
</Tooltip.Trigger>
<Tooltip.Content className="bg-mainColors-light-200 rounded-none">
<div>Popover content</div>
</Tooltip.Content>
</Tooltip.Root>
),
};
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"peerDependencies": {
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tailwindcss/typography": "^0.5.13",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down
51 changes: 51 additions & 0 deletions packages/ui/src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Text } from "../Text";
import { cn } from "../../utils/cn";
import type { TooltipRootProps, TooltipContentProps, TooltipTriggerProps } from "./types";
import { sharedTooltipStyles } from "./styles";

export const TooltipRoot = ({ children, defaultOpen, open, onOpenChange }: TooltipRootProps) => {
return (
<TooltipPrimitive.Provider>
<TooltipPrimitive.Root delayDuration={100} open={open} onOpenChange={onOpenChange} defaultOpen={defaultOpen}>
{children}
</TooltipPrimitive.Root>
</TooltipPrimitive.Provider>
);
};

TooltipRoot.displayName = "Tooltip.Root";

export const TooltipContent = ({ children, label, className }: TooltipContentProps) => {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content sideOffset={5} forceMount className={cn(sharedTooltipStyles, className)}>
{label ? (
<Text variant="body-small-200" noTranslations className="m-0 p-0">
{label}
</Text>
) : (
children
)}
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
);
};

TooltipContent.displayName = "Tooltip.Content";

export const TooltipTrigger = ({ children }: TooltipTriggerProps) => {
return (
<TooltipPrimitive.Trigger asChild>
<div>{children}</div>
</TooltipPrimitive.Trigger>
);
};

TooltipTrigger.displayName = "Tooltip.Trigger";

export const Tooltip = {
Root: TooltipRoot,
Content: TooltipContent,
Trigger: TooltipTrigger,
};
10 changes: 10 additions & 0 deletions packages/ui/src/components/Tooltip/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { cn } from "../../utils/cn";

export const sharedTooltipStyles = cn(
"py-2 px-3 rounded-md bg-gray-700 text-white max-w-[200px]",
"will-change-[transform,opacity]",
"data-[state=delayed-open]:data-[side=top]:animate-slide-up-and-fade",
"data-[state=delayed-open]:data-[side=right]:animate-slide-right-and-fade",
"data-[state=delayed-open]:data-[side=left]:animate-slide-left-and-fade",
"data-[state=delayed-open]:data-[side=bottom]:animate-slide-down-and-fade",
);
22 changes: 22 additions & 0 deletions packages/ui/src/components/Tooltip/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type TooltipRootProps = {
children: React.ReactNode;
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
};

export type TooltipContentProps = {
children?: React.ReactNode;
label?: string;
className?: string;
};

export type TooltipTriggerProps = {
children: React.ReactNode;
};

export type TooltipProps = {
Root: TooltipRootProps;
Content: TooltipContentProps;
Trigger: TooltipTriggerProps;
};
4 changes: 4 additions & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export { Text } from "./components/Text";
export * from "./components/Text/types";
export type { TextProps } from "./components/Text/types";

export { Tooltip } from "./components/Tooltip";
export * from "./components/Tooltip/types";
export type { TooltipProps } from "./components/Tooltip/types";

export * from "./designSystemTokens";

export { shadcnPreset } from "./tailwind/shadcn-preset";
Expand Down
20 changes: 20 additions & 0 deletions packages/ui/src/tailwind/shadcn-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ export const shadcnPlugin = plugin(
transform: "translateY(0px)",
},
},
"slide-down-and-fade": {
from: { opacity: "0", transform: "translateY(-5px)" },
to: { opacity: "1", transform: "translateY(0)" },
},
"slide-left-and-fade": {
from: { opacity: "0", transform: "translateX(5px)" },
to: { opacity: "1", transform: "translateX(0)" },
},
"slide-up-and-fade": {
from: { opacity: "0", transform: "translateY(5px)" },
to: { opacity: "1", transform: "translateY(0)" },
},
"slide-right-and-fade": {
from: { opacity: "0", transform: "translateX(-5px)" },
to: { opacity: "1", transform: "translateX(0)" },
},
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
Expand All @@ -161,6 +177,10 @@ export const shadcnPlugin = plugin(
"fade-down": "fade-down 0.5s",
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"slide-down-and-fade": "slide-down-and-fade 0.2s ease-out",
"slide-up-and-fade": "slide-up-and-fade 0.2s ease-out",
"slide-left-and-fade": "slide-left-and-fade 0.2s ease-out",
"slide-right-and-fade": "slide-right-and-fade 0.2s ease-out",
},
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/ui/vite.config.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default defineConfig({
"@biomejs/biome",
"@radix-ui/react-checkbox",
"@radix-ui/react-slot",
"@radix-ui/react-tooltip",
"@tailwindcss/typography",
"@types/fs-extra",
"@types/node",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default defineConfig({
"@biomejs/biome",
"@radix-ui/react-checkbox",
"@radix-ui/react-slot",
"@radix-ui/react-tooltip",
"@tailwindcss/typography",
"@types/fs-extra",
"@types/node",
Expand Down