Skip to content

Commit

Permalink
Tests, missing props, fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks committed Apr 11, 2024
1 parent 25d62cc commit 11f1654
Show file tree
Hide file tree
Showing 25 changed files with 650 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ export default function UnstyledTooltipIntroduction() {
return (
<Tooltip>
<Tooltip.AnchorFragment>
<AnchorButton type="button">Anchor</AnchorButton>
<AnchorButton>Anchor</AnchorButton>
</Tooltip.AnchorFragment>
<TooltipContent
sideOffset={7}
style={{
maxWidth: 'var(--tooltip-available-width)',
maxHeight: 'var(--tooltip-available-height)',
}}
>
<TooltipContent sideOffset={7}>
Tooltip
<TooltipArrow />
</TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ export default function UnstyledTooltipIntroduction() {
return (
<Tooltip>
<Tooltip.AnchorFragment>
<AnchorButton type="button">Anchor</AnchorButton>
<AnchorButton>Anchor</AnchorButton>
</Tooltip.AnchorFragment>
<TooltipContent
sideOffset={7}
style={{
maxWidth: 'var(--tooltip-available-width)',
maxHeight: 'var(--tooltip-available-height)',
}}
>
<TooltipContent sideOffset={7}>
Tooltip
<TooltipArrow />
</TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
<Tooltip>
<Tooltip.AnchorFragment>
<AnchorButton type="button">Anchor</AnchorButton>
<AnchorButton>Anchor</AnchorButton>
</Tooltip.AnchorFragment>
<TooltipContent
sideOffset={7}
style={{
maxWidth: 'var(--tooltip-available-width)',
maxHeight: 'var(--tooltip-available-height)',
}}
>
<TooltipContent sideOffset={7}>
Tooltip
<TooltipArrow />
</TooltipContent>
Expand Down
24 changes: 16 additions & 8 deletions docs/data/base/components/tooltip/tooltip.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/

# Tooltip

<p class="description">Tooltips display informative text when users hover or focus an element.</p>
<p class="description">Tooltips are floating elements that display information when a user hovers or focuses an element.</p>

{{"component": "modules/components/ComponentLinkHeader.js", "design": false}}

{{"component": "modules/components/ComponentPageTabs.js"}}

## Introduction

Tooltips are visual-only elements that display informative text about an anchor element for sighted users.
Tooltips are visual-only floating elements that display information about an anchor element for sighted users when using a mouse to hover or keyboard to focus — the content is a tip for a "tool" that performs an action.

{{"demo": "UnstyledTooltipIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}

:::info
If critical information is hidden behind a tooltip, you're likely looking for a `Infotip` instead. These are floating elements that anchor themselves to an icon designed only to display information, not a button ("tool") that performs an action. All inputs, including touch, can see an infotip.
:::

## Component

```jsx
Expand Down Expand Up @@ -108,6 +112,10 @@ Tooltips are only for sighted users with access to a pointer with hover capabili
</Tooltip>
```

Your `aria-label` and tooltip content should closely match or be identical so that screen reader users and sighted users receive the same information.

Tooltips should ideally also be secondary in nature, because touch users cannot see them. They are most useful as progressive enhancement in high-density desktop applications that have many icon buttons where visual labels are impractical to use. They are also useful for things like thumbnail tooltips when hovering over a progress bar when using a mouse.

### Placement

By default, the tooltip is placed on the top side of its anchor. To change this, use the `side` prop on `Tooltip.Content`:
Expand Down Expand Up @@ -235,20 +243,20 @@ This has accessibility consequences and should only be disabled when necessary,

The `Tooltip.Content` element receives the following CSS variables:

- `--tooltip-anchor-width`: Specifies the width of the anchor element. You can use this to match the width of the tooltip with its anchor.
- `--tooltip.anchor-height`: Specifies the height of the anchor element. You can use this to match the width of the tooltip with its anchor.
- `--tooltip-available-width`: Specifies the available width of the tooltip element before it will overflow the viewport.
- `--tooltip-available-height`: Specifies the available height of the tooltip element before it will overflow the viewport.
- `--anchor-width`: Specifies the width of the anchor element. You can use this to match the width of the tooltip with its anchor.
- `--anchor-height`: Specifies the height of the anchor element. You can use this to match the width of the tooltip with its anchor.
- `--available-width`: Specifies the available width of the tooltip element before it overflows the viewport.
- `--available-height`: Specifies the available height of the tooltip element before it overflows the viewport.

```jsx
<Tooltip.Content
style={{
width: 'var(--tooltip-anchor-width)',
height: 'var(--tooltip-anchor-height)',
maxWidth: 'var(--tooltip-available-width)',
maxHeight: 'var(--tooltip-available-height)',
}}
>
Content
</Tooltip.Content>
```

By default, `maxWidth` and `maxHeight` are already specified using `--available-{width,height}` to prevent the tooltip from being too big to fit on the screen.
2 changes: 2 additions & 0 deletions docs/pages/base-ui/api/tooltip-anchor-fragment.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"import { TooltipAnchorFragment } from '@mui/base';"
],
"classes": [],
"spread": true,
"themeDefaultProps": null,
"muiName": "TooltipAnchorFragment",
"filename": "/packages/mui-base/src/Tooltip/TooltipAnchorFragment.tsx",
"inheritance": null,
Expand Down
5 changes: 1 addition & 4 deletions docs/pages/base-ui/api/tooltip-arrow.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"props": {
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"render": { "type": { "name": "func" } }
},
"props": {},
"name": "TooltipArrow",
"imports": [
"import { TooltipArrow } from '@mui/base/Tooltip';",
Expand Down
4 changes: 3 additions & 1 deletion docs/pages/base-ui/api/tooltip-content.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"default": "'center'"
},
"alignmentOffset": { "type": { "name": "number" }, "default": "0" },
"arrowPadding": { "type": { "name": "number" }, "default": "5" },
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"collisionBoundary": { "type": { "name": "any" }, "default": "'clippingAncestors'" },
"collisionPadding": {
Expand All @@ -17,7 +18,7 @@
},
"default": "5"
},
"container": { "type": { "name": "any" } },
"container": { "type": { "name": "any" }, "default": "document.body" },
"followCursorAxis": {
"type": {
"name": "enum",
Expand All @@ -27,6 +28,7 @@
},
"hideWhenDetached": { "type": { "name": "bool" }, "default": "false" },
"hoverable": { "type": { "name": "bool" }, "default": "true" },
"keepMounted": { "type": { "name": "bool" }, "default": "false" },
"render": { "type": { "name": "func" } },
"side": {
"type": {
Expand Down
5 changes: 4 additions & 1 deletion docs/pages/base-ui/api/tooltip-group.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"props": {},
"props": {
"closeDelay": { "type": { "name": "number" }, "default": "0" },
"delay": { "type": { "name": "number" }, "default": "0" }
},
"name": "TooltipGroup",
"imports": [
"import { TooltipGroup } from '@mui/base/Tooltip';",
Expand Down
12 changes: 11 additions & 1 deletion docs/pages/base-ui/api/tooltip.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"props": {},
"props": {
"closeDelay": { "type": { "name": "number" }, "default": "0" },
"defaultOpen": { "type": { "name": "bool" } },
"delay": { "type": { "name": "number" }, "default": "200" },
"delayType": {
"type": { "name": "enum", "description": "'hover'<br>&#124;&nbsp;'rest'" },
"default": "'rest'"
},
"onOpenChange": { "type": { "name": "func" } },
"open": { "type": { "name": "bool" } }
},
"name": "Tooltip",
"imports": [
"import { Tooltip } from '@mui/base/Tooltip';",
Expand Down
1 change: 1 addition & 0 deletions docs/pages/base-ui/api/use-tooltip.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"type": { "name": "Element | null", "description": "Element | null" },
"default": "null"
},
"arrowPadding": { "type": { "name": "number", "description": "number" }, "default": "5" },
"closeDelay": { "type": { "name": "number", "description": "number" }, "default": "0" },
"collisionBoundary": {
"type": { "name": "Boundary", "description": "Boundary" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
{
"componentDescription": "The tooltip arrow caret element.",
"propDescriptions": {
"className": {
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"render": { "description": "A function to customize rendering of the component." }
},
"propDescriptions": {},
"classDescriptions": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"alignmentOffset": {
"description": "The offset of the tooltip element along its alignment axis."
},
"arrowPadding": {
"description": "Determines the padding between the arrow and the tooltip content. Useful when the tooltip has rounded corners via <code>border-radius</code>."
},
"className": {
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
Expand All @@ -24,6 +27,9 @@
"description": "If <code>true</code>, the tooltip will be hidden if it is detached from its anchor element due to differing clipping contexts."
},
"hoverable": { "description": "If <code>true</code>, the tooltip content will be hoverable." },
"keepMounted": {
"description": "If <code>true</code>, the tooltip content will be kept mounted in the DOM."
},
"render": { "description": "A function to customize rendering of the component." },
"side": {
"description": "The side of the anchor element that the tooltip element should align to."
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"componentDescription": "Groups tooltips' delays together so that once one of the tooltips opens, subsequent tooltips will\nnot open with a delay.",
"propDescriptions": {},
"propDescriptions": {
"closeDelay": {
"description": "The delay in milliseconds until the tooltip content is closed."
},
"delay": {
"description": "The delay in milliseconds until tooltips within the group are open."
}
},
"classDescriptions": {}
}
19 changes: 18 additions & 1 deletion docs/translations/api-docs-base/tooltip/tooltip.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
{
"componentDescription": "The foundation for building custom-styled tooltips.",
"propDescriptions": {},
"propDescriptions": {
"closeDelay": {
"description": "The delay in milliseconds until the tooltip content is closed."
},
"defaultOpen": {
"description": "Specifies whether the tooltip is open initially when uncontrolled."
},
"delay": { "description": "The delay in milliseconds until the tooltip content is opened." },
"delayType": {
"description": "The delay type to use. <code>rest</code> means the <code>delay</code> represents how long the user&#39;s cursor must rest on the anchor before the tooltip content is opened. <code>hover</code> means the <code>delay</code> represents how long to wait once the user&#39;s cursor has entered the anchor."
},
"onOpenChange": {
"description": "Callback fired when the tooltip content is requested to be opened or closed. Use when controlled."
},
"open": {
"description": "If <code>true</code>, the tooltip content is open. Use when controlled."
}
},
"classDescriptions": {}
}
3 changes: 3 additions & 0 deletions docs/translations/api-docs/use-tooltip/use-tooltip.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"description": "The offset of the tooltip element along its alignment axis."
},
"anchorEl": { "description": "The anchor element of the tooltip." },
"arrowPadding": {
"description": "Determines the padding between the arrow and the tooltip content. Useful when the tooltip has rounded corners via <code>border-radius</code>."
},
"closeDelay": {
"description": "The delay in milliseconds before the tooltip closes after the anchor element is unhovered."
},
Expand Down
20 changes: 20 additions & 0 deletions packages/mui-base/src/Tooltip/Tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import { Tooltip } from '@base_ui/react/Tooltip';
import { createRenderer, screen } from '@mui/internal-test-utils';
import { expect } from 'chai';

describe('<Tooltip />', () => {
const { render } = createRenderer();

it('should render the children', () => {
render(
<Tooltip>
<Tooltip.AnchorFragment>
<div>Content</div>
</Tooltip.AnchorFragment>
</Tooltip>,
);

expect(screen.getByText('Content')).not.to.equal(null);
});
});
41 changes: 37 additions & 4 deletions packages/mui-base/src/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,32 @@ function Tooltip(props: TooltipProps) {
const { open, setOpen } = useTooltipOpenState({
open: props.open,
onOpenChange: props.onOpenChange,
defaultOpen: props.defaultOpen,
});

const { delay = 200, delayType = 'rest', closeDelay = 0 } = props;

const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
const [anchorProps, setAnchorProps] = React.useState<React.HTMLProps<Element>>({});
const [anchorProps, setAnchorProps] = React.useState<React.HTMLProps<Element>>({
// Anchor props are set only once hydration has occurred, so we provide initial values for SSR.
// Props that only make sense once hydration has occurred (indicating interactivity will work)
// are ignored.
['data-state' as string]: 'closed',
});

const contextValue = React.useMemo(
() => ({
delay,
delayType,
closeDelay,
open,
setOpen,
anchorEl,
anchorProps,
setAnchorEl,
setAnchorProps,
}),
[open, setOpen, anchorEl, anchorProps],
[delay, delayType, closeDelay, open, setOpen, anchorEl, anchorProps],
);

return <TooltipContext.Provider value={contextValue}>{props.children}</TooltipContext.Provider>;
Expand All @@ -49,11 +60,33 @@ Tooltip.propTypes /* remove-proptypes */ = {
*/
children: PropTypes.node,
/**
* @ignore
* The delay in milliseconds until the tooltip content is closed.
* @default 0
*/
closeDelay: PropTypes.number,
/**
* Specifies whether the tooltip is open initially when uncontrolled.
*/
defaultOpen: PropTypes.bool,
/**
* The delay in milliseconds until the tooltip content is opened.
* @default 200
*/
delay: PropTypes.number,
/**
* The delay type to use. `rest` means the `delay` represents how long the user's cursor must
* rest on the anchor before the tooltip content is opened. `hover` means the `delay` represents
* how long to wait once the user's cursor has entered the anchor.
* @default 'rest'
*/
delayType: PropTypes.oneOf(['hover', 'rest']),
/**
* Callback fired when the tooltip content is requested to be opened or closed. Use when
* controlled.
*/
onOpenChange: PropTypes.func,
/**
* @ignore
* If `true`, the tooltip content is open. Use when controlled.
*/
open: PropTypes.bool,
} as any;
Expand Down
Loading

0 comments on commit 11f1654

Please sign in to comment.