diff --git a/plugins/woocommerce/changelog/issue-37839 b/plugins/woocommerce/changelog/issue-37839
new file mode 100644
index 00000000000..ee9daa1f1fc
--- /dev/null
+++ b/plugins/woocommerce/changelog/issue-37839
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Design enhancements for the Attributes tab.
diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss
index dceabb9ef7d..7253c2d1d2a 100644
--- a/plugins/woocommerce/client/legacy/css/admin.scss
+++ b/plugins/woocommerce/client/legacy/css/admin.scss
@@ -5303,6 +5303,14 @@ img.help_tip {
visibility: visible;
}
}
+
+ &.wc-metabox {
+ &.postbox {
+ border-top: 0px;
+ border-left: 0px;
+ border-right: 0px;
+ }
+ }
}
.woocommerce_options_panel {
@@ -5517,7 +5525,8 @@ img.help_tip {
* WooCommerce meta boxes
*/
.wc-metaboxes-wrapper {
- :not(#variable_product_options_inner) {
+ :not(#variable_product_options_inner),
+ product_attributes {
.toolbar:not(.expand-close-hidden) {
border-bottom: 1px solid #eee;
}
diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
index c23ed507e29..388621a26b8 100644
--- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
+++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
@@ -463,6 +463,14 @@ jQuery( function ( $ ) {
.find( 'option[value="' + $( el ).data( 'taxonomy' ) + '"]' )
.attr( 'disabled', 'disabled' );
}
+
+ if ( 'undefined' === $(el).attr( 'data-taxonomy' ) || false === $(el).attr( 'data-taxonomy' ) || '' === $(el).attr( 'data-taxonomy' ) ) {
+ add_placeholder_to_attribute_values_field( $(el) );
+
+ $( '.woocommerce_attribute input.woocommerce_attribute_used_for_variations' ).on( 'change', function() {
+ add_placeholder_to_attribute_values_field( $(el) );
+ } );
+ }
} );
$( 'select.wc-attribute-search' ).data(
'disabled-items',
@@ -514,6 +522,26 @@ jQuery( function ( $ ) {
$attributeListItem.find( 'h3' ).trigger( 'click' );
}
+ function toggle_selection_of_attribute_list_item_terms( $attributeListItem ) {
+
+ var $attributeListItemSelectAllButton = $attributeListItem.find( 'button.select_all_attributes' );
+
+ if ( $attributeListItemSelectAllButton.length ) {
+ $attributeListItemSelectAllButton.trigger( 'click' );
+ }
+ }
+
+ function add_placeholder_to_attribute_values_field( $attributeListItem ) {
+
+ var $used_for_variations_checkbox = $attributeListItem.find( 'input.woocommerce_attribute_used_for_variations' );
+
+ if ( $used_for_variations_checkbox.length && $used_for_variations_checkbox.is( ':checked' ) ) {
+ $attributeListItem.find( 'textarea' ).attr( 'placeholder', woocommerce_admin_meta_boxes.i18n_attributes_used_for_variations_placeholder );
+ } else {
+ $attributeListItem.find( 'textarea' ).attr( 'placeholder', woocommerce_admin_meta_boxes.i18n_attributes_default_placeholder );
+ }
+ }
+
function init_select_controls() {
$( document.body ).trigger( 'wc-enhanced-select-init' );
}
@@ -547,6 +575,18 @@ jQuery( function ( $ ) {
toggle_expansion_of_attribute_list_item( $attributeListItem );
+ // Automatically pre-select all terms when a global Attribute is chosen.
+ toggle_selection_of_attribute_list_item_terms( $attributeListItem );
+
+ // Conditionally change the placeholder of product-level Attributes depending on the value of the "Use for variations" checkbox.
+ if ( 'undefined' === typeof globalAttributeId ) {
+ add_placeholder_to_attribute_values_field( $attributeListItem );
+
+ $( '.woocommerce_attribute input.woocommerce_attribute_used_for_variations' ).on( 'change', function() {
+ add_placeholder_to_attribute_values_field( $(this).closest( '.woocommerce_attribute' ) );
+ } );
+ }
+
$( document.body ).trigger( 'woocommerce_added_attribute' );
jQuery.maybe_disable_save_button();
@@ -602,6 +642,17 @@ jQuery( function ( $ ) {
}
}
+ // Handle the Attributes onboarding dismissible notice.
+ // If users dismiss the notice, never show it again.
+ if ( localStorage.getItem('attributes-notice-dismissed' ) ) {
+ $( '#product_attributes .notice' ).hide();
+ }
+
+ $( '#product_attributes .notice.woocommerce-message button' ).on( 'click', function( e ) {
+ $( '#product_attributes .notice' ).hide();
+ localStorage.setItem( 'attributes-notice-dismissed', 'true');
+ } );
+
$( 'select.wc-attribute-search' ).on( 'select2:select', function ( e ) {
const attributeId = e && e.params && e.params.data && e.params.data.id;
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
index 1ffac1ef198..abaca87c5ac 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
@@ -369,74 +369,77 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
}
$params = array(
- 'remove_item_notice' => $remove_item_notice,
- 'remove_fee_notice' => $remove_fee_notice,
- 'remove_shipping_notice' => $remove_shipping_notice,
- 'i18n_select_items' => __( 'Please select some items.', 'woocommerce' ),
- 'i18n_do_refund' => __( 'Are you sure you wish to process this refund? This action cannot be undone.', 'woocommerce' ),
- 'i18n_delete_refund' => __( 'Are you sure you wish to delete this refund? This action cannot be undone.', 'woocommerce' ),
- 'i18n_delete_tax' => __( 'Are you sure you wish to delete this tax column? This action cannot be undone.', 'woocommerce' ),
- 'remove_item_meta' => __( 'Remove this item meta?', 'woocommerce' ),
- 'name_label' => __( 'Name', 'woocommerce' ),
- 'remove_label' => __( 'Remove', 'woocommerce' ),
- 'click_to_toggle' => __( 'Click to toggle', 'woocommerce' ),
- 'values_label' => __( 'Value(s)', 'woocommerce' ),
- 'text_attribute_tip' => __( 'Enter some text, or some attributes by pipe (|) separating values.', 'woocommerce' ),
- 'visible_label' => __( 'Visible on the product page', 'woocommerce' ),
- 'used_for_variations_label' => __( 'Used for variations', 'woocommerce' ),
- 'new_attribute_prompt' => __( 'Enter a name for the new attribute term:', 'woocommerce' ),
- 'calc_totals' => __( 'Recalculate totals? This will calculate taxes based on the customers country (or the store base country) and update totals.', 'woocommerce' ),
- 'copy_billing' => __( 'Copy billing information to shipping information? This will remove any currently entered shipping information.', 'woocommerce' ),
- 'load_billing' => __( "Load the customer's billing information? This will remove any currently entered billing information.", 'woocommerce' ),
- 'load_shipping' => __( "Load the customer's shipping information? This will remove any currently entered shipping information.", 'woocommerce' ),
- 'featured_label' => __( 'Featured', 'woocommerce' ),
- 'prices_include_tax' => esc_attr( get_option( 'woocommerce_prices_include_tax' ) ),
- 'tax_based_on' => esc_attr( get_option( 'woocommerce_tax_based_on' ) ),
- 'round_at_subtotal' => esc_attr( get_option( 'woocommerce_tax_round_at_subtotal' ) ),
- 'no_customer_selected' => __( 'No customer selected', 'woocommerce' ),
- 'plugin_url' => WC()->plugin_url(),
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
- 'order_item_nonce' => wp_create_nonce( 'order-item' ),
- 'add_attribute_nonce' => wp_create_nonce( 'add-attribute' ),
- 'save_attributes_nonce' => wp_create_nonce( 'save-attributes' ),
- 'add_attributes_and_variations' => wp_create_nonce( 'add-attributes-and-variations' ),
- 'calc_totals_nonce' => wp_create_nonce( 'calc-totals' ),
- 'get_customer_details_nonce' => wp_create_nonce( 'get-customer-details' ),
- 'search_products_nonce' => wp_create_nonce( 'search-products' ),
- 'grant_access_nonce' => wp_create_nonce( 'grant-access' ),
- 'revoke_access_nonce' => wp_create_nonce( 'revoke-access' ),
- 'add_order_note_nonce' => wp_create_nonce( 'add-order-note' ),
- 'delete_order_note_nonce' => wp_create_nonce( 'delete-order-note' ),
- 'calendar_image' => WC()->plugin_url() . '/assets/images/calendar.png',
- 'post_id' => $this->is_order_meta_box_screen( $screen_id ) && isset( $order_or_post_object ) ? \Automattic\WooCommerce\Utilities\OrderUtil::get_post_or_order_id( $order_or_post_object ) : $post_id,
- 'base_country' => WC()->countries->get_base_country(),
- 'currency_format_num_decimals' => wc_get_price_decimals(),
- 'currency_format_symbol' => get_woocommerce_currency_symbol( $currency ),
- 'currency_format_decimal_sep' => esc_attr( wc_get_price_decimal_separator() ),
- 'currency_format_thousand_sep' => esc_attr( wc_get_price_thousand_separator() ),
- 'currency_format' => esc_attr( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_price_format() ) ), // For accounting JS.
- 'rounding_precision' => wc_get_rounding_precision(),
- 'tax_rounding_mode' => wc_get_tax_rounding_mode(),
- 'product_types' => array_unique( array_merge( array( 'simple', 'grouped', 'variable', 'external' ), array_keys( wc_get_product_types() ) ) ),
- 'i18n_download_permission_fail' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'woocommerce' ),
- 'i18n_permission_revoke' => __( 'Are you sure you want to revoke access to this download?', 'woocommerce' ),
- 'i18n_tax_rate_already_exists' => __( 'You cannot add the same tax rate twice!', 'woocommerce' ),
- 'i18n_delete_note' => __( 'Are you sure you wish to delete this note? This action cannot be undone.', 'woocommerce' ),
- 'i18n_apply_coupon' => __( 'Enter a coupon code to apply. Discounts are applied to line totals, before taxes.', 'woocommerce' ),
- 'i18n_add_fee' => __( 'Enter a fixed amount or percentage to apply as a fee.', 'woocommerce' ),
- 'i18n_attribute_name_placeholder' => __( 'New attribute', 'woocommerce' ),
- 'i18n_product_simple_tip' => __( 'Simple – covers the vast majority of any products you may sell. Simple products are shipped and have no options. For example, a book.', 'woocommerce' ),
- 'i18n_product_grouped_tip' => __( 'Grouped – a collection of related products that can be purchased individually and only consist of simple products. For example, a set of six drinking glasses.', 'woocommerce' ),
- 'i18n_product_external_tip' => __( 'External or Affiliate – one that you list and describe on your website but is sold elsewhere.', 'woocommerce' ),
- 'i18n_product_variable_tip' => __( 'Variable – a product with variations, each of which may have a different SKU, price, stock option, etc. For example, a t-shirt available in different colors and/or sizes.', 'woocommerce' ),
- 'i18n_product_other_tip' => __( 'Product types define available product details and attributes, such as downloadable files and variations. They’re also used for analytics and inventory management.', 'woocommerce' ),
- 'i18n_product_description_tip' => __( 'Describe this product. What makes it unique? What are its most important features?', 'woocommerce' ),
- 'i18n_product_short_description_tip' => __( 'Summarize this product in 1-2 short sentences. We’ll show it at the top of the page.', 'woocommerce' ),
- 'i18n_save_attribute_variation_tip' => __( 'Make sure you enter the name and values for each attribute.', 'woocommerce' ),
+ 'remove_item_notice' => $remove_item_notice,
+ 'remove_fee_notice' => $remove_fee_notice,
+ 'remove_shipping_notice' => $remove_shipping_notice,
+ 'i18n_select_items' => __( 'Please select some items.', 'woocommerce' ),
+ 'i18n_do_refund' => __( 'Are you sure you wish to process this refund? This action cannot be undone.', 'woocommerce' ),
+ 'i18n_delete_refund' => __( 'Are you sure you wish to delete this refund? This action cannot be undone.', 'woocommerce' ),
+ 'i18n_delete_tax' => __( 'Are you sure you wish to delete this tax column? This action cannot be undone.', 'woocommerce' ),
+ 'remove_item_meta' => __( 'Remove this item meta?', 'woocommerce' ),
+ 'name_label' => __( 'Name', 'woocommerce' ),
+ 'remove_label' => __( 'Remove', 'woocommerce' ),
+ 'click_to_toggle' => __( 'Click to toggle', 'woocommerce' ),
+ 'values_label' => __( 'Value(s)', 'woocommerce' ),
+ 'text_attribute_tip' => __( 'Enter some text, or some attributes by pipe (|) separating values.', 'woocommerce' ),
+ 'visible_label' => __( 'Visible on the product page', 'woocommerce' ),
+ 'used_for_variations_label' => __( 'Used for variations', 'woocommerce' ),
+ 'new_attribute_prompt' => __( 'Enter a name for the new attribute term:', 'woocommerce' ),
+ 'calc_totals' => __( 'Recalculate totals? This will calculate taxes based on the customers country (or the store base country) and update totals.', 'woocommerce' ),
+ 'copy_billing' => __( 'Copy billing information to shipping information? This will remove any currently entered shipping information.', 'woocommerce' ),
+ 'load_billing' => __( "Load the customer's billing information? This will remove any currently entered billing information.", 'woocommerce' ),
+ 'load_shipping' => __( "Load the customer's shipping information? This will remove any currently entered shipping information.", 'woocommerce' ),
+ 'featured_label' => __( 'Featured', 'woocommerce' ),
+ 'prices_include_tax' => esc_attr( get_option( 'woocommerce_prices_include_tax' ) ),
+ 'tax_based_on' => esc_attr( get_option( 'woocommerce_tax_based_on' ) ),
+ 'round_at_subtotal' => esc_attr( get_option( 'woocommerce_tax_round_at_subtotal' ) ),
+ 'no_customer_selected' => __( 'No customer selected', 'woocommerce' ),
+ 'plugin_url' => WC()->plugin_url(),
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
+ 'order_item_nonce' => wp_create_nonce( 'order-item' ),
+ 'add_attribute_nonce' => wp_create_nonce( 'add-attribute' ),
+ 'save_attributes_nonce' => wp_create_nonce( 'save-attributes' ),
+ 'add_attributes_and_variations' => wp_create_nonce( 'add-attributes-and-variations' ),
+ 'calc_totals_nonce' => wp_create_nonce( 'calc-totals' ),
+ 'get_customer_details_nonce' => wp_create_nonce( 'get-customer-details' ),
+ 'search_products_nonce' => wp_create_nonce( 'search-products' ),
+ 'grant_access_nonce' => wp_create_nonce( 'grant-access' ),
+ 'revoke_access_nonce' => wp_create_nonce( 'revoke-access' ),
+ 'add_order_note_nonce' => wp_create_nonce( 'add-order-note' ),
+ 'delete_order_note_nonce' => wp_create_nonce( 'delete-order-note' ),
+ 'calendar_image' => WC()->plugin_url() . '/assets/images/calendar.png',
+ 'post_id' => $this->is_order_meta_box_screen( $screen_id ) && isset( $order_or_post_object ) ? \Automattic\WooCommerce\Utilities\OrderUtil::get_post_or_order_id( $order_or_post_object ) : $post_id,
+ 'base_country' => WC()->countries->get_base_country(),
+ 'currency_format_num_decimals' => wc_get_price_decimals(),
+ 'currency_format_symbol' => get_woocommerce_currency_symbol( $currency ),
+ 'currency_format_decimal_sep' => esc_attr( wc_get_price_decimal_separator() ),
+ 'currency_format_thousand_sep' => esc_attr( wc_get_price_thousand_separator() ),
+ 'currency_format' => esc_attr( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_price_format() ) ), // For accounting JS.
+ 'rounding_precision' => wc_get_rounding_precision(),
+ 'tax_rounding_mode' => wc_get_tax_rounding_mode(),
+ 'product_types' => array_unique( array_merge( array( 'simple', 'grouped', 'variable', 'external' ), array_keys( wc_get_product_types() ) ) ),
+ 'i18n_download_permission_fail' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'woocommerce' ),
+ 'i18n_permission_revoke' => __( 'Are you sure you want to revoke access to this download?', 'woocommerce' ),
+ 'i18n_tax_rate_already_exists' => __( 'You cannot add the same tax rate twice!', 'woocommerce' ),
+ 'i18n_delete_note' => __( 'Are you sure you wish to delete this note? This action cannot be undone.', 'woocommerce' ),
+ 'i18n_apply_coupon' => __( 'Enter a coupon code to apply. Discounts are applied to line totals, before taxes.', 'woocommerce' ),
+ 'i18n_add_fee' => __( 'Enter a fixed amount or percentage to apply as a fee.', 'woocommerce' ),
+ 'i18n_attribute_name_placeholder' => __( 'New attribute', 'woocommerce' ),
+ 'i18n_product_simple_tip' => __( 'Simple – covers the vast majority of any products you may sell. Simple products are shipped and have no options. For example, a book.', 'woocommerce' ),
+ 'i18n_product_grouped_tip' => __( 'Grouped – a collection of related products that can be purchased individually and only consist of simple products. For example, a set of six drinking glasses.', 'woocommerce' ),
+ 'i18n_product_external_tip' => __( 'External or Affiliate – one that you list and describe on your website but is sold elsewhere.', 'woocommerce' ),
+ 'i18n_product_variable_tip' => __( 'Variable – a product with variations, each of which may have a different SKU, price, stock option, etc. For example, a t-shirt available in different colors and/or sizes.', 'woocommerce' ),
+ 'i18n_product_other_tip' => __( 'Product types define available product details and attributes, such as downloadable files and variations. They’re also used for analytics and inventory management.', 'woocommerce' ),
+ 'i18n_product_description_tip' => __( 'Describe this product. What makes it unique? What are its most important features?', 'woocommerce' ),
+ 'i18n_product_short_description_tip' => __( 'Summarize this product in 1-2 short sentences. We’ll show it at the top of the page.', 'woocommerce' ),
+ 'i18n_save_attribute_variation_tip' => __( 'Make sure you enter the name and values for each attribute.', 'woocommerce' ),
/* translators: %1$s: maximum file size */
- 'i18n_product_image_tip' => sprintf( __( 'For best results, upload JPEG or PNG files that are 1000 by 1000 pixels or larger. Maximum upload file size: %1$s.', 'woocommerce' ) , size_format( wp_max_upload_size() ) ),
+ 'i18n_product_image_tip' => sprintf( __( 'For best results, upload JPEG or PNG files that are 1000 by 1000 pixels or larger. Maximum upload file size: %1$s.', 'woocommerce' ), size_format( wp_max_upload_size() ) ),
'i18n_remove_used_attribute_confirmation_message' => __( 'If you remove this attribute, customers will no longer be able to purchase some variations of this product.', 'woocommerce' ),
- 'i18n_add_attribute_error_notice' => __( 'Adding new attribute failed.', 'woocommerce' ),
+ 'i18n_add_attribute_error_notice' => __( 'Adding new attribute failed.', 'woocommerce' ),
+ /* translators: %s: WC_DELIMITER */
+ 'i18n_attributes_default_placeholder' => sprintf( esc_attr__( 'Enter some descriptive text. Use “%s” to separate different values.', 'woocommerce' ), esc_attr( WC_DELIMITER ) ),
+ 'i18n_attributes_used_for_variations_placeholder' => sprintf( esc_attr__( 'Enter options for customers to choose from, f.e. “Blue” or “Large”. Use “%s” to separate different options.', 'woocommerce' ), esc_attr( WC_DELIMITER ) )
);
wp_localize_script( 'wc-admin-meta-boxes', 'woocommerce_admin_meta_boxes', $params );
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
index 30db0fac065..ae515cb8de5 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
@@ -41,7 +41,7 @@ if ( ! defined( 'ABSPATH' ) ) {