diff --git a/src/wp-admin/css/wp-tooltip.css b/src/wp-admin/css/wp-tooltip.css new file mode 100644 index 0000000000000..91e15180589ed --- /dev/null +++ b/src/wp-admin/css/wp-tooltip.css @@ -0,0 +1,110 @@ +/* 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, #2271b1); +} + +.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-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; + } + + /* Arrow: gray border (::before) under a white fill (::after). */ + .wp-tooltip .wp-tooltip__bubble::before, + .wp-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 .wp-tooltip__bubble::before { + margin-left: -8px; + border-top-color: #c3c4c7; + } + + .wp-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 e7640720d3c73..ac14325153628 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -369,6 +369,77 @@ function get_search_form( $args = array() ) { } } +/** + * Retrieves the markup for an accessible tooltip. + * + * Returns a help button and its tooltip popover, linked with `aria-describedby`. Enqueue the + * `wp-tooltip` style where it is used; the login styles already do. + * + * @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 'More information'. + * @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'. + * @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() ) { + $content = trim( (string) $content ); + + if ( '' === $content ) { + return ''; + } + + $defaults = array( + 'id' => '', + 'label' => __( 'More information' ), + 'close_label' => __( 'Close' ), + 'icon' => 'dashicons-editor-help', + 'class' => '', + ); + + $args = wp_parse_args( $args, $defaults ); + + $id = '' !== $args['id'] ? $args['id'] : wp_unique_id( 'wp-tooltip-' ); + + $classes = 'wp-tooltip'; + if ( '' !== $args['class'] ) { + $classes .= ' ' . $args['class']; + } + + $icon = '' !== $args['icon'] ? ' ' . $args['icon'] : ''; + + return sprintf( + '' . + '' . + '' . + '%5$s' . + '' . + '' . + '', + esc_attr( $classes ), + esc_attr( $id ), + esc_attr( $args['label'] ), + esc_attr( $icon ), + esc_html( $content ), + esc_attr( $args['close_label'] ) + ); +} + /** * Displays the Log In/Out link. * diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 134d86c26a08a..93053635732d3 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1617,6 +1617,7 @@ function wp_default_styles( $styles ) { $suffix = SCRIPT_DEBUG ? '' : '.min'; // Admin CSS. + $styles->add( 'wp-tooltip', "/wp-admin/css/wp-tooltip$suffix.css", array( 'dashicons' ) ); $styles->add( 'common', "/wp-admin/css/common$suffix.css" ); $styles->add( 'forms', "/wp-admin/css/forms$suffix.css" ); $styles->add( 'admin-menu', "/wp-admin/css/admin-menu$suffix.css" ); @@ -1636,7 +1637,7 @@ function wp_default_styles( $styles ) { $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' ) ); - $styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) ); + $styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles', 'wp-tooltip' ) ); $styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) ); $styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" ); $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'imgareaselect' ) ); diff --git a/src/wp-login.php b/src/wp-login.php index abedea82c3589..0a9fd24a98d8b 100644 --- a/src/wp-login.php +++ b/src/wp-login.php @@ -1541,7 +1541,34 @@ function wp_login_viewport_meta() { do_action( 'login_form' ); ?> -
/>
+ ++ /> + + 'rememberme-help', + 'label' => __( 'More information about “Remember Me”' ), + ) + ); + ?> +
assertSame( '', wp_get_tooltip( '' ) ); + $this->assertSame( '', wp_get_tooltip( ' ' ) ); + } + + /** + * Tests that the markup contains the expected accessible structure. + * + * @ticket 55343 + */ + public function test_wp_get_tooltip_returns_accessible_markup() { + $html = wp_get_tooltip( 'Helpful text.', array( 'id' => 'my-tip' ) ); + + // Toggle is a button that controls the popover and describes it. + $this->assertStringContainsString( '