diff --git a/Gruntfile.js b/Gruntfile.js index ab56358b8f60d..61f18481e23a8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -585,6 +585,7 @@ module.exports = function(grunt) { [ WORKING_DIR + 'wp-includes/js/wp-ajax-response.js' ]: [ './src/js/_enqueues/lib/ajax-response.js' ], [ WORKING_DIR + 'wp-includes/js/wp-api.js' ]: [ './src/js/_enqueues/wp/api.js' ], [ WORKING_DIR + 'wp-includes/js/wp-auth-check.js' ]: [ './src/js/_enqueues/lib/auth-check.js' ], + [ WORKING_DIR + 'wp-includes/js/wp-tooltip.js' ]: [ './src/js/_enqueues/wp/wp-tooltip.js' ], [ WORKING_DIR + 'wp-includes/js/wp-backbone.js' ]: [ './src/js/_enqueues/wp/backbone.js' ], [ WORKING_DIR + 'wp-includes/js/wp-custom-header.js' ]: [ './src/js/_enqueues/wp/custom-header.js' ], [ WORKING_DIR + 'wp-includes/js/wp-embed-template.js' ]: [ './src/js/_enqueues/lib/embed-template.js' ], diff --git a/src/js/_enqueues/wp/wp-tooltip.js b/src/js/_enqueues/wp/wp-tooltip.js new file mode 100644 index 0000000000000..35a320183240d --- /dev/null +++ b/src/js/_enqueues/wp/wp-tooltip.js @@ -0,0 +1,44 @@ +/** + * @output wp-admin/js/wp-tooltip.js + */ + +/** + * Add focus and hover support for the 'tooltip' type in `wp_tooltip()`. + * This script can be made obsolete when support is available for Interest Invokers. + */ +(() => { + + const popovers = document.querySelectorAll( '.wp-is-tooltip' ); + let openTimeout; + + popovers.forEach( function( popover ) { + let trigger = popover.querySelector( 'button.wp-tooltip__toggle' ); + let panel = popover.querySelector( 'span.wp-tooltip__bubble' ); + + // Show Tooltip Function (with delay to prevent flickering). + const showTooltip = () => { + clearTimeout( openTimeout ); + openTimeout = setTimeout( () => { + // Only show if it's not already open. + if ( ! panel.matches( ':popover-open' ) ) { + // pass the triggering element so implicit position anchors work. + panel.showPopover( { source: trigger } ); + } + }, 300 ); + }; + // Hide Tooltip Function. + const hideTooltip = () => { + clearTimeout( openTimeout ); + if ( panel.matches( ':popover-open' ) ) { + panel.hidePopover(); + } + }; + + // Bind Hover and Focus Events. + trigger.addEventListener( 'mouseenter', showTooltip ); + trigger.addEventListener( 'focus', showTooltip ); + + trigger.addEventListener( 'mouseleave', hideTooltip ); + trigger.addEventListener( 'blur', hideTooltip ); + }); +})(); diff --git a/src/wp-admin/css/wp-tooltip.css b/src/wp-admin/css/wp-tooltip.css new file mode 100644 index 0000000000000..04ac5cd131ea9 --- /dev/null +++ b/src/wp-admin/css/wp-tooltip.css @@ -0,0 +1,122 @@ +/* Accessible tooltip component. Markup from wp_get_tooltip(). */ + +.wp-tooltip { + display: inline-flex; + align-items: center; + vertical-align: middle; +} + +/* Toggle and close buttons. */ +.wp-tooltip .wp-tooltip__toggle, +.wp-tooltip .wp-tooltip__close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: 0; + border-radius: 2px; + background: none; + color: #50575e; + cursor: pointer; +} + +.wp-tooltip .wp-tooltip__toggle:hover, +.wp-tooltip .wp-tooltip__toggle:focus, +.wp-tooltip .wp-tooltip__close:hover, +.wp-tooltip .wp-tooltip__close:focus { + color: var(--wp-admin-theme-color, #3858e9); +} + +.wp-tooltip .wp-tooltip__toggle:focus, +.wp-tooltip .wp-tooltip__close:focus { + outline: 2px solid transparent; + box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color, #3858e9); +} + +.wp-tooltip .wp-tooltip__toggle .dashicons { + width: 20px; + height: 20px; + font-size: 20px; +} + +.wp-tooltip .wp-tooltip__close .dashicons { + width: 18px; + height: 18px; + font-size: 18px; +} + +/* Bubble. No position here, so it falls back to the UA's centered popover without anchoring. */ +.wp-tooltip .wp-tooltip__bubble { + max-width: min(280px, calc(100vw - 32px)); + margin: auto; + padding: 12px 16px; + overflow: visible; + border: 1px solid #c3c4c7; + border-radius: 4px; + background: #fff; + color: #1d2327; + font-size: 13px; + font-weight: 400; + line-height: 1.5; + text-align: left; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); +} + +.wp-tooltip.wp-is-tooltip .wp-tooltip__bubble { + background: #000; + color: #f0f0f0; + text-align: center; + padding: 4px 8px; + line-height: 1.4; +} + +.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__text { + display: block; + padding-inline-end: 24px; +} + +.wp-tooltip .wp-tooltip__close { + position: absolute; + top: 6px; + inset-inline-end: 6px; +} + +/* Anchor the bubble above its toggle, with a downward arrow. */ +@supports (anchor-name: --a) { + .wp-tooltip .wp-tooltip__bubble { + position-area: top; + margin: 0 0 8px; + position-try-fallbacks: flip-block, flip-inline; + } + + .wp-tooltip.wp-is-tooltip .wp-tooltip__bubble { + margin: 0; + } + + /* Arrow: gray border (::before) under a white fill (::after). */ + .wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::before, + .wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + width: 0; + height: 0; + border: 8px solid transparent; + border-bottom: 0; + } + + .wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::before { + margin-left: -8px; + border-top-color: #c3c4c7; + } + + .wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::after { + margin-left: -7px; + border-width: 7px; + border-bottom: 0; + border-top-color: #fff; + } +} diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index 121cc4a4c0c48..36f3db3beba10 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -369,6 +369,168 @@ function get_search_form( $args = array() ) { } } + +/** + * Retrieves the markup for an accessible tooltip. + * + * Returns a button with an accessible name popover. + * + * @since 7.1.0 + * + * @param string $content Plain-text tooltip content. An empty value returns an empty string. + * @param array $args { + * Optional. Arguments for building the tooltip. + * + * @type string $id Unique ID for the popover element. Default is a + * generated unique ID. + * @type string $label Not used for tooltips. + * @type string $close_label Not used for tooltips. + * @type string $icon Dashicons icon class for the toggle button. + * Default 'dashicons-editor-help'. Should match the control's + * visible label. + * @type string $class Additional class(es) for the wrapping element. + * Default empty. + * } + * @return string Tooltip HTML markup, or an empty string when no content is provided. + */ +function wp_get_tooltip( $content, $args = array() ) { + $args['type'] = 'tooltip'; + return wp_get_tooltip_helper( $content, $args ); +} + +/** + * Retrieves the markup for an accessible tooltip or toggletip. + * + * Returns a button and either a hover/focus triggered tooltip popover or an action + * triggered toggle tip. Enqueue the `wp-tooltip` style and script where it is used. + * Tooltips are used to show the accessible name of a control. + * Toggletips are be used for longer supporting text explaining context. + * + * @since 7.1.0 + * + * @param string $content Plain-text tooltip content. An empty value returns an empty string. + * @param array $args { + * Optional. Arguments for building the tooltip. + * + * @type string $id Unique ID for the popover element. Default is a + * generated unique ID. + * @type string $label Accessible label for the toggle button. + * Default 'Help', matching the default icon. + * Ignored for tooltips. + * @type string $close_label Accessible label for the close button. Default 'Close'. + * @type string $icon Dashicons icon class for the toggle button. + * Default 'dashicons-editor-help'. Should match the control's + * visible label. + * @type string $class Additional class(es) for the wrapping element. + * Default empty. + * } + * @return string Tooltip HTML markup, or an empty string when no content is provided. + */ +function wp_get_toggletip( $content, $args ) { + $args['type'] = 'toggletip'; + return wp_get_tooltip_helper( $content, $args ); +} +/** + * Retrieves the markup for an accessible tooltip or toggletip. + * + * Returns a button and either a hover/focus triggered tooltip popover or an action + * triggered toggle tip. Enqueue the `wp-tooltip` style and script where it is used. + * Tooltips are used to show the accessible name of a control. + * Toggletips are be used for longer supporting text explaining context. + * + * @since 7.1.0 + * + * @param string $content Plain-text tooltip content. An empty value returns an empty string. + * @param array $args { + * Optional. Arguments for building the tooltip. + * + * @type string $id Unique ID for the popover element. Default is a + * generated unique ID. + * @type string $label Accessible label for the toggle button. + * Default 'Help', matching the default icon. + * Ignored for tooltips. + * @type string $close_label Accessible label for the close button. Default 'Close'. + * @type string $icon Dashicons icon class for the toggle button. + * Default 'dashicons-editor-help'. Should match the control's + * visible label. + * @type string $class Additional class(es) for the wrapping element. + * Default empty. + * @type string $type Type of tooltip: either `tooltip` or `toggletip`. + * Default 'tooltip'. + * } + * @return string Tooltip HTML markup, or an empty string when no content is provided. + */ +function wp_get_tooltip_helper( $content, $args = array() ) { + $content = trim( (string) $content ); + + if ( '' === $content ) { + return ''; + } + + $defaults = array( + 'id' => '', + 'label' => __( 'Help' ), + 'close_label' => __( 'Close' ), + 'icon' => 'dashicons-editor-help', + 'class' => '', + 'type' => 'tooltip', + 'attributes' => array(), + ); + + $args = wp_parse_args( $args, $defaults ); + + $id = '' !== $args['id'] ? $args['id'] : wp_unique_id( 'wp-tooltip-' ); + + $classes = ( 'tooltip' === $args['type'] ) ? 'wp-tooltip wp-is-tooltip' : 'wp-tooltip wp-is-toggletip'; + if ( '' !== $args['class'] ) { + $classes .= ' ' . $args['class']; + } + + $icon = '' !== $args['icon'] ? ' ' . $args['icon'] : ''; + + if ( 'tooltip' === $args['type'] ) { + // Tooltips are only used to visually display labels. + $label = wp_strip_all_tags( $content, true ); + $markup = sprintf( + '