From 15889890b5b7399e6993eb560e6dc93c3182e50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 12 Apr 2023 09:25:44 +1000 Subject: [PATCH] Tooltip: Update the component markup for a11y remediations & refactor the existing styles towards more static styling (#3032) * Refactor tooltip markup without styled components and apply a11y remediations * experiment tooltip * checking interactive elements and clean the code a bit * add another story * style fixes and more stories * refactor it to use internal open state and make css presudo classes redundant * apply code review feedback * Refactor the styles to use data-attr and styled-components * update events * align and wrap styles with data attr * remove unused var * restructure markup and make tooltip hoverable * wrap up, clean up, interactive children * Apply suggestions from code review Co-authored-by: Josh Black * code review feedback * add outline for force-color media * Add unit tests * check the trigger element's content for label * useOnEscapePress hook to close the tooltip when mouse is hovering over the trigger element * fix tests * docs and move tooltip into folder * Update generated/components.json * add changeset * fix imports * update snapshot * update snapshot * default value to the docs * Update generated/components.json * snaps * remove snapshot tests --------- Co-authored-by: Josh Black Co-authored-by: broccolinisoup --- .changeset/flat-drinks-retire.md | 5 + docs/content/Tooltip.mdx | 83 +- generated/components.json | 93 +- src/Tooltip.tsx | 266 ---- src/{ => Tooltip}/Tooltip.docs.json | 16 +- src/Tooltip/Tooltip.features.stories.tsx | 134 ++ src/{stories => Tooltip}/Tooltip.stories.tsx | 14 +- src/Tooltip/Tooltip.tsx | 396 +++++ src/Tooltip/__tests__/Tooltip.test.tsx | 106 ++ .../__tests__/Tooltip.types.test.tsx | 2 +- src/Tooltip/index.ts | 1 + src/_TextInputInnerAction.tsx | 2 +- src/__tests__/TextInput.test.tsx | 30 - src/__tests__/Tooltip.test.tsx | 51 - .../__snapshots__/TextInput.test.tsx.snap | 1359 +---------------- .../__snapshots__/Tooltip.test.tsx.snap | 232 --- src/index.ts | 2 +- 17 files changed, 851 insertions(+), 1941 deletions(-) create mode 100644 .changeset/flat-drinks-retire.md delete mode 100644 src/Tooltip.tsx rename src/{ => Tooltip}/Tooltip.docs.json (54%) create mode 100644 src/Tooltip/Tooltip.features.stories.tsx rename src/{stories => Tooltip}/Tooltip.stories.tsx (54%) create mode 100644 src/Tooltip/Tooltip.tsx create mode 100644 src/Tooltip/__tests__/Tooltip.test.tsx rename src/{ => Tooltip}/__tests__/Tooltip.types.test.tsx (88%) create mode 100644 src/Tooltip/index.ts delete mode 100644 src/__tests__/Tooltip.test.tsx delete mode 100644 src/__tests__/__snapshots__/Tooltip.test.tsx.snap diff --git a/.changeset/flat-drinks-retire.md b/.changeset/flat-drinks-retire.md new file mode 100644 index 00000000000..74e8856b1c9 --- /dev/null +++ b/.changeset/flat-drinks-retire.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Tooltip: Address accessibility remediations and refactor styles towards more static styling diff --git a/docs/content/Tooltip.mdx b/docs/content/Tooltip.mdx index c50776cae4c..90dab885ac8 100644 --- a/docs/content/Tooltip.mdx +++ b/docs/content/Tooltip.mdx @@ -4,7 +4,7 @@ title: Tooltip status: Alpha --- -import data from '../../src/Tooltip.docs.json' +import data from '../../src/Tooltip/Tooltip.docs.json' The Tooltip component adds a tooltip to add context to interactive elements on the page. @@ -20,10 +20,85 @@ A tooltip may only be used on an element that is interactive such as a button or ## Examples +### Default (As a label type) + +```jsx live + + + +``` + +### As a description type + +```jsx live + + + +``` + +### With direction + ```jsx live - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### With wrap + +```jsx live + + + +``` + +### With no delay + +```jsx live + + + +``` + +### With align + +```jsx live + + + + + + ``` diff --git a/generated/components.json b/generated/components.json index 3c139a17c03..32289f70c61 100644 --- a/generated/components.json +++ b/generated/components.json @@ -295,44 +295,6 @@ ], "subcomponents": [] }, - "tooltip": { - "id": "tooltip", - "name": "Tooltip", - "status": "alpha", - "a11yReviewed": false, - "stories": [], - "props": [ - { - "name": "align", - "type": "'left' | 'right'" - }, - { - "name": "direction", - "type": "'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw'", - "description": "Sets where the tooltip renders in relation to the target." - }, - { - "name": "noDelay", - "type": "boolean", - "description": "When set to `true`, tooltip appears without any delay." - }, - { - "name": "aria-label", - "type": "string", - "description": "Text used in `aria-label` (for accessibility)" - }, - { - "name": "wrap", - "type": "boolean", - "description": "Use `true` to allow text within tooltip to wrap." - }, - { - "name": "sx", - "type": "SystemStyleObject" - } - ], - "subcomponents": [] - }, "truncate": { "id": "truncate", "name": "Truncate", @@ -4677,6 +4639,61 @@ } ] }, + "tooltip": { + "id": "tooltip", + "name": "Tooltip", + "status": "alpha", + "a11yReviewed": false, + "stories": [ + { + "id": "components-tooltip--default", + "code": "() => (\n \n \n \n \n \n)" + } + ], + "props": [ + { + "name": "align", + "type": "'left' | 'right'" + }, + { + "name": "aria-label", + "type": "string", + "description": "Should be utilised for label type tooltips and it is going to be used to label the tooltip trigger." + }, + { + "name": "direction", + "type": "'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw'", + "defaultValue": "n", + "description": "Sets where the tooltip renders in relation to the target." + }, + { + "name": "noDelay", + "type": "boolean", + "description": "When set to `true`, tooltip appears without any delay." + }, + { + "name": "text", + "type": "string", + "description": "Should be utilised for description type tooltips and it is going to be used to describe the tooltip trigger." + }, + { + "name": "type", + "type": "'label' | 'description'", + "defaultValue": "label", + "description": "The type of tooltip. `label` is used for labelling the element that triggers tooltip. `description` is used for describing or adding a suplementary information to the element that triggers the tooltip." + }, + { + "name": "wrap", + "type": "boolean", + "description": "Use `true` to allow text within tooltip to wrap." + }, + { + "name": "sx", + "type": "SystemStyleObject" + } + ], + "subcomponents": [] + }, "tree_view": { "id": "tree_view", "name": "TreeView", diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx deleted file mode 100644 index 28fb1c8645f..00000000000 --- a/src/Tooltip.tsx +++ /dev/null @@ -1,266 +0,0 @@ -import classnames from 'classnames' -import React from 'react' -import styled from 'styled-components' -import {get} from './constants' -import sx, {SxProp} from './sx' -import {ComponentProps} from './utils/types' - -const TooltipBase = styled.span` - position: relative; - - &::before { - position: absolute; - z-index: 1000001; - display: none; - width: 0px; - height: 0px; - color: ${get('colors.neutral.emphasisPlus')}; - pointer-events: none; - content: ''; - border: 6px solid transparent; - opacity: 0; - } - - &::after { - position: absolute; - z-index: 1000000; - display: none; - padding: 0.5em 0.75em; - font: normal normal 11px/1.5 ${get('fonts.normal')}; - -webkit-font-smoothing: subpixel-antialiased; - color: ${get('colors.fg.onEmphasis')}; - text-align: center; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-wrap: break-word; - white-space: pre; - pointer-events: none; - content: attr(aria-label); - background: ${get('colors.neutral.emphasisPlus')}; - border-radius: ${get('radii.1')}; - opacity: 0; - } - - // delay animation for tooltip - @keyframes tooltip-appear { - from { - opacity: 0; - } - - to { - opacity: 1; - } - } - - &:hover, - &:active, - &:focus, - &:focus-within { - &::before, - &::after { - display: inline-block; - text-decoration: none; - animation-name: tooltip-appear; - animation-duration: 0.1s; - animation-fill-mode: forwards; - animation-timing-function: ease-in; - animation-delay: 0.4s; - } - } - - &.tooltipped-no-delay:hover, - &.tooltipped-no-delay:active, - &.tooltipped-no-delay:focus, - &.tooltipped-no-delay:focus-within { - &::before, - &::after { - animation-delay: 0s; - } - } - - &.tooltipped-multiline:hover, - &.tooltipped-multiline:active, - &.tooltipped-multiline:focus, - &.tooltipped-multiline:focus-within { - &::after { - display: table-cell; - } - } - - // Tooltipped south - &.tooltipped-s, - &.tooltipped-se, - &.tooltipped-sw { - &::after { - top: 100%; - right: 50%; - margin-top: 6px; - } - - &::before { - top: auto; - right: 50%; - bottom: -7px; - margin-right: -6px; - border-bottom-color: ${get('colors.neutral.emphasisPlus')}; - } - } - - &.tooltipped-se { - &::after { - right: auto; - left: 50%; - margin-left: -${get('space.3')}; - } - } - - &.tooltipped-sw::after { - margin-right: -${get('space.3')}; - } - - // Tooltips above the object - &.tooltipped-n, - &.tooltipped-ne, - &.tooltipped-nw { - &::after { - right: 50%; - bottom: 100%; - margin-bottom: 6px; - } - - &::before { - top: -7px; - right: 50%; - bottom: auto; - margin-right: -6px; - border-top-color: ${get('colors.neutral.emphasisPlus')}; - } - } - - &.tooltipped-ne { - &::after { - right: auto; - left: 50%; - margin-left: -${get('space.3')}; - } - } - - &.tooltipped-nw::after { - margin-right: -${get('space.3')}; - } - - // Move the tooltip body to the center of the object. - &.tooltipped-s::after, - &.tooltipped-n::after { - transform: translateX(50%); - } - - // Tooltipped to the left - &.tooltipped-w { - &::after { - right: 100%; - bottom: 50%; - margin-right: 6px; - transform: translateY(50%); - } - - &::before { - top: 50%; - bottom: 50%; - left: -7px; - margin-top: -6px; - border-left-color: ${get('colors.neutral.emphasisPlus')}; - } - } - - // tooltipped to the right - &.tooltipped-e { - &::after { - bottom: 50%; - left: 100%; - margin-left: 6px; - transform: translateY(50%); - } - - &::before { - top: 50%; - right: -7px; - bottom: 50%; - margin-top: -6px; - border-right-color: ${get('colors.neutral.emphasisPlus')}; - } - } - - &.tooltipped-multiline { - &::after { - width: max-content; - max-width: 250px; - word-wrap: break-word; - white-space: pre-line; - border-collapse: separate; - } - - &.tooltipped-s::after, - &.tooltipped-n::after { - right: auto; - left: 50%; - transform: translateX(-50%); - } - - &.tooltipped-w::after, - &.tooltipped-e::after { - right: 100%; - } - } - - &.tooltipped-align-right-2::after { - right: 0; - margin-right: 0; - } - - &.tooltipped-align-right-2::before { - right: 15px; - } - - &.tooltipped-align-left-2::after { - left: 0; - margin-left: 0; - } - - &.tooltipped-align-left-2::before { - left: 10px; - } - - ${sx}; -` - -export type TooltipProps = { - direction?: 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' - text?: string - noDelay?: boolean - align?: 'left' | 'right' - wrap?: boolean -} & ComponentProps - -function Tooltip({direction = 'n', children, className, text, noDelay, align, wrap, ...rest}: TooltipProps) { - const classes = classnames( - className, - `tooltipped-${direction}`, - align && `tooltipped-align-${align}-2`, - noDelay && 'tooltipped-no-delay', - wrap && 'tooltipped-multiline', - ) - return ( - - {children} - - ) -} - -Tooltip.alignments = ['left', 'right'] - -Tooltip.directions = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'] - -export default Tooltip diff --git a/src/Tooltip.docs.json b/src/Tooltip/Tooltip.docs.json similarity index 54% rename from src/Tooltip.docs.json rename to src/Tooltip/Tooltip.docs.json index d4b88a8c6ec..d964fadd4ae 100644 --- a/src/Tooltip.docs.json +++ b/src/Tooltip/Tooltip.docs.json @@ -9,9 +9,15 @@ "name": "align", "type": "'left' | 'right'" }, + { + "name": "aria-label", + "type": "string", + "description": "Should be utilised for label type tooltips and it is going to be used to label the tooltip trigger." + }, { "name": "direction", "type": "'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw'", + "defaultValue": "n", "description": "Sets where the tooltip renders in relation to the target." }, { @@ -20,9 +26,15 @@ "description": "When set to `true`, tooltip appears without any delay." }, { - "name": "aria-label", + "name": "text", "type": "string", - "description": "Text used in `aria-label` (for accessibility)" + "description": "Should be utilised for description type tooltips and it is going to be used to describe the tooltip trigger." + }, + { + "name": "type", + "type": "'label' | 'description'", + "defaultValue": "label", + "description": "The type of tooltip. `label` is used for labelling the element that triggers tooltip. `description` is used for describing or adding a suplementary information to the element that triggers the tooltip." }, { "name": "wrap", diff --git a/src/Tooltip/Tooltip.features.stories.tsx b/src/Tooltip/Tooltip.features.stories.tsx new file mode 100644 index 00000000000..2a5a6ebec44 --- /dev/null +++ b/src/Tooltip/Tooltip.features.stories.tsx @@ -0,0 +1,134 @@ +import React from 'react' +import {Meta} from '@storybook/react' +import {BaseStyles, ThemeProvider, IconButton, Button} from '..' +import {Tooltip} from '.' +import {SearchIcon} from '@primer/octicons-react' +import Box from '../Box' + +export default { + title: 'Components/Tooltip/Features', + component: Tooltip, + + decorators: [ + Story => { + return ( + + + + + + ) + }, + ], +} as Meta + +// As a label for an IconButton +export const TooltipLabelTypeTooltip = () => ( + + + + + +) + +// As a label for an IconButton +export const TooltipNativeHTMLButton = () => ( + + + + + +) + +// As a supplementary description for a button +export const TooltipDescriptionTypeTooltip = () => ( + + + + + +) + +// As a supplementary description for an IconButton +export const TooltipIconButtonWithDescription = () => ( + + + + + +) + +export const TooltipWithDirection = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export const TooltipNoDelay = () => ( + + + +) + +export const TooltipWithAlign = () => ( + + + + + + + + +) + +export const TooltipWithWrap = () => ( + + + + + + + + +) diff --git a/src/stories/Tooltip.stories.tsx b/src/Tooltip/Tooltip.stories.tsx similarity index 54% rename from src/stories/Tooltip.stories.tsx rename to src/Tooltip/Tooltip.stories.tsx index 316b0f469b3..e51629f8e05 100644 --- a/src/stories/Tooltip.stories.tsx +++ b/src/Tooltip/Tooltip.stories.tsx @@ -1,12 +1,11 @@ import React from 'react' import {Meta} from '@storybook/react' -import {BaseStyles, ThemeProvider, IconButton} from '..' +import {BaseStyles, ThemeProvider, Button} from '..' +import {Tooltip} from '../Tooltip' import Box from '../Box' -import Tooltip from '../Tooltip' -import {SearchIcon} from '@primer/octicons-react' export default { - title: 'Components/Tooltip/Default', + title: 'Components/Tooltip', component: Tooltip, decorators: [ @@ -22,10 +21,11 @@ export default { ], } as Meta -export const IconButtonTooltip = () => ( +// As a label for an IconButton +export const Default = () => ( - - + + ) diff --git a/src/Tooltip/Tooltip.tsx b/src/Tooltip/Tooltip.tsx new file mode 100644 index 00000000000..9be7dbf95f7 --- /dev/null +++ b/src/Tooltip/Tooltip.tsx @@ -0,0 +1,396 @@ +import React, {Children, useEffect, useRef, useState} from 'react' +import Box from '../Box' +import sx, {SxProp} from '../sx' +import {useId} from '../hooks/useId' +import {isFocusable} from '@primer/behaviors/utils' +import {invariant} from '../utils/invariant' +import styled from 'styled-components' +import {get} from '../constants' +import {useOnEscapePress} from '../hooks/useOnEscapePress' + +export type TooltipProps = { + direction?: 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' + text?: string + noDelay?: boolean + align?: 'left' | 'right' + wrap?: boolean + type?: 'label' | 'description' + 'aria-label'?: React.AriaAttributes['aria-label'] +} & SxProp + +export type TriggerPropsType = { + 'aria-describedby'?: string + 'aria-labelledby'?: string + 'aria-label'?: string + onBlur?: React.FocusEventHandler + onFocus?: React.FocusEventHandler + onMouseEnter?: React.MouseEventHandler + ref?: React.RefObject +} + +const TooltipEL = styled.div` + // tooltip element itself + position: absolute; + z-index: 1000000; + padding: 0.5em 0.75em; + font: normal normal 11px/1.5 ${get('fonts.normal')}; + -webkit-font-smoothing: subpixel-antialiased; + color: ${get('colors.fg.onEmphasis')}; + text-align: center; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-wrap: break-word; + white-space: normal; + background: ${get('colors.neutral.emphasisPlus')}; + border-radius: ${get('radii.1')}; + width: max-content; + opacity: 0; + max-width: 250px; + @media (forced-colors: active) { + outline: 1px solid transparent; + } + + /* tooltip element should be rendered visually hidden when it is not opened. */ + &:not([data-state='open']) { + /* Visually hidden styles */ + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + // the caret + &::before { + position: absolute; + z-index: 1000001; + color: ${get('colors.neutral.emphasisPlus')}; + content: ''; + border: 6px solid transparent; + opacity: 0; + } + + // This is needed to keep the tooltip open when the user leaves the trigger element to hover tooltip + &::after { + position: absolute; + display: block; + right: 0; + left: 0; + height: 8px; + content: ''; + } + + // delay animation for tooltip + @keyframes tooltip-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + /* South, East, Southeast, Southwest before */ + + &[data-direction='n']::before, + &[data-direction='ne']::before, + &[data-direction='nw']::before { + top: 100%; + border-top-color: ${get('colors.neutral.emphasisPlus')}; + } + + &[data-direction='s']::before, + &[data-direction='se']::before, + &[data-direction='sw']::before { + bottom: 100%; + border-bottom-color: ${get('colors.neutral.emphasisPlus')}; + } + + &[data-direction='n']:before, + &[data-direction='s']:before { + right: 50%; + margin-right: -6px; + } + + &[data-direction='ne']::before, + &[data-direction='se']::before { + left: 0; + margin-left: 6px; + } + + &[data-direction='sw']::before, + &[data-direction='nw']::before { + right: 0; + margin-right: 6px; + } + + /* South, East, Southeast, Southwest after */ + + &[data-direction='n']::after, + &[data-direction='ne']::after, + &[data-direction='nw']::after { + top: 100%; + } + + &[data-direction='s']::after, + &[data-direction='se']::after, + &[data-direction='sw']::after { + bottom: 100%; + } + + /* West before and after */ + + &[data-direction='w']::before { + top: 50%; + bottom: 50%; + left: 100%; + margin-top: -6px; + border-left-color: ${get('colors.neutral.emphasisPlus')}; + } + + &[data-direction='w']::after { + position: absolute; + display: block; + height: 100%; + width: 8px; + content: ''; + bottom: 0; + left: 100%; + } + + /* East before and after */ + + &[data-direction='e']::after { + position: absolute; + display: block; + height: 100%; + width: 8px; + content: ''; + bottom: 0; + right: 100%; + margin-left: -8px; + } + + &[data-direction='e']::before { + top: 50%; + bottom: 50%; + right: 100%; + margin-top: -6px; + border-right-color: ${get('colors.neutral.emphasisPlus')}; + } + + /* Animation styles */ + + &[data-state='open'], + &[data-state='open']::before { + animation-name: tooltip-appear; + animation-duration: 0.1s; + animation-fill-mode: forwards; + animation-timing-function: ease-in; + animation-delay: 0.4s; + } + + /* Position of the tooltip element when it is opened. */ + + &[data-state='open'] { + &[data-no-delay='true'], + &[data-no-delay='true']::before { + animation-delay: 0s; + } + &[data-direction='s'], + &[data-direction='se'], + &[data-direction='sw'] { + top: 100%; + right: 50%; + margin-top: 6px; + } + + &[data-direction='n'], + &[data-direction='ne'], + &[data-direction='nw'] { + bottom: 100%; + margin-bottom: 6px; + right: 50%; + } + + &[data-direction='n'], + &[data-direction='s'] { + transform: translateX(50%); + } + + &[data-direction='se'] { + right: auto; + left: 50%; + margin-left: -${get('space.3')}; + } + &[data-direction='ne'] { + right: auto; + left: 50%; + margin-left: -${get('space.3')}; + } + + &[data-direction='sw'] { + margin-right: -${get('space.3')}; + } + + &[data-direction='e'] { + bottom: 50%; + left: 100%; + margin-left: 6px; + transform: translateY(50%); + } + + &[data-direction='w'] { + bottom: 50%; + right: 100%; + margin-right: 6px; + transform: translateY(50%); + } + + /* Align and wrap styles */ + + &[data-align='left'] { + right: 100%; + margin-left: 0; + } + &[data-align='left']::before { + right: 40px; + } + &[data-align='right'] { + right: 0; + margin-right: 0; + } + &[data-align='right']::before { + right: 72px; + } + + &[data-wrap='true'] { + display: table-cell; + width: max-content; + max-width: 250px; + word-wrap: break-word; + white-space: pre-line; + border-collapse: separate; + } + + &[data-wrap='true'][data-direction='n'], + &[data-wrap='true'][data-direction='s'] { + transform: translateX(-50%); + right: auto; + left: 50%; + } + + &[data-wrap='true'][data-direction='w'], + &[data-wrap='true'][data-direction='e'] { + right: 100%; + } + } + + ${sx}; +` + +export const Tooltip: React.FC> = ({ + direction = 'n', + // used for description type + text, + // used for label type + 'aria-label': label, + align, + wrap, + noDelay, + type = 'label', + children, + ...rest +}) => { + const id = useId() + const triggerRef = useRef(null) + const child = Children.only(children) + const [open, setOpen] = useState(false) + + // we need this check for every render + if (__DEV__) { + // Practically, this is not a conditional hook, it is a compile time check + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if (triggerRef.current) { + // Has trigger element or any of its children interactive elements? + const isTriggerInteractive = isFocusable(triggerRef.current) + const triggerChildren = triggerRef.current.childNodes + const hasInteractiveChild = Array.from(triggerChildren).some(child => { + return child instanceof HTMLElement && isFocusable(child) + }) + invariant( + isTriggerInteractive || hasInteractiveChild, + 'The `Tooltip` component expects a single React element that contains interactive content. Consider using a ` + +) + +describe('Tooltip', () => { + checkExports('Tooltip', { + default: undefined, + Tooltip, + }) + + checkStoriesForAxeViolations('Tooltip.features', '../Tooltip/') + + it('renders `data-direction="n"` by default', () => { + const {getByText} = HTMLRender() + expect(getByText('label type tooltip')).toHaveAttribute('data-direction', 'n') + }) + it('renders `data-direction` attribute with the correct value when the `direction` prop is specified', () => { + const {getByText} = HTMLRender() + expect(getByText('label type tooltip')).toHaveAttribute('data-direction', 's') + }) + + it('renders `data-no-delay` attribute with the correct value when the `noDelay` prop is specified', () => { + const {getByText} = HTMLRender() + expect(getByText('label type tooltip')).toHaveAttribute('data-no-delay', 'true') + }) + + it('renders `data-wrap` attribute with the correct value when the `wrap` prop is specified', () => { + const {getByText} = HTMLRender() + expect(getByText('label type tooltip')).toHaveAttribute('data-wrap', 'true') + }) + it('renders `data-align` attribute with the correct value when the `align` prop is specified', () => { + const {getByText} = HTMLRender() + expect(getByText('label type tooltip')).toHaveAttribute('data-align', 'right') + }) + it('should label the trigger element by its tooltip when the tooltip type is label', () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + const tooltipEl = getByText('label type tooltip') + expect(triggerEL).toHaveAttribute('aria-labelledby', tooltipEl.id) + }) + it('should render aria-hidden on the tooltip element when the tooltip is label type', () => { + const {getByText} = HTMLRender() + expect(getByText('label type tooltip')).toHaveAttribute('aria-hidden', 'true') + }) + it('should describe the trigger element by its tooltip when the tooltip type is describe', () => { + const {getByRole, getByText} = HTMLRender( + , + ) + const triggerEL = getByRole('button') + const tooltipEl = getByText('This is description for the trigger element') + expect(triggerEL).toHaveAttribute('aria-describedby', tooltipEl.id) + }) + it('should render the tooltip element with role="tooltip" when the tooltip type is describe', () => { + const {getByText} = HTMLRender( + , + ) + expect(getByText('This is description for the trigger element')).toHaveAttribute('role', 'tooltip') + }) + it('should display the tooltip when the trigger element is hovered', async () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + const user = userEvent.setup() + await user.hover(triggerEL) + expect(getByText('label type tooltip')).toHaveAttribute('data-state', 'open') + }) + it('should display the tooltip when the trigger element is focused', () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + act(() => { + triggerEL.focus() + }) + expect(getByText('label type tooltip')).toHaveAttribute('data-state', 'open') + }) + it('should hide the tooltip when the trigger element is blurred', async () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + const user = userEvent.setup() + act(() => { + triggerEL.focus() + }) + await user.keyboard('{TAB}') + expect(getByText('label type tooltip')).not.toHaveAttribute('data-state', 'open') + }) + it('should hide the tooltip when the ESC key is pressed', async () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + const user = userEvent.setup() + act(() => { + triggerEL.focus() + }) + await user.keyboard('{Escape}') + expect(getByText('label type tooltip')).not.toHaveAttribute('data-state', 'open') + }) +}) diff --git a/src/__tests__/Tooltip.types.test.tsx b/src/Tooltip/__tests__/Tooltip.types.test.tsx similarity index 88% rename from src/__tests__/Tooltip.types.test.tsx rename to src/Tooltip/__tests__/Tooltip.types.test.tsx index c9280121588..65d27666df2 100644 --- a/src/__tests__/Tooltip.types.test.tsx +++ b/src/Tooltip/__tests__/Tooltip.types.test.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Tooltip from '../Tooltip' +import {Tooltip} from '..' export function shouldAcceptCallWithNoProps() { return diff --git a/src/Tooltip/index.ts b/src/Tooltip/index.ts new file mode 100644 index 00000000000..ba15f407377 --- /dev/null +++ b/src/Tooltip/index.ts @@ -0,0 +1 @@ +export * from './Tooltip' diff --git a/src/_TextInputInnerAction.tsx b/src/_TextInputInnerAction.tsx index 492ceb8a96a..dbbe086d3ac 100644 --- a/src/_TextInputInnerAction.tsx +++ b/src/_TextInputInnerAction.tsx @@ -2,7 +2,7 @@ import React, {forwardRef} from 'react' import {IconProps} from '@primer/octicons-react' import Box from './Box' import {Button, IconButton, ButtonProps} from './Button' -import Tooltip from './Tooltip' +import {Tooltip} from './Tooltip' import {BetterSystemStyleObject, merge, SxProp} from './sx' type TextInputActionProps = Omit< diff --git a/src/__tests__/TextInput.test.tsx b/src/__tests__/TextInput.test.tsx index 2257963b8fa..6ab50f64269 100644 --- a/src/__tests__/TextInput.test.tsx +++ b/src/__tests__/TextInput.test.tsx @@ -76,36 +76,6 @@ describe('TextInput', () => { ).toMatchSnapshot() }) - it('renders trailingAction text button with a tooltip', () => { - const handleAction = jest.fn() - expect( - render( - - Clear - - } - />, - ), - ).toMatchSnapshot() - }) - - it('renders trailingAction icon button', () => { - const handleAction = jest.fn() - expect( - render( - } - />, - ), - ).toMatchSnapshot() - }) - it('focuses the text input if you do not click the input element', () => { const {container, getByLabelText} = HTMLRender( <> diff --git a/src/__tests__/Tooltip.test.tsx b/src/__tests__/Tooltip.test.tsx deleted file mode 100644 index 2f96bfb1728..00000000000 --- a/src/__tests__/Tooltip.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react' -import Tooltip, {TooltipProps} from '../Tooltip' -import {render, renderClasses, rendersClass, behavesAsComponent, checkExports} from '../utils/testing' -import {render as HTMLRender} from '@testing-library/react' -import {axe, toHaveNoViolations} from 'jest-axe' - -expect.extend(toHaveNoViolations) - -describe('Tooltip', () => { - behavesAsComponent({Component: Tooltip}) - - checkExports('Tooltip', { - default: Tooltip, - }) - - it('should have no axe violations', async () => { - const {container} = HTMLRender() - const results = await axe(container) - expect(results).toHaveNoViolations() - }) - - it('renders a with the "tooltipped" class', () => { - expect(render().type).toEqual('span') - expect(renderClasses()).toContain('tooltipped-n') - }) - - it('respects the "align" prop', () => { - expect(rendersClass(, 'tooltipped-align-left-2')).toBe(true) - expect(rendersClass(, 'tooltipped-align-right-2')).toBe(true) - }) - - it('respects the "direction" prop', () => { - for (const direction of Tooltip.directions) { - expect( - rendersClass(, `tooltipped-${direction}`), - ).toBe(true) - } - }) - - it('respects the "noDelay" prop', () => { - expect(rendersClass(, 'tooltipped-no-delay')).toBe(true) - }) - - it('respects the "text" prop', () => { - expect(render().props['aria-label']).toEqual('hi') - }) - - it('respects the "wrap" prop', () => { - expect(rendersClass(, 'tooltipped-multiline')).toBe(true) - }) -}) diff --git a/src/__tests__/__snapshots__/TextInput.test.tsx.snap b/src/__tests__/__snapshots__/TextInput.test.tsx.snap index 085b35d792a..9848aabca2e 100644 --- a/src/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/src/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -1250,1047 +1250,21 @@ exports[`TextInput renders small 1`] = ` `; -exports[`TextInput renders trailingAction icon button 1`] = ` -.c2 { - margin-left: 4px; - margin-right: 4px; - line-height: 0; -} - -.c4 { - border-radius: 6px; - border: 1px solid; - border-color: transparent; - font-family: inherit; - font-weight: 500; - font-size: 14px; - cursor: pointer; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-text-decoration: none; - text-decoration: none; - text-align: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - height: 32px; - padding: 0 12px; - gap: 8px; - min-width: -webkit-max-content; - min-width: -moz-max-content; - min-width: max-content; - -webkit-transition: 80ms cubic-bezier(0.65,0,0.35,1); - transition: 80ms cubic-bezier(0.65,0,0.35,1); - -webkit-transition-property: color,fill,background-color,border-color; - transition-property: color,fill,background-color,border-color; - color: #0969da; - background-color: transparent; - box-shadow: none; -} - -.c4:focus:not(:disabled) { - box-shadow: none; - outline: 2px solid #0969da; - outline-offset: -2px; -} - -.c4:focus:not(:disabled):not(:focus-visible) { - outline: solid 1px transparent; -} - -.c4:focus-visible:not(:disabled) { - box-shadow: none; - outline: 2px solid #0969da; - outline-offset: -2px; -} - -.c4[href] { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c4[href]:hover { - -webkit-text-decoration: none; - text-decoration: none; -} - -.c4:hover { - -webkit-transition-duration: 80ms; - transition-duration: 80ms; -} - -.c4:active { - -webkit-transition: none; - transition: none; -} - -.c4:disabled { - cursor: not-allowed; - box-shadow: none; - color: #8c959f; -} - -.c4:disabled [data-component=ButtonCounter] { - color: inherit; -} - -.c4 [data-component=ButtonCounter] { - font-size: 14px; -} - -.c4[data-component=IconButton] { - display: inline-grid; - padding: unset; - place-content: center; - width: 32px; - min-width: unset; -} - -.c4[data-size="small"] { - padding: 0 8px; - height: 28px; - gap: 4px; - font-size: 12px; -} - -.c4[data-size="small"] [data-component="text"] { - line-height: calc(20 / 12); -} - -.c4[data-size="small"] [data-component=ButtonCounter] { - font-size: 12px; -} - -.c4[data-size="small"] [data-component="buttonContent"] > :not(:last-child) { - margin-right: 4px; -} - -.c4[data-size="small"][data-component=IconButton] { - width: 28px; - padding: unset; -} - -.c4[data-size="large"] { - padding: 0 16px; - height: 40px; - gap: 8px; -} - -.c4[data-size="large"] [data-component="buttonContent"] > :not(:last-child) { - margin-right: 8px; -} - -.c4[data-size="large"][data-component=IconButton] { - width: 40px; - padding: unset; -} - -.c4[data-block="block"] { - width: 100%; -} - -.c4 [data-component="leadingVisual"] { - grid-area: leadingVisual; - color: #656d76; -} - -.c4 [data-component="text"] { - grid-area: text; - line-height: calc(20/14); - white-space: nowrap; -} - -.c4 [data-component="trailingVisual"] { - grid-area: trailingVisual; -} - -.c4 [data-component="trailingAction"] { - margin-right: -4px; - color: #656d76; -} - -.c4 [data-component="buttonContent"] { - -webkit-flex: 1 0 auto; - -ms-flex: 1 0 auto; - flex: 1 0 auto; - display: grid; - grid-template-areas: "leadingVisual text trailingVisual"; - grid-template-columns: min-content minmax(0,auto) min-content; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-align-content: center; - -ms-flex-line-pack: center; - align-content: center; -} - -.c4 [data-component="buttonContent"] > :not(:last-child) { - margin-right: 8px; -} - -.c4:hover:not([disabled]) { - background-color: #f3f4f6; -} - -.c4:active:not([disabled]) { - background-color: hsla(220,14%,94%,1); -} - -.c4[aria-expanded=true] { - background-color: hsla(220,14%,94%,1); -} - -.c4[data-component="IconButton"][data-no-visuals] { - color: #656d76; -} - -.c4[data-no-visuals] { - color: #0969da; -} - -.c4:has([data-component="ButtonCounter"]) { - color: #0969da; -} - -.c4[data-size="small"][data-no-visuals="true"] { - padding-top: 2px; - padding-right: 4px; - padding-bottom: 2px; - padding-left: 4px; - position: relative; -} - -.c4[data-size="small"][data-no-visuals="true"][data-component="IconButton"] { - width: var(--inner-action-size); - height: var(--inner-action-size); -} - -.c0 { - font-size: 14px; - line-height: 20px; - color: #1F2328; - vertical-align: middle; - background-color: #ffffff; - border: 1px solid #d0d7de; - border-radius: 6px; - outline: none; - box-shadow: inset 0 1px 0 rgba(208,215,222,0.2); - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: stretch; - -webkit-box-align: stretch; - -ms-flex-align: stretch; - align-items: stretch; - min-height: 32px; - background-repeat: no-repeat; - background-position: right 8px center; - padding-left: 0; - padding-right: 0; -} - -.c0 input, -.c0 textarea { - cursor: text; -} - -.c0 select { - cursor: pointer; -} - -.c0::-webkit-input-placeholder { - color: #6e7781; -} - -.c0::-moz-placeholder { - color: #6e7781; -} - -.c0:-ms-input-placeholder { - color: #6e7781; -} - -.c0::placeholder { - color: #6e7781; -} - -.c0 > textarea { - padding: 12px; -} - -.c0 > :not(:last-child) { - margin-right: 8px; -} - -.c0 .TextInput-icon, -.c0 .TextInput-action { - -webkit-align-self: center; - -ms-flex-item-align: center; - align-self: center; - color: #656d76; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; -} - -.c0 > input, -.c0 > select { - padding-left: 12px; - padding-right: 0; -} - -.c1 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c1:focus { - outline: 0; -} - -.c3 { - position: relative; -} - -.c3::before { - position: absolute; - z-index: 1000001; - display: none; - width: 0px; - height: 0px; - color: #24292f; - pointer-events: none; - content: ''; - border: 6px solid transparent; - opacity: 0; -} - -.c3::after { - position: absolute; - z-index: 1000000; - display: none; - padding: 0.5em 0.75em; - font: normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; - -webkit-font-smoothing: subpixel-antialiased; - color: #ffffff; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - text-shadow: none; - text-transform: none; - -webkit-letter-spacing: normal; - -moz-letter-spacing: normal; - -ms-letter-spacing: normal; - letter-spacing: normal; - word-wrap: break-word; - white-space: pre; - pointer-events: none; - content: attr(aria-label); - background: #24292f; - border-radius: 3px; - opacity: 0; -} - -.c3:hover::before, -.c3:active::before, -.c3:focus::before, -.c3:focus-within::before, -.c3:hover::after, -.c3:active::after, -.c3:focus::after, -.c3:focus-within::after { - display: inline-block; - -webkit-text-decoration: none; - text-decoration: none; - -webkit-animation-name: tooltip-appear; - animation-name: tooltip-appear; - -webkit-animation-duration: 0.1s; - animation-duration: 0.1s; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} - -.c3.tooltipped-no-delay:hover::before, -.c3.tooltipped-no-delay:active::before, -.c3.tooltipped-no-delay:focus::before, -.c3.tooltipped-no-delay:focus-within::before, -.c3.tooltipped-no-delay:hover::after, -.c3.tooltipped-no-delay:active::after, -.c3.tooltipped-no-delay:focus::after, -.c3.tooltipped-no-delay:focus-within::after { - -webkit-animation-delay: 0s; - animation-delay: 0s; -} - -.c3.tooltipped-multiline:hover::after, -.c3.tooltipped-multiline:active::after, -.c3.tooltipped-multiline:focus::after, -.c3.tooltipped-multiline:focus-within::after { - display: table-cell; -} - -.c3.tooltipped-s::after, -.c3.tooltipped-se::after, -.c3.tooltipped-sw::after { - top: 100%; - right: 50%; - margin-top: 6px; -} - -.c3.tooltipped-s::before, -.c3.tooltipped-se::before, -.c3.tooltipped-sw::before { - top: auto; - right: 50%; - bottom: -7px; - margin-right: -6px; - border-bottom-color: #24292f; -} - -.c3.tooltipped-se::after { - right: auto; - left: 50%; - margin-left: -16px; -} - -.c3.tooltipped-sw::after { - margin-right: -16px; -} - -.c3.tooltipped-n::after, -.c3.tooltipped-ne::after, -.c3.tooltipped-nw::after { - right: 50%; - bottom: 100%; - margin-bottom: 6px; -} - -.c3.tooltipped-n::before, -.c3.tooltipped-ne::before, -.c3.tooltipped-nw::before { - top: -7px; - right: 50%; - bottom: auto; - margin-right: -6px; - border-top-color: #24292f; -} - -.c3.tooltipped-ne::after { - right: auto; - left: 50%; - margin-left: -16px; -} - -.c3.tooltipped-nw::after { - margin-right: -16px; -} - -.c3.tooltipped-s::after, -.c3.tooltipped-n::after { - -webkit-transform: translateX(50%); - -ms-transform: translateX(50%); - transform: translateX(50%); -} - -.c3.tooltipped-w::after { - right: 100%; - bottom: 50%; - margin-right: 6px; - -webkit-transform: translateY(50%); - -ms-transform: translateY(50%); - transform: translateY(50%); -} - -.c3.tooltipped-w::before { - top: 50%; - bottom: 50%; - left: -7px; - margin-top: -6px; - border-left-color: #24292f; -} - -.c3.tooltipped-e::after { - bottom: 50%; - left: 100%; - margin-left: 6px; - -webkit-transform: translateY(50%); - -ms-transform: translateY(50%); - transform: translateY(50%); -} - -.c3.tooltipped-e::before { - top: 50%; - right: -7px; - bottom: 50%; - margin-top: -6px; - border-right-color: #24292f; -} - -.c3.tooltipped-multiline::after { - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; - max-width: 250px; - word-wrap: break-word; - white-space: pre-line; - border-collapse: separate; -} - -.c3.tooltipped-multiline.tooltipped-s::after, -.c3.tooltipped-multiline.tooltipped-n::after { - right: auto; - left: 50%; - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); -} - -.c3.tooltipped-multiline.tooltipped-w::after, -.c3.tooltipped-multiline.tooltipped-e::after { - right: 100%; -} - -.c3.tooltipped-align-right-2::after { - right: 0; - margin-right: 0; -} - -.c3.tooltipped-align-right-2::before { - right: 15px; -} - -.c3.tooltipped-align-left-2::after { - left: 0; - margin-left: 0; -} - -.c3.tooltipped-align-left-2::before { - left: 10px; -} - -@media (forced-colors:active) { - .c4:focus { - outline: solid 1px transparent; - } -} - -@media (pointer:coarse) { - .c4[data-size="small"][data-no-visuals="true"]:after { - content: ""; - position: absolute; - left: 0; - right: 0; - -webkit-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - top: 50%; - min-height: 44px; - } -} - -@media (min-width:768px) { - .c0 { - font-size: 14px; - } -} - - - - - - - - - -`; - -exports[`TextInput renders trailingAction text button 1`] = ` -.c2 { - margin-left: 4px; - margin-right: 4px; - line-height: 0; -} - -.c4 { - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; -} - -.c3 { - border-radius: 6px; - border: 1px solid; - border-color: transparent; - font-family: inherit; - font-weight: 500; - font-size: 14px; - cursor: pointer; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-text-decoration: none; - text-decoration: none; - text-align: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - height: 32px; - padding: 0 12px; - gap: 8px; - min-width: -webkit-max-content; - min-width: -moz-max-content; - min-width: max-content; - -webkit-transition: 80ms cubic-bezier(0.65,0,0.35,1); - transition: 80ms cubic-bezier(0.65,0,0.35,1); - -webkit-transition-property: color,fill,background-color,border-color; - transition-property: color,fill,background-color,border-color; - color: #0969da; - background-color: transparent; - box-shadow: none; -} - -.c3:focus:not(:disabled) { - box-shadow: none; - outline: 2px solid #0969da; - outline-offset: -2px; -} - -.c3:focus:not(:disabled):not(:focus-visible) { - outline: solid 1px transparent; -} - -.c3:focus-visible:not(:disabled) { - box-shadow: none; - outline: 2px solid #0969da; - outline-offset: -2px; -} - -.c3[href] { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c3[href]:hover { - -webkit-text-decoration: none; - text-decoration: none; -} - -.c3:hover { - -webkit-transition-duration: 80ms; - transition-duration: 80ms; -} - -.c3:active { - -webkit-transition: none; - transition: none; -} - -.c3:disabled { - cursor: not-allowed; - box-shadow: none; - color: #8c959f; -} - -.c3:disabled [data-component=ButtonCounter] { - color: inherit; -} - -.c3 [data-component=ButtonCounter] { - font-size: 14px; -} - -.c3[data-component=IconButton] { - display: inline-grid; - padding: unset; - place-content: center; - width: 32px; - min-width: unset; -} - -.c3[data-size="small"] { - padding: 0 8px; - height: 28px; - gap: 4px; - font-size: 12px; -} - -.c3[data-size="small"] [data-component="text"] { - line-height: calc(20 / 12); -} - -.c3[data-size="small"] [data-component=ButtonCounter] { - font-size: 12px; -} - -.c3[data-size="small"] [data-component="buttonContent"] > :not(:last-child) { - margin-right: 4px; -} - -.c3[data-size="small"][data-component=IconButton] { - width: 28px; - padding: unset; -} - -.c3[data-size="large"] { - padding: 0 16px; - height: 40px; - gap: 8px; -} - -.c3[data-size="large"] [data-component="buttonContent"] > :not(:last-child) { - margin-right: 8px; -} - -.c3[data-size="large"][data-component=IconButton] { - width: 40px; - padding: unset; -} - -.c3[data-block="block"] { - width: 100%; -} - -.c3 [data-component="leadingVisual"] { - grid-area: leadingVisual; - color: #656d76; -} - -.c3 [data-component="text"] { - grid-area: text; - line-height: calc(20/14); - white-space: nowrap; -} - -.c3 [data-component="trailingVisual"] { - grid-area: trailingVisual; -} - -.c3 [data-component="trailingAction"] { - margin-right: -4px; - color: #656d76; -} - -.c3 [data-component="buttonContent"] { - -webkit-flex: 1 0 auto; - -ms-flex: 1 0 auto; - flex: 1 0 auto; - display: grid; - grid-template-areas: "leadingVisual text trailingVisual"; - grid-template-columns: min-content minmax(0,auto) min-content; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-align-content: center; - -ms-flex-line-pack: center; - align-content: center; -} - -.c3 [data-component="buttonContent"] > :not(:last-child) { - margin-right: 8px; -} - -.c3:hover:not([disabled]) { - background-color: #f3f4f6; -} - -.c3:active:not([disabled]) { - background-color: hsla(220,14%,94%,1); -} - -.c3[aria-expanded=true] { - background-color: hsla(220,14%,94%,1); -} - -.c3[data-component="IconButton"][data-no-visuals] { - color: #656d76; -} - -.c3[data-no-visuals] { - color: #0969da; -} - -.c3:has([data-component="ButtonCounter"]) { - color: #0969da; -} - -.c3[data-no-visuals="true"] { - padding-top: 2px; - padding-right: 4px; - padding-bottom: 2px; - padding-left: 4px; - position: relative; -} - -.c3[data-no-visuals="true"][data-component="IconButton"] { - width: var(--inner-action-size); - height: var(--inner-action-size); -} - -.c0 { - font-size: 14px; - line-height: 20px; - color: #1F2328; - vertical-align: middle; - background-color: #ffffff; - border: 1px solid #d0d7de; - border-radius: 6px; - outline: none; - box-shadow: inset 0 1px 0 rgba(208,215,222,0.2); - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: stretch; - -webkit-box-align: stretch; - -ms-flex-align: stretch; - align-items: stretch; - min-height: 32px; - background-repeat: no-repeat; - background-position: right 8px center; - padding-left: 0; - padding-right: 0; -} - -.c0 input, -.c0 textarea { - cursor: text; -} - -.c0 select { - cursor: pointer; -} - -.c0::-webkit-input-placeholder { - color: #6e7781; -} - -.c0::-moz-placeholder { - color: #6e7781; -} - -.c0:-ms-input-placeholder { - color: #6e7781; -} - -.c0::placeholder { - color: #6e7781; -} - -.c0 > textarea { - padding: 12px; -} - -.c0 > :not(:last-child) { - margin-right: 8px; -} - -.c0 .TextInput-icon, -.c0 .TextInput-action { - -webkit-align-self: center; - -ms-flex-item-align: center; - align-self: center; - color: #656d76; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; -} - -.c0 > input, -.c0 > select { - padding-left: 12px; - padding-right: 0; -} - -.c1 { - border: 0; - font-size: inherit; - font-family: inherit; - background-color: transparent; - -webkit-appearance: none; - color: inherit; - width: 100%; -} - -.c1:focus { - outline: 0; -} - -@media (forced-colors:active) { - .c3:focus { - outline: solid 1px transparent; - } -} - -@media (pointer:coarse) { - .c3[data-no-visuals="true"]:after { - content: ""; - position: absolute; - left: 0; - right: 0; - -webkit-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - top: 50%; - min-height: 44px; - } -} - -@media (min-width:768px) { - .c0 { - font-size: 14px; - } -} - - - - - - - -`; - -exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` +exports[`TextInput renders trailingAction text button 1`] = ` .c2 { margin-left: 4px; margin-right: 4px; line-height: 0; } -.c5 { +.c4 { -webkit-box-pack: center; -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; } -.c4 { +.c3 { border-radius: 6px; border: 1px solid; border-color: transparent; @@ -2335,59 +1309,59 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` box-shadow: none; } -.c4:focus:not(:disabled) { +.c3:focus:not(:disabled) { box-shadow: none; outline: 2px solid #0969da; outline-offset: -2px; } -.c4:focus:not(:disabled):not(:focus-visible) { +.c3:focus:not(:disabled):not(:focus-visible) { outline: solid 1px transparent; } -.c4:focus-visible:not(:disabled) { +.c3:focus-visible:not(:disabled) { box-shadow: none; outline: 2px solid #0969da; outline-offset: -2px; } -.c4[href] { +.c3[href] { display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; display: inline-flex; } -.c4[href]:hover { +.c3[href]:hover { -webkit-text-decoration: none; text-decoration: none; } -.c4:hover { +.c3:hover { -webkit-transition-duration: 80ms; transition-duration: 80ms; } -.c4:active { +.c3:active { -webkit-transition: none; transition: none; } -.c4:disabled { +.c3:disabled { cursor: not-allowed; box-shadow: none; color: #8c959f; } -.c4:disabled [data-component=ButtonCounter] { +.c3:disabled [data-component=ButtonCounter] { color: inherit; } -.c4 [data-component=ButtonCounter] { +.c3 [data-component=ButtonCounter] { font-size: 14px; } -.c4[data-component=IconButton] { +.c3[data-component=IconButton] { display: inline-grid; padding: unset; place-content: center; @@ -2395,70 +1369,70 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` min-width: unset; } -.c4[data-size="small"] { +.c3[data-size="small"] { padding: 0 8px; height: 28px; gap: 4px; font-size: 12px; } -.c4[data-size="small"] [data-component="text"] { +.c3[data-size="small"] [data-component="text"] { line-height: calc(20 / 12); } -.c4[data-size="small"] [data-component=ButtonCounter] { +.c3[data-size="small"] [data-component=ButtonCounter] { font-size: 12px; } -.c4[data-size="small"] [data-component="buttonContent"] > :not(:last-child) { +.c3[data-size="small"] [data-component="buttonContent"] > :not(:last-child) { margin-right: 4px; } -.c4[data-size="small"][data-component=IconButton] { +.c3[data-size="small"][data-component=IconButton] { width: 28px; padding: unset; } -.c4[data-size="large"] { +.c3[data-size="large"] { padding: 0 16px; height: 40px; gap: 8px; } -.c4[data-size="large"] [data-component="buttonContent"] > :not(:last-child) { +.c3[data-size="large"] [data-component="buttonContent"] > :not(:last-child) { margin-right: 8px; } -.c4[data-size="large"][data-component=IconButton] { +.c3[data-size="large"][data-component=IconButton] { width: 40px; padding: unset; } -.c4[data-block="block"] { +.c3[data-block="block"] { width: 100%; } -.c4 [data-component="leadingVisual"] { +.c3 [data-component="leadingVisual"] { grid-area: leadingVisual; color: #656d76; } -.c4 [data-component="text"] { +.c3 [data-component="text"] { grid-area: text; line-height: calc(20/14); white-space: nowrap; } -.c4 [data-component="trailingVisual"] { +.c3 [data-component="trailingVisual"] { grid-area: trailingVisual; } -.c4 [data-component="trailingAction"] { +.c3 [data-component="trailingAction"] { margin-right: -4px; color: #656d76; } -.c4 [data-component="buttonContent"] { +.c3 [data-component="buttonContent"] { -webkit-flex: 1 0 auto; -ms-flex: 1 0 auto; flex: 1 0 auto; @@ -2474,35 +1448,35 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` align-content: center; } -.c4 [data-component="buttonContent"] > :not(:last-child) { +.c3 [data-component="buttonContent"] > :not(:last-child) { margin-right: 8px; } -.c4:hover:not([disabled]) { +.c3:hover:not([disabled]) { background-color: #f3f4f6; } -.c4:active:not([disabled]) { +.c3:active:not([disabled]) { background-color: hsla(220,14%,94%,1); } -.c4[aria-expanded=true] { +.c3[aria-expanded=true] { background-color: hsla(220,14%,94%,1); } -.c4[data-component="IconButton"][data-no-visuals] { +.c3[data-component="IconButton"][data-no-visuals] { color: #656d76; } -.c4[data-no-visuals] { +.c3[data-no-visuals] { color: #0969da; } -.c4:has([data-component="ButtonCounter"]) { +.c3:has([data-component="ButtonCounter"]) { color: #0969da; } -.c4[data-no-visuals="true"] { +.c3[data-no-visuals="true"] { padding-top: 2px; padding-right: 4px; padding-bottom: 2px; @@ -2510,7 +1484,7 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` position: relative; } -.c4[data-no-visuals="true"][data-component="IconButton"] { +.c3[data-no-visuals="true"][data-component="IconButton"] { width: var(--inner-action-size); height: var(--inner-action-size); } @@ -2604,239 +1578,14 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` outline: 0; } -.c3 { - position: relative; - display: inline-block; -} - -.c3::before { - position: absolute; - z-index: 1000001; - display: none; - width: 0px; - height: 0px; - color: #24292f; - pointer-events: none; - content: ''; - border: 6px solid transparent; - opacity: 0; -} - -.c3::after { - position: absolute; - z-index: 1000000; - display: none; - padding: 0.5em 0.75em; - font: normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; - -webkit-font-smoothing: subpixel-antialiased; - color: #ffffff; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - text-shadow: none; - text-transform: none; - -webkit-letter-spacing: normal; - -moz-letter-spacing: normal; - -ms-letter-spacing: normal; - letter-spacing: normal; - word-wrap: break-word; - white-space: pre; - pointer-events: none; - content: attr(aria-label); - background: #24292f; - border-radius: 3px; - opacity: 0; -} - -.c3:hover::before, -.c3:active::before, -.c3:focus::before, -.c3:focus-within::before, -.c3:hover::after, -.c3:active::after, -.c3:focus::after, -.c3:focus-within::after { - display: inline-block; - -webkit-text-decoration: none; - text-decoration: none; - -webkit-animation-name: tooltip-appear; - animation-name: tooltip-appear; - -webkit-animation-duration: 0.1s; - animation-duration: 0.1s; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} - -.c3.tooltipped-no-delay:hover::before, -.c3.tooltipped-no-delay:active::before, -.c3.tooltipped-no-delay:focus::before, -.c3.tooltipped-no-delay:focus-within::before, -.c3.tooltipped-no-delay:hover::after, -.c3.tooltipped-no-delay:active::after, -.c3.tooltipped-no-delay:focus::after, -.c3.tooltipped-no-delay:focus-within::after { - -webkit-animation-delay: 0s; - animation-delay: 0s; -} - -.c3.tooltipped-multiline:hover::after, -.c3.tooltipped-multiline:active::after, -.c3.tooltipped-multiline:focus::after, -.c3.tooltipped-multiline:focus-within::after { - display: table-cell; -} - -.c3.tooltipped-s::after, -.c3.tooltipped-se::after, -.c3.tooltipped-sw::after { - top: 100%; - right: 50%; - margin-top: 6px; -} - -.c3.tooltipped-s::before, -.c3.tooltipped-se::before, -.c3.tooltipped-sw::before { - top: auto; - right: 50%; - bottom: -7px; - margin-right: -6px; - border-bottom-color: #24292f; -} - -.c3.tooltipped-se::after { - right: auto; - left: 50%; - margin-left: -16px; -} - -.c3.tooltipped-sw::after { - margin-right: -16px; -} - -.c3.tooltipped-n::after, -.c3.tooltipped-ne::after, -.c3.tooltipped-nw::after { - right: 50%; - bottom: 100%; - margin-bottom: 6px; -} - -.c3.tooltipped-n::before, -.c3.tooltipped-ne::before, -.c3.tooltipped-nw::before { - top: -7px; - right: 50%; - bottom: auto; - margin-right: -6px; - border-top-color: #24292f; -} - -.c3.tooltipped-ne::after { - right: auto; - left: 50%; - margin-left: -16px; -} - -.c3.tooltipped-nw::after { - margin-right: -16px; -} - -.c3.tooltipped-s::after, -.c3.tooltipped-n::after { - -webkit-transform: translateX(50%); - -ms-transform: translateX(50%); - transform: translateX(50%); -} - -.c3.tooltipped-w::after { - right: 100%; - bottom: 50%; - margin-right: 6px; - -webkit-transform: translateY(50%); - -ms-transform: translateY(50%); - transform: translateY(50%); -} - -.c3.tooltipped-w::before { - top: 50%; - bottom: 50%; - left: -7px; - margin-top: -6px; - border-left-color: #24292f; -} - -.c3.tooltipped-e::after { - bottom: 50%; - left: 100%; - margin-left: 6px; - -webkit-transform: translateY(50%); - -ms-transform: translateY(50%); - transform: translateY(50%); -} - -.c3.tooltipped-e::before { - top: 50%; - right: -7px; - bottom: 50%; - margin-top: -6px; - border-right-color: #24292f; -} - -.c3.tooltipped-multiline::after { - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; - max-width: 250px; - word-wrap: break-word; - white-space: pre-line; - border-collapse: separate; -} - -.c3.tooltipped-multiline.tooltipped-s::after, -.c3.tooltipped-multiline.tooltipped-n::after { - right: auto; - left: 50%; - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); -} - -.c3.tooltipped-multiline.tooltipped-w::after, -.c3.tooltipped-multiline.tooltipped-e::after { - right: 100%; -} - -.c3.tooltipped-align-right-2::after { - right: 0; - margin-right: 0; -} - -.c3.tooltipped-align-right-2::before { - right: 15px; -} - -.c3.tooltipped-align-left-2::after { - left: 0; - margin-left: 0; -} - -.c3.tooltipped-align-left-2::before { - left: 10px; -} - @media (forced-colors:active) { - .c4:focus { + .c3:focus { outline: solid 1px transparent; } } @media (pointer:coarse) { - .c4[data-no-visuals="true"]:after { + .c3[data-no-visuals="true"]:after { content: ""; position: absolute; left: 0; @@ -2873,30 +1622,24 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` - - - + + `; diff --git a/src/__tests__/__snapshots__/Tooltip.test.tsx.snap b/src/__tests__/__snapshots__/Tooltip.test.tsx.snap deleted file mode 100644 index d461c34404c..00000000000 --- a/src/__tests__/__snapshots__/Tooltip.test.tsx.snap +++ /dev/null @@ -1,232 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Tooltip renders consistently 1`] = ` -.c0 { - position: relative; -} - -.c0::before { - position: absolute; - z-index: 1000001; - display: none; - width: 0px; - height: 0px; - color: #24292f; - pointer-events: none; - content: ''; - border: 6px solid transparent; - opacity: 0; -} - -.c0::after { - position: absolute; - z-index: 1000000; - display: none; - padding: 0.5em 0.75em; - font: normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; - -webkit-font-smoothing: subpixel-antialiased; - color: #ffffff; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - text-shadow: none; - text-transform: none; - -webkit-letter-spacing: normal; - -moz-letter-spacing: normal; - -ms-letter-spacing: normal; - letter-spacing: normal; - word-wrap: break-word; - white-space: pre; - pointer-events: none; - content: attr(aria-label); - background: #24292f; - border-radius: 3px; - opacity: 0; -} - -.c0:hover::before, -.c0:active::before, -.c0:focus::before, -.c0:focus-within::before, -.c0:hover::after, -.c0:active::after, -.c0:focus::after, -.c0:focus-within::after { - display: inline-block; - -webkit-text-decoration: none; - text-decoration: none; - -webkit-animation-name: tooltip-appear; - animation-name: tooltip-appear; - -webkit-animation-duration: 0.1s; - animation-duration: 0.1s; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} - -.c0.tooltipped-no-delay:hover::before, -.c0.tooltipped-no-delay:active::before, -.c0.tooltipped-no-delay:focus::before, -.c0.tooltipped-no-delay:focus-within::before, -.c0.tooltipped-no-delay:hover::after, -.c0.tooltipped-no-delay:active::after, -.c0.tooltipped-no-delay:focus::after, -.c0.tooltipped-no-delay:focus-within::after { - -webkit-animation-delay: 0s; - animation-delay: 0s; -} - -.c0.tooltipped-multiline:hover::after, -.c0.tooltipped-multiline:active::after, -.c0.tooltipped-multiline:focus::after, -.c0.tooltipped-multiline:focus-within::after { - display: table-cell; -} - -.c0.tooltipped-s::after, -.c0.tooltipped-se::after, -.c0.tooltipped-sw::after { - top: 100%; - right: 50%; - margin-top: 6px; -} - -.c0.tooltipped-s::before, -.c0.tooltipped-se::before, -.c0.tooltipped-sw::before { - top: auto; - right: 50%; - bottom: -7px; - margin-right: -6px; - border-bottom-color: #24292f; -} - -.c0.tooltipped-se::after { - right: auto; - left: 50%; - margin-left: -16px; -} - -.c0.tooltipped-sw::after { - margin-right: -16px; -} - -.c0.tooltipped-n::after, -.c0.tooltipped-ne::after, -.c0.tooltipped-nw::after { - right: 50%; - bottom: 100%; - margin-bottom: 6px; -} - -.c0.tooltipped-n::before, -.c0.tooltipped-ne::before, -.c0.tooltipped-nw::before { - top: -7px; - right: 50%; - bottom: auto; - margin-right: -6px; - border-top-color: #24292f; -} - -.c0.tooltipped-ne::after { - right: auto; - left: 50%; - margin-left: -16px; -} - -.c0.tooltipped-nw::after { - margin-right: -16px; -} - -.c0.tooltipped-s::after, -.c0.tooltipped-n::after { - -webkit-transform: translateX(50%); - -ms-transform: translateX(50%); - transform: translateX(50%); -} - -.c0.tooltipped-w::after { - right: 100%; - bottom: 50%; - margin-right: 6px; - -webkit-transform: translateY(50%); - -ms-transform: translateY(50%); - transform: translateY(50%); -} - -.c0.tooltipped-w::before { - top: 50%; - bottom: 50%; - left: -7px; - margin-top: -6px; - border-left-color: #24292f; -} - -.c0.tooltipped-e::after { - bottom: 50%; - left: 100%; - margin-left: 6px; - -webkit-transform: translateY(50%); - -ms-transform: translateY(50%); - transform: translateY(50%); -} - -.c0.tooltipped-e::before { - top: 50%; - right: -7px; - bottom: 50%; - margin-top: -6px; - border-right-color: #24292f; -} - -.c0.tooltipped-multiline::after { - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; - max-width: 250px; - word-wrap: break-word; - white-space: pre-line; - border-collapse: separate; -} - -.c0.tooltipped-multiline.tooltipped-s::after, -.c0.tooltipped-multiline.tooltipped-n::after { - right: auto; - left: 50%; - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); -} - -.c0.tooltipped-multiline.tooltipped-w::after, -.c0.tooltipped-multiline.tooltipped-e::after { - right: 100%; -} - -.c0.tooltipped-align-right-2::after { - right: 0; - margin-right: 0; -} - -.c0.tooltipped-align-right-2::before { - right: 15px; -} - -.c0.tooltipped-align-left-2::after { - left: 0; - margin-left: 0; -} - -.c0.tooltipped-align-left-2::before { - left: 10px; -} - - -`; diff --git a/src/index.ts b/src/index.ts index 335f542f443..cd1d3b4de92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -168,7 +168,7 @@ export type { } from './Timeline' export {default as Token, IssueLabelToken, AvatarToken} from './Token' export type {TokenProps} from './Token' -export {default as Tooltip} from './Tooltip' +export {Tooltip} from './Tooltip' export type {TooltipProps} from './Tooltip' export {default as Truncate} from './Truncate' export type {TruncateProps} from './Truncate'