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' ) ) { + diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php index 753dc99f84f..7e5ab78ded5 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php @@ -17,14 +17,15 @@ $product_attributes = $product_object->get_attributes( 'edit' ); ?>