diff --git a/packages/react/src/components/Tooltip/Tooltip.featureflag.stories.js b/packages/react/src/components/Tooltip/Tooltip.featureflag.stories.js index 3115f1c6a100..fd72bca46a10 100644 --- a/packages/react/src/components/Tooltip/Tooltip.featureflag.stories.js +++ b/packages/react/src/components/Tooltip/Tooltip.featureflag.stories.js @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2023 + * Copyright IBM Corp. 2016, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -71,4 +71,19 @@ FloatingStyles.argTypes = { type: 'select', }, }, + label: { + control: { + type: 'text', + }, + }, + description: { + control: { + type: 'text', + }, + }, + highContrast: { + table: { + disable: true, + }, + }, }; diff --git a/packages/react/src/components/Tooltip/Tooltip.stories.js b/packages/react/src/components/Tooltip/Tooltip.stories.js index 440fae90e59b..f810bb8ca16a 100644 --- a/packages/react/src/components/Tooltip/Tooltip.stories.js +++ b/packages/react/src/components/Tooltip/Tooltip.stories.js @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2023 + * Copyright IBM Corp. 2016, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -25,6 +25,45 @@ export default { page: mdx, }, }, + argTypes: { + align: { + options: [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-end', + 'left-start', + + 'right', + 'right-end', + 'right-start', + ], + control: { + type: 'select', + }, + }, + highContrast: { + table: { + disable: true, + }, + }, + label: { + control: { + type: 'text', + }, + }, + description: { + control: { + type: 'text', + }, + }, + }, decorators: [ (Story, context) => { if (context.name.toLowerCase().includes('auto align')) { @@ -52,54 +91,15 @@ export const Default = (args) => { ); }; -Default.argTypes = { - align: { - // TODO: - // 1. Should the deprecated options be deleted? - // 2. The list doesn't include all of the options available in the - // component. Is it supposed to? - options: [ - 'top', - 'top-left', - 'top-right', - - 'bottom', - 'bottom-left', - 'bottom-right', - - 'left', - 'left-bottom', - 'left-top', - - 'right', - 'right-bottom', - 'right-top', - ], - control: { - type: 'select', - }, - }, - label: { - control: { - type: 'text', - }, - }, - description: { - control: { - type: 'text', - }, - }, -}; - -export const Alignment = () => { +export const Alignment = (args) => { return ( - + ); }; -export const ExperimentalAutoAlign = () => { +export const ExperimentalAutoAlign = (args) => { const ref = useRef(); const tooltipLabel = 'Scroll the container up, down, left or right to observe how the tooltip will automatically change its position in attempt to stay within the viewport. This works on initial render in addition to on scroll.'; @@ -115,7 +115,7 @@ export const ExperimentalAutoAlign = () => { top: '2500px', left: '2500px', }}> - + @@ -125,9 +125,14 @@ export const ExperimentalAutoAlign = () => { // Note: autoAlign is used here only to make tooltips visible in StackBlitz, // autoAlign is in preview and not part of the actual implementation. -export const Duration = () => { +export const Duration = (args) => { return ( - + ); diff --git a/packages/react/src/components/Tooltip/Tooltip.tsx b/packages/react/src/components/Tooltip/Tooltip.tsx index 6859b4669654..ebc7b51e248c 100644 --- a/packages/react/src/components/Tooltip/Tooltip.tsx +++ b/packages/react/src/components/Tooltip/Tooltip.tsx @@ -84,7 +84,7 @@ interface TooltipBaseProps { /** * Render the component using the high-contrast theme */ - highContrast?: boolean; + highContrast?: boolean; // TODO: remove in v12, highContrast should not be configurable /** * Provide the label to be rendered inside of the Tooltip. The label will use @@ -126,7 +126,7 @@ const Tooltip: TooltipComponent = React.forwardRef( defaultOpen = false, closeOnActivation = false, dropShadow = false, - highContrast = true, + highContrast = true, // TODO: remove in v12, highContrast should not be configurable ...rest }: TooltipProps, ref?: PolymorphicRef @@ -289,7 +289,7 @@ const Tooltip: TooltipComponent = React.forwardRef( align={align} className={cx(`${prefix}--tooltip`, customClassName)} dropShadow={dropShadow} - highContrast={highContrast} + highContrast={highContrast} // TODO: v12 hard-set highContrast to true onKeyDown={onKeyDown} onMouseLeave={onMouseLeave} open={open}> @@ -391,7 +391,7 @@ const Tooltip: TooltipComponent = React.forwardRef( /** * Render the component using the high-contrast theme */ - highContrast: PropTypes.bool, + highContrast: PropTypes.bool, // TODO: remove in v12, highContrast should not be configurable /** * Provide the label to be rendered inside of the Tooltip. The label will use diff --git a/packages/web-components/src/components/popover/popover.scss b/packages/web-components/src/components/popover/popover.scss index c360fc8a1888..f83bde291216 100644 --- a/packages/web-components/src/components/popover/popover.scss +++ b/packages/web-components/src/components/popover/popover.scss @@ -283,13 +283,21 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-slug[alignment='bottom-start']:not([autoalign])) { .#{$prefix}--popover-content { inset-block-end: 0; - inset-inline-start: 0; + inset-inline-start: calc(50% - $popover-offset); transform: translate( calc(-1 * $popover-offset), calc(100% + $popover-offset) ); } } +:host(#{$prefix}-popover-content[tabTip][align='bottom-left']:not([autoalign])), +:host( + #{$prefix}-popover-content[tabTip][align='bottom-start']:not([autoalign]) + ) { + .#{$prefix}--popover-content { + inset-inline-start: 0; + } +} // rtl :host( @@ -311,11 +319,26 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-ai-label:dir(rtl)[alignment='bottom-start']:not([autoalign])), :host(#{$prefix}-slug:dir(rtl)[alignment='bottom-start']:not([autoalign])) { .#{$prefix}--popover-content { - inset-inline-end: 0; + inset-inline-end: calc(50% - $popover-offset); inset-inline-start: initial; } } +:host( + #{$prefix}-popover-content:dir(rtl)[tabTip][align='bottom-left']:not( + [autoalign] + ) + ), +:host( + #{$prefix}-popover-content:dir(rtl)[tabTip][align='bottom-start']:not( + [autoalign] + ) + ) { + .#{$prefix}--popover-content { + inset-inline-end: 0; + } +} + :host(#{$prefix}-tooltip-content[align='bottom-right']:not([autoalign])), :host(#{$prefix}-popover-content[align='bottom-right']:not([autoalign])), :host(#{$prefix}-toggletip[alignment='bottom-right']:not([autoalign])), @@ -328,10 +351,18 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-slug[alignment='bottom-end']:not([autoalign])) { .#{$prefix}--popover-content { inset-block-end: 0; - inset-inline-end: 0; + inset-inline-end: calc(50% - $popover-offset); transform: translate($popover-offset, calc(100% + $popover-offset)); } } +:host( + #{$prefix}-popover-content[tabTip][align='bottom-right']:not([autoalign]) + ), +:host(#{$prefix}-popover-content[tabTip][align='bottom-end']:not([autoalign])) { + .#{$prefix}--popover-content { + inset-inline-end: 0; + } +} // rtl :host( @@ -348,6 +379,21 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-toggletip:dir(rtl)[alignment='bottom-end']:not([autoalign])), :host(#{$prefix}-ai-label:dir(rtl)[alignment='bottom-end']:not([autoalign])), :host(#{$prefix}-slug:dir(rtl)[alignment='bottom-end']:not([autoalign])) { + .#{$prefix}--popover-content { + inset-inline-start: calc(50% - $popover-offset); + } +} + +:host( + #{$prefix}-popover-content:dir(rtl)[tabTip][align='bottom-right']:not( + [autoalign] + ) + ), +:host( + #{$prefix}-popover-content:dir(rtl)[tabTip][align='bottom-end']:not( + [autoalign] + ) + ) { .#{$prefix}--popover-content { inset-inline-start: 0; } @@ -697,7 +743,7 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-slug[alignment='top-start']:not([autoalign])) { .#{$prefix}--popover-content { inset-block-start: 0; - inset-inline-start: 0; + inset-inline-start: calc(50% - $popover-offset); transform: translate( calc(-1 * $popover-offset), calc(-100% - $popover-offset) @@ -717,7 +763,7 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-ai-label:dir(rtl)[alignment='top-start']:not([autoalign])), :host(#{$prefix}-slug[alignment='top-start']:not([autoalign])) { .#{$prefix}--popover-content { - inset-inline-end: 0; + inset-inline-end: calc(50% - $popover-offset); inset-inline-start: initial; } } @@ -734,7 +780,7 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-slug[alignment='top-end']:not([autoalign])) { .#{$prefix}--popover-content { inset-block-start: 0; - inset-inline-end: 0; + inset-inline-end: calc(50% - $popover-offset); transform: translate($popover-offset, calc(-100% - $popover-offset)); } } @@ -751,7 +797,7 @@ $popover-border-color: custom-property.get-var( :host(#{$prefix}-ai-label:dir(rtl)[alignment='top-end']:not([autoalign])), :host(#{$prefix}-slug[alignment='top-end']:not([autoalign])) { .#{$prefix}--popover-content { - inset-inline-start: 0; + inset-inline-start: calc(50% - $popover-offset); } } @@ -780,7 +826,8 @@ $popover-border-color: custom-property.get-var( //----------------------------------------------------------------------------- // autoalign caret -:host(#{$prefix}-popover-content[open][caret][autoalign]) { +:host(#{$prefix}-popover-content[open][caret][autoalign]), +:host(#{$prefix}-tooltip-content[open][caret][autoalign]) { .#{$prefix}--popover-caret { &::before { block-size: 8px; @@ -809,8 +856,8 @@ $popover-border-color: custom-property.get-var( .#{$prefix}--popover-content[align^='bottom'] > .#{$prefix}--popover-caret { &::after { - inset-block-end: -1px; - inset-inline-start: 0.5px; + inset-block-start: 1px; + inset-inline-start: 1px; } } @@ -823,7 +870,7 @@ $popover-border-color: custom-property.get-var( .#{$prefix}--popover-content[align^='right'] > .#{$prefix}--popover-caret { &::after { inset-block-start: -1px; - inset-inline-end: 0.5px; + inset-inline-start: 1px; } } } diff --git a/packages/web-components/src/components/tooltip/tooltip-story.scss b/packages/web-components/src/components/tooltip/tooltip-story.scss index 0196febc8773..f1e9694c4c44 100644 --- a/packages/web-components/src/components/tooltip/tooltip-story.scss +++ b/packages/web-components/src/components/tooltip/tooltip-story.scss @@ -1,5 +1,5 @@ // -// Copyright IBM Corp. 2019, 2024 +// Copyright IBM Corp. 2019, 2025 // // This source code is licensed under the Apache-2.0 license found in the // LICENSE file in the root directory of this source tree. @@ -10,6 +10,7 @@ @use '@carbon/styles/scss/spacing' as *; @use '@carbon/styles/scss/theme'; @use '@carbon/styles/scss/type'; +@use '@carbon/styles/scss/utilities/focus-outline'; // This is a utility class to make sure that tooltip stories have a minimum // height when used in MDX docs @@ -27,9 +28,12 @@ display: flex; align-items: center; justify-content: center; - border: 1px solid theme.$border-subtle; - block-size: $spacing-07; - inline-size: $spacing-07; + block-size: $spacing-05; + inline-size: $spacing-05; +} + +.sb-tooltip-trigger:focus { + @include focus-outline.focus-outline; } .sb-tooltip-trigger svg { diff --git a/packages/web-components/src/components/tooltip/tooltip.stories.ts b/packages/web-components/src/components/tooltip/tooltip.stories.ts index f873393c6110..45a8f3cf4124 100644 --- a/packages/web-components/src/components/tooltip/tooltip.stories.ts +++ b/packages/web-components/src/components/tooltip/tooltip.stories.ts @@ -14,30 +14,16 @@ import './index'; import { POPOVER_ALIGNMENT } from '../popover/defs'; import { iconLoader } from '../../globals/internal/icon-loader'; import styles from './tooltip-story.scss?lit'; -import Information16 from '@carbon/icons/es/information/16.js'; - -const tooltipAlignments = { - [`top`]: POPOVER_ALIGNMENT.TOP, - [`top-left`]: POPOVER_ALIGNMENT.TOP_LEFT, - [`top-right`]: POPOVER_ALIGNMENT.TOP_RIGHT, - [`bottom`]: POPOVER_ALIGNMENT.BOTTOM, - [`bottom-left`]: POPOVER_ALIGNMENT.BOTTOM_LEFT, - [`bottom-right`]: POPOVER_ALIGNMENT.BOTTOM_RIGHT, - [`left`]: POPOVER_ALIGNMENT.LEFT, - [`left-bottom`]: POPOVER_ALIGNMENT.LEFT_BOTTOM, - [`left-top`]: POPOVER_ALIGNMENT.LEFT_TOP, - [`right`]: POPOVER_ALIGNMENT.RIGHT, - [`right-bottom`]: POPOVER_ALIGNMENT.RIGHT_BOTTOM, - [`right-top`]: POPOVER_ALIGNMENT.RIGHT_TOP, -}; +import OverflowMenuVertical16 from '@carbon/icons/es/overflow-menu--vertical/16.js'; +import '../button'; const defaultArgs = { - align: POPOVER_ALIGNMENT.BOTTOM, + align: POPOVER_ALIGNMENT.TOP, closeOnActivation: false, defaultOpen: false, + dropShadow: false, enterDelayMs: 100, - label: - 'Occassionally, services are updated in a specified time window to ensure no down time for customers.', + label: 'Options', leaveDelayMs: 300, }; @@ -45,7 +31,20 @@ const controls = { align: { control: 'select', description: 'Specify how the trigger should align with the tooltip', - options: tooltipAlignments, + options: [ + POPOVER_ALIGNMENT.TOP, + POPOVER_ALIGNMENT.TOP_START, + POPOVER_ALIGNMENT.TOP_END, + POPOVER_ALIGNMENT.BOTTOM, + POPOVER_ALIGNMENT.BOTTOM_START, + POPOVER_ALIGNMENT.BOTTOM_END, + POPOVER_ALIGNMENT.LEFT, + POPOVER_ALIGNMENT.LEFT_END, + POPOVER_ALIGNMENT.LEFT_START, + POPOVER_ALIGNMENT.RIGHT, + POPOVER_ALIGNMENT.RIGHT_END, + POPOVER_ALIGNMENT.RIGHT_START, + ], }, closeOnActivation: { control: 'boolean', @@ -57,6 +56,10 @@ const controls = { description: 'Specify whether the tooltip should be open when it first renders', }, + dropShadow: { + control: 'boolean', + description: 'Specify whether a drop shadow should be rendered', + }, enterDelayMs: { control: 'number', description: @@ -80,6 +83,7 @@ export const Default = { align, closeOnActivation, defaultOpen, + dropShadow, enterDelayMs, label, leaveDelayMs, @@ -87,6 +91,7 @@ export const Default = { @@ -94,7 +99,7 @@ export const Default = { class="sb-tooltip-trigger" role="button" aria-labelledby="content"> - ${iconLoader(Information16)} + ${iconLoader(OverflowMenuVertical16)} ${label} @@ -102,35 +107,117 @@ export const Default = { }; export const Alignment = { - render: () => html` - - - - Tooltip alignment - + argTypes: controls, + args: { + ...defaultArgs, + align: POPOVER_ALIGNMENT.BOTTOM_START, + label: 'Tooltip alignment', + }, + render: ({ + align, + closeOnActivation, + defaultOpen, + dropShadow, + label, + enterDelayMs, + leaveDelayMs, + }) => html` + + + This button has a tooltip + + ${label} `, }; export const Duration = { - render: () => html` - - - Label one + argTypes: controls, + args: { + ...defaultArgs, + enterDelayMs: 0, + leaveDelayMs: 300, + label: 'Label one', + }, + render: ({ + align, + closeOnActivation, + defaultOpen, + dropShadow, + label, + enterDelayMs, + leaveDelayMs, + }) => html` + + + This button has a tooltip + + ${label} `, }; +export const ExperimentalAutoAlign = { + argTypes: controls, + parameters: { + controls: { + exclude: ['align'], + }, + }, + args: { + ...defaultArgs, + label: + 'Scroll the container up, down, left or right to observe how the tooltip will automatically change its position in attempt to stay within the viewport. This works on initial render in addition to on scroll.', + }, + render: ({ + closeOnActivation, + defaultOpen, + dropShadow, + label, + enterDelayMs, + leaveDelayMs, + }) => { + requestAnimationFrame(() => { + document.querySelector('cds-tooltip')?.scrollIntoView({ + block: 'center', + inline: 'center', + }); + }); + return html` +
+
+ + + This button has a tooltip + + ${label} + +
+
+ `; + }, +}; + const meta = { title: 'Components/Tooltip', decorators: [ diff --git a/packages/web-components/src/components/tooltip/tooltip.ts b/packages/web-components/src/components/tooltip/tooltip.ts index 841445864973..b0f9023e5233 100644 --- a/packages/web-components/src/components/tooltip/tooltip.ts +++ b/packages/web-components/src/components/tooltip/tooltip.ts @@ -232,7 +232,7 @@ class CDSTooltip extends HostListenerMixin(CDSPopover) { : toolTipContent?.removeAttribute('open'); } - ['align', 'caret', 'autoalign'].forEach((name) => { + ['align', 'caret', 'autoalign', 'dropShadow'].forEach((name) => { if (changedProperties.has(name)) { const { [name as keyof CDSTooltip]: value } = this; (toolTipContent as CDSTooltipContent)[name] = value;