Skip to content

Commit

Permalink
🪟 🎨 Update Tooltip component to match design library (#14816)
Browse files Browse the repository at this point in the history
* Add ToolTip storybook

* Update ToolTip to scss

* gitignore storybook-static dir

* Move InfoIcon from ToolTip to icons and remove duplicate

* Fix disabled class name in ToolTip

* Update ToolTip colors and spacing to match updated design and add light mode

* Update ToolTip to show from any side perfectly centered

* Add react-tether dependency
* Update ToolTip to use react-tether
* Add align property to ToolTip

* Add link colors to tooltip links and update storybook

* Fix font-size in Tooltip

* Add minor tweaks to Tooltip component

* Remove "help" cursor from state badge
Update tooltip corner to 5px
Update package-lock with react-tether

* Tooltip mode -> Tooltip theme

* Add tooltip helper components for learn more url and table

* Add tooltip context
* Update tooltip stories to demo with new components

* REname tooltip index from tsx to ts

* Update tooltip to use floating-ui instead of react-tether

* Move z-index values to own z-indices file

* Update InformationTooltip to use regular tooltip style
  • Loading branch information
edmundito authored Aug 18, 2022
1 parent 313ac11 commit e380c26
Show file tree
Hide file tree
Showing 22 changed files with 325 additions and 98 deletions.
2 changes: 2 additions & 0 deletions airbyte-webapp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ yarn-error.log*
.env.development
.env.production

storybook-static/

# Ignore generated API client, since it's automatically generated
/src/core/request/AirbyteClient.ts
47 changes: 47 additions & 0 deletions airbyte-webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions airbyte-webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"validate-links": "ts-node --skip-project ./scripts/validate-links.ts"
},
"dependencies": {
"@floating-ui/react-dom": "^1.0.0",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-brands-svg-icons": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const ReleaseStageBadge: React.FC<ReleaseStageBadgeProps> = ({ stage, sma
);

return tooltip ? (
<ToolTip control={badge} cursor="help">
<ToolTip control={badge}>
<FormattedMessage id={`connector.releaseStage.${stage}.description`} />
</ToolTip>
) : (
Expand Down
34 changes: 34 additions & 0 deletions airbyte-webapp/src/components/ToolTip/ToolTip.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@use "../../scss/colors";
@use "../../scss/variables";
@use "../../scss/z-indices";

.container {
display: inline;
position: relative;
}

.tooltip {
font-size: 12px;
line-height: initial;

padding: variables.$spacing-md;
border-radius: 5px;
max-width: 300px;
z-index: z-indices.$tooltip;
box-shadow: 0px 2px 4px rgba(colors.$dark-blue, 0.12);
background: rgba(colors.$dark-blue, 0.9);
color: colors.$white;

a {
color: rgba(colors.$white, 0.5);
}

&.light {
background: rgba(colors.$white, 0.9);
color: colors.$dark-blue;

a {
color: colors.$blue;
}
}
}
125 changes: 77 additions & 48 deletions airbyte-webapp/src/components/ToolTip/ToolTip.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,80 @@
import React from "react";
import styled from "styled-components";

interface ToolTipProps {
control: React.ReactNode;
className?: string;
disabled?: boolean;
cursor?: "pointer" | "help" | "not-allowed";
}

const Control = styled.div<{ $cursor?: "pointer" | "help" | "not-allowed"; $showCursor?: boolean }>`
display: inline;
position: relative;
${({ $cursor, $showCursor = true }) => ($showCursor && $cursor ? `cursor: ${$cursor}` : "")};
`;

const ToolTipView = styled.div<{ $disabled?: boolean }>`
display: none;
font-size: 14px;
line-height: initial;
position: absolute;
padding: 9px 8px 8px;
box-shadow: 0 24px 38px rgba(53, 53, 66, 0.14), 0 9px 46px rgba(53, 53, 66, 0.12), 0 11px 15px rgba(53, 53, 66, 0.2);
border-radius: 4px;
background: rgba(26, 26, 33, 0.9);
color: ${({ theme }) => theme.whiteColor};
top: calc(100% + 10px);
left: -50px;
min-width: 100px;
width: max-content;
max-width: 380px;
z-index: 10;
div:hover > &&,
&&:hover {
display: ${({ $disabled }) => ($disabled ? "none" : "block")};
}
`;

const ToolTip: React.FC<ToolTipProps> = ({ children, control, className, disabled, cursor }) => {
import { flip, offset, shift, useFloating } from "@floating-ui/react-dom";
import classNames from "classnames";
import React, { useState, useEffect } from "react";

import { tooltipContext } from "./context";
import styles from "./ToolTip.module.scss";
import { ToolTipProps } from "./types";

const MOUSE_OUT_TIMEOUT_MS = 50;

export const ToolTip: React.FC<ToolTipProps> = (props) => {
const { children, control, className, disabled, cursor, theme = "dark", placement = "bottom" } = props;

const [isMouseOver, setIsMouseOver] = useState(false);
const [isVisible, setIsVisible] = useState(false);

const { x, y, reference, floating, strategy } = useFloating({
placement,
middleware: [
offset(5), // $spacing-sm
flip(),
shift(),
],
});

useEffect(() => {
if (isMouseOver) {
setIsVisible(true);
return;
}

const timeout = window.setTimeout(() => {
setIsVisible(false);
}, MOUSE_OUT_TIMEOUT_MS);

return () => {
window.clearTimeout(timeout);
};
}, [isMouseOver]);

const canShowTooltip = isVisible && !disabled;

const onMouseOver = () => {
setIsMouseOver(true);
};

const onMouseOut = () => {
setIsMouseOver(false);
};

return (
<Control $cursor={cursor} $showCursor={!disabled}>
{control}
<ToolTipView className={className} $disabled={disabled}>
{children}
</ToolTipView>
</Control>
<>
<div
ref={reference}
className={styles.container}
style={disabled ? undefined : { cursor }}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
>
{control}
</div>
{canShowTooltip && (
<div
role="tooltip"
ref={floating}
className={classNames(styles.tooltip, theme === "light" && styles.light, className)}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
>
<tooltipContext.Provider value={props}>{children}</tooltipContext.Provider>
</div>
)}
</>
);
};

export default ToolTip;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use "../../scss/variables";

.container {
margin-top: variables.$spacing-md;
}
15 changes: 15 additions & 0 deletions airbyte-webapp/src/components/ToolTip/TooltipLearnMoreLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FormattedMessage } from "react-intl";

import styles from "./TooltipLearnMoreLink.module.scss";

interface TooltipLearnMoreLinkProps {
url: string;
}

export const TooltipLearnMoreLink: React.VFC<TooltipLearnMoreLinkProps> = ({ url }) => (
<div className={styles.container}>
<a href={url} target="_blank" rel="noreferrer">
<FormattedMessage id="ui.learnMore" />
</a>
</div>
);
13 changes: 13 additions & 0 deletions airbyte-webapp/src/components/ToolTip/TooltipTable.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use "../../scss/colors";
@use "../../scss/variables";

.label {
color: rgba(colors.$white, 0.7);
padding-right: variables.$spacing-sm;
}

.light {
.label {
color: rgba(colors.$dark-blue, 0.7);
}
}
24 changes: 24 additions & 0 deletions airbyte-webapp/src/components/ToolTip/TooltipTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useTooltipContext } from "./context";
import styles from "./TooltipTable.module.scss";

interface TooltipTableProps {
rows: React.ReactNode[][];
}

export const TooltipTable: React.VFC<TooltipTableProps> = ({ rows }) => {
const { theme } = useTooltipContext();

return rows.length > 0 ? (
<table className={theme === "light" ? styles.light : undefined}>
<tbody>
{rows?.map((cols) => (
<tr>
{cols.map((col, index) => (
<td className={index === 0 ? styles.label : undefined}>{col}</td>
))}
</tr>
))}
</tbody>
</table>
) : null;
};
15 changes: 15 additions & 0 deletions airbyte-webapp/src/components/ToolTip/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext, useContext } from "react";

import { TooltipContext } from "./types";

export const tooltipContext = createContext<TooltipContext | null>(null);

export const useTooltipContext = () => {
const ctx = useContext(tooltipContext);

if (!ctx) {
throw new Error("useTooltipContext should be used within tooltipContext.Provider");
}

return ctx;
};
57 changes: 57 additions & 0 deletions airbyte-webapp/src/components/ToolTip/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";

import { ToolTip } from "./ToolTip";
import { TooltipLearnMoreLink } from "./TooltipLearnMoreLink";
import { TooltipTable } from "./TooltipTable";

export default {
title: "Ui/ToolTip",
component: ToolTip,
argTypes: {
control: { type: { name: "string", required: true } },
children: { type: { name: "string", required: true } },
},
} as ComponentMeta<typeof ToolTip>;

const Template: ComponentStory<typeof ToolTip> = (args) => (
<div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center" }}>
<ToolTip {...args} />
</div>
);

export const Primary = Template.bind({});
Primary.args = {
control: "Hover to see Tooltip",
children: (
<>
Looking for a job?{" "}
<a href="https://www.airbyte.com/careers" target="_blank" rel="noreferrer">
Apply at Airbyte!
</a>
</>
),
};

export const WithLearnMoreUrl = Template.bind({});
WithLearnMoreUrl.args = {
control: "Hover to see Tooltip with Body",
children: (
<>
Airbyte is hiring! <TooltipLearnMoreLink url="https://www.airbyte.com/careers" />
</>
),
};

export const WithTable = Template.bind({});
WithTable.args = {
control: "Hover to see Tooltip with Table",
children: (
<TooltipTable
rows={[
["String", "Value"],
["Number", 32768],
["With a longer label", "And here is a longer value"],
]}
/>
),
};
Loading

0 comments on commit e380c26

Please sign in to comment.