Skip to content

Commit 98b6b9d

Browse files
committed
Administration: Add function for tooltips and toggle tips.
Add functions `wp_get_tooltip()` and `wp_get_toggletip()` to support adding these popover interfaces in the core admin. `wp_get_tooltip()` is used as mechanism to expose accessible names when a control has focus or hover, and `wp_get_toggletip()` implements a popover disclosure dialog to provide extended help information. Functions generate accessible markup, written to avoid excess verbosity for screen readers and apply best practices for voice command users as much as possible for icon-only controls. Add an initial implementation of a toggle tip to explain the 'remember me' option on the login screen. Developed in WordPress#12212 Props joedolson, oglekler, afercia, davidbaumwald, rajinsharwar, mukesh27, swissspidy, sirlouen, rutviksavsani, wildworks, ayazahmed12, generosus, sergeybiryukov. Fixes #51006, #55343. git-svn-id: https://develop.svn.wordpress.org/trunk@62741 602fd350-edb4-49c9-b593-d223f7449a82
1 parent a833ca0 commit 98b6b9d

7 files changed

Lines changed: 479 additions & 2 deletions

File tree

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ module.exports = function(grunt) {
585585
[ WORKING_DIR + 'wp-includes/js/wp-ajax-response.js' ]: [ './src/js/_enqueues/lib/ajax-response.js' ],
586586
[ WORKING_DIR + 'wp-includes/js/wp-api.js' ]: [ './src/js/_enqueues/wp/api.js' ],
587587
[ WORKING_DIR + 'wp-includes/js/wp-auth-check.js' ]: [ './src/js/_enqueues/lib/auth-check.js' ],
588+
[ WORKING_DIR + 'wp-includes/js/wp-tooltip.js' ]: [ './src/js/_enqueues/wp/wp-tooltip.js' ],
588589
[ WORKING_DIR + 'wp-includes/js/wp-backbone.js' ]: [ './src/js/_enqueues/wp/backbone.js' ],
589590
[ WORKING_DIR + 'wp-includes/js/wp-custom-header.js' ]: [ './src/js/_enqueues/wp/custom-header.js' ],
590591
[ WORKING_DIR + 'wp-includes/js/wp-embed-template.js' ]: [ './src/js/_enqueues/lib/embed-template.js' ],

src/js/_enqueues/wp/wp-tooltip.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @output wp-admin/js/wp-tooltip.js
3+
*/
4+
5+
/**
6+
* Add focus and hover support for the 'tooltip' type in `wp_tooltip()`.
7+
* This script can be made obsolete when support is available for Interest Invokers.
8+
*/
9+
(() => {
10+
11+
const popovers = document.querySelectorAll( '.wp-is-tooltip' );
12+
let openTimeout;
13+
14+
popovers.forEach( function( popover ) {
15+
let trigger = popover.querySelector( 'button.wp-tooltip__toggle' );
16+
let panel = popover.querySelector( 'span.wp-tooltip__bubble' );
17+
18+
// Show Tooltip Function (with delay to prevent flickering).
19+
const showTooltip = () => {
20+
clearTimeout( openTimeout );
21+
openTimeout = setTimeout( () => {
22+
// Only show if it's not already open.
23+
if ( ! panel.matches( ':popover-open' ) ) {
24+
// pass the triggering element so implicit position anchors work.
25+
panel.showPopover( { source: trigger } );
26+
}
27+
}, 300 );
28+
};
29+
// Hide Tooltip Function.
30+
const hideTooltip = () => {
31+
clearTimeout( openTimeout );
32+
if ( panel.matches( ':popover-open' ) ) {
33+
panel.hidePopover();
34+
}
35+
};
36+
37+
// Bind Hover and Focus Events.
38+
trigger.addEventListener( 'mouseenter', showTooltip );
39+
trigger.addEventListener( 'focus', showTooltip );
40+
41+
trigger.addEventListener( 'mouseleave', hideTooltip );
42+
trigger.addEventListener( 'blur', hideTooltip );
43+
});
44+
})();

src/wp-admin/css/wp-tooltip.css

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/* Accessible tooltip component. Markup from wp_get_tooltip(). */
2+
3+
.wp-tooltip {
4+
display: inline-flex;
5+
align-items: center;
6+
vertical-align: middle;
7+
}
8+
9+
/* Toggle and close buttons. */
10+
.wp-tooltip .wp-tooltip__toggle,
11+
.wp-tooltip .wp-tooltip__close {
12+
display: inline-flex;
13+
align-items: center;
14+
justify-content: center;
15+
width: 24px;
16+
height: 24px;
17+
padding: 0;
18+
border: 0;
19+
border-radius: 2px;
20+
background: none;
21+
color: #50575e;
22+
cursor: pointer;
23+
}
24+
25+
.wp-tooltip .wp-tooltip__toggle:hover,
26+
.wp-tooltip .wp-tooltip__toggle:focus,
27+
.wp-tooltip .wp-tooltip__close:hover,
28+
.wp-tooltip .wp-tooltip__close:focus {
29+
color: var(--wp-admin-theme-color, #3858e9);
30+
}
31+
32+
.wp-tooltip .wp-tooltip__toggle:focus,
33+
.wp-tooltip .wp-tooltip__close:focus {
34+
outline: 2px solid transparent;
35+
box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color, #3858e9);
36+
}
37+
38+
.wp-tooltip .wp-tooltip__toggle .dashicons {
39+
width: 20px;
40+
height: 20px;
41+
font-size: 20px;
42+
}
43+
44+
.wp-tooltip .wp-tooltip__close .dashicons {
45+
width: 18px;
46+
height: 18px;
47+
font-size: 18px;
48+
}
49+
50+
/* Bubble. No position here, so it falls back to the UA's centered popover without anchoring. */
51+
.wp-tooltip .wp-tooltip__bubble {
52+
max-width: min(280px, calc(100vw - 32px));
53+
margin: auto;
54+
padding: 12px 16px;
55+
overflow: visible;
56+
border: 1px solid #c3c4c7;
57+
border-radius: 4px;
58+
background: #fff;
59+
color: #1d2327;
60+
font-size: 13px;
61+
font-weight: 400;
62+
line-height: 1.5;
63+
text-align: left;
64+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
65+
}
66+
67+
.wp-tooltip.wp-is-tooltip .wp-tooltip__bubble {
68+
background: #000;
69+
color: #f0f0f0;
70+
text-align: center;
71+
padding: 4px 8px;
72+
line-height: 1.4;
73+
}
74+
75+
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__text {
76+
display: block;
77+
padding-inline-end: 24px;
78+
}
79+
80+
.wp-tooltip .wp-tooltip__close {
81+
position: absolute;
82+
top: 6px;
83+
inset-inline-end: 6px;
84+
}
85+
86+
/* Anchor the bubble above its toggle, with a downward arrow. */
87+
@supports (anchor-name: --a) {
88+
.wp-tooltip .wp-tooltip__bubble {
89+
position-area: top;
90+
margin: 0 0 8px;
91+
position-try-fallbacks: flip-block, flip-inline;
92+
}
93+
94+
.wp-tooltip.wp-is-tooltip .wp-tooltip__bubble {
95+
margin: 0;
96+
}
97+
98+
/* Arrow: gray border (::before) under a white fill (::after). */
99+
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::before,
100+
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::after {
101+
content: "";
102+
position: absolute;
103+
top: 100%;
104+
left: 50%;
105+
width: 0;
106+
height: 0;
107+
border: 8px solid transparent;
108+
border-bottom: 0;
109+
}
110+
111+
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::before {
112+
margin-left: -8px;
113+
border-top-color: #c3c4c7;
114+
}
115+
116+
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::after {
117+
margin-left: -7px;
118+
border-width: 7px;
119+
border-bottom: 0;
120+
border-top-color: #fff;
121+
}
122+
}

src/wp-includes/general-template.php

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,168 @@ function get_search_form( $args = array() ) {
369369
}
370370
}
371371

372+
373+
/**
374+
* Retrieves the markup for an accessible tooltip.
375+
*
376+
* Returns a button with an accessible name popover.
377+
*
378+
* @since 7.1.0
379+
*
380+
* @param string $content Plain-text tooltip content. An empty value returns an empty string.
381+
* @param array $args {
382+
* Optional. Arguments for building the tooltip.
383+
*
384+
* @type string $id Unique ID for the popover element. Default is a
385+
* generated unique ID.
386+
* @type string $label Not used for tooltips.
387+
* @type string $close_label Not used for tooltips.
388+
* @type string $icon Dashicons icon class for the toggle button.
389+
* Default 'dashicons-editor-help'. Should match the control's
390+
* visible label.
391+
* @type string $class Additional class(es) for the wrapping element.
392+
* Default empty.
393+
* }
394+
* @return string Tooltip HTML markup, or an empty string when no content is provided.
395+
*/
396+
function wp_get_tooltip( $content, $args = array() ) {
397+
$args['type'] = 'tooltip';
398+
return wp_get_tooltip_helper( $content, $args );
399+
}
400+
401+
/**
402+
* Retrieves the markup for an accessible tooltip or toggletip.
403+
*
404+
* Returns a button and either a hover/focus triggered tooltip popover or an action
405+
* triggered toggle tip. Enqueue the `wp-tooltip` style and script where it is used.
406+
* Tooltips are used to show the accessible name of a control.
407+
* Toggletips are be used for longer supporting text explaining context.
408+
*
409+
* @since 7.1.0
410+
*
411+
* @param string $content Plain-text tooltip content. An empty value returns an empty string.
412+
* @param array $args {
413+
* Optional. Arguments for building the tooltip.
414+
*
415+
* @type string $id Unique ID for the popover element. Default is a
416+
* generated unique ID.
417+
* @type string $label Accessible label for the toggle button.
418+
* Default 'Help', matching the default icon.
419+
* Ignored for tooltips.
420+
* @type string $close_label Accessible label for the close button. Default 'Close'.
421+
* @type string $icon Dashicons icon class for the toggle button.
422+
* Default 'dashicons-editor-help'. Should match the control's
423+
* visible label.
424+
* @type string $class Additional class(es) for the wrapping element.
425+
* Default empty.
426+
* }
427+
* @return string Tooltip HTML markup, or an empty string when no content is provided.
428+
*/
429+
function wp_get_toggletip( $content, $args ) {
430+
$args['type'] = 'toggletip';
431+
return wp_get_tooltip_helper( $content, $args );
432+
}
433+
/**
434+
* Retrieves the markup for an accessible tooltip or toggletip.
435+
*
436+
* Returns a button and either a hover/focus triggered tooltip popover or an action
437+
* triggered toggle tip. Enqueue the `wp-tooltip` style and script where it is used.
438+
* Tooltips are used to show the accessible name of a control.
439+
* Toggletips are be used for longer supporting text explaining context.
440+
*
441+
* @since 7.1.0
442+
*
443+
* @param string $content Plain-text tooltip content. An empty value returns an empty string.
444+
* @param array $args {
445+
* Optional. Arguments for building the tooltip.
446+
*
447+
* @type string $id Unique ID for the popover element. Default is a
448+
* generated unique ID.
449+
* @type string $label Accessible label for the toggle button.
450+
* Default 'Help', matching the default icon.
451+
* Ignored for tooltips.
452+
* @type string $close_label Accessible label for the close button. Default 'Close'.
453+
* @type string $icon Dashicons icon class for the toggle button.
454+
* Default 'dashicons-editor-help'. Should match the control's
455+
* visible label.
456+
* @type string $class Additional class(es) for the wrapping element.
457+
* Default empty.
458+
* @type string $type Type of tooltip: either `tooltip` or `toggletip`.
459+
* Default 'tooltip'.
460+
* }
461+
* @return string Tooltip HTML markup, or an empty string when no content is provided.
462+
*/
463+
function wp_get_tooltip_helper( $content, $args = array() ) {
464+
$content = trim( (string) $content );
465+
466+
if ( '' === $content ) {
467+
return '';
468+
}
469+
470+
$defaults = array(
471+
'id' => '',
472+
'label' => __( 'Help' ),
473+
'close_label' => __( 'Close' ),
474+
'icon' => 'dashicons-editor-help',
475+
'class' => '',
476+
'type' => 'tooltip',
477+
'attributes' => array(),
478+
);
479+
480+
$args = wp_parse_args( $args, $defaults );
481+
482+
$id = '' !== $args['id'] ? $args['id'] : wp_unique_id( 'wp-tooltip-' );
483+
484+
$classes = ( 'tooltip' === $args['type'] ) ? 'wp-tooltip wp-is-tooltip' : 'wp-tooltip wp-is-toggletip';
485+
if ( '' !== $args['class'] ) {
486+
$classes .= ' ' . $args['class'];
487+
}
488+
489+
$icon = '' !== $args['icon'] ? ' ' . $args['icon'] : '';
490+
491+
if ( 'tooltip' === $args['type'] ) {
492+
// Tooltips are only used to visually display labels.
493+
$label = wp_strip_all_tags( $content, true );
494+
$markup = sprintf(
495+
'<div class="%1$s">' .
496+
'<button type="button" class="wp-tooltip__toggle" popovertarget="%2$s" aria-label="%3$s">' .
497+
'<span class="dashicons%4$s" aria-hidden="true"></span>' .
498+
'</button>' .
499+
'<span popover="hint" id="%2$s" class="wp-tooltip__bubble" role="tooltip">' .
500+
'<span id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
501+
'</span>' .
502+
'</div>',
503+
esc_attr( $classes ),
504+
esc_attr( $id ),
505+
esc_attr( $label ),
506+
esc_attr( $icon ),
507+
esc_html( $content ),
508+
);
509+
} else {
510+
$markup = sprintf(
511+
'<div class="%1$s">' .
512+
'<button type="button" class="wp-tooltip__toggle" popovertarget="%2$s" aria-label="%3$s" aria-haspopup="dialog">' .
513+
'<span class="dashicons%4$s" aria-hidden="true"></span>' .
514+
'</button>' .
515+
'<dialog popover="auto" id="%2$s" class="wp-tooltip__bubble" autofocus>' .
516+
'<span id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
517+
'<button type="button" class="wp-tooltip__close" popovertarget="%2$s" popovertargetaction="hide" aria-label="%6$s">' .
518+
'<span class="dashicons dashicons-no-alt" aria-hidden="true"></span>' .
519+
'</button>' .
520+
'</dialog>' .
521+
'</div>',
522+
esc_attr( $classes ),
523+
esc_attr( $id ),
524+
esc_attr( $args['label'] ),
525+
esc_attr( $icon ),
526+
esc_html( $content ),
527+
esc_attr( $args['close_label'] ),
528+
);
529+
}
530+
531+
return $markup;
532+
}
533+
372534
/**
373535
* Displays the Log In/Out link.
374536
*

src/wp-includes/script-loader.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,8 @@ function wp_default_scripts( $scripts ) {
879879
$scripts->add( 'wp-auth-check', "/wp-includes/js/wp-auth-check$suffix.js", array( 'heartbeat' ), false, 1 );
880880
$scripts->set_translations( 'wp-auth-check' );
881881

882+
$scripts->add( 'wp-tooltip', '/wp-includes/js/wp-tooltip.js', array(), false, 1 );
883+
882884
$scripts->add( 'wp-lists', "/wp-includes/js/wp-lists$suffix.js", array( 'wp-ajax-response', 'jquery-color' ), false, 1 );
883885

884886
$scripts->add( 'site-icon', '/wp-admin/js/site-icon.js', array( 'jquery' ), false, 1 );
@@ -1617,6 +1619,7 @@ function wp_default_styles( $styles ) {
16171619
$suffix = SCRIPT_DEBUG ? '' : '.min';
16181620

16191621
// Admin CSS.
1622+
$styles->add( 'wp-tooltip', "/wp-admin/css/wp-tooltip$suffix.css", array( 'dashicons' ) );
16201623
$styles->add( 'common', "/wp-admin/css/common$suffix.css" );
16211624
$styles->add( 'forms', "/wp-admin/css/forms$suffix.css" );
16221625
$styles->add( 'admin-menu', "/wp-admin/css/admin-menu$suffix.css" );
@@ -1636,7 +1639,7 @@ function wp_default_styles( $styles ) {
16361639

16371640
$styles->add( 'wp-admin', false, array( 'dashicons', 'common', 'forms', 'admin-menu', 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', 'nav-menus', 'widgets', 'site-icon', 'l10n', 'wp-base-styles' ) );
16381641

1639-
$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) );
1642+
$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles', 'wp-tooltip' ) );
16401643
$styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) );
16411644
$styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" );
16421645
$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'imgareaselect' ) );

0 commit comments

Comments
 (0)