Commit 72b8d3edb92 for woocommerce
commit 72b8d3edb9294e43b89d86cfd26894c8be49a72c
Author: Lucio Giannotta <lucio.giannotta@a8c.com>
Date: Mon Mar 2 22:15:49 2026 +0800
Add dropdown navigation to Customer Account block (#63301)
* Add dropdown navigation to Customer Account block
Implements a new dropdown navigation feature for the Customer Account
block that displays account menu items (Dashboard, Orders, Downloads,
etc.) when clicked. Disabled by default, can be enabled via a toggle
in the block settings.
- Add WordPress Interactivity API store for dropdown state management
- Implement dropdown positioning logic to avoid viewport overflow
- Add keyboard navigation (Escape key closes dropdown)
- Add click-outside-to-close functionality
- Extend PHP render method to support both link and dropdown variants
- Add comprehensive SCSS styling with viewport-aware positioning
- Add caret icon for visual indicator
---------
Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/63301-wooplug-6258-customer-account-block-enable-dropdown-navigation b/plugins/woocommerce/changelog/63301-wooplug-6258-customer-account-block-enable-dropdown-navigation
new file mode 100644
index 00000000000..982cb6708a6
--- /dev/null
+++ b/plugins/woocommerce/changelog/63301-wooplug-6258-customer-account-block-enable-dropdown-navigation
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Add optional dropdown navigation to the Customer Account block, displaying account menu links in a dropdown menu when enabled.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json
index 95b9bf03825..313fe84e4ba 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json
@@ -5,13 +5,11 @@
"category": "woocommerce",
"keywords": [ "WooCommerce", "My Account" ],
"supports": {
- "interactivity": {
- "clientNavigation": true
- },
"align": true,
"color": {
"text": true
},
+ "interactivity": true,
"typography": {
"fontSize": true,
"__experimentalFontFamily": true
@@ -26,6 +24,10 @@
"type": "string",
"default": "icon_and_text"
},
+ "hasDropdownNavigation": {
+ "type": "boolean",
+ "default": false
+ },
"iconStyle": {
"type": "string",
"default": "default"
@@ -35,6 +37,9 @@
"default": "icon"
}
},
+ "viewScriptModule": "woocommerce/customer-account",
+ "style": "file:../woocommerce/customer-account-style.css",
+ "editorStyle": "file:../woocommerce/customer-account-editor.css",
"textdomain": "woocommerce",
"apiVersion": 3,
"$schema": "https://schemas.wp.org/trunk/block.json"
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx
index 27391f321eb..27dc9205597 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx
@@ -6,6 +6,7 @@ import {
customerAccountStyle,
customerAccountStyleAlt,
customerAccountStyleLine,
+ caret,
} from '@woocommerce/icons';
import { getSetting } from '@woocommerce/settings';
import { __ } from '@wordpress/i18n';
@@ -56,7 +57,8 @@ export const CustomerAccountBlock = ( {
}: {
attributes: Attributes;
} ): JSX.Element => {
- const { displayStyle, iconStyle, iconClass } = attributes;
+ const { displayStyle, hasDropdownNavigation, iconStyle, iconClass } =
+ attributes;
const ariaAttributes =
displayStyle === DisplayStyle.ICON_ONLY
@@ -67,6 +69,7 @@ export const CustomerAccountBlock = ( {
return (
<a
+ className="wc-block-customer-account__link"
href={ getSetting(
'dashboardUrl',
getSetting( 'wpLoginUrl', '/wp-login.php' )
@@ -79,6 +82,13 @@ export const CustomerAccountBlock = ( {
iconClass={ iconClass }
/>
<Label displayStyle={ displayStyle } />
+ { hasDropdownNavigation && (
+ <Icon
+ className="wc-block-customer-account__caret"
+ icon={ caret }
+ size={ 10 }
+ />
+ ) }
</a>
);
};
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/frontend.ts
new file mode 100644
index 00000000000..2482485c0b4
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/frontend.ts
@@ -0,0 +1,148 @@
+/**
+ * External dependencies
+ */
+import { store, getContext, getElement } from '@wordpress/interactivity';
+
+export interface CustomerAccountContext {
+ isDropdownOpen: boolean;
+ showAbove: boolean;
+ alignRight: boolean;
+}
+
+const SELECTORS = {
+ wrapper: '.wc-block-customer-account--has-dropdown',
+ dropdown: '.wc-block-customer-account__dropdown',
+ trigger: '.wc-block-customer-account__toggle',
+};
+
+const FLIP_THRESHOLD = 16;
+const SHOW_ABOVE_THRESHOLD = 0.6;
+
+const getWrapper = (): HTMLElement | null => {
+ const { ref } = getElement();
+ return ref?.closest( SELECTORS.wrapper ) as HTMLElement | null;
+};
+
+const getDropdown = ( wrapper: HTMLElement ): HTMLElement | null => {
+ return wrapper.querySelector( SELECTORS.dropdown ) as HTMLElement | null;
+};
+
+const focusTrigger = () => {
+ const { ref } = getElement();
+ const trigger = ref?.querySelector(
+ SELECTORS.trigger
+ ) as HTMLElement | null;
+ trigger?.focus();
+};
+
+const updateDropdownPosition = (
+ context: CustomerAccountContext,
+ wrapper: HTMLElement
+) => {
+ const rect = wrapper.getBoundingClientRect();
+ const viewportHeight = window.innerHeight;
+ const viewportWidth = document.documentElement.clientWidth;
+
+ context.showAbove = rect.bottom > viewportHeight * SHOW_ABOVE_THRESHOLD;
+
+ const dropdown = getDropdown( wrapper );
+ if ( ! dropdown ) {
+ return;
+ }
+
+ dropdown.hidden = false;
+ const dropdownWidth = dropdown.offsetWidth;
+ dropdown.hidden = true;
+
+ const rightSpace = viewportWidth - ( rect.left + dropdownWidth );
+ context.alignRight = rightSpace < FLIP_THRESHOLD;
+};
+
+const { actions: privateActions } = store(
+ 'woocommerce/customer-account/private',
+ {
+ actions: {
+ handleDocumentClick: ( event: MouseEvent ) => {
+ const context = getContext< CustomerAccountContext >();
+ if ( ! context.isDropdownOpen ) {
+ return;
+ }
+ const { ref } = getElement();
+ if ( ref && ! ref.contains( event.target as Node ) ) {
+ context.isDropdownOpen = false;
+ }
+ },
+ handleKeydown: ( event: KeyboardEvent ) => {
+ if ( event.key !== 'Escape' ) {
+ return;
+ }
+
+ const context = getContext< CustomerAccountContext >();
+ if ( ! context.isDropdownOpen ) {
+ return;
+ }
+
+ context.isDropdownOpen = false;
+ focusTrigger();
+ },
+ handleFocusOut: ( event: FocusEvent ) => {
+ const context = getContext< CustomerAccountContext >();
+ if ( ! context.isDropdownOpen ) {
+ return;
+ }
+
+ const { ref } = getElement();
+ const relatedTarget = event.relatedTarget as Node | null;
+ if (
+ ref &&
+ ( ! relatedTarget || ! ref.contains( relatedTarget ) )
+ ) {
+ context.isDropdownOpen = false;
+ }
+ },
+ toggleDropdown: ( event: MouseEvent ) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const context = getContext< CustomerAccountContext >();
+ if ( context.isDropdownOpen ) {
+ context.isDropdownOpen = false;
+ return;
+ }
+
+ const wrapper = getWrapper();
+ if ( wrapper ) {
+ updateDropdownPosition( context, wrapper );
+ }
+
+ context.isDropdownOpen = true;
+ },
+ },
+ }
+);
+
+store( 'woocommerce/customer-account', {
+ state: {
+ /**
+ * Whether the dropdown is open.
+ *
+ * @type {boolean}
+ */
+ get isDropdownOpen() {
+ const context = getContext< CustomerAccountContext >();
+ return context.isDropdownOpen;
+ },
+ },
+ actions: {
+ /**
+ * Toggle the dropdown.
+ *
+ * Public API for third-party toggling of the dropdown.
+ *
+ * @param event MouseEvent The event that triggered the toggle.
+ */
+ toggleDropdown: ( event: MouseEvent ) => {
+ privateActions.toggleDropdown( event as MouseEvent );
+ },
+ },
+} );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx
index 7985c4d8066..f89a2e780f1 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx
@@ -11,7 +11,6 @@ import { __ } from '@wordpress/i18n';
*/
import metadata from './block.json';
import edit from './edit';
-import './style.scss';
registerBlockType( metadata, {
icon: {
@@ -43,6 +42,7 @@ registerBlockVariation( 'woocommerce/customer-account', {
attributes: {
...metadata.attributes,
displayStyle: 'icon_and_text',
+ hasDropdownNavigation: false,
iconStyle: 'default',
iconClass: 'wc-block-customer-account__account-icon',
},
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx
index da9543edc04..6edd0ff0473 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx
@@ -16,6 +16,7 @@ import { createInterpolateElement } from '@wordpress/element';
import {
PanelBody,
SelectControl,
+ ToggleControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControl as ToggleGroupControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
@@ -159,6 +160,20 @@ export const BlockSettings = ( {
/>
</ToggleGroupControl>
) : null }
+ <ToggleControl
+ __nextHasNoMarginBottom
+ label={ __( 'Show dropdown navigation', 'woocommerce' ) }
+ help={ __(
+ 'Display a dropdown menu with account navigation links when clicked.',
+ 'woocommerce'
+ ) }
+ checked={ attributes.hasDropdownNavigation ?? false }
+ onChange={ ( value: boolean ) => {
+ setAttributes( {
+ hasDropdownNavigation: value,
+ } );
+ } }
+ />
</PanelBody>
</InspectorControls>
);
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss
index 5c5738da13a..f0b5268b4b9 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss
@@ -15,24 +15,146 @@
@include flex-justify-content(center);
}
-.wp-block-woocommerce-customer-account {
- a {
- text-decoration: none !important;
- align-items: center;
- display: flex;
- color: currentColor !important;
- gap: em($gap-smaller);
- white-space: nowrap;
- line-height: 1;
- padding: em($gap-smaller);
-
- &:hover {
- text-decoration: underline !important;
+:where(.wp-block-woocommerce-customer-account) {
+ --wc-customer-account-padding: var(
+ --wp--preset--spacing--20,
+ #{em($gap-smaller)}
+ );
+ --wc-customer-account-viewport-gutter: var(--wp--preset--spacing--20, 16px);
+ --wc-customer-account-dropdown-padding-inline: var(
+ --wp--preset--spacing--30,
+ #{$gap}
+ );
+ --wc-customer-account-dropdown-padding-block: var(
+ --wp--preset--spacing--20,
+ #{$gap-smaller}
+ );
+ --wc-customer-account-dropdown-surface: var(
+ --wp--preset--color--base,
+ #fff
+ );
+ --wc-customer-account-dropdown-border-color: color-mix(
+ in srgb,
+ currentColor 20%,
+ transparent
+ );
+ --wc-customer-account-dropdown-item-hover-bg: color-mix(
+ in srgb,
+ currentColor 8%,
+ transparent
+ );
+}
+
+:where(.wp-block-woocommerce-customer-account .wc-block-customer-account__link),
+:where(
+ .wp-block-woocommerce-customer-account.wc-block-customer-account--has-dropdown
+ .wc-block-customer-account__toggle
+ ) {
+ align-items: center;
+ color: inherit;
+ display: flex;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ line-height: 1;
+ padding: var(--wc-customer-account-padding);
+ text-decoration: none;
+ white-space: nowrap;
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ .wc-block-customer-account__account-icon {
+ width: em($grid-unit-30);
+ height: em($grid-unit-30);
+ }
+}
+
+:where(.wp-block-woocommerce-customer-account) {
+ .wc-block-customer-account__caret {
+ margin-left: em($gap-smallest);
+ transition: transform 0.2s ease;
+ width: em(10px);
+ height: em(6px);
+ }
+
+ &.wc-block-customer-account--has-dropdown {
+ position: relative;
+
+ .wc-block-customer-account__toggle {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font: inherit;
+ }
+
+ &.is-dropdown-open .wc-block-customer-account__caret {
+ transform: rotate(180deg);
}
- .wc-block-customer-account__account-icon {
- width: em($grid-unit-30);
- height: em($grid-unit-30);
+ .wc-block-customer-account__dropdown {
+ background: var(--wc-customer-account-dropdown-surface);
+ border: 1px solid var(--wc-customer-account-dropdown-border-color);
+ border-radius: $universal-border-radius;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 200px;
+ max-width: calc(
+ 100vw - (2 * var(--wc-customer-account-viewport-gutter))
+ );
+ z-index: 100;
+
+ &[hidden] {
+ display: none;
+ }
+ }
+
+ // Show above the trigger when near viewport bottom.
+ &.wc-block-customer-account--show-above
+ .wc-block-customer-account__dropdown {
+ top: auto;
+ bottom: 100%;
+ }
+
+ // Align right when too close to the edge.
+ &.wc-block-customer-account--align-right
+ .wc-block-customer-account__dropdown {
+ left: auto;
+ right: 0;
+ }
+
+ .wc-block-customer-account__dropdown-divider {
+ background: var(--wc-customer-account-dropdown-border-color);
+ height: 1px;
+ }
+
+ .wc-block-customer-account__dropdown-item {
+ color: inherit;
+ display: block;
+ font-size: var(--wp--preset--font-size--small);
+ line-height: 1.5;
+ padding: calc(var(--wc-customer-account-dropdown-padding-block) / 2)
+ var(--wc-customer-account-dropdown-padding-block);
+ text-decoration: none;
+ white-space: nowrap;
+
+ &:only-child {
+ padding: var(--wc-customer-account-dropdown-padding-block);
+ }
+
+ &:hover,
+ &:focus {
+ background: var(--wc-customer-account-dropdown-item-hover-bg);
+ text-decoration: none;
+ }
+
+ &:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: -2px;
+ }
}
}
}
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts
index fe5e88b9ba2..7b9b7b75553 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts
@@ -1,6 +1,7 @@
export interface Attributes {
className?: string;
displayStyle: DisplayStyle;
+ hasDropdownNavigation?: boolean;
iconStyle: IconStyle;
iconClass: string;
}
diff --git a/plugins/woocommerce/client/blocks/assets/js/icons/index.js b/plugins/woocommerce/client/blocks/assets/js/icons/index.js
index cfd63540290..6702fa1e334 100644
--- a/plugins/woocommerce/client/blocks/assets/js/icons/index.js
+++ b/plugins/woocommerce/client/blocks/assets/js/icons/index.js
@@ -2,6 +2,7 @@ export { default as Alert } from './library/alert';
export { default as bag } from './library/bag';
export { default as bagAlt } from './library/bag-alt';
export { default as barcode } from './library/barcode';
+export { default as caret } from './library/caret';
export { default as cart } from './library/cart';
export { default as cartOutline } from './library/cart-outline';
export { default as checkMark } from './library/check-mark';
diff --git a/plugins/woocommerce/client/blocks/assets/js/icons/library/caret.tsx b/plugins/woocommerce/client/blocks/assets/js/icons/library/caret.tsx
new file mode 100644
index 00000000000..b7e7cc31946
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/icons/library/caret.tsx
@@ -0,0 +1,18 @@
+/**
+ * External dependencies
+ */
+import { Path, SVG } from '@wordpress/primitives';
+
+const caret = (
+ <SVG viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <Path
+ d="M1 1L5 5L9 1"
+ stroke="currentColor"
+ strokeWidth="1.5"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ />
+ </SVG>
+);
+
+export default caret;
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php b/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php
index 29b49118fba..55ebef2a9e9 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php
@@ -10,6 +10,7 @@ use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;
*/
class CustomerAccount extends AbstractBlock {
use BlockHooksTrait;
+ use EnableBlockJsonAssetsTrait;
const TEXT_ONLY = 'text_only';
const ICON_ONLY = 'icon_only';
@@ -121,10 +122,191 @@ class CustomerAccount extends AbstractBlock {
*/
protected function render( $attributes, $content, $block ) {
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
+ $has_myaccount_page = get_option( 'woocommerce_myaccount_page_id' );
+ $account_link = $has_myaccount_page ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
+ $has_dropdown = ! empty( $attributes['hasDropdownNavigation'] ) && is_user_logged_in() && $has_myaccount_page;
- $account_link = get_option( 'woocommerce_myaccount_page_id' ) ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
+ $aria_label = self::ICON_ONLY === $attributes['displayStyle'] ? ' aria-label="' . esc_attr( $this->render_label() ) . '"' : '';
+ $label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '<span class="label">' . wp_kses( $this->render_label(), array() ) . '</span>';
+
+ if ( ! $has_dropdown ) {
+ return $this->render_link( $attributes, $classes_and_styles, $account_link, $aria_label, $label_markup );
+ }
+
+ return $this->render_dropdown( $attributes, $classes_and_styles, $aria_label, $label_markup );
+ }
+
+ /**
+ * Render the block as a simple link (default behavior).
+ *
+ * @param array $attributes Block attributes.
+ * @param array $classes_and_styles Classes and styles from block attributes.
+ * @param string $account_link URL to link to.
+ * @param string $aria_label Pre-computed aria-label attribute string.
+ * @param string $label_markup Pre-computed label HTML markup.
+ *
+ * @return string Rendered block output.
+ */
+ private function render_link( $attributes, $classes_and_styles, $account_link, $aria_label, $label_markup ) {
+ $allowed_svg = $this->get_allowed_svg();
+
+ ob_start();
+ ?>
+ <div
+ class="wp-block-woocommerce-customer-account <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
+ style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
+ >
+ <a
+ class="wc-block-customer-account__link"
+ href="<?php echo esc_url( $account_link ); ?>"
+ <?php echo $aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ >
+ <?php echo wp_kses( $this->render_icon( $attributes ), $allowed_svg ); ?>
+ <?php echo $label_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ </a>
+ </div>
+ <?php
+ return (string) ob_get_clean();
+ }
+
+ /**
+ * Render the block as a dropdown navigation.
+ *
+ * @param array $attributes Block attributes.
+ * @param array $classes_and_styles Classes and styles from block attributes.
+ * @param string $aria_label Pre-computed aria-label attribute string.
+ * @param string $label_markup Pre-computed label HTML markup.
+ *
+ * @return string Rendered block output.
+ */
+ private function render_dropdown( $attributes, $classes_and_styles, $aria_label, $label_markup ) {
+ $allowed_svg = $this->get_allowed_svg();
+
+ $context = array(
+ 'isDropdownOpen' => false,
+ 'showAbove' => false,
+ 'alignRight' => false,
+ );
+
+ $menu_items = wc_get_account_menu_items();
+ $dropdown_html = $this->render_dropdown_menu( $menu_items );
+
+ ob_start();
+ ?>
+ <div
+ class="wp-block-woocommerce-customer-account wc-block-customer-account--has-dropdown <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
+ style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
+ data-wp-interactive="woocommerce/customer-account/private"
+ <?php echo wp_interactivity_data_wp_context( $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ data-wp-class--wc-block-customer-account--align-right="context.alignRight"
+ data-wp-class--wc-block-customer-account--is-dropdown-open="context.isDropdownOpen"
+ data-wp-class--wc-block-customer-account--show-above="context.showAbove"
+ data-wp-on--focusout="actions.handleFocusOut"
+ data-wp-on-document--click="actions.handleDocumentClick"
+ data-wp-on-document--keydown="actions.handleKeydown"
+ >
+ <button
+ type="button"
+ class="wc-block-customer-account__toggle"
+ <?php echo $aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ aria-haspopup="true"
+ data-wp-bind--aria-expanded="context.isDropdownOpen"
+ data-wp-on--click="actions.toggleDropdown"
+ >
+ <?php echo wp_kses( $this->render_icon( $attributes ), $allowed_svg ); ?>
+ <?php echo $label_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ <?php echo $this->render_caret_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ </button>
+ <nav
+ class="wc-block-customer-account__dropdown"
+ aria-label="<?php echo esc_attr__( 'Account navigation', 'woocommerce' ); ?>"
+ data-wp-bind--hidden="!context.isDropdownOpen"
+ >
+ <?php echo $dropdown_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+ </nav>
+ </div>
+ <?php
+ return (string) ob_get_clean();
+ }
+
+ /**
+ * Render the dropdown menu content with three sections.
+ *
+ * @param array $menu_items Account menu items from wc_get_account_menu_items().
+ *
+ * @return string Rendered dropdown menu HTML.
+ */
+ private function render_dropdown_menu( $menu_items ) {
+ $sections = array();
+
+ if ( isset( $menu_items['dashboard'] ) ) {
+ $sections[] = $this->render_section( array( 'dashboard' => $menu_items['dashboard'] ) );
+ }
+
+ $nav_items = array_diff_key(
+ $menu_items,
+ array_flip( array( 'dashboard', 'customer-logout' ) )
+ );
+ if ( ! empty( $nav_items ) ) {
+ $sections[] = $this->render_section( $nav_items );
+ }
+
+ if ( isset( $menu_items['customer-logout'] ) ) {
+ $sections[] = $this->render_section( array( 'customer-logout' => $menu_items['customer-logout'] ) );
+ }
+
+ return implode( '<div class="wc-block-customer-account__dropdown-divider"></div>', $sections );
+ }
+
+ /**
+ * Render a dropdown section wrapping one or more menu items.
+ *
+ * @param array $items Associative array of endpoint => label pairs.
+ *
+ * @return string Rendered section HTML.
+ */
+ private function render_section( $items ) {
+ $output = '<div class="wc-block-customer-account__dropdown-section">';
+ foreach ( $items as $endpoint => $label ) {
+ $output .= $this->render_menu_item( $endpoint, $label );
+ }
+ $output .= '</div>';
+ return $output;
+ }
+
+ /**
+ * Render a single dropdown menu item.
+ *
+ * @param string $endpoint The account endpoint key.
+ * @param string $label The menu item label.
+ *
+ * @return string Rendered menu item HTML.
+ */
+ private function render_menu_item( $endpoint, $label ) {
+ $url = wc_get_account_endpoint_url( $endpoint );
+ return '<a href="' . esc_url( $url ) . '" class="wc-block-customer-account__dropdown-item">'
+ . esc_html( $label )
+ . '</a>';
+ }
+
+ /**
+ * Render the caret/chevron icon for the dropdown toggle.
+ *
+ * @return string SVG markup for the caret icon.
+ */
+ private function render_caret_icon() {
+ return '<svg class="wc-block-customer-account__caret" width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M1 1L5 5L9 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>';
+ }
- $allowed_svg = array(
+ /**
+ * Get the allowed SVG tags and attributes for wp_kses.
+ *
+ * @return array Allowed SVG elements and attributes.
+ */
+ private function get_allowed_svg() {
+ return array(
'svg' => array(
'class' => true,
'xmlns' => true,
@@ -147,17 +329,6 @@ class CustomerAccount extends AbstractBlock {
'fill' => true,
),
);
-
- // Only provide aria-label if the display style is icon only.
- $aria_label = self::ICON_ONLY === $attributes['displayStyle'] ? 'aria-label="' . esc_attr( $this->render_label() ) . '"' : '';
-
- $label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '<span class="label">' . wp_kses( $this->render_label(), array() ) . '</span>';
-
- return "<div class='wp-block-woocommerce-customer-account " . esc_attr( $classes_and_styles['classes'] ) . "' style='" . esc_attr( $classes_and_styles['styles'] ) . "'>
- <a " . $aria_label . " href='" . esc_attr( $account_link ) . "'>
- " . wp_kses( $this->render_icon( $attributes ), $allowed_svg ) . $label_markup . '
- </a>
- </div>';
}
/**
@@ -220,15 +391,4 @@ class CustomerAccount extends AbstractBlock {
? __( 'My Account', 'woocommerce' )
: __( 'Login', 'woocommerce' );
}
-
- /**
- * Get the frontend script handle for this block type.
- *
- * @param string $key Data to get, or default to everything.
- *
- * @return null This block has no frontend script.
- */
- protected function get_block_type_script( $key = null ) {
- return null;
- }
}