feat(ui): replace --bui-bg-popover with layered bg approach
Remove the --bui-bg-popover token and adopt a two-layer background pattern across overlay components: outer shell uses --bui-bg-app, inner content uses Box bg="neutral-1". Components updated: Popover, Tooltip, Menu, Dialog. Each component gets a local border-radius CSS variable, a Box content wrapper with neutral-1 background, and updated arrow fills where applicable. Story "is open" variants for each component now use a dot-grid background pattern to detect background transparency issues in Chromatic snapshots. Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `--bui-bg-popover` CSS token. Popover, Tooltip, Menu, and Dialog now use `--bui-bg-app` for their outer shell and `Box bg="neutral-1"` for content areas, providing better theme consistency and eliminating a redundant token.
|
||||
|
||||
**Migration:**
|
||||
|
||||
Replace any usage of `--bui-bg-popover` with `--bui-bg-neutral-1` (for content surfaces) or `--bui-bg-app` (for outer shells):
|
||||
|
||||
```diff
|
||||
- background: var(--bui-bg-popover);
|
||||
+ background: var(--bui-bg-neutral-1);
|
||||
```
|
||||
|
||||
**Affected components:** Popover, Tooltip, Menu, Dialog
|
||||
@@ -156,7 +156,6 @@ These colors form a layered neutral scale for your application backgrounds. `--b
|
||||
| Token Name | Description |
|
||||
| ----------------------------- | ------------------------------------------------------------ |
|
||||
| `--bui-bg-app` | The base background color of your Backstage instance. |
|
||||
| `--bui-bg-popover` | The background color used for popovers, tooltips, and menus. |
|
||||
| `--bui-bg-neutral-1` | First elevated layer. Use for cards, dialogs, and panels. |
|
||||
| `--bui-bg-neutral-1-hover` | Hover state for elements on neutral-1. |
|
||||
| `--bui-bg-neutral-1-pressed` | Pressed state for elements on neutral-1. |
|
||||
|
||||
@@ -897,6 +897,7 @@ export const DialogDefinition: {
|
||||
readonly classNames: {
|
||||
readonly overlay: 'bui-DialogOverlay';
|
||||
readonly dialog: 'bui-Dialog';
|
||||
readonly content: 'bui-DialogContent';
|
||||
readonly header: 'bui-DialogHeader';
|
||||
readonly headerTitle: 'bui-DialogHeaderTitle';
|
||||
readonly body: 'bui-DialogBody';
|
||||
@@ -1331,6 +1332,7 @@ export const MenuDefinition: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Menu';
|
||||
readonly popover: 'bui-MenuPopover';
|
||||
readonly inner: 'bui-MenuInner';
|
||||
readonly content: 'bui-MenuContent';
|
||||
readonly section: 'bui-MenuSection';
|
||||
readonly sectionHeader: 'bui-MenuSectionHeader';
|
||||
@@ -2218,6 +2220,7 @@ export const Tooltip: ForwardRefExoticComponent<
|
||||
export const TooltipDefinition: {
|
||||
readonly classNames: {
|
||||
readonly tooltip: 'bui-Tooltip';
|
||||
readonly content: 'bui-TooltipContent';
|
||||
readonly arrow: 'bui-TooltipArrow';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -43,8 +43,10 @@
|
||||
}
|
||||
|
||||
.bui-Dialog {
|
||||
background: var(--bui-bg-popover);
|
||||
border-radius: 0.5rem;
|
||||
--dialog-border-radius: 0.5rem;
|
||||
background: var(--bui-bg-app);
|
||||
box-shadow: var(--bui-shadow);
|
||||
border-radius: var(--dialog-border-radius);
|
||||
border: 1px solid var(--bui-border-1);
|
||||
color: var(--bui-fg-primary);
|
||||
position: relative;
|
||||
@@ -52,9 +54,14 @@
|
||||
max-width: calc(100vw - 3rem);
|
||||
height: min(var(--bui-dialog-min-height, auto), calc(100vh - 3rem));
|
||||
max-height: calc(100vh - 3rem);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.bui-DialogContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
border-radius: var(--dialog-border-radius);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Dialog entering animation */
|
||||
|
||||
@@ -63,6 +63,24 @@ export const Default = meta.story({
|
||||
});
|
||||
|
||||
export const Open = Default.extend({
|
||||
parameters: { layout: 'fullscreen' },
|
||||
decorators: [
|
||||
Story => (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage:
|
||||
'radial-gradient(circle, var(--bui-border-1) 1px, transparent 1px)',
|
||||
backgroundSize: '16px 16px',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
defaultOpen: true,
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ import { Button } from '../Button';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { DialogDefinition } from './definition';
|
||||
import { Flex } from '../Flex';
|
||||
import { Box } from '../Box';
|
||||
import styles from './Dialog.module.css';
|
||||
|
||||
/** @public */
|
||||
@@ -71,7 +72,12 @@ export const Dialog = forwardRef<React.ElementRef<typeof Modal>, DialogProps>(
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</RADialog>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ export const DialogDefinition = {
|
||||
classNames: {
|
||||
overlay: 'bui-DialogOverlay',
|
||||
dialog: 'bui-Dialog',
|
||||
content: 'bui-DialogContent',
|
||||
header: 'bui-DialogHeader',
|
||||
headerTitle: 'bui-DialogHeaderTitle',
|
||||
body: 'bui-DialogBody',
|
||||
|
||||
@@ -18,12 +18,13 @@
|
||||
|
||||
@layer components {
|
||||
.bui-MenuPopover {
|
||||
--menu-border-radius: var(--bui-radius-2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(--bui-shadow);
|
||||
border: 1px solid var(--bui-border-1);
|
||||
border-radius: var(--bui-radius-2);
|
||||
background: var(--bui-bg-popover);
|
||||
border-radius: var(--menu-border-radius);
|
||||
background: var(--bui-bg-app);
|
||||
color: var(--bui-fg-primary);
|
||||
outline: none;
|
||||
transition: transform 200ms, opacity 200ms;
|
||||
@@ -55,6 +56,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuInner {
|
||||
border-radius: var(--menu-border-radius);
|
||||
}
|
||||
|
||||
.bui-MenuContent {
|
||||
max-height: inherit;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -198,6 +198,24 @@ export const PreviewLinks = meta.story({
|
||||
});
|
||||
|
||||
export const Opened = meta.story({
|
||||
parameters: { layout: 'fullscreen' },
|
||||
decorators: [
|
||||
Story => (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage:
|
||||
'radial-gradient(circle, var(--bui-border-1) 1px, transparent 1px)',
|
||||
backgroundSize: '16px 16px',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
...Preview.input.args,
|
||||
},
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
} from '../InternalLinkProvider';
|
||||
import styles from './Menu.module.css';
|
||||
import clsx from 'clsx';
|
||||
import { Box } from '../Box';
|
||||
|
||||
const { RoutingProvider, useRoutingRegistrationEffect } =
|
||||
createRoutingRegistration();
|
||||
@@ -119,18 +120,23 @@ export const Menu = (props: MenuProps<object>) => {
|
||||
)}
|
||||
placement={placement}
|
||||
>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{menuContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
menuContent
|
||||
)}
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.inner, styles[classNames.inner])}
|
||||
>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{menuContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
menuContent
|
||||
)}
|
||||
</Box>
|
||||
</RAPopover>
|
||||
</RoutingProvider>
|
||||
);
|
||||
@@ -169,18 +175,23 @@ export const MenuListBox = (props: MenuListBoxProps<object>) => {
|
||||
)}
|
||||
placement={placement}
|
||||
>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{listBoxContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
listBoxContent
|
||||
)}
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.inner, styles[classNames.inner])}
|
||||
>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{listBoxContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
listBoxContent
|
||||
)}
|
||||
</Box>
|
||||
</RAPopover>
|
||||
);
|
||||
};
|
||||
@@ -219,43 +230,48 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps<object>) => {
|
||||
)}
|
||||
placement={placement}
|
||||
>
|
||||
<RAAutocomplete filter={contains}>
|
||||
<RASearchField
|
||||
className={clsx(
|
||||
classNames.searchField,
|
||||
styles[classNames.searchField],
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.inner, styles[classNames.inner])}
|
||||
>
|
||||
<RAAutocomplete filter={contains}>
|
||||
<RASearchField
|
||||
className={clsx(
|
||||
classNames.searchField,
|
||||
styles[classNames.searchField],
|
||||
)}
|
||||
aria-label={props.placeholder || 'Search'}
|
||||
>
|
||||
<RAInput
|
||||
className={clsx(
|
||||
classNames.searchFieldInput,
|
||||
styles[classNames.searchFieldInput],
|
||||
)}
|
||||
placeholder={props.placeholder || 'Search...'}
|
||||
/>
|
||||
<RAButton
|
||||
className={clsx(
|
||||
classNames.searchFieldClear,
|
||||
styles[classNames.searchFieldClear],
|
||||
)}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
</RAButton>
|
||||
</RASearchField>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{menuContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
menuContent
|
||||
)}
|
||||
aria-label={props.placeholder || 'Search'}
|
||||
>
|
||||
<RAInput
|
||||
className={clsx(
|
||||
classNames.searchFieldInput,
|
||||
styles[classNames.searchFieldInput],
|
||||
)}
|
||||
placeholder={props.placeholder || 'Search...'}
|
||||
/>
|
||||
<RAButton
|
||||
className={clsx(
|
||||
classNames.searchFieldClear,
|
||||
styles[classNames.searchFieldClear],
|
||||
)}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
</RAButton>
|
||||
</RASearchField>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{menuContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
menuContent
|
||||
)}
|
||||
</RAAutocomplete>
|
||||
</RAAutocomplete>
|
||||
</Box>
|
||||
</RAPopover>
|
||||
</RoutingProvider>
|
||||
);
|
||||
@@ -298,43 +314,48 @@ export const MenuAutocompleteListbox = (
|
||||
)}
|
||||
placement={placement}
|
||||
>
|
||||
<RAAutocomplete filter={contains}>
|
||||
<RASearchField
|
||||
className={clsx(
|
||||
classNames.searchField,
|
||||
styles[classNames.searchField],
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.inner, styles[classNames.inner])}
|
||||
>
|
||||
<RAAutocomplete filter={contains}>
|
||||
<RASearchField
|
||||
className={clsx(
|
||||
classNames.searchField,
|
||||
styles[classNames.searchField],
|
||||
)}
|
||||
aria-label={props.placeholder || 'Search'}
|
||||
>
|
||||
<RAInput
|
||||
className={clsx(
|
||||
classNames.searchFieldInput,
|
||||
styles[classNames.searchFieldInput],
|
||||
)}
|
||||
placeholder={props.placeholder || 'Search...'}
|
||||
/>
|
||||
<RAButton
|
||||
className={clsx(
|
||||
classNames.searchFieldClear,
|
||||
styles[classNames.searchFieldClear],
|
||||
)}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
</RAButton>
|
||||
</RASearchField>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{listBoxContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
listBoxContent
|
||||
)}
|
||||
aria-label={props.placeholder || 'Search'}
|
||||
>
|
||||
<RAInput
|
||||
className={clsx(
|
||||
classNames.searchFieldInput,
|
||||
styles[classNames.searchFieldInput],
|
||||
)}
|
||||
placeholder={props.placeholder || 'Search...'}
|
||||
/>
|
||||
<RAButton
|
||||
className={clsx(
|
||||
classNames.searchFieldClear,
|
||||
styles[classNames.searchFieldClear],
|
||||
)}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
</RAButton>
|
||||
</RASearchField>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
{listBoxContent}
|
||||
</Virtualizer>
|
||||
) : (
|
||||
listBoxContent
|
||||
)}
|
||||
</RAAutocomplete>
|
||||
</RAAutocomplete>
|
||||
</Box>
|
||||
</RAPopover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ export const MenuDefinition = {
|
||||
classNames: {
|
||||
root: 'bui-Menu',
|
||||
popover: 'bui-MenuPopover',
|
||||
inner: 'bui-MenuInner',
|
||||
content: 'bui-MenuContent',
|
||||
section: 'bui-MenuSection',
|
||||
sectionHeader: 'bui-MenuSectionHeader',
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
|
||||
@layer components {
|
||||
.bui-Popover {
|
||||
--popover-border-radius: var(--bui-radius-3);
|
||||
box-shadow: var(--bui-shadow);
|
||||
border-radius: var(--bui-radius-3);
|
||||
background: var(--bui-bg-popover);
|
||||
border-radius: var(--popover-border-radius);
|
||||
background: var(--bui-bg-app);
|
||||
border: 1px solid var(--bui-border-1);
|
||||
forced-color-adjust: none;
|
||||
outline: none;
|
||||
@@ -76,6 +77,7 @@
|
||||
padding: var(--bui-space-4);
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
border-radius: var(--popover-border-radius);
|
||||
}
|
||||
|
||||
.bui-PopoverArrow {
|
||||
@@ -89,11 +91,14 @@
|
||||
we split the stroke and fill across separate
|
||||
elements in order to guarantee that the stroke is
|
||||
always overlaying a consistent color. */
|
||||
path:nth-child(1) {
|
||||
fill: var(--bui-bg-popover);
|
||||
use:nth-of-type(1) {
|
||||
fill: var(--bui-bg-app);
|
||||
}
|
||||
use:nth-of-type(2) {
|
||||
fill: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
path:nth-child(2) {
|
||||
path {
|
||||
fill: var(--bui-border-1);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Button } from '../Button/Button';
|
||||
import { DialogTrigger } from '../Dialog/Dialog';
|
||||
import { Text } from '../Text/Text';
|
||||
import { Flex } from '../Flex/Flex';
|
||||
import { Box } from '../Box';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/Popover',
|
||||
@@ -74,6 +75,24 @@ export const Default = meta.story({
|
||||
});
|
||||
|
||||
export const IsOpen = Default.extend({
|
||||
parameters: { layout: 'fullscreen' },
|
||||
decorators: [
|
||||
Story => (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage:
|
||||
'radial-gradient(circle, var(--bui-border-1) 1px, transparent 1px)',
|
||||
backgroundSize: '16px 16px',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
isOpen: true,
|
||||
},
|
||||
@@ -189,6 +208,9 @@ export const WithRichContent = Default.extend({
|
||||
This is a popover with rich content. It can contain multiple
|
||||
elements and formatted text.
|
||||
</Text>
|
||||
<Box bg="neutral-auto" p="2">
|
||||
<Text>You can also use the automatic bg system inside it.</Text>
|
||||
</Box>
|
||||
<Flex gap="2" justify="end">
|
||||
<Button variant="tertiary" size="small">
|
||||
Cancel
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { useId } from 'react-aria';
|
||||
import { OverlayArrow, Popover as AriaPopover } from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { PopoverProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { PopoverDefinition } from './definition';
|
||||
import styles from './Popover.module.css';
|
||||
import { Box } from '../Box';
|
||||
|
||||
/**
|
||||
* A popover component built on React Aria Components that displays floating
|
||||
@@ -61,6 +63,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(PopoverDefinition, props);
|
||||
const { className, children, hideArrow, ...rest } = cleanedProps;
|
||||
const svgPathId = useId();
|
||||
|
||||
return (
|
||||
<AriaPopover
|
||||
@@ -77,16 +80,27 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
className={clsx(classNames.arrow, styles[classNames.arrow])}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10.3356 7.39793L15.1924 3.02682C15.9269 2.36577 16.8801 2 17.8683 2H20V7.94781e-07L1.74846e-07 -9.53674e-07L0 2L1.4651 2C2.4532 2 3.4064 2.36577 4.1409 3.02682L8.9977 7.39793C9.378 7.7402 9.9553 7.74021 10.3356 7.39793Z" />
|
||||
<defs>
|
||||
<path
|
||||
id={svgPathId}
|
||||
fillRule="evenodd"
|
||||
d="M10.3356 7.39793L15.1924 3.02682C15.9269 2.36577 16.8801 2 17.8683 2H20V7.94781e-07L1.74846e-07 -9.53674e-07L0 2L1.4651 2C2.4532 2 3.4064 2.36577 4.1409 3.02682L8.9977 7.39793C9.378 7.7402 9.9553 7.74021 10.3356 7.39793Z M11.0046 8.14124C10.2439 8.82575 9.08939 8.82578 8.32869 8.14122L3.47189 3.77011C2.92109 3.27432 2.20619 2.99999 1.46509 2.99999L4.10999 3L8.99769 7.39793C9.37799 7.7402 9.95529 7.7402 10.3356 7.39793L15.2226 3L17.8683 2.99999C17.1271 2.99999 16.4122 3.27432 15.8614 3.77011L11.0046 8.14124Z"
|
||||
/>
|
||||
</defs>
|
||||
|
||||
<use href={`#${svgPathId}`} />
|
||||
<use href={`#${svgPathId}`} />
|
||||
|
||||
<path d="M11.0046 8.14124C10.2439 8.82575 9.08939 8.82578 8.32869 8.14122L3.47189 3.77011C2.92109 3.27432 2.20619 2.99999 1.46509 2.99999L4.10999 3L8.99769 7.39793C9.37799 7.7402 9.95529 7.7402 10.3356 7.39793L15.2226 3L17.8683 2.99999C17.1271 2.99999 16.4122 3.27432 15.8614 3.77011L11.0046 8.14124Z" />
|
||||
</svg>
|
||||
</OverlayArrow>
|
||||
)}
|
||||
<div
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</AriaPopover>
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
|
||||
@layer components {
|
||||
.bui-Tooltip {
|
||||
--tooltip-border-radius: 4px;
|
||||
box-shadow: var(--bui-shadow);
|
||||
border-radius: 4px;
|
||||
background: var(--bui-bg-popover);
|
||||
border-radius: var(--tooltip-border-radius);
|
||||
background: var(--bui-bg-app);
|
||||
border: 1px solid var(--bui-border-1);
|
||||
forced-color-adjust: none;
|
||||
outline: none;
|
||||
padding: var(--bui-space-2) var(--bui-space-3);
|
||||
max-width: 240px;
|
||||
/* fixes FF gap */
|
||||
transform: translate3d(0, 0, 0);
|
||||
@@ -62,6 +62,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TooltipContent {
|
||||
padding: var(--bui-space-2) var(--bui-space-3);
|
||||
border-radius: var(--tooltip-border-radius);
|
||||
}
|
||||
|
||||
.bui-TooltipArrow {
|
||||
& svg {
|
||||
display: block;
|
||||
@@ -73,11 +78,14 @@
|
||||
we split the stroke and fill across separate
|
||||
elements in order to guarantee that the stroke is
|
||||
always overlaying a consistent color. */
|
||||
path:nth-child(1) {
|
||||
fill: var(--bui-bg-popover);
|
||||
use:nth-of-type(1) {
|
||||
fill: var(--bui-bg-app);
|
||||
}
|
||||
use:nth-of-type(2) {
|
||||
fill: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
path:nth-child(2) {
|
||||
path {
|
||||
fill: var(--bui-border-1);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,24 @@ export const Default = meta.story({
|
||||
});
|
||||
|
||||
export const IsOpen = meta.story({
|
||||
parameters: { layout: 'fullscreen' },
|
||||
decorators: [
|
||||
Story => (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage:
|
||||
'radial-gradient(circle, var(--bui-border-1) 1px, transparent 1px)',
|
||||
backgroundSize: '16px 16px',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
...Default.input.args,
|
||||
isOpen: true,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { useId } from 'react-aria';
|
||||
import {
|
||||
OverlayArrow,
|
||||
Tooltip as AriaTooltip,
|
||||
@@ -26,6 +27,7 @@ import { TooltipProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { TooltipDefinition } from './definition';
|
||||
import styles from './Tooltip.module.css';
|
||||
import { Box } from '../Box';
|
||||
|
||||
/** @public */
|
||||
export const TooltipTrigger = (props: TooltipTriggerComponentProps) => {
|
||||
@@ -39,6 +41,7 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(TooltipDefinition, props);
|
||||
const { className, children, ...rest } = cleanedProps;
|
||||
const svgPathId = useId();
|
||||
|
||||
return (
|
||||
<AriaTooltip
|
||||
@@ -54,11 +57,26 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
className={clsx(classNames.arrow, styles[classNames.arrow])}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10.3356 7.39793L15.1924 3.02682C15.9269 2.36577 16.8801 2 17.8683 2H20V7.94781e-07L1.74846e-07 -9.53674e-07L0 2L1.4651 2C2.4532 2 3.4064 2.36577 4.1409 3.02682L8.9977 7.39793C9.378 7.7402 9.9553 7.74021 10.3356 7.39793Z" />
|
||||
<defs>
|
||||
<path
|
||||
id={svgPathId}
|
||||
fillRule="evenodd"
|
||||
d="M10.3356 7.39793L15.1924 3.02682C15.9269 2.36577 16.8801 2 17.8683 2H20V7.94781e-07L1.74846e-07 -9.53674e-07L0 2L1.4651 2C2.4532 2 3.4064 2.36577 4.1409 3.02682L8.9977 7.39793C9.378 7.7402 9.9553 7.74021 10.3356 7.39793Z M11.0046 8.14124C10.2439 8.82575 9.08939 8.82578 8.32869 8.14122L3.47189 3.77011C2.92109 3.27432 2.20619 2.99999 1.46509 2.99999L4.10999 3L8.99769 7.39793C9.37799 7.7402 9.95529 7.7402 10.3356 7.39793L15.2226 3L17.8683 2.99999C17.1271 2.99999 16.4122 3.27432 15.8614 3.77011L11.0046 8.14124Z"
|
||||
/>
|
||||
</defs>
|
||||
|
||||
<use href={`#${svgPathId}`} />
|
||||
<use href={`#${svgPathId}`} />
|
||||
|
||||
<path d="M11.0046 8.14124C10.2439 8.82575 9.08939 8.82578 8.32869 8.14122L3.47189 3.77011C2.92109 3.27432 2.20619 2.99999 1.46509 2.99999L4.10999 3L8.99769 7.39793C9.37799 7.7402 9.95529 7.7402 10.3356 7.39793L15.2226 3L17.8683 2.99999C17.1271 2.99999 16.4122 3.27432 15.8614 3.77011L11.0046 8.14124Z" />
|
||||
</svg>
|
||||
</OverlayArrow>
|
||||
{children}
|
||||
<Box
|
||||
bg="neutral-1"
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</AriaTooltip>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { ComponentDefinition } from '../../types';
|
||||
export const TooltipDefinition = {
|
||||
classNames: {
|
||||
tooltip: 'bui-Tooltip',
|
||||
content: 'bui-TooltipContent',
|
||||
arrow: 'bui-TooltipArrow',
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
|
||||
/* Neutral background colors */
|
||||
--bui-bg-app: #f8f8f8;
|
||||
--bui-bg-popover: #ffffff;
|
||||
|
||||
--bui-bg-neutral-1: #fff;
|
||||
--bui-bg-neutral-1-hover: oklch(0% 0 0 / 12%);
|
||||
@@ -153,7 +152,6 @@
|
||||
|
||||
/* Neutral background colors */
|
||||
--bui-bg-app: #333333;
|
||||
--bui-bg-popover: #1a1a1a;
|
||||
|
||||
--bui-bg-neutral-1: oklch(100% 0 0 / 10%);
|
||||
--bui-bg-neutral-1-hover: oklch(100% 0 0 / 14%);
|
||||
|
||||
Reference in New Issue
Block a user