Skip to content

Commit

Permalink
Update styles and add arrow
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer committed Mar 7, 2024
1 parent 5f30348 commit 6e2de01
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 33 deletions.
33 changes: 23 additions & 10 deletions packages/circuit-ui/components/Tooltip/Tooltip.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@

.base {
position: absolute;
bottom: 100%;
bottom: calc(100% + 16px); /* 8px (arrow size) + 4px (offset) */
left: 50%;
z-index: var(--cui-z-index-tooltip);
padding: var(--cui-spacings-byte);
transition: opacity var(--transition-slow);
transition: opacity var(--cui-transitions-default);
transform: translateX(-50%);
}

Expand All @@ -19,7 +18,7 @@
.component:focus + .base {
pointer-events: auto;
opacity: 1;
transition-delay: 0.6s;
transition-delay: 1s;
}

.base[data-state="open"] {
Expand All @@ -36,16 +35,30 @@
transition-delay: 0s;
}

.arrow {
position: absolute;
width: var(--cui-spacings-kilo);
height: var(--cui-spacings-kilo);
background-color: var(--cui-bg-elevated);
border-right: var(--cui-border-width-kilo) solid var(--cui-border-subtle);
border-bottom: var(--cui-border-width-kilo) solid var(--cui-border-subtle);
border-bottom-right-radius: 2px;
}

[data-state="closed"] .arrow {
display: none;
}

.content {
width: max-content;
max-width: 280px;
padding: var(--cui-spacings-bit) var(--cui-spacings-byte);
max-width: 360px;
padding: var(--cui-spacings-byte) var(--cui-spacings-kilo);
font-size: var(--cui-typography-body-two-font-size);
font-weight: var(--cui-font-weight-regular);
line-height: var(--cui-typography-body-two-line-height);
color: var(--cui-fg-normal);
background-color: var(--cui-bg-normal);
border: var(--cui-border-width-kilo) solid var(--cui-border-divider);
border-radius: var(--cui-border-radius-bit);
box-shadow: 0 3px 8px 0 rgb(0 0 0 / 20%);
background-color: var(--cui-bg-elevated);
border: var(--cui-border-width-kilo) solid var(--cui-border-subtle);
border-radius: var(--cui-border-radius-byte);
box-shadow: 0 2px 6px 0 rgb(0 0 0 / 8%);
}
79 changes: 56 additions & 23 deletions packages/circuit-ui/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
useEffect,
useId,
useState,
useRef,
type ComponentType,
type FocusEventHandler,
type HTMLAttributes,
Expand All @@ -28,15 +29,17 @@ import {
} from 'react';
import {
useFloating,
arrow,
flip,
offset,
shift,
type Placement,
type Side,
} from '@floating-ui/react-dom';

import { clsx } from '../../styles/clsx.js';
import { applyMultipleRefs } from '../../util/refs.js';
import { useEscapeKey } from '../../hooks/useEscapeKey/index.js';
import Portal from '../Portal/index.js';

import classes from './Tooltip.module.css';

Expand Down Expand Up @@ -75,6 +78,13 @@ export interface TooltipProps extends HTMLAttributes<HTMLDivElement> {
placement?: Placement;
}

const ARROW_ROTATION_MAP: Record<Side, `${number}deg`> = {
top: '45deg',
right: '135deg',
bottom: '225deg',
left: '315deg',
};

enum State {
initial = 'initial',
open = 'open',
Expand All @@ -87,13 +97,14 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
label,
component: Component,
type = 'label',
placement = 'top',
placement: defaultPlacement = 'top',
className,
...props
},
ref,
) => {
const tooltipId = useId();
const arrowRef = useRef<HTMLDivElement>(null);
// The tooltip works without JavaScript using only CSS (the "initial" state).
// When JS is available, the component is progressively enhanced and toggles
// between the "closed" and "open" states.
Expand All @@ -105,11 +116,24 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(

useEscapeKey(() => setState(State.closed), state === State.open);

const { refs, floatingStyles, update } = useFloating({
open: state === State.open,
placement,
middleware: [flip(), shift()],
});
const { refs, floatingStyles, middlewareData, update, placement } =
useFloating({
open: state === State.open,
placement: defaultPlacement,
middleware: [
// 8px (arrow size) + 4px (actual offset)
offset(12),
flip(),
shift(),
arrow({
element: arrowRef,
// This accounts for the content's border radius
padding: 8,
}),
],
});

const side = placement.split('-')[0] as Side;

useEffect(() => {
/**
Expand Down Expand Up @@ -148,7 +172,7 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
};

return (
<>
<div className={clsx(classes.parent, className)}>
<Component
{...referenceProps}
onFocus={handleOpen}
Expand All @@ -158,22 +182,31 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
className={classes.component}
ref={refs.setReference}
/>
<Portal>
<div
{...props}
ref={applyMultipleRefs(ref, refs.setFloating)}
id={tooltipId}
role="tooltip"
onMouseEnter={handleOpen}
onMouseLeave={handleClose}
data-state={state}
className={classes.base}
style={{ ...props.style, ...floatingStyles }}
>
<div className={classes.content}>{label}</div>
<div
{...props}
ref={applyMultipleRefs(ref, refs.setFloating)}
id={tooltipId}
role="tooltip"
onMouseEnter={handleOpen}
onMouseLeave={handleClose}
data-state={state}
style={{ ...props.style, ...floatingStyles }}
className={clsx(classes.base, className)}
>
<div className={classes.content}>{label}</div>
</div>
</Portal>
</>
ref={arrowRef}
className={classes.arrow}
// @ts-expect-error The dynamic style rules are valid.
style={{
left: middlewareData.arrow?.x,
top: middlewareData.arrow?.y,
[side]: 'calc(100% - (var(--cui-spacings-kilo) / 2))',
transform: `rotate(${ARROW_ROTATION_MAP[side]})`,
}}
/>
</div>
</div>
);
},
);

0 comments on commit 6e2de01

Please sign in to comment.