Update Tooltip to use React Aria
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -10461,66 +10461,59 @@
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.canon-TooltipPopup {
|
||||
box-sizing: border-box;
|
||||
transform-origin: var(--transform-origin);
|
||||
background-color: canvas;
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
.canon-Tooltip {
|
||||
background: var(--canon-bg-surface-1);
|
||||
color: var(--canon-fg-primary);
|
||||
outline: 1px solid var(--canon-border);
|
||||
box-shadow: 0 10px 15px -3px var(--canon-border), 0 4px 6px -4px var(--canon-border);
|
||||
border-radius: .375rem;
|
||||
flex-direction: column;
|
||||
padding: .25rem .5rem;
|
||||
font-size: .875rem;
|
||||
line-height: 1.25rem;
|
||||
transition: transform .15s, opacity .15s;
|
||||
display: flex;
|
||||
forced-color-adjust: none;
|
||||
padding: var(--canon-space-1_5) var(--canon-space-2);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
max-width: 150px;
|
||||
transition: transform .2s, opacity .2s;
|
||||
transform: translate3d(0, 0, 0);
|
||||
box-shadow: 0 8px 20px #0000001a;
|
||||
|
||||
&[data-starting-style], &[data-ending-style] {
|
||||
&[data-entering], &[data-exiting] {
|
||||
transform: var(--origin);
|
||||
opacity: 0;
|
||||
transform: scale(.9);
|
||||
}
|
||||
|
||||
&[data-instant] {
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TooltipArrow {
|
||||
display: flex;
|
||||
|
||||
&[data-side="top"] {
|
||||
bottom: -8px;
|
||||
rotate: 180deg;
|
||||
&[data-placement="top"] {
|
||||
--origin: translateY(4px);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&[data-side="bottom"] {
|
||||
top: -8px;
|
||||
rotate: none;
|
||||
}
|
||||
&[data-placement="bottom"] {
|
||||
--origin: translateY(-4px);
|
||||
margin-top: 8px;
|
||||
|
||||
&[data-side="left"] {
|
||||
right: -13px;
|
||||
rotate: 90deg;
|
||||
}
|
||||
|
||||
&[data-side="right"] {
|
||||
left: -13px;
|
||||
rotate: -90deg;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TooltipArrow-fill {
|
||||
fill: var(--canon-bg-surface-1);
|
||||
}
|
||||
|
||||
.canon-TooltipArrow-outer-stroke {
|
||||
@media (prefers-color-scheme: light) {
|
||||
& {
|
||||
fill: var(--canon-border);
|
||||
& .canon-TooltipArrow svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-placement="right"] {
|
||||
--origin: translateX(-4px);
|
||||
margin-left: 8px;
|
||||
|
||||
& .canon-TooltipArrow svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-placement="left"] {
|
||||
--origin: translateX(4px);
|
||||
margin-right: 8px;
|
||||
|
||||
& .canon-TooltipArrow svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TooltipArrow svg {
|
||||
fill: var(--canon-bg-surface-1);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-ScrollAreaRoot {
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import { TooltipTrigger, Tooltip } from './Tooltip';
|
||||
import { Button } from '../Button/Button';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Tooltip',
|
||||
component: Tooltip.Root,
|
||||
} satisfies Meta<typeof Tooltip.Root>;
|
||||
component: TooltipTrigger,
|
||||
} satisfies Meta<typeof TooltipTrigger>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
@@ -30,50 +30,58 @@ export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<Tooltip.Trigger
|
||||
render={props => (
|
||||
<Button {...props} size="small">
|
||||
Button
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Positioner sideOffset={10}>
|
||||
<Tooltip.Popup>Nice!</Tooltip.Popup>
|
||||
</Tooltip.Positioner>
|
||||
</Tooltip.Portal>
|
||||
<Button>Button</Button>
|
||||
<Tooltip>I am a tooltip</Tooltip>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const Open: Story = {
|
||||
export const IsOpen: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
open: true,
|
||||
isOpen: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithArrow: Story = {
|
||||
export const IsDisabled: Story = {
|
||||
args: {
|
||||
open: true,
|
||||
...Default.args,
|
||||
isDisabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const PlacementTop: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
children: (
|
||||
<>
|
||||
<Tooltip.Trigger
|
||||
render={props => (
|
||||
<Button {...props} size="small">
|
||||
Button
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Positioner sideOffset={10}>
|
||||
<Tooltip.Popup>
|
||||
<Tooltip.Arrow />
|
||||
Nice!
|
||||
</Tooltip.Popup>
|
||||
</Tooltip.Positioner>
|
||||
</Tooltip.Portal>
|
||||
<Button>Tooltip on top</Button>
|
||||
<Tooltip placement="top">I'm a tooltip</Tooltip>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const PlacementRight: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
children: (
|
||||
<>
|
||||
<Button>Tooltip on top</Button>
|
||||
<Tooltip placement="right">I'm a tooltip</Tooltip>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const PlacementLeft: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
children: (
|
||||
<>
|
||||
<Button>Tooltip on top</Button>
|
||||
<Tooltip placement="left">I'm a tooltip</Tooltip>
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -14,70 +14,59 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.canon-TooltipPopup {
|
||||
box-sizing: border-box;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: canvas;
|
||||
transform-origin: var(--transform-origin);
|
||||
transition: transform 150ms, opacity 150ms;
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
.canon-Tooltip {
|
||||
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
|
||||
border-radius: 4px;
|
||||
background: var(--canon-bg-surface-1);
|
||||
color: var(--canon-fg-primary);
|
||||
outline: 1px solid var(--canon-border);
|
||||
box-shadow: 0 10px 15px -3px var(--canon-border),
|
||||
0 4px 6px -4px var(--canon-border);
|
||||
forced-color-adjust: none;
|
||||
outline: none;
|
||||
padding: var(--canon-space-1_5) var(--canon-space-2);
|
||||
max-width: 150px;
|
||||
/* fixes FF gap */
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: transform 200ms, opacity 200ms;
|
||||
|
||||
&[data-starting-style],
|
||||
&[data-ending-style] {
|
||||
&[data-entering],
|
||||
&[data-exiting] {
|
||||
transform: var(--origin);
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
&[data-instant] {
|
||||
transition-duration: 0ms;
|
||||
&[data-placement='top'] {
|
||||
margin-bottom: 8px;
|
||||
--origin: translateY(4px);
|
||||
}
|
||||
|
||||
&[data-placement='bottom'] {
|
||||
margin-top: 8px;
|
||||
--origin: translateY(-4px);
|
||||
|
||||
& .canon-TooltipArrow svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-placement='right'] {
|
||||
margin-left: 8px;
|
||||
--origin: translateX(-4px);
|
||||
|
||||
& .canon-TooltipArrow svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-placement='left'] {
|
||||
margin-right: 8px;
|
||||
--origin: translateX(4px);
|
||||
|
||||
& .canon-TooltipArrow svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TooltipArrow {
|
||||
display: flex;
|
||||
|
||||
&[data-side='top'] {
|
||||
bottom: -8px;
|
||||
rotate: 180deg;
|
||||
}
|
||||
|
||||
&[data-side='bottom'] {
|
||||
top: -8px;
|
||||
rotate: 0deg;
|
||||
}
|
||||
|
||||
&[data-side='left'] {
|
||||
right: -13px;
|
||||
rotate: 90deg;
|
||||
}
|
||||
|
||||
&[data-side='right'] {
|
||||
left: -13px;
|
||||
rotate: -90deg;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TooltipArrow-fill {
|
||||
.canon-TooltipArrow svg {
|
||||
display: block;
|
||||
fill: var(--canon-bg-surface-1);
|
||||
}
|
||||
|
||||
.canon-TooltipArrow-outer-stroke {
|
||||
@media (prefers-color-scheme: light) {
|
||||
fill: var(--canon-border);
|
||||
}
|
||||
}
|
||||
|
||||
.canon-TooltipArrow-inner-stroke {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* fill: var(--canon-border); */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,96 +15,42 @@
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { Tooltip as TooltipPrimitive } from '@base-ui-components/react/tooltip';
|
||||
import {
|
||||
OverlayArrow,
|
||||
Tooltip as AriaTooltip,
|
||||
TooltipTrigger as AriaTooltipTrigger,
|
||||
TooltipTriggerComponentProps,
|
||||
} from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { TooltipProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
|
||||
const TooltipTrigger = forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Tooltip');
|
||||
export const TooltipTrigger = (props: TooltipTriggerComponentProps) => {
|
||||
const { delay = 600 } = props;
|
||||
|
||||
return (
|
||||
<TooltipPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={clsx(classNames.trigger, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;
|
||||
|
||||
const TooltipPositioner = forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Positioner>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Positioner>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Tooltip');
|
||||
|
||||
return (
|
||||
<TooltipPrimitive.Positioner
|
||||
ref={ref}
|
||||
className={clsx(classNames.positioner, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
TooltipPositioner.displayName = TooltipPrimitive.Positioner.displayName;
|
||||
|
||||
const TooltipPopup = forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Popup>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Popup>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Tooltip');
|
||||
|
||||
return (
|
||||
<TooltipPrimitive.Popup
|
||||
ref={ref}
|
||||
className={clsx(classNames.popup, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
TooltipPopup.displayName = TooltipPrimitive.Popup.displayName;
|
||||
|
||||
const TooltipArrow = forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Arrow>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Arrow>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Tooltip');
|
||||
|
||||
return (
|
||||
<TooltipPrimitive.Arrow
|
||||
ref={ref}
|
||||
className={clsx(classNames.arrow, className)}
|
||||
{...props}
|
||||
>
|
||||
<svg width="20" height="10" viewBox="0 0 20 10" fill="none">
|
||||
<path
|
||||
d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
|
||||
className="canon-TooltipArrow-fill"
|
||||
/>
|
||||
<path
|
||||
d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
|
||||
className="canon-TooltipArrow-outer-stroke"
|
||||
/>
|
||||
<path
|
||||
d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
|
||||
className="canon-TooltipArrow-inner-stroke"
|
||||
/>
|
||||
</svg>
|
||||
</TooltipPrimitive.Arrow>
|
||||
);
|
||||
});
|
||||
TooltipArrow.displayName = TooltipPrimitive.Arrow.displayName;
|
||||
return <AriaTooltipTrigger delay={delay} {...props} />;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const Tooltip = {
|
||||
Provider: TooltipPrimitive.Provider,
|
||||
Root: TooltipPrimitive.Root,
|
||||
Trigger: TooltipTrigger,
|
||||
Portal: TooltipPrimitive.Portal,
|
||||
Positioner: TooltipPositioner,
|
||||
Popup: TooltipPopup,
|
||||
Arrow: TooltipArrow,
|
||||
};
|
||||
export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
({ className, children, ...rest }, ref) => {
|
||||
const { classNames } = useStyles('Tooltip');
|
||||
|
||||
return (
|
||||
<AriaTooltip
|
||||
className={clsx(classNames.tooltip, className)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
>
|
||||
<OverlayArrow className={classNames.arrow}>
|
||||
<svg width={8} height={8} viewBox="0 0 8 8">
|
||||
<path d="M0 0 L4 4 L8 0" />
|
||||
</svg>
|
||||
</OverlayArrow>
|
||||
{children}
|
||||
</AriaTooltip>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Tooltip.displayName = Tooltip.displayName;
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './Tooltip';
|
||||
export { TooltipTrigger, Tooltip } from './Tooltip';
|
||||
export type { TooltipProps } from './types';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TooltipProps as AriaTooltipProps } from 'react-aria-components';
|
||||
|
||||
/** @public */
|
||||
export interface TooltipProps extends Omit<AriaTooltipProps, 'children'> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
@@ -257,9 +257,7 @@ export const componentDefinitions = {
|
||||
},
|
||||
Tooltip: {
|
||||
classNames: {
|
||||
trigger: 'canon-TooltipTrigger',
|
||||
positioner: 'canon-TooltipPositioner',
|
||||
popup: 'canon-TooltipPopup',
|
||||
tooltip: 'canon-Tooltip',
|
||||
arrow: 'canon-TooltipArrow',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user