';
+ }
+ );
+ add_action( 'woocommerce_checkout_after_order_review', [ $this, 'close_div' ] );
+
+ add_action(
+ 'woocommerce_before_single_product_summary',
+ function () {
+ echo '
';
+
+ },
+ 11
+ );
+ add_action( 'woocommerce_after_single_product_summary', [ $this, 'close_div' ] );
+ // Change default for shop columns WooCommerce option.
+ add_filter( 'default_option_woocommerce_catalog_columns', [ $this, 'change_default_shop_cols' ] );
+ }
+ }
+
+ /**
+ * Change default for catalog columns.
+ *
+ * @param int $default default value -> 4.
+ *
+ * @return int
+ */
+ public function change_default_shop_cols( $default ) {
+ return 3;
}
/**
@@ -256,6 +292,12 @@ public function update_woo_width() {
* @return mixed
*/
public function change_breadcrumbs_delimiter( $default ) {
+ if ( neve_is_new_skin() ) {
+ $default['delimiter'] = '
\';
+
+ return $default;
+ }
+
$default['delimiter'] = '
»';
return $default;
@@ -377,9 +419,19 @@ public function sidebar_toggle() {
return;
}
- $button_text = apply_filters( 'neve_filter_woo_sidebar_open_button_text', __( 'Filter', 'neve' ) . '»' );
$button_attrs = apply_filters( 'neve_woocommerce_sidebar_filter_btn_data_attrs', '' );
- echo '';
+
+
+ if ( neve_is_new_skin() ) {
+ echo '';
+
+ return;
+ }
+
+ $button_text = apply_filters( 'neve_filter_woo_sidebar_open_button_text', __( 'Filter', 'neve' ) . '»' );
+ echo '';
}
/**
@@ -502,7 +554,7 @@ public function add_inputs_selectors( $selectors ) {
.woocommerce-page .select2-container--default .select2-selection--single,
.woocommerce-page .woocommerce form .form-row input.input-text,
.woocommerce-page .woocommerce form .form-row textarea,
- .wc-block-product-search form input.wc-block-product-search__field';
+ input.wc-block-product-search__field';
}
/**
@@ -516,7 +568,6 @@ public function add_inputs_spacing_selectors( $selectors ) {
return $selectors . ', .woocommerce-page .select2';
}
-
/**
* Add checkout labels to style.
*
@@ -677,17 +728,16 @@ private function should_render_sidebar_toggle() {
if ( ! is_active_sidebar( 'shop-sidebar' ) ) {
return false;
}
- if ( is_shop() ) {
- $theme_mod = apply_filters( 'neve_sidebar_position', get_theme_mod( 'neve_shop_archive_sidebar_layout', 'right' ) );
- if ( $theme_mod !== 'right' && $theme_mod !== 'left' ) {
- return false;
- }
- }
+
+ $mod = 'neve_shop_archive_sidebar_layout';
if ( is_product() ) {
- $theme_mod = apply_filters( 'neve_sidebar_position', get_theme_mod( 'neve_single_product_sidebar_layout', 'right' ) );
- if ( $theme_mod !== 'right' && $theme_mod !== 'left' ) {
- return false;
- }
+ $mod = 'neve_single_product_sidebar_layout';
+ }
+
+ $default = $this->sidebar_layout_alignment_default( $mod );
+ $theme_mod = apply_filters( 'neve_sidebar_position', get_theme_mod( $mod, $default ) );
+ if ( $theme_mod !== 'right' && $theme_mod !== 'left' ) {
+ return false;
}
return true;
@@ -784,9 +834,11 @@ public function add_buttons_hover_selectors( $selector ) {
}
/**
- * Setup Form Buttons Type
+ * Setup legacy form buttons.
+ *
+ * @since 3.0.0
*/
- public function setup_form_buttons() {
+ private function setup_legacy_form_buttons() {
$form_buttons_type = get_theme_mod( 'neve_form_button_type', 'primary' );
if ( $form_buttons_type === 'primary' ) {
add_filter(
@@ -829,4 +881,39 @@ public function setup_form_buttons() {
1
);
}
+
+ /**
+ * Setup Form Buttons Type
+ */
+ public function setup_form_buttons() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_form_buttons();
+
+ return;
+ }
+ add_filter(
+ 'neve_selectors_' . Config::CSS_SELECTOR_FORM_BUTTON,
+ function ( $selectors ) {
+ return $selectors . '
+ ,#review_form #respond input#submit,
+ .woocommerce-cart .wc-proceed-to-checkout a.checkout-button,
+ .woocommerce-checkout #payment .place-order button#place_order,
+ .woocommerce-account .woocommerce [type="submit"]';
+ },
+ 10,
+ 1
+ );
+ add_filter(
+ 'neve_selectors_' . Config::CSS_SELECTOR_FORM_BUTTON_HOVER,
+ function ( $selectors ) {
+ return $selectors . '
+ ,#review_form #respond input#submit:hover,
+ .woocommerce-cart .wc-proceed-to-checkout a.checkout-button:hover,
+ .woocommerce-checkout #payment .place-order button#place_order:hover,
+ .woocommerce-account .woocommerce [type="submit"]:hover';
+ },
+ 10,
+ 1
+ );
+ }
}
diff --git a/inc/core/admin.php b/inc/core/admin.php
index b3c3ac7868..7932318740 100644
--- a/inc/core/admin.php
+++ b/inc/core/admin.php
@@ -10,6 +10,8 @@
namespace Neve\Core;
+use Neve\Core\Settings\Mods_Migrator;
+
/**
* Class Admin
*
@@ -70,6 +72,158 @@ function () {
add_filter( 'all_plugins', array( $this, 'change_plugin_names' ) );
add_action( 'after_switch_theme', array( $this, 'migrate_options' ) );
+
+ add_action( 'init', [ $this, 'run_skin_and_builder_switches' ] );
+ add_filter( 'ti_tpc_theme_mods_pre_import', [ $this, 'migrate_theme_mods_for_new_skin' ] );
+
+ add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
+ add_filter( 'neve_pro_react_controls_localization', [ $this, 'adapt_conditional_headers' ] );
+ if ( class_exists( '\Neve_Pro\Modules\Header_Footer_Grid\Customizer\Conditional_Headers' ) ) {
+ \Neve_Pro\Modules\Header_Footer_Grid\Customizer\Conditional_Headers::$theme_mods_keys[] = 'hfg_header_layout_v2';
+ }
+ }
+
+ /**
+ * Switch to the new 3.0 features.
+ *
+ * @return void
+ *
+ * @since 3.0.0
+ */
+ public function run_skin_and_builder_switches() {
+ $flag = 'neve_ran_migrations';
+
+ if ( get_theme_mod( $flag ) === true ) {
+ return;
+ }
+
+ set_theme_mod( $flag, true );
+
+ if ( neve_had_old_hfb() ) {
+ set_theme_mod( 'neve_migrated_builders', false );
+ }
+
+ $all_mods = get_theme_mods();
+
+ $mods = [
+ 'hfg_header_layout',
+ 'hfg_footer_layout',
+ 'neve_blog_archive_layout',
+ 'neve_headings_font_family',
+ 'neve_body_font_family',
+ 'neve_global_colors',
+ 'neve_button_appearance',
+ 'neve_secondary_button_appearance',
+ 'neve_typeface_general',
+ 'neve_form_fields_padding',
+ 'neve_default_sidebar_layout',
+ 'neve_advanced_layout_options',
+ ];
+
+ $should_switch = false;
+ foreach ( $mods as $mod_to_check ) {
+ if ( isset( $all_mods[ $mod_to_check ] ) ) {
+ $should_switch = true;
+ break;
+ }
+ }
+
+ if ( ! $should_switch ) {
+ return;
+ }
+
+ set_theme_mod( 'neve_new_skin', 'old' );
+ set_theme_mod( 'neve_had_old_skin', true );
+ }
+
+ /**
+ * Filter out old HFG values if the new builder is active.
+ *
+ * @param array $theme_mods the theme mods array.
+ *
+ * @return array
+ * @since 3.0.0
+ */
+ public function migrate_theme_mods_for_new_skin( $theme_mods ) {
+ if ( ! neve_is_new_skin() ) {
+ return $theme_mods;
+ }
+ $migrator = new Mods_Migrator( $theme_mods );
+ return $migrator->get_migrated_mods();
+ }
+
+ /**
+ * Filter localization data to adapt to the new builder.
+ *
+ * @param array $array localization array.
+ *
+ * @return array
+ */
+ public function adapt_conditional_headers( $array ) {
+ if ( ! neve_is_new_builder() ) {
+ return $array;
+ }
+
+ if ( isset( $array['headerControls'] ) ) {
+ $array['headerControls'][] = 'hfg_header_layout_v2';
+ }
+
+ $array['currentValues'] = [ 'hfg_header_layout_v2' => json_decode( get_theme_mod( 'hfg_header_layout_v2', wp_json_encode( neve_hfg_header_settings() ) ), true ) ];
+
+ return $array;
+ }
+
+ /**
+ * Register Rest Routes.
+ */
+ public function register_rest_routes() {
+ register_rest_route(
+ 'nv/migration',
+ '/new_header_builder',
+ array(
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => [ $this, 'migrate_builders_data' ],
+ 'permission_callback' => function () {
+ return current_user_can( 'manage_options' );
+ },
+ )
+ );
+ }
+
+ /**
+ * Migration routine request.
+ *
+ * @param \WP_REST_Request $request the received request.
+ *
+ * @return \WP_REST_Response
+ *
+ * @since 3.0.0
+ */
+ public function migrate_builders_data( \WP_REST_Request $request ) {
+ $is_rollback = $request->get_header( 'rollback' );
+ $is_dismiss = $request->get_header( 'dismiss' );
+
+ if ( $is_dismiss === 'yes' ) {
+ remove_theme_mod( 'hfg_header_layout' );
+ remove_theme_mod( 'hfg_footer_layout' );
+
+ return new \WP_REST_Response( [ 'success' => true ], 200 );
+ }
+
+ if ( $is_rollback === 'yes' ) {
+ set_theme_mod( 'neve_migrated_builders', false );
+
+ return new \WP_REST_Response( [ 'success' => true ], 200 );
+ }
+
+ $migrator = new Builder_Migrator();
+ $response = $migrator->run();
+
+ if ( $response === true ) {
+ set_theme_mod( 'neve_migrated_builders', true );
+ }
+
+ return new \WP_REST_Response( [ 'success' => $response ], 200 );
}
/**
@@ -159,6 +313,7 @@ public function admin_notice() {
if ( ! empty( $activated_time ) ) {
if ( time() - intval( $activated_time ) > WEEK_IN_SECONDS ) {
update_option( $this->dismiss_notice_key, 'yes' );
+
return;
}
}
@@ -390,7 +545,10 @@ public function enqueue_gutenberg_scripts() {
NEVE_VERSION,
true
);
- wp_enqueue_style( 'neve-gutenberg-style', NEVE_ASSETS_URL . 'css/gutenberg-editor-style' . ( ( NEVE_DEBUG ) ? '' : '.min' ) . '.css', array(), NEVE_VERSION );
+
+ $path = neve_is_new_skin() ? 'gutenberg-editor-style' : 'gutenberg-editor-legacy-style';
+
+ wp_enqueue_style( 'neve-gutenberg-style', NEVE_ASSETS_URL . 'css/' . $path . ( ( NEVE_DEBUG ) ? '' : '.min' ) . '.css', array(), NEVE_VERSION );
}
/**
@@ -462,6 +620,7 @@ public function change_plugin_names( $plugins ) {
if ( array_key_exists( 'otter-blocks/otter-blocks.php', $plugins ) ) {
$plugins['otter-blocks/otter-blocks.php']['Name'] = 'Gutenberg Blocks and Template Library by Neve theme';
}
+
return $plugins;
}
diff --git a/inc/core/builder_migrator.php b/inc/core/builder_migrator.php
new file mode 100644
index 0000000000..52de4c9500
--- /dev/null
+++ b/inc/core/builder_migrator.php
@@ -0,0 +1,478 @@
+ [ 'top', 'main', 'bottom', 'sidebar' ],
+ 'footer' => [ 'top', 'main', 'bottom' ],
+ 'page_header' => [ 'top', 'bottom' ],
+ ];
+
+ /**
+ * Current device migrating.
+ *
+ * @var string
+ */
+ private static $current_device = null;
+
+ /**
+ * Current builder migrating.
+ *
+ * @var string
+ */
+ private static $current_builder = null;
+
+ /**
+ * Current Row Migrating
+ *
+ * @var string
+ */
+ private static $current_row = null;
+
+ /**
+ * Row slots.
+ *
+ * @var string[]
+ */
+ private $row_slots = [ 'left', 'c-left', 'center', 'c-right', 'right' ];
+
+ /**
+ * Migrate row for columned builder.
+ *
+ * @param array $old_row old row values.
+ * @param array $next_row empty row array.
+ *
+ * @return array
+ */
+ private function migrate_columns_row( $old_row, $next_row ) {
+ $items_no = count( $old_row );
+ $columns_setting = 'hfg_' . self::$current_builder . '_layout_' . self::$current_row . '_columns_number';
+
+ if ( $items_no > 5 ) {
+ $items_no = 5;
+ }
+
+ set_theme_mod( $columns_setting, $items_no );
+
+ foreach ( $old_row as $index => $item ) {
+ $slot = $this->row_slots[ $index ];
+
+ $next_row[ $slot ][] = [ 'id' => $item['id'] ];
+ }
+
+ return $next_row;
+ }
+
+ /**
+ * Get the component horizontal alignment on currently migrating device.
+ *
+ * @param string $component_id the component id.
+ *
+ * @return string
+ */
+ private function get_component_alignment( $component_id ) {
+ $default = [
+ 'desktop' => 'left',
+ 'mobile' => 'left',
+ 'tablet' => 'left',
+ ];
+
+ if ( strpos( $component_id, 'primary-menu' ) !== false ) {
+ $default['desktop'] = 'right';
+ }
+
+ $alignment = get_theme_mod( $component_id . '_component_align', $default );
+
+
+ if ( ! isset( $alignment[ self::$current_device ] ) ) {
+ return 'left';
+ }
+
+ $allowed = [ 'left', 'right', 'center' ];
+
+ if ( ! in_array( $alignment[ self::$current_device ], $allowed ) ) {
+ return 'left';
+ }
+
+ return $alignment[ self::$current_device ];
+ }
+
+ /**
+ * Migrate row for fluid builder.
+ *
+ * @param array $next_row empty row array.
+ * @param array $old_items old row values.
+ *
+ * @return array
+ */
+ private function migrate_fluid_row( $next_row, $old_items ) {
+ $items_no = count( $old_items );
+
+ // We have only one item.
+ if ( $items_no === 1 ) {
+ $alignment = $this->get_component_alignment( $old_items[0]['id'] );
+ $width = $old_items[0]['width'];
+ $start_position = $old_items[0]['x'];
+ $next_row_content = [ 'id' => $old_items[0]['id'] ];
+
+ // Item is at start of row. Slot it according to the alignment.
+ // In the previous version, if the item was alone and started at the beginning of the row, it spanned the whole width.
+ if ( $start_position === 0 ) {
+ $next_row[ $alignment ][] = $next_row_content;
+
+ return $next_row;
+ }
+
+ // Item is not at start or end. It spans until the end of the row.
+ if ( $start_position > 0 ) {
+ if ( $alignment === 'right' ) {
+ $next_row['right'][] = $next_row_content;
+
+ return $next_row;
+ }
+
+ $next_row['center'][] = $next_row_content;
+
+ return $next_row;
+ }
+
+ // Item is at end of row. Slot it to the right.
+ if ( $width + $start_position === 12 ) {
+ $next_row['right'][] = $next_row_content;
+
+ return $next_row;
+ }
+
+ // Item is not at start or end. Slot it at center.
+ $next_row['center'][] = $next_row_content;
+
+ return $next_row;
+ }
+
+ // Check if items fill the whole row so we can know if it has gaps.
+ $filled_columns = array_reduce(
+ $old_items,
+ function ( $columns, $item ) {
+ $columns += $item['width'];
+
+ return $columns;
+ }
+ );
+ $no_gaps = $filled_columns === 12;
+
+ // There are no gaps so we will only slot left and right;
+ if ( $no_gaps ) {
+ foreach ( $old_items as $item ) {
+ $width = $item['width'];
+ $start_position = $item['x'];
+ $next_item_content = [ 'id' => $item['id'] ];
+ $alignment = $this->get_component_alignment( $item['id'] );
+
+ // Item touches right. Slot it to the right.
+ if ( $start_position + $width === 12 ) {
+ $next_row['right'][] = $next_item_content;
+ continue;
+ }
+
+ // Item is before center. Slot it to the left only if it isn't aligned to the right.
+ if ( $start_position < 5 ) {
+ if ( $alignment === 'right' ) {
+ $next_row['right'][] = $next_item_content;
+ continue;
+ }
+
+ $next_row['left'][] = $next_item_content;
+ continue;
+ }
+
+ // Item is after center. Slot it to the right.
+ if ( $start_position >= 5 ) {
+ $next_row['right'][] = $next_item_content;
+ continue;
+ }
+ }
+
+ return $next_row;
+ }
+
+ $previous_item = null;
+ $previous_slot = null;
+
+ foreach ( $old_items as $index => $item ) {
+ $width = $item['width'];
+ $start_position = $item['x'];
+ $item_value = [ 'id' => $item['id'] ];
+
+ // Item starts at the most left point. Slot it to the left.
+ if ( $start_position === 0 ) {
+ $next_row['left'][] = $item_value;
+ $previous_item = $item;
+ $previous_slot = 'left';
+ continue;
+ }
+
+ // If we already had an item, check if it touches the new one.
+ if ( $previous_item && $previous_slot ) {
+ // Slot it inside the same slot if it does and there is no gap.
+ if ( $previous_item['x'] + $previous_item['width'] === $start_position ) {
+ $next_row[ $previous_slot ][] = $item_value;
+ $previous_item = $item;
+ continue;
+ }
+
+ // If item is at the end slot it right.
+ // Accounts for previous but where items were extending the whole row when last.
+ if ( $item['x'] + $item['width'] === 12 || $index === count( $old_items ) - 1 ) {
+ $next_row['right'][] = $item_value;
+ $previous_item = $item;
+ $previous_slot = 'right';
+ continue;
+ }
+
+ // Move to center slot if there is a gap and previous slotted was left.
+ if ( $previous_slot === 'left' ) {
+ $next_row['center'][] = $item_value;
+ $previous_item = $item;
+ $previous_slot = 'center';
+ continue;
+ }
+
+ // All other cases fall inside the right slot.
+ $next_row['right'][] = $item_value;
+ $previous_item = $item;
+ $previous_slot = 'right';
+ continue;
+ }
+
+ // Item touches end. Slot it to the right.
+ if ( $start_position + $width === 12 ) {
+ $next_row['right'][] = $item_value;
+ $previous_item = $item;
+ $previous_slot = 'right';
+ continue;
+ }
+
+ // Item is first but doesn't start at the left most point. Slot it to the center.
+ if ( $index === 0 ) {
+ $next_row['center'][] = $item_value;
+ $previous_item = $item;
+ $previous_slot = 'center';
+ continue;
+ }
+
+ // Does not touch sides. Is not nearby previous.
+ $next_row['center'][] = $item_value;
+ $previous_slot = 'center';
+ $previous_item = $item;
+ }
+
+ return $next_row;
+ }
+
+ /**
+ * Migrate single row of the builder.
+ *
+ * @param array $old_items old items inside the row.
+ *
+ * @return array
+ */
+ private function migrate_single_row( $old_items ) {
+ $next_row_value = array_fill_keys( $this->row_slots, [] );
+
+ if ( count( $old_items ) === 0 ) {
+ return $next_row_value;
+ }
+
+ if ( self::$current_builder === 'footer' ) {
+ return $this->migrate_columns_row( $old_items, $next_row_value );
+ }
+
+ return $this->migrate_fluid_row( $next_row_value, $old_items );
+ }
+
+ /**
+ * Migrate single builder value.
+ *
+ * @return bool
+ */
+ private function migrate_single_builder() {
+ $old_value = $this->get_builder_value( self::$current_builder );
+
+ if ( empty( $old_value ) ) {
+ return true;
+ }
+
+ $old_value = json_decode( $old_value, true );
+
+ $new_value = $this->get_new_builder_value_from_old( $old_value );
+
+ set_theme_mod( $this->get_new_builder_mod_slug( self::$current_builder ), wp_json_encode( $new_value ) );
+
+ return true;
+ }
+
+ /**
+ * Migrate old builder value to new format.
+ *
+ * @param array $old_value old builder value.
+ *
+ * @return array|boolean
+ */
+ public function get_new_builder_value_from_old( $old_value ) {
+ if ( ! is_array( $old_value ) ) {
+ return false;
+ }
+
+ $empty_row = array_fill_keys( $this->row_slots, [] );
+
+ $new_value = [];
+
+ foreach ( $old_value as $device => $rows ) {
+
+ self::$current_device = $device;
+
+ // Setup the builders for each device.
+ $new_value[ $device ] = array_fill_keys( $this->builders[ self::$current_builder ], $empty_row );
+
+ // Sidebar is available only on mobile. We should remove it on other devices.
+ if ( $device !== 'mobile' && isset( $new_value[ $device ]['sidebar'] ) ) {
+ unset( $new_value[ $device ]['sidebar'] );
+ }
+
+ foreach ( $rows as $row_slug => $items ) {
+ self::$current_row = $row_slug;
+ // Sidebar row is treated differently.
+ if ( $row_slug === 'sidebar' ) {
+ // Make sure we have an empty array for the sidebar.
+ $new_value[ $device ]['sidebar'] = [];
+ // Push items inside the sidebar.
+ foreach ( $items as $item ) {
+ $new_value[ $device ]['sidebar'][] = [ 'id' => $item['id'] ];
+ }
+ continue;
+ }
+
+ // Proceed with normal migration for each row.
+ $new_value[ $device ][ $row_slug ] = $this->migrate_single_row( $items );
+ }
+ self::$current_row = null;
+ }
+
+ self::$current_device = null;
+
+ return $new_value;
+ }
+
+ /**
+ * Get the new theme mod slug for the specified builder.
+ *
+ * @param string $builder builder slug.
+ *
+ * @return string
+ */
+ private function get_new_builder_mod_slug( $builder ) {
+ return 'hfg_' . $builder . '_layout_v2';
+ }
+
+ /**
+ * Get individual builder value.
+ *
+ * @param string $builder builder slug.
+ *
+ * @return string
+ */
+ private function get_builder_value( $builder ) {
+ return get_theme_mod( 'hfg_' . $builder . '_layout' );
+ }
+
+ /**
+ * Main run function of the migrator.
+ *
+ * @return bool
+ */
+ public function run() {
+ $expected_builders = array_keys( $this->builders );
+
+ foreach ( $expected_builders as $builder ) {
+ // Attempt migration for every builder.
+ self::$current_builder = $builder;
+ $success = $this->migrate_single_builder();
+
+ // If it fails for one, it failed.
+ if ( ! $success ) {
+ return false;
+ }
+ }
+ self::$current_builder = null;
+
+ $success = $this->migrate_conditional_headers();
+
+ if ( ! $success ) {
+ return false;
+ }
+
+ // Migration success.
+ return true;
+ }
+
+ /**
+ * Migrate conditional headers
+ *
+ * @return boolean
+ */
+ private function migrate_conditional_headers() {
+ if ( ! class_exists( '\Neve_Pro\Admin\Custom_Layouts_Cpt' ) ) {
+ return true;
+ }
+
+ if ( ! method_exists( '\Neve_Pro\Admin\Custom_Layouts_Cpt', 'get_conditional_headers' ) ) {
+ return true;
+ }
+
+ $headers = \Neve_Pro\Admin\Custom_Layouts_Cpt::get_conditional_headers();
+
+ self::$current_builder = 'header';
+
+ foreach ( $headers as $cpt_id => $header ) {
+ $decoded = json_decode( $header, true );
+
+ if ( ! is_array( $decoded ) || empty( $decoded ) ) {
+ continue;
+ }
+ if ( ! isset( $decoded['hfg_header_layout'] ) ) {
+ continue;
+ }
+
+
+ $migrated_value = $this->get_new_builder_value_from_old( $decoded['hfg_header_layout'] );
+ $new_mod_key = $this->get_new_builder_mod_slug( 'header' );
+ $decoded[ $new_mod_key ] = $migrated_value;
+
+ update_post_meta( $cpt_id, 'theme-mods', wp_json_encode( $decoded ) );
+ delete_transient( 'custom_layouts_post_map_v2' );
+ }
+
+ self::$current_builder = null;
+
+ return true;
+ }
+}
diff --git a/inc/core/dynamic_css.php b/inc/core/dynamic_css.php
index fb5211f841..60d3930be0 100644
--- a/inc/core/dynamic_css.php
+++ b/inc/core/dynamic_css.php
@@ -99,7 +99,10 @@ public static function minify_css( $css ) {
public function add_customize_vars_tag() {
wp_register_style( 'nv-css-vars', false );
wp_enqueue_style( 'nv-css-vars' );
- wp_add_inline_style( 'nv-css-vars', self::minify_css(':root{' . $this->get_css_vars() . '}' ) );
+
+ $css = ':root{' . $this->get_css_vars() . '}';
+ $css .= apply_filters( 'neve_after_css_root', $css );
+ wp_add_inline_style( 'nv-css-vars', self::minify_css($css ) );
}
/**
@@ -115,6 +118,8 @@ public function get_root_css() {
$css .= '}';
+ $css .= apply_filters( 'neve_after_css_root', $css );
+
return self::minify_css($css);
}
diff --git a/inc/core/front_end.php b/inc/core/front_end.php
index 8b9bb5e321..bb284bf14c 100644
--- a/inc/core/front_end.php
+++ b/inc/core/front_end.php
@@ -21,8 +21,6 @@
*/
class Front_End {
-
-
/**
* Theme setup.
*/
@@ -286,13 +284,17 @@ public function enqueue_scripts() {
*/
private function add_styles() {
if ( class_exists( 'WooCommerce', false ) ) {
- wp_register_style( 'neve-woocommerce', NEVE_ASSETS_URL . 'css/woocommerce' . ( ( NEVE_DEBUG ) ? '' : '.min' ) . '.css', array(), apply_filters( 'neve_version_filter', NEVE_VERSION ) );
+ $style_path = neve_is_new_skin() ? 'css/woocommerce' : 'css/woocommerce-legacy';
+
+ wp_register_style( 'neve-woocommerce', NEVE_ASSETS_URL . $style_path . ( ( NEVE_DEBUG ) ? '' : '.min' ) . '.css', array(), apply_filters( 'neve_version_filter', NEVE_VERSION ) );
wp_style_add_data( 'neve-woocommerce', 'rtl', 'replace' );
wp_style_add_data( 'neve-woocommerce', 'suffix', '.min' );
wp_enqueue_style( 'neve-woocommerce' );
}
- wp_register_style( 'neve-style', get_template_directory_uri() . '/style-main' . ( ( NEVE_DEBUG ) ? '' : '.min' ) . '.css', array(), apply_filters( 'neve_version_filter', NEVE_VERSION ) );
+ $style_path = neve_is_new_skin() ? '/style-main' : '/assets/css/style-legacy';
+
+ wp_register_style( 'neve-style', get_template_directory_uri() . $style_path . ( ( NEVE_DEBUG ) ? '' : '.min' ) . '.css', array(), apply_filters( 'neve_version_filter', NEVE_VERSION ) );
wp_style_add_data( 'neve-style', 'rtl', 'replace' );
wp_style_add_data( 'neve-style', 'suffix', '.min' );
wp_enqueue_style( 'neve-style' );
diff --git a/inc/core/settings/config.php b/inc/core/settings/config.php
index 1f95ea0a8b..1526902269 100644
--- a/inc/core/settings/config.php
+++ b/inc/core/settings/config.php
@@ -79,6 +79,27 @@ class Config {
const MODS_FORM_FIELDS_COLOR = 'neve_input_text_color';
const MODS_FORM_FIELDS_LABELS_TYPEFACE = 'neve_label_typeface';
+ const MODS_ARCHIVE_POST_META_AUTHOR_AVATAR_SIZE = 'neve_author_avatar_size';
+ const MODS_SINGLE_POST_META_AUTHOR_AVATAR_SIZE = 'neve_single_post_avatar_size';
+ const MODS_SINGLE_POST_ELEMENTS_SPACING = 'neve_single_post_elements_spacing';
+
+ const MODS_POST_COVER_HEIGHT = 'neve_post_cover_height';
+ const MODS_POST_COVER_PADDING = 'neve_post_cover_padding';
+ const MODS_POST_COVER_BACKGROUND_COLOR = 'neve_post_cover_background_color';
+ const MODS_POST_COVER_OVERLAY_OPACITY = 'neve_post_cover_overlay_opacity';
+ const MODS_POST_COVER_TEXT_COLOR = 'neve_post_cover_text_color';
+ const MODS_POST_COVER_BLEND_MODE = 'neve_post_cover_blend_mode';
+ const MODS_POST_COVER_TITLE_POSITION = 'neve_post_title_position';
+ const MODS_POST_COVER_BOXED_TITLE_PADDING = 'neve_post_cover_title_boxed_padding';
+ const MODS_POST_COVER_BOXED_TITLE_BACKGROUND = 'neve_post_cover_title_boxed_background_color';
+
+ const MODS_POST_COMMENTS_PADDING = 'neve_comments_boxed_padding';
+ const MODS_POST_COMMENTS_BACKGROUND_COLOR = 'neve_comments_boxed_background_color';
+ const MODS_POST_COMMENTS_TEXT_COLOR = 'neve_comments_boxed_text_color';
+ const MODS_POST_COMMENTS_FORM_PADDING = 'neve_comments_form_boxed_padding';
+ const MODS_POST_COMMENTS_FORM_BACKGROUND_COLOR = 'neve_comments_form_boxed_background_color';
+ const MODS_POST_COMMENTS_FORM_TEXT_COLOR = 'neve_comments_form_boxed_text_color';
+
const CSS_PROP_BORDER_COLOR = 'border-color';
const CSS_PROP_BACKGROUND_COLOR = 'background-color';
const CSS_PROP_COLOR = 'color';
@@ -112,6 +133,9 @@ class Config {
const CSS_PROP_TEXT_TRANSFORM = 'text-transform';
const CSS_PROP_FONT_FAMILY = 'font-family';
const CSS_PROP_BOX_SHADOW = 'box-shadow';
+ const CSS_PROP_MIX_BLEND_MODE = 'mix-blend-mode';
+ const CSS_PROP_OPACITY = 'opacity';
+ const CSS_PROP_GRID_TEMPLATE_COLS = 'grid-template-columns';
const CSS_PROP_CUSTOM_BTN_TYPE = 'btn-type';
const CSS_PROP_CUSTOM_FONT_WEIGHT_FAMILY = 'btn-type';
@@ -144,9 +168,18 @@ class Config {
const CSS_SELECTOR_FORM_INPUTS = 'form_inputs';
const CSS_SELECTOR_FORM_INPUTS_LABELS = 'form_labels';
const CSS_SELECTOR_FORM_BUTTON = 'form_buttons';
+ const CSS_SELECTOR_FORM_BUTTON_HOVER = 'form_buttons_hover';
const CSS_SELECTOR_FORM_SEARCH_INPUTS = 'search_form_inputs';
const CONTENT_DEFAULT_PADDING = 30;
+
+ /**
+ * Keys for directional values.
+ *
+ * @var string[]
+ */
+ public static $directional_keys = [ 'top', 'right', 'bottom', 'left' ];
+
/**
* Holds tag->css selector mapper.
*
@@ -184,7 +217,8 @@ class Config {
self::CSS_SELECTOR_FORM_INPUTS_WITH_SPACING => 'form:not([role="search"]):not(.woocommerce-cart-form):not(.woocommerce-ordering):not(.cart) input:read-write:not(#coupon_code), form textarea, form select, .widget select',
self::CSS_SELECTOR_FORM_INPUTS => 'form input:read-write, form textarea, form select, form select option, form.wp-block-search input.wp-block-search__input, .widget select',
self::CSS_SELECTOR_FORM_INPUTS_LABELS => 'form label, .wpforms-container .wpforms-field-label',
- self::CSS_SELECTOR_FORM_BUTTON => 'form input[type="submit"]',
+ self::CSS_SELECTOR_FORM_BUTTON => 'form input[type="submit"], form button[type="submit"], form *[value*="ubmit"], #comments input[type="submit"]',
+ self::CSS_SELECTOR_FORM_BUTTON_HOVER => 'form input[type="submit"]:hover, form button[type="submit"]:hover, form *[value*="ubmit"]:hover, #comments input[type="submit"]:hover',
self::CSS_SELECTOR_FORM_SEARCH_INPUTS => 'form.search-form input:read-write',
];
}
diff --git a/inc/core/settings/mods.php b/inc/core/settings/mods.php
index c880d57ef5..7fd0cd7ad4 100644
--- a/inc/core/settings/mods.php
+++ b/inc/core/settings/mods.php
@@ -64,7 +64,7 @@ public static function get( $key, $default = false ) {
return isset( $value[ $subkey ] ) ? $value[ $subkey ] : $default;
}
- /***
+ /**
* Forced defaults.
*
* @param string $key Key name.
@@ -78,7 +78,7 @@ private static function defaults( $key ) {
case Config::MODS_BUTTON_PRIMARY_STYLE:
return neve_get_button_appearance_default();
case Config::MODS_BUTTON_SECONDARY_STYLE:
- return neve_get_button_appearance_default( 'secondary_button' );
+ return neve_get_button_appearance_default( 'secondary' );
case Config::MODS_TYPEFACE_GENERAL:
$defaults = self::get_typography_defaults(
[
@@ -203,4 +203,200 @@ public static function to_json( $key, $default = false, $as_array = true ) {
return json_decode( self::get( $key, $default ), $as_array );
}
+ /**
+ * Get alternative mod default.
+ *
+ * @param string $key theme mod key.
+ *
+ * @return string | array
+ */
+ public static function get_alternative_mod_default( $key ) {
+ $new = neve_is_new_skin();
+ $headings_generic_setup = [
+ 'fontWeight' => $new ? '700' : '600',
+ 'textTransform' => 'none',
+ 'letterSpacing' => [
+ 'mobile' => 0,
+ 'tablet' => 0,
+ 'desktop' => 0,
+ ],
+ ];
+ $headings_sufix = [
+ 'mobile' => $new ? 'px' : 'em',
+ 'tablet' => $new ? 'px' : 'em',
+ 'desktop' => $new ? 'px' : 'em',
+ ];
+ switch ( $key ) {
+ case Config::MODS_FONT_GENERAL:
+ return $new ? 'Arial, Helvetica, sans-serif' : false;
+ case Config::MODS_TYPEFACE_GENERAL:
+ return [
+ 'fontSize' => [
+ 'suffix' => [
+ 'mobile' => 'px',
+ 'tablet' => 'px',
+ 'desktop' => 'px',
+ ],
+ 'mobile' => 15,
+ 'tablet' => 16,
+ 'desktop' => 16,
+ ],
+ 'lineHeight' => [
+ 'mobile' => 1.6,
+ 'tablet' => 1.6,
+ 'desktop' => $new ? 1.7 : 1.6,
+ ],
+ 'letterSpacing' => [
+ 'mobile' => 0,
+ 'tablet' => 0,
+ 'desktop' => 0,
+ ],
+ 'fontWeight' => '400',
+ 'textTransform' => 'none',
+ ];
+ case Config::MODS_TYPEFACE_H1:
+ return array_merge(
+ $headings_generic_setup,
+ array(
+ 'fontSize' => [
+ 'mobile' => $new ? '36' : '1.5',
+ 'tablet' => $new ? '38' : '1.5',
+ 'desktop' => $new ? '40' : '2',
+ 'suffix' => $headings_sufix,
+ ],
+ 'lineHeight' => [
+ 'mobile' => $new ? 1.2 : 1.6,
+ 'tablet' => $new ? 1.2 : 1.6,
+ 'desktop' => $new ? 1.1 : 1.6,
+ ],
+ )
+ );
+ case Config::MODS_TYPEFACE_H2:
+ return array_merge(
+ $headings_generic_setup,
+ array(
+ 'fontSize' => [
+ 'mobile' => $new ? '28' : '1.3',
+ 'tablet' => $new ? '30' : '1.3',
+ 'desktop' => $new ? '32' : '1.75',
+ 'suffix' => $headings_sufix,
+ ],
+ 'lineHeight' => [
+ 'mobile' => $new ? 1.3 : 1.6,
+ 'tablet' => $new ? 1.2 : 1.6,
+ 'desktop' => $new ? 1.2 : 1.6,
+ ],
+ )
+ );
+ case Config::MODS_TYPEFACE_H3:
+ return array_merge(
+ $headings_generic_setup,
+ array(
+ 'fontSize' => [
+ 'mobile' => $new ? '24' : '1.1',
+ 'tablet' => $new ? '26' : '1.1',
+ 'desktop' => $new ? '28' : '1.5',
+ 'suffix' => $headings_sufix,
+ ],
+ 'lineHeight' => [
+ 'mobile' => $new ? 1.4 : 1.6,
+ 'tablet' => $new ? 1.4 : 1.6,
+ 'desktop' => $new ? 1.4 : 1.6,
+ ],
+ )
+ );
+ case Config::MODS_TYPEFACE_H4:
+ return array_merge(
+ $headings_generic_setup,
+ array(
+ 'fontSize' => [
+ 'mobile' => $new ? '20' : '1',
+ 'tablet' => $new ? '22' : '1',
+ 'desktop' => $new ? '24' : '1.25',
+ 'suffix' => $headings_sufix,
+ ],
+ 'lineHeight' => [
+ 'mobile' => 1.6,
+ 'tablet' => $new ? 1.5 : 1.6,
+ 'desktop' => $new ? 1.5 : 1.6,
+ ],
+ )
+ );
+ case Config::MODS_TYPEFACE_H5:
+ return array_merge(
+ $headings_generic_setup,
+ array(
+ 'fontSize' => [
+ 'mobile' => $new ? '16' : '0.75',
+ 'tablet' => $new ? '18' : '0.75',
+ 'desktop' => $new ? '20' : '1',
+ 'suffix' => $headings_sufix,
+ ],
+ 'lineHeight' => [
+ 'mobile' => 1.6,
+ 'tablet' => 1.6,
+ 'desktop' => 1.6,
+ ],
+ )
+ );
+ case Config::MODS_TYPEFACE_H6:
+ return array_merge(
+ $headings_generic_setup,
+ array(
+ 'fontSize' => [
+ 'mobile' => $new ? '14' : '0.75',
+ 'tablet' => $new ? '14' : '0.75',
+ 'desktop' => $new ? '16' : '1',
+ 'suffix' => $headings_sufix,
+ ],
+ 'lineHeight' => [
+ 'mobile' => 1.6,
+ 'tablet' => 1.6,
+ 'desktop' => 1.6,
+ ],
+ )
+ );
+ case Config::MODS_BUTTON_PRIMARY_PADDING:
+ $device = $new ? [
+ 'top' => 13,
+ 'right' => 15,
+ 'bottom' => 13,
+ 'left' => 15,
+ ] : [
+ 'top' => 8,
+ 'right' => 12,
+ 'bottom' => 8,
+ 'left' => 12,
+ ];
+
+ return [
+ 'desktop' => $device,
+ 'tablet' => $device,
+ 'mobile' => $device,
+ 'desktop-unit' => 'px',
+ 'tablet-unit' => 'px',
+ 'mobile-unit' => 'px',
+ ];
+ case Config::MODS_FORM_FIELDS_SPACING:
+ return $new ? 40 : 10;
+ case Config::MODS_FORM_FIELDS_PADDING:
+ return [
+ 'top' => $new ? 10 : 7,
+ 'bottom' => $new ? 10 : 7,
+ 'left' => 12,
+ 'right' => 12,
+ 'unit' => 'px',
+ ];
+ case Config::MODS_FORM_FIELDS_BORDER_WIDTH:
+ return [
+ 'top' => $new ? 2 : 1,
+ 'right' => $new ? 2 : 1,
+ 'left' => $new ? 2 : 1,
+ 'bottom' => $new ? 2 : 1,
+ 'unit' => 'px',
+ ];
+ default:
+ return false;
+ }
+ }
}
diff --git a/inc/core/settings/mods_migrator.php b/inc/core/settings/mods_migrator.php
new file mode 100644
index 0000000000..4cde93e8d1
--- /dev/null
+++ b/inc/core/settings/mods_migrator.php
@@ -0,0 +1,236 @@
+ [
+ 'neve_h6_font_size' => 'fontSize',
+ 'neve_h6_line_height' => 'lineHeight',
+ ],
+ Config::MODS_TYPEFACE_H5 => [
+ 'neve_h5_font_size' => 'fontSize',
+ 'neve_h5_line_height' => 'lineHeight',
+ ],
+ Config::MODS_TYPEFACE_H4 => [
+ 'neve_h4_font_size' => 'fontSize',
+ 'neve_h4_line_height' => 'lineHeight',
+ ],
+ Config::MODS_TYPEFACE_H3 => [
+ 'neve_h3_font_size' => 'fontSize',
+ 'neve_h3_line_height' => 'lineHeight',
+ ],
+ Config::MODS_TYPEFACE_H2 => [
+ 'neve_h2_font_size' => 'fontSize',
+ 'neve_h2_line_height' => 'lineHeight',
+ ],
+ Config::MODS_TYPEFACE_H1 => [
+ 'neve_h1_font_size' => 'fontSize',
+ 'neve_h1_line_height' => 'lineHeight',
+ ],
+ ];
+
+ /**
+ * Builders
+ *
+ * @var string[]
+ */
+ private $builder_map = [ 'hfg_header_layout', 'hfg_footer_layout', 'hfg_page_header_layout' ];
+
+ /**
+ * Mods array.
+ *
+ * @var array
+ */
+ private $mods = [];
+
+ /**
+ * Mods to migrate.
+ *
+ * @var array
+ */
+ private $mods_to_migrate_to = [
+ Config::MODS_TYPEFACE_GENERAL,
+ Config::MODS_TYPEFACE_H1,
+ Config::MODS_TYPEFACE_H2,
+ Config::MODS_TYPEFACE_H3,
+ Config::MODS_TYPEFACE_H4,
+ Config::MODS_TYPEFACE_H5,
+ Config::MODS_TYPEFACE_H6,
+ ];
+
+ /**
+ * Mods_Migrator constructor.
+ *
+ * @param array $incoming_mods the incoming mods from import.
+ */
+ public function __construct( $incoming_mods ) {
+ $this->mods = $incoming_mods;
+ }
+
+ /**
+ * Get migrated mods.
+ *
+ * @return array
+ */
+ public function get_migrated_mods() {
+ $this->migrate_mods();
+ $this->attempt_builders_migration();
+ $this->unset_unused();
+
+ return $this->mods;
+ }
+
+ /**
+ * Migrate mods.
+ *
+ * @return void
+ */
+ private function migrate_mods() {
+ foreach ( $this->mods_to_migrate_to as $new_mod_key ) {
+ // If the new mod is already in use, we don't need to migrate anything.
+ if ( isset( $this->mods[ $new_mod_key ] ) ) {
+ continue;
+ }
+
+ $next_value = $this->transform_to_new_value( $new_mod_key );
+
+ if ( empty( $next_value ) ) {
+ continue;
+ }
+
+ $this->mods[ $new_mod_key ] = $next_value;
+ }
+ }
+
+ /**
+ * Attempt to migrate builders.
+ *
+ * @return void
+ */
+ private function attempt_builders_migration() {
+ $hfg_migrator = new Builder_Migrator();
+
+ foreach ( $this->builder_map as $builder ) {
+ $new_builder_mod = $builder . '_v2';
+ if ( isset( $this->mods[ $new_builder_mod ] ) ) {
+ continue;
+ }
+
+ if ( ! isset( $this->mods[ $builder ] ) ) {
+ continue;
+ }
+
+ $new_value = $hfg_migrator->get_new_builder_value_from_old( json_decode( $this->mods[ $builder ], true ) );
+
+ if ( $new_value === false ) {
+ continue;
+ }
+
+ $this->mods[ $new_builder_mod ] = wp_json_encode( $new_value );
+ unset( $this->mods[ $builder ] );
+ }
+ }
+
+ /**
+ * Get the array of old values that will match the new values.
+ *
+ * @param string $new_mod_key the new mod key.
+ *
+ * @return array
+ */
+ private function transform_to_new_value( $new_mod_key ) {
+ $defaults = Mods::get_alternative_mod_default( $new_mod_key );
+
+ switch ( $new_mod_key ) {
+ case Config::MODS_TYPEFACE_GENERAL:
+ $old_value = $this->get_composed_value(
+ [
+ 'neve_body_line_height' => 'lineHeight',
+ 'neve_body_letter_spacing' => 'letterSpacing',
+ 'neve_body_font_weight' => 'fontWeight',
+ 'neve_body_text_transform' => 'textTransform',
+ 'neve_body_font_size' => 'fontSize',
+ ]
+ );
+
+ return array_merge( $defaults, $old_value );
+
+ case Config::MODS_TYPEFACE_H1:
+ case Config::MODS_TYPEFACE_H2:
+ case Config::MODS_TYPEFACE_H3:
+ case Config::MODS_TYPEFACE_H4:
+ case Config::MODS_TYPEFACE_H5:
+ case Config::MODS_TYPEFACE_H6:
+ $partial = [
+ 'neve_headings_line_height' => 'lineHeight',
+ 'neve_headings_letter_spacing' => 'letterSpacing',
+ 'neve_headings_font_weight' => 'fontWeight',
+ 'neve_headings_text_transform' => 'textTransform',
+ ];
+
+ $keys = array_merge( $partial, self::LEGACY_HEADINGS[ $new_mod_key ] );
+ $old_value = $this->get_composed_value( $keys );
+
+ return array_merge( $defaults, $old_value );
+ }
+ }
+
+ /**
+ * Get the old values for the new mod.
+ *
+ * @param array $args args array [$old_mod => $key_on_new_mod].
+ *
+ * @return array
+ */
+ private function get_composed_value( $args ) {
+ $new_values = [];
+ foreach ( $args as $old_mod => $new_key ) {
+ if ( ! isset( $this->mods[ $old_mod ] ) ) {
+ continue;
+ }
+
+ $final_value = $this->mods[ $old_mod ];
+ // If the value is either font-size or line-height we should migrate it from previous json format.
+ if ( in_array( $new_key, [ 'fontSize', 'lineHeight' ] ) ) {
+ $final_value = json_decode( $final_value, true );
+ }
+
+ $new_values[ $new_key ] = $final_value;
+
+ unset( $this->mods[ $old_mod ] );
+ }
+
+ return $new_values;
+ }
+
+ /**
+ * Unset unused theme mods.
+ *
+ * @return void
+ */
+ private function unset_unused() {
+ $to_remove = array_merge( $this->builder_map, [ 'background_color' ] );
+
+ foreach ( $to_remove as $slug ) {
+ if ( isset( $this->mods[ $slug ] ) ) {
+ unset( $this->mods[ $slug ] );
+ }
+ }
+ }
+}
diff --git a/inc/core/styles/css_prop.php b/inc/core/styles/css_prop.php
index 1d44ccbe2e..93c6120a90 100644
--- a/inc/core/styles/css_prop.php
+++ b/inc/core/styles/css_prop.php
@@ -14,12 +14,43 @@ class Css_Prop {
*/
public static function minus_100( $css_prop, $value, $meta, $device ) {
return sprintf( "%s: %s%s;",
- ($css_prop),
- (100 - $value),
+ ( $css_prop ),
+ ( 100 - $value ),
isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'px'
);
}
+ /**
+ * Get suffix from controls that store data in the following format:
+ * { desktop: value, tablet: value, mobile: value, deskotp-unit: px, tablet-unit: px, mobile-unit: px }
+ *
+ * @param array $meta Subscribers meta data.
+ */
+ public static function get_unit_responsive( $meta, $device ) {
+ $all_value = Mods::get( $meta['key'], isset( $meta[ Dynamic_Selector::META_DEFAULT ] ) ? $meta[ Dynamic_Selector::META_DEFAULT ] : null );
+ $suffix = 'px';
+ if ( isset( $all_value[ $device . '-unit' ] ) ) {
+ $suffix = $all_value[ $device . '-unit' ];
+ } elseif ( isset( $all_value['unit'] ) ) {
+ $suffix = $all_value['unit'];
+ }
+
+ return $suffix;
+ }
+
+ /**
+ * Get suffix from controls that store data in the following format:
+ * { desktop: value, tablet: value, mobile: value, suffix : { deskop: px, tablet: px, mobile: px} }
+ *
+ * @param array $meta Subscribers meta data.
+ */
+ public static function get_suffix_responsive( $meta, $device ) {
+ $default_value = isset( $meta[ Dynamic_Selector::META_DEFAULT ] ) ? $meta[ Dynamic_Selector::META_DEFAULT ] : null;
+ $all_value = isset( $meta[ Dynamic_Selector::META_AS_JSON ] ) ? Mods::to_json( $meta['key'], $default_value ) : Mods::get( $meta['key'], $default_value );
+
+ return isset( $all_value['suffix'][ $device ] ) ? $all_value['suffix'][ $device ] : ( isset( $all_value['suffix'] ) && is_string( $all_value['suffix'] ) ? $all_value['suffix'] : 'px' );;
+ }
+
/**
* Transform rule meta into CSS rule string.
*
@@ -32,32 +63,34 @@ public static function minus_100( $css_prop, $value, $meta, $device ) {
*/
public static function transform( $css_prop, $value, $meta, $device ) {
//If we have a custom filter, let's call it.
- if ( isset( $meta[ 'filter' ] ) ) {
- if ( is_callable( $meta[ 'filter' ] ) ) {
- return call_user_func_array( $meta[ 'filter' ], [ $css_prop, $value, $meta, $device ] );
+ if ( isset( $meta['filter'] ) ) {
+ if ( is_callable( $meta['filter'] ) ) {
+ return call_user_func_array( $meta['filter'], [ $css_prop, $value, $meta, $device ] );
}
- if ( method_exists( __CLASS__, $meta[ 'filter' ] ) ) {
- return call_user_func_array( [ __CLASS__, $meta[ 'filter' ] ], [ $css_prop, $value, $meta, $device ] );
+ if ( method_exists( __CLASS__, $meta['filter'] ) ) {
+ return call_user_func_array( [ __CLASS__, $meta['filter'] ], [ $css_prop, $value, $meta, $device ] );
}
return '';
}
+ if ( isset( $meta['override'] ) ) {
+ return sprintf( '%s:%s;', $css_prop, $meta['override'] );
+ }
switch ( $css_prop ) {
case Config::CSS_PROP_BACKGROUND_COLOR:
case Config::CSS_PROP_COLOR:
case Config::CSS_PROP_FILL_COLOR:
case Config::CSS_PROP_BORDER_COLOR:
- $mode = (false === strpos( $value, 'rgba' )) ? 'hex' : 'rgba';
- $is_var = (strpos( $value, 'var' ) !== false);
+ $mode = ( false === strpos( $value, 'rgba' ) ) ? 'hex' : 'rgba';
+ $is_var = ( strpos( $value, 'var' ) !== false );
if ( $mode === 'hex' && ! $is_var ) {
$value = strpos( $value, "#" ) === 0 ? $value : '#' . $value;
}
- return sprintf( "%s: %s%s;", ($css_prop), neve_sanitize_colors( $value ), isset( $meta[ 'important' ] ) && $meta[ 'important' ] ? '!important' : '' );
- break;
+ return sprintf( "%s: %s%s;", ( $css_prop ), neve_sanitize_colors( $value ), isset( $meta['important'] ) && $meta['important'] ? '!important' : '' );
case Config::CSS_PROP_MAX_WIDTH:
case Config::CSS_PROP_WIDTH:
case Config::CSS_PROP_FLEX_BASIS:
@@ -72,18 +105,16 @@ public static function transform( $css_prop, $value, $meta, $device ) {
case Config::CSS_PROP_LEFT:
case Config::CSS_PROP_RIGHT:
$suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'px';
- if ( $suffix === 'responsive_suffix' ) {
- $all_value = Mods::get( $meta[ 'key' ], isset( $meta[ Dynamic_Selector::META_DEFAULT ] ) ? $meta[ Dynamic_Selector::META_DEFAULT ] : null );
- $suffix = isset( $all_value[ 'suffix' ] ) ? $all_value[ 'suffix' ][ $device ] : (isset( $all_value[ 'suffix' ] ) ? $all_value[ 'suffix' ] : 'px');;
+ if ( $suffix === 'responsive_suffix' ) {
+ $suffix = self::get_suffix_responsive( $meta, $device );
}
return sprintf( "%s: %s%s;",
- ($css_prop),
- ($value),
+ ( $css_prop ),
+ ( $value ),
$suffix
);
- break;
case Config::CSS_PROP_BORDER_RADIUS:
case Config::CSS_PROP_BORDER_WIDTH:
case Config::CSS_PROP_PADDING:
@@ -100,29 +131,24 @@ public static function transform( $css_prop, $value, $meta, $device ) {
}
if ( $suffix === 'responsive_unit' ) {
- $all_value = Mods::get( $meta['key'], isset( $meta[ Dynamic_Selector::META_DEFAULT ] ) ? $meta[ Dynamic_Selector::META_DEFAULT ] : null );
- $suffix = 'px';
- if ( isset( $all_value[ $device . '-unit' ] ) ) {
- $suffix = $all_value[ $device . '-unit' ];
- } elseif ( isset( $all_value['unit'] ) ) {
- $suffix = $all_value['unit'];
- }
+ $suffix = self::get_unit_responsive( $meta, $device );
}
+
$non_empty_values = array_filter( $value, 'strlen' );
if ( count( $non_empty_values ) === 4 ) {
return sprintf( "%s:%s%s %s%s %s%s %s%s;",
$css_prop,
- (int) $value[ 'top' ],
+ (int) $value['top'],
$suffix,
- (int) $value[ 'right' ],
+ (int) $value['right'],
$suffix,
- (int) $value[ 'bottom' ],
+ (int) $value['bottom'],
$suffix,
- (int) $value[ 'left' ],
+ (int) $value['left'],
$suffix
);
}
- $rule = '';
+ $rule = '';
$patterns = [
Config::CSS_PROP_MARGIN => 'margin-%s',
Config::CSS_PROP_PADDING => 'padding-%s',
@@ -135,20 +161,19 @@ public static function transform( $css_prop, $value, $meta, $device ) {
],
];
- if( isset( $non_empty_values['unit'] ) ) {
- unset ($non_empty_values['unit']);
+ if ( isset( $non_empty_values['unit'] ) ) {
+ unset ( $non_empty_values['unit'] );
}
foreach ( $non_empty_values as $position => $position_value ) {
$rule .= sprintf( "%s:%s%s;",
- sprintf( (is_array( $patterns[ $css_prop ] ) ? $patterns[ $css_prop ][ $position ] : $patterns[ $css_prop ]), $position ),
+ sprintf( ( is_array( $patterns[ $css_prop ] ) ? $patterns[ $css_prop ][ $position ] : $patterns[ $css_prop ] ), $position ),
(int) $position_value,
$suffix
);
}
return $rule;
- break;
//Line height uses an awkward format saved, and we can't define it as responsive because we would need to use the suffix part.
case Config::CSS_PROP_LINE_HEIGHT:
case Config::CSS_PROP_FONT_SIZE:
@@ -156,46 +181,178 @@ public static function transform( $css_prop, $value, $meta, $device ) {
$suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'em';
// We consider the provided suffix as default, in case that we have a responsive setting with responsive suffix.
if ( isset( $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) && $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) {
- $all_value = Mods::get( $meta[ 'key' ] );
- $suffix = isset( $all_value[ 'suffix' ][ $device ] ) ? $all_value[ 'suffix' ][ $device ] : (isset( $all_value[ 'suffix' ] ) ? $all_value[ 'suffix' ] : $suffix);
+ $all_value = Mods::get( $meta['key'] );
+ $suffix = isset( $all_value['suffix'][ $device ] ) ? $all_value['suffix'][ $device ] : ( isset( $all_value['suffix'] ) ? $all_value['suffix'] : $suffix );
}
- return sprintf( ' %s: %s%s; ', $css_prop, $value, $suffix );
- break;
+ return sprintf( ' %s: %s%s;', $css_prop, $value, $suffix );
//Letter spacing has a legacy value of non-responsive which we need to take into consideration.
case Config::CSS_PROP_LETTER_SPACING:
- return sprintf( ' %s: %spx; ', $css_prop, $value );
- break;
+ return sprintf( ' %s: %spx;', $css_prop, $value );
case Config::CSS_PROP_CUSTOM_BTN_TYPE:
if ( $value !== 'outline' ) {
return 'border:none;';
}
return "border:1px solid;";
- break;
case Config::CSS_PROP_FONT_WEIGHT:
- if ( isset( $meta[ 'font' ] ) ) {
- $font = strpos( $meta[ 'font' ], 'mods_' ) === 0 ? Mods::get( str_replace( 'mods_', '', $meta[ 'font' ] ) ) : $meta[ 'font' ];
+ if ( isset( $meta['font'] ) ) {
+ $font = strpos( $meta['font'], 'mods_' ) === 0 ? Mods::get( str_replace( 'mods_', '', $meta['font'] ) ) : $meta['font'];
Font_Manager::add_google_font( $font, strval( $value ) );
}
return sprintf( ' %s: %s;', $css_prop, intval( $value ) );
- break;
case Config::CSS_PROP_FONT_FAMILY:
if ( $value === 'default' ) {
return '';
}
Font_Manager::add_google_font( $value );
- return sprintf( ' %s: %s, var(--nv-fallback-ff); ', $css_prop, $value );
-
- break;
+ return sprintf( ' %s: %s, var(--nv-fallback-ff);', $css_prop, $value );
case Config::CSS_PROP_TEXT_TRANSFORM:
case Config::CSS_PROP_BOX_SHADOW:
- return sprintf( ' %s: %s; ', $css_prop, $value );
+ case Config::CSS_PROP_MIX_BLEND_MODE:
+ case Config::CSS_PROP_OPACITY:
+ case Config::CSS_PROP_GRID_TEMPLATE_COLS:
+ return sprintf( ' %s: %s;', $css_prop, $value );
+ default:
+ $is_font_family_var = strpos( strtolower( $css_prop ), 'fontfamily' ) > - 1;
+
+ if ( $is_font_family_var ) {
+ Font_Manager::add_google_font( $value );
+ }
+
+ if ( isset( $meta['directional-prop'] ) ) {
+ return self::transform_directional_prop( $meta, $device, $value, $css_prop, $meta['directional-prop'] );
+ }
+
+ $suffix = self::get_suffix( $meta, $device, $value, $css_prop );
+
+ return sprintf( ' %s: %s%s;', $css_prop, $value, $suffix );
break;
}
return '';
}
+
+ /**
+ * Get suffix for generic settings.
+ *
+ * @param array $meta Meta array.
+ * @param string $device Current device.
+ * @param string $value Value.
+ *
+ * @return string
+ *
+ * @since 3.0.0
+ */
+ public static function get_suffix( $meta, $device, $value, $css_prop ) {
+ $suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : '';
+
+ // If not responsive, most controls use 'unit' key inside value.
+ if ( ! isset( $meta['is_responsive'] ) || $meta['is_responsive'] === false ) {
+ $suffix = isset( $value['unit'] ) ? $value['unit'] : $suffix;
+ }
+
+ // If responsive, try to find the suffix.
+ if ( isset( $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) && $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) {
+ $all_value = Mods::get( $meta['key'] );
+ $suffix = isset( $all_value['suffix'][ $device ] ) ? $all_value['suffix'][ $device ] : ( isset( $all_value['suffix'] ) ? $all_value['suffix'] : $suffix );
+ }
+
+ if ( $suffix === 'responsive_unit' ) {
+ $suffix = self::get_unit_responsive( $meta, $device );
+ }
+
+ if ( $suffix === 'responsive_suffix' ) {
+ $suffix = self::get_suffix_responsive( $meta, $device );
+ }
+
+ // Enqueue any google fonts we might be missing.
+ if ( isset ( $meta['font'] ) ) {
+ $font = strpos( $meta['font'], 'mods_' ) === 0 ? Mods::get( str_replace( 'mods_', '', $meta['font'] ) ) : $meta['font'];
+ Font_Manager::add_google_font( $font, strval( $value ) );
+ }
+
+ return $suffix;
+ }
+
+ /**
+ * Transforms the directional properties.
+ *
+ * @param array $meta Meta array.
+ * @param string $device Current device.
+ * @param string $value Value.
+ * @param string $css_prop Css Property.
+ * @param string $type Type of directional property.
+ *
+ * @return string
+ */
+ public static function transform_directional_prop( $meta, $device, $value, $css_prop, $type ) {
+
+ $suffix = self::get_suffix( $meta, $device, $value, $css_prop );
+ $suffix = $suffix ? $suffix : 'px';
+ $template = '';
+
+
+ // Make sure that this is directional, even if an int value is provided.
+ if ( is_int( $value ) ) {
+ $directions = Config::$directional_keys;
+ $value = array_fill_keys( $directions, $value );
+ }
+
+ // If we still don't have an array. Make sure to drop this setting.
+ if ( ! is_array( $value ) ) {
+ return '';
+ }
+
+ // Directional array without any other keys than the actual directions.
+ $filtered = array_filter( $value, function ( $key ) {
+ return in_array( $key, Config::$directional_keys, true );
+ }, ARRAY_FILTER_USE_KEY );
+
+ if ( count( array_unique( $filtered ) ) === 1 ) {
+ if ( absint( $value['top'] ) === 0 ) {
+ $suffix = '';
+ }
+
+ if ( empty( $value['top'] ) && absint( $value['top'] ) !== 0 ) {
+ return '';
+ }
+
+ $template .= $value['top'] . $suffix;
+
+ return $css_prop . ':' . $template . ';';
+ }
+
+ if ( count( array_unique( $filtered ) ) === 2 && $value['top'] === $value['bottom'] && $value['right'] === $value['left'] ) {
+ $top_suffix = absint( $value['top'] ) === 0 ? '' : $suffix;
+ $right_suffix = absint( $value['right'] ) === 0 ? '' : $suffix;
+
+ if ( empty( $value['top'] ) && absint( $value['top'] ) !== 0 && empty( $value['right'] ) && absint( $value['right'] ) ) {
+ return '';
+ }
+
+ $template .= $value['top'] . $top_suffix . ' ' . $value['right'] . $right_suffix;
+
+ return $css_prop . ':' . $template . ';';
+ }
+
+ foreach ( Config::$directional_keys as $direction ) {
+ if ( ! isset( $value[ $direction ] ) || absint( $value[ $direction ] ) === 0 ) {
+ $template .= '0 ';
+
+ continue;
+ }
+ $template .= $value[ $direction ] . $suffix . ' ';
+ }
+
+ if ( empty( $template ) ) {
+ return '';
+ }
+
+ $template = trim( $template ) . ';';
+
+ return $css_prop . ':' . $template . ';';
+ }
}
diff --git a/inc/core/styles/css_vars.php b/inc/core/styles/css_vars.php
new file mode 100644
index 0000000000..6eebd1002b
--- /dev/null
+++ b/inc/core/styles/css_vars.php
@@ -0,0 +1,256 @@
+ [
+ Dynamic_Selector::META_KEY => Config::MODS_CONTAINER_WIDTH,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_DEFAULT => '{ "mobile": 748, "tablet": 992, "desktop": 1170 }',
+ ],
+ ];
+ }
+
+ /**
+ * Get button rules.
+ *
+ * @return array
+ */
+ public function get_button_rules() {
+ $mod_key_primary = Config::MODS_BUTTON_PRIMARY_STYLE;
+ $default_primary = neve_get_button_appearance_default();
+ $mod_key_secondary = Config::MODS_BUTTON_SECONDARY_STYLE;
+ $default_secondary = neve_get_button_appearance_default( 'secondary' );
+
+ $rules = [
+ '--primaryBtnBg' => [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.background',
+ ],
+ '--secondaryBtnBg' => [
+ Dynamic_Selector::META_KEY => $mod_key_secondary . '.background',
+ ],
+ '--primaryBtnHoverBg' => [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.backgroundHover',
+ ],
+ '--secondaryBtnHoverBg' => [
+ Dynamic_Selector::META_KEY => $mod_key_secondary . '.backgroundHover',
+ ],
+ '--primaryBtnColor' => [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.text',
+ ],
+ '--secondaryBtnColor' => [
+ Dynamic_Selector::META_KEY => $mod_key_secondary . '.text',
+ ],
+ '--primaryBtnHoverColor' => [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.textHover',
+ ],
+ '--secondaryBtnHoverColor' => [
+ Dynamic_Selector::META_KEY => $mod_key_secondary . '.textHover',
+ ],
+ '--primaryBtnBorderRadius' => [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.borderRadius',
+ Dynamic_Selector::META_SUFFIX => 'px',
+ 'directional-prop' => Config::CSS_PROP_BORDER_RADIUS,
+ ],
+ '--secondaryBtnBorderRadius' => [
+ Dynamic_Selector::META_KEY => $mod_key_secondary . '.borderRadius',
+ Dynamic_Selector::META_SUFFIX => 'px',
+ 'directional-prop' => Config::CSS_PROP_BORDER_RADIUS,
+ ],
+ ];
+
+
+ $primary_values = get_theme_mod( $mod_key_primary, $default_primary );
+ $secondary_values = get_theme_mod( $mod_key_secondary, $default_secondary );
+
+ // Border Width
+ if ( isset( $primary_values['type'] ) && $primary_values['type'] === 'outline' ) {
+ $rules['--primaryBtnBorderWidth'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.borderWidth',
+ Dynamic_Selector::META_SUFFIX => 'px',
+ 'directional-prop' => Config::CSS_PROP_BORDER_WIDTH,
+ ];
+ }
+ if ( isset( $secondary_values['type'] ) && $secondary_values['type'] === 'outline' ) {
+ $rules['--secondaryBtnBorderWidth'] = [
+ Dynamic_Selector::META_KEY => $mod_key_secondary . '.borderWidth',
+ Dynamic_Selector::META_SUFFIX => 'px',
+ 'directional-prop' => Config::CSS_PROP_BORDER_WIDTH,
+ ];
+ }
+
+ $mod_key_primary = Config::MODS_BUTTON_PRIMARY_PADDING;
+ $default_primary = Mods::get_alternative_mod_default( Config::MODS_BUTTON_PRIMARY_PADDING );
+ $rules['--btnPadding'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary,
+ Dynamic_Selector::META_DEFAULT => $default_primary,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
+ $mod_key_primary = Config::MODS_BUTTON_PRIMARY_STYLE;
+ $default_primary = neve_get_button_appearance_default();
+
+ $mod_key_secondary = Config::MODS_BUTTON_SECONDARY_STYLE;
+ $default_secondary = neve_get_button_appearance_default( 'secondary' );
+
+ $values = [
+ 'primary' => get_theme_mod( $mod_key_primary, $default_primary ),
+ 'secondary' => get_theme_mod( $mod_key_secondary, $default_secondary ),
+ ];
+ $paddings = [
+ 'primary' => $value,
+ 'secondary' => $value,
+ ];
+
+ foreach ( $values as $btn_type => $appearance_values ) {
+ if ( ! isset( $appearance_values['type'] ) || $appearance_values['type'] !== 'outline' ) {
+ continue;
+ }
+
+ $border_width = $appearance_values['borderWidth'];
+
+ foreach ( $paddings[ $btn_type ] as $direction => $padding_value ) {
+ if ( ! isset( $border_width[ $direction ] ) || absint( $border_width[ $direction ] ) === 0 ) {
+ continue;
+ }
+
+ $paddings[ $btn_type ][ $direction ] = $padding_value - $border_width[ $direction ];
+ }
+ }
+ $final_value_default = Css_Prop::transform_directional_prop( $meta, $device, $value, '--btnPadding', Config::CSS_PROP_PADDING );
+ $final_value_primary = Css_Prop::transform_directional_prop( $meta, $device, $paddings['primary'], '--primaryBtnPadding', Config::CSS_PROP_PADDING );
+ $final_value_secondary = Css_Prop::transform_directional_prop( $meta, $device, $paddings['secondary'], '--secondaryBtnPadding', Config::CSS_PROP_PADDING );
+
+ return $final_value_default . $final_value_primary . $final_value_secondary;
+ },
+ 'directional-prop' => Config::CSS_PROP_PADDING,
+ ];
+
+ $mod_key_primary = Config::MODS_BUTTON_TYPEFACE;
+ $rules['--btnFs'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.fontSize',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ];
+ $rules['--btnLineHeight'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.lineHeight',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ];
+ $rules['--btnLetterSpacing'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.letterSpacing',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ];
+ $rules['--btnTextTransform'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.textTransform',
+ Dynamic_Selector::META_IS_RESPONSIVE => false,
+ ];
+ $rules['--btnFontWeight'] = [
+ Dynamic_Selector::META_KEY => $mod_key_primary . '.fontWeight',
+ ];
+
+ return $rules;
+ }
+
+ /**
+ * Get the common typography rules
+ *
+ * @retun array
+ */
+ public function get_typography_rules() {
+ $default = Mods::get_alternative_mod_default( Config::MODS_TYPEFACE_GENERAL );
+ $mod_key = Config::MODS_TYPEFACE_GENERAL;
+
+ $rules = [
+ '--bodyFontFamily' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FONT_GENERAL,
+ Dynamic_Selector::META_DEFAULT => Mods::get_alternative_mod_default( Config::MODS_FONT_GENERAL ),
+ ],
+ '--bodyFontSize' => [
+ Dynamic_Selector::META_KEY => $mod_key . '.fontSize',
+ Dynamic_Selector::META_DEFAULT => $default['fontSize'],
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ '--bodyLineHeight' => [
+ Dynamic_Selector::META_KEY => $mod_key . '.lineHeight',
+ Dynamic_Selector::META_DEFAULT => $default['lineHeight'],
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => '',
+ ],
+ '--bodyLetterSpacing' => [
+ Dynamic_Selector::META_KEY => $mod_key . '.letterSpacing',
+ Dynamic_Selector::META_DEFAULT => $default['letterSpacing'],
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ '--bodyFontWeight' => [
+ Dynamic_Selector::META_KEY => $mod_key . '.fontWeight',
+ Dynamic_Selector::META_DEFAULT => $default['fontWeight'],
+ 'font' => 'mods_' . Config::MODS_FONT_HEADINGS,
+ ],
+ '--bodyTextTransform' => [
+ Dynamic_Selector::META_KEY => $mod_key . '.textTransform',
+ ],
+ '--headingsFontFamily' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FONT_HEADINGS,
+ ],
+ ];
+ foreach ( neve_get_headings_selectors() as $id => $heading_selector ) {
+ $composed_key = sprintf( 'neve_%s_typeface_general', $id );
+ $mod_key = $composed_key;
+ $default = Mods::get_alternative_mod_default( $composed_key );
+
+ $rules[ '--' . $id . 'FontSize' ] = [
+ Dynamic_Selector::META_KEY => $mod_key . '.fontSize',
+ Dynamic_Selector::META_DEFAULT => $default['fontSize'],
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ];
+
+ $rules[ '--' . $id . 'FontWeight' ] = [
+ Dynamic_Selector::META_KEY => $mod_key . '.fontWeight',
+ Dynamic_Selector::META_DEFAULT => $default['fontWeight'],
+ 'font' => 'mods_' . Config::MODS_FONT_HEADINGS,
+ ];
+
+ $rules[ '--' . $id . 'LineHeight' ] = [
+ Dynamic_Selector::META_KEY => $mod_key . '.lineHeight',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $default['lineHeight'],
+ Dynamic_Selector::META_SUFFIX => '',
+ ];
+
+ $rules[ '--' . $id . 'LetterSpacing' ] = [
+ Dynamic_Selector::META_KEY => $mod_key . '.letterSpacing',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $default['letterSpacing'],
+ ];
+
+ $rules[ '--' . $id . 'TextTransform' ] = [
+ Dynamic_Selector::META_KEY => $mod_key . '.textTransform',
+ Dynamic_Selector::META_DEFAULT => $default['textTransform'],
+ ];
+ }
+
+ return $rules;
+ }
+
+}
diff --git a/inc/core/styles/dynamic_selector.php b/inc/core/styles/dynamic_selector.php
index 3585410ef8..54e490248f 100644
--- a/inc/core/styles/dynamic_selector.php
+++ b/inc/core/styles/dynamic_selector.php
@@ -27,6 +27,7 @@ class Dynamic_Selector {
const META_DEFAULT = 'default';
const META_DEVICE_ONLY = 'device_only';
const META_FILTER = 'filter';
+ const META_AS_JSON = 'as_json';
const KEY_SELECTOR = 'selectors';
const KEY_RULES = 'rules';
@@ -138,9 +139,10 @@ public function transform_selectors() {
}
if ( $this->get_context() === self::CONTEXT_GUTENBERG ) {
$expanded_selectors = explode( ',', $expanded_selectors );
+
$expanded_selectors = array_map(
function ( $value ) {
- return '.editor-styles-wrapper ' . $value;
+ return $value === ':root' ? $value : '.editor-styles-wrapper ' . $value;
},
$expanded_selectors
);
diff --git a/inc/core/styles/frontend.php b/inc/core/styles/frontend.php
index fe54ad6bfc..d5104dc0ba 100644
--- a/inc/core/styles/frontend.php
+++ b/inc/core/styles/frontend.php
@@ -9,6 +9,8 @@
use Neve\Core\Settings\Config;
use Neve\Core\Settings\Mods;
+use Neve\Customizer\Defaults\Layout;
+use Neve\Customizer\Defaults\Single_Post;
/**
* Class Generator for Frontend.
@@ -16,26 +18,62 @@
* @package Neve\Core\Styles
*/
class Frontend extends Generator {
+ use Css_Vars;
+ use Single_Post;
+ use Layout;
+
+ /**
+ * Box shadow map values
+ *
+ * @var string[]
+ */
+ private $box_shadow_map = [
+ 1 => '0 1px 3px -2px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.1)',
+ 2 => '0 3px 6px -5px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.1)',
+ 3 => '0 10px 20px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.1)',
+ 4 => '0 14px 28px rgba(0, 0, 0, 0.12), 0 10px 10px rgba(0, 0, 0, 0.12)',
+ 5 => '0 16px 38px -12px rgba(0,0,0,0.56), 0 4px 25px 0 rgba(0,0,0,0.12), 0 8px 10px -5px rgba(0,0,0,0.2)',
+ ];
+
/**
* Generator constructor.
*/
public function __construct() {
- $this->_subscribers = [
- '.container' => [
- Config::CSS_PROP_MAX_WIDTH => [
- Dynamic_Selector::META_KEY => Config::MODS_CONTAINER_WIDTH,
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- ],
- ],
- ];
- $this->setup_form_buttons();
+ $this->_subscribers = [];
+ $this->setup_container();
+ $this->setup_blog_layout();
$this->setup_legacy_gutenberg_palette();
$this->setup_layout_subscribers();
$this->setup_buttons();
$this->setup_typography();
+ $this->setup_blog_meta();
$this->setup_blog_typography();
$this->setup_blog_colors();
$this->setup_form_fields_style();
+ $this->setup_single_post_style();
+ }
+
+ /**
+ * Setup the container styles.
+ *
+ * @return false
+ */
+ private function setup_container() {
+ if ( ! neve_is_new_skin() ) {
+ $this->_subscribers['.container'] = [
+ Config::CSS_PROP_MAX_WIDTH => [
+ Dynamic_Selector::META_KEY => Config::MODS_CONTAINER_WIDTH,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ ];
+
+ return false;
+ }
+
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => ':root',
+ Dynamic_Selector::KEY_RULES => $this->get_container_rules(),
+ ];
}
/**
@@ -66,9 +104,9 @@ private function setup_legacy_gutenberg_palette() {
}
/**
- * Add css for blog colors.
+ * Setup legacy blog colors.
*/
- public function setup_blog_colors() {
+ private function setup_legacy_blog_colors() {
$this->_subscribers['.cover-post .inner, .cover-post .inner a:not(.button), .cover-post .inner a:not(.button):hover, .cover-post .inner a:not(.button):focus, .cover-post .inner li'] = [
Config::CSS_PROP_COLOR => [
Dynamic_Selector::META_KEY => 'neve_blog_covers_text_color',
@@ -84,18 +122,47 @@ public function setup_blog_colors() {
if ( absint( $value ) === 0 ) {
return '';
}
- $map = [
- 1 => '0 1px 3px -2px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.1)',
- 2 => '0 3px 6px -5px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.1)',
- 3 => '0 10px 20px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.1)',
- 4 => '0 14px 28px rgba(0, 0, 0, 0.12), 0 10px 10px rgba(0, 0, 0, 0.12)',
- 5 => '0 16px 38px -12px rgba(0,0,0,0.56), 0 4px 25px 0 rgba(0,0,0,0.12), 0 8px 10px -5px rgba(0,0,0,0.2)',
- ];
- if ( ! array_key_exists( absint( $value ), $map ) ) {
+
+ if ( ! array_key_exists( absint( $value ), $this->box_shadow_map ) ) {
return '';
}
- return sprintf( '%s:%s;', $css_prop, $map[ $value ] );
+ return sprintf( '%s:%s;', $css_prop, $this->box_shadow_map[ $value ] );
+ },
+ ],
+ ];
+ }
+
+ /**
+ * Add css for blog colors.
+ */
+ public function setup_blog_colors() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_blog_colors();
+
+ return;
+ }
+
+ $layout = get_theme_mod( 'neve_blog_archive_layout', 'grid' );
+ if ( $layout === 'covers' ) {
+ $this->_subscribers['.cover-post'] = [
+ '--color' => 'neve_blog_covers_text_color',
+ ];
+ }
+
+ $this->_subscribers['.nv-post-thumbnail-wrap'] = [
+ '--boxShadow' => [
+ Dynamic_Selector::META_KEY => 'neve_post_thumbnail_box_shadow',
+ Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
+ if ( absint( $value ) === 0 ) {
+ return '';
+ }
+
+ if ( ! array_key_exists( absint( $value ), $this->box_shadow_map ) ) {
+ return '';
+ }
+
+ return sprintf( '%s:%s;', $css_prop, $this->box_shadow_map[ $value ] );
},
],
];
@@ -105,42 +172,105 @@ public function setup_blog_colors() {
* Add css for blog typography.
*/
public function setup_blog_typography() {
- $archive_typography = array(
- Config::CSS_SELECTOR_ARCHIVE_POST_TITLE => Config::MODS_TYPEFACE_ARCHIVE_POST_TITLE,
- Config::CSS_SELECTOR_ARCHIVE_POST_EXCERPT => Config::MODS_TYPEFACE_ARCHIVE_POST_EXCERPT,
- Config::CSS_SELECTOR_ARCHIVE_POST_META => Config::MODS_TYPEFACE_ARCHIVE_POST_META,
- Config::CSS_SELECTOR_SINGLE_POST_TITLE => Config::MODS_TYPEFACE_SINGLE_POST_TITLE,
- Config::CSS_SELECTOR_SINGLE_POST_META => Config::MODS_TYPEFACE_SINGLE_POST_META,
- Config::CSS_SELECTOR_SINGLE_POST_COMMENT_TITLE => Config::MODS_TYPEFACE_SINGLE_POST_COMMENT_TITLE,
- );
- foreach ( $archive_typography as $selector => $mod ) {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_blog_typography();
+
+ return;
+ }
+
+ $archive_typography = [
+ Config::CSS_SELECTOR_ARCHIVE_POST_TITLE => [
+ 'mod' => Config::MODS_TYPEFACE_ARCHIVE_POST_TITLE,
+ 'font' => Config::MODS_FONT_HEADINGS,
+ ],
+ Config::CSS_SELECTOR_ARCHIVE_POST_EXCERPT => [
+ 'mod' => Config::MODS_TYPEFACE_ARCHIVE_POST_EXCERPT,
+ 'font' => Config::MODS_FONT_GENERAL,
+ ],
+ Config::CSS_SELECTOR_ARCHIVE_POST_META => [
+ 'mod' => Config::MODS_TYPEFACE_ARCHIVE_POST_META,
+ 'font' => Config::MODS_FONT_GENERAL,
+ ],
+ Config::CSS_SELECTOR_SINGLE_POST_TITLE => [
+ 'mod' => Config::MODS_TYPEFACE_SINGLE_POST_TITLE,
+ 'font' => Config::MODS_FONT_HEADINGS,
+ ],
+ Config::CSS_SELECTOR_SINGLE_POST_META => [
+ 'mod' => Config::MODS_TYPEFACE_SINGLE_POST_META,
+ 'font' => Config::MODS_FONT_GENERAL,
+ ],
+ Config::CSS_SELECTOR_SINGLE_POST_COMMENT_TITLE => [
+ 'mod' => Config::MODS_TYPEFACE_SINGLE_POST_COMMENT_TITLE,
+ 'font' => Config::MODS_FONT_HEADINGS,
+ ],
+ ];
+ foreach ( $archive_typography as $selector => $args ) {
$this->_subscribers[ $selector ] = [
- Config::CSS_PROP_FONT_SIZE => [
- Dynamic_Selector::META_KEY => $mod . '.fontSize',
+ '--fontSize' => [
+ Dynamic_Selector::META_KEY => $args['mod'] . '.fontSize',
Dynamic_Selector::META_IS_RESPONSIVE => true,
Dynamic_Selector::META_SUFFIX => 'px',
],
- Config::CSS_PROP_LINE_HEIGHT => [
- Dynamic_Selector::META_KEY => $mod . '.lineHeight',
+ '--lineHeight' => [
+ Dynamic_Selector::META_KEY => $args['mod'] . '.lineHeight',
Dynamic_Selector::META_IS_RESPONSIVE => true,
Dynamic_Selector::META_SUFFIX => '',
],
- Config::CSS_PROP_LETTER_SPACING => [
- Dynamic_Selector::META_KEY => $mod . '.letterSpacing',
+ '--letterSpacing' => [
+ Dynamic_Selector::META_KEY => $args['mod'] . '.letterSpacing',
Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
],
- Config::CSS_PROP_FONT_WEIGHT => [
- Dynamic_Selector::META_KEY => $mod . '.fontWeight',
+ '--fontWeight' => [
+ Dynamic_Selector::META_KEY => $args['mod'] . '.fontWeight',
+ 'font' => 'mods_' . $args['font'],
],
- Config::CSS_PROP_TEXT_TRANSFORM => $mod . '.textTransform',
+ '--textTransform' => $args['mod'] . '.textTransform',
];
}
}
/**
- * Setup typography subscribers.
+ * Add css for blog layout.
+ *
+ * Removed grid in new skin CSS so this should handle the grid.
+ *
+ * @since 3.0.0
+ *
+ * @return bool|void
*/
- public function setup_typography() {
+ public function setup_blog_layout() {
+ if ( ! neve_is_new_skin() ) {
+ return false;
+ }
+
+ $this->_subscribers[':root'] = [
+ '--postWidth' => [
+ Dynamic_Selector::META_KEY => 'neve_grid_layout',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $this->grid_columns_default(),
+ Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
+ $blog_layout = get_theme_mod( 'neve_blog_archive_layout', 'grid' );
+ if ( ! in_array( $blog_layout, [ 'grid', 'covers' ], true ) ) {
+ return sprintf( '%s:%s;', $css_prop, '100%' );
+ }
+
+ if ( $value < 1 ) {
+ $value = 1;
+ }
+
+ return sprintf( '%s:%s;', $css_prop, 100 / $value . '%' );
+ },
+ ],
+ ];
+ }
+
+ /**
+ * Setups the legacy typography, used before 3.0.
+ *
+ * @since 3.0.0
+ */
+ public function setup_legacy_typography() {
$this->_subscribers[ Config::CSS_SELECTOR_TYPEFACE_GENERAL ] = [
Config::CSS_PROP_FONT_SIZE => [
Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.fontSize',
@@ -199,8 +329,8 @@ public function setup_typography() {
];
}
-
$extra_selectors_body = apply_filters( 'neve_body_font_family_selectors', '' );
+
if ( ! empty( $extra_selectors_body ) ) {
$extra_selectors_body = ltrim( $extra_selectors_body, ', ' );
$this->_subscribers[ $extra_selectors_body ] = [
@@ -219,9 +349,25 @@ public function setup_typography() {
}
/**
- * Setup button subscribers.
+ * Setup typography subscribers.
*/
- public function setup_buttons() {
+ public function setup_typography() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_typography();
+
+ return;
+ }
+ $rules = $this->get_typography_rules();
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => ':root',
+ Dynamic_Selector::KEY_RULES => $rules,
+ ];
+ }
+
+ /**
+ * Setup legacy button.
+ */
+ private function setup_legacy_buttons() {
// Primary button config.
$this->_subscribers[] = [
Dynamic_Selector::KEY_SELECTOR => Config::CSS_SELECTOR_BTN_PRIMARY_NORMAL,
@@ -431,6 +577,23 @@ public function setup_buttons() {
];
}
+ /**
+ * Setup button subscribers.
+ */
+ public function setup_buttons() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_buttons();
+
+ return;
+ }
+
+ $rules = $this->get_button_rules();
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => ':root',
+ Dynamic_Selector::KEY_RULES => $rules,
+ ];
+ }
+
/**
* Setup settings subscribers for layout.
*
@@ -438,7 +601,7 @@ public function setup_buttons() {
* TODO: Better exclude classes when Woo is not present, i.e shop-sidebar class is added even when Woo is not used.
*/
public function setup_layout_subscribers() {
- $is_advanced_on = Mods::get( Config::MODS_ADVANCED_LAYOUT_OPTIONS, false );
+ $is_advanced_on = Mods::get( Config::MODS_ADVANCED_LAYOUT_OPTIONS, neve_is_new_skin() );
if ( ! $is_advanced_on ) {
$this->_subscribers['#content .container .col, #content .container-fluid .col'] = [
@@ -487,16 +650,18 @@ public function setup_layout_subscribers() {
return;
}
// Others content width.
- $this->_subscribers['body:not(.single):not(.archive):not(.blog):not(.search) .neve-main > .container .col'] = [
+ $this->_subscribers['body:not(.single):not(.archive):not(.blog):not(.search) .neve-main > .container .col, body.post-type-archive-course .neve-main > .container .col, body.post-type-archive-llms_membership .neve-main > .container .col'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_OTHERS_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_OTHERS_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_SUFFIX => '%',
],
];
- $this->_subscribers['body:not(.single):not(.archive):not(.blog):not(.search) .nv-sidebar-wrap'] = [
+ $this->_subscribers['body:not(.single):not(.archive):not(.blog):not(.search) .nv-sidebar-wrap, body.post-type-archive-course .nv-sidebar-wrap, body.post-type-archive-llms_membership .nv-sidebar-wrap'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_OTHERS_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_OTHERS_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => 'minus_100',
Dynamic_Selector::META_SUFFIX => '%',
@@ -506,6 +671,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.neve-main > .archive-container .nv-index-posts.col'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_ARCHIVE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_ARCHIVE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_SUFFIX => '%',
],
@@ -513,6 +679,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.neve-main > .archive-container .nv-sidebar-wrap'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_ARCHIVE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_ARCHIVE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => 'minus_100',
Dynamic_Selector::META_SUFFIX => '%',
@@ -522,6 +689,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.neve-main > .single-post-container .nv-single-post-wrap.col'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SINGLE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_SUFFIX => '%',
],
@@ -530,7 +698,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.single-post-container .alignfull > [class*="__inner-container"], .single-post-container .alignwide > [class*="__inner-container"]'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SINGLE_CONTENT_WIDTH,
- Dynamic_Selector::META_DEFAULT => 70,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_IS_RESPONSIVE => true,
Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
$width = Mods::to_json( Config::MODS_CONTAINER_WIDTH );
@@ -543,6 +711,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.container-fluid.single-post-container .alignfull > [class*="__inner-container"], .container-fluid.single-post-container .alignwide > [class*="__inner-container"]'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SINGLE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
return sprintf( 'max-width:calc(%s%% + %spx)', $value, Config::CONTENT_DEFAULT_PADDING / 2 );
@@ -553,6 +722,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.neve-main > .single-post-container .nv-sidebar-wrap'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SINGLE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => 'minus_100',
Dynamic_Selector::META_SUFFIX => '%',
@@ -567,6 +737,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.archive.woocommerce .neve-main > .shop-container .nv-shop.col'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SHOP_ARCHIVE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SHOP_ARCHIVE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_SUFFIX => '%',
],
@@ -574,6 +745,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.archive.woocommerce .neve-main > .shop-container .nv-sidebar-wrap'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SHOP_ARCHIVE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SHOP_ARCHIVE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => 'minus_100',
Dynamic_Selector::META_SUFFIX => '%',
@@ -584,6 +756,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.single-product .neve-main > .shop-container .nv-shop.col'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SHOP_SINGLE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SHOP_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_SUFFIX => '%',
],
@@ -592,7 +765,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.single-product .alignfull > [class*="__inner-container"], .single-product .alignwide > [class*="__inner-container"]'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SHOP_SINGLE_CONTENT_WIDTH,
- Dynamic_Selector::META_DEFAULT => 70,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SHOP_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_IS_RESPONSIVE => true,
Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
$width = Mods::to_json( Config::MODS_CONTAINER_WIDTH );
@@ -605,6 +778,7 @@ public function setup_layout_subscribers() {
$this->_subscribers['.single-product .container-fluid .alignfull > [class*="__inner-container"], .single-product .alignwide > [class*="__inner-container"]'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SHOP_SINGLE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SHOP_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => function ( $css_prop, $value, $meta, $device ) {
return sprintf( 'max-width:calc(%s%% + %spx)', $value, Config::CONTENT_DEFAULT_PADDING / 2 );
@@ -614,18 +788,261 @@ public function setup_layout_subscribers() {
$this->_subscribers['.single-product .neve-main > .shop-container .nv-sidebar-wrap'] = [
Config::CSS_PROP_MAX_WIDTH => [
Dynamic_Selector::META_KEY => Config::MODS_SHOP_SINGLE_CONTENT_WIDTH,
+ Dynamic_Selector::META_DEFAULT => $this->sidebar_layout_width_default( Config::MODS_SHOP_SINGLE_CONTENT_WIDTH ),
Dynamic_Selector::META_DEVICE_ONLY => Dynamic_Selector::DESKTOP,
Dynamic_Selector::META_FILTER => 'minus_100',
Dynamic_Selector::META_SUFFIX => '%',
],
];
-
}
/**
* Adds form field styles
*/
private function setup_form_fields_style() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_form_fields_style();
+
+ return;
+ }
+
+ $border_width_default = array_fill_keys( Config::$directional_keys, '2' );
+ $border_radius_default = array_fill_keys( Config::$directional_keys, '3' );
+
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => ':root',
+ Dynamic_Selector::KEY_RULES => [
+ '--formFieldSpacing' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_SPACING,
+ Dynamic_Selector::META_DEFAULT => Mods::get_alternative_mod_default( Config::MODS_FORM_FIELDS_SPACING ),
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ '--formFieldBorderWidth' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_BORDER_WIDTH,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_DEFAULT => $border_width_default,
+ 'directional-prop' => Config::CSS_PROP_BORDER_WIDTH,
+ ],
+ '--formFieldBorderRadius' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_BORDER_RADIUS,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_DEFAULT => $border_radius_default,
+ 'directional-prop' => Config::CSS_PROP_BORDER_RADIUS,
+ ],
+ '--formFieldBgColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_BACKGROUND_COLOR,
+ Dynamic_Selector::META_DEFAULT => 'var(--nv-site-bg)',
+ ],
+ '--formFieldBorderColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_BORDER_COLOR,
+ Dynamic_Selector::META_DEFAULT => '#dddddd',
+ ],
+ '--formFieldColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_COLOR,
+ Dynamic_Selector::META_DEFAULT => 'var(--nv-text-color)',
+ ],
+ '--formFieldPadding' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_PADDING,
+ Dynamic_Selector::META_DEFAULT => Mods::get_alternative_mod_default( Config::MODS_FORM_FIELDS_PADDING ),
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_IS_RESPONSIVE => false,
+ 'directional-prop' => Config::CSS_PROP_PADDING,
+ ],
+ '--formFieldTextTransform' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_TYPEFACE . '.textTransform',
+ Dynamic_Selector::META_IS_RESPONSIVE => false,
+ ],
+ '--formFieldFontSize' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_TYPEFACE . '.fontSize',
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ '--formFieldLineHeight' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_TYPEFACE . '.lineHeight',
+ Dynamic_Selector::META_SUFFIX => '',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ '--formFieldLetterSpacing' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_TYPEFACE . '.letterSpacing',
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ '--formFieldFontWeight' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_TYPEFACE . '.fontWeight',
+ ],
+ // Form Labels
+ '--formLabelSpacing' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_LABELS_SPACING,
+ Dynamic_Selector::META_DEFAULT => 10,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ '--formLabelFontSize' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_LABELS_TYPEFACE . '.fontSize',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ '--formLabelLineHeight' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_LABELS_TYPEFACE . '.lineHeight',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => '',
+ ],
+ '--formLabelLetterSpacing' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_LABELS_TYPEFACE . '.letterSpacing',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ '--formLabelFontWeight' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_LABELS_TYPEFACE . '.fontWeight',
+ ],
+ '--formLabelTextTransform' => [
+ Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_LABELS_TYPEFACE . '.textTransform',
+ ],
+ ],
+ ];
+
+ // Form button style. Override if needed.
+ $form_buttons_type = get_theme_mod( 'neve_form_button_type', 'primary' );
+
+ if ( $form_buttons_type === 'primary' ) {
+ return;
+ }
+
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON ]['background-color'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnBg, transparent)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON ]['color'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnColor)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON ]['padding'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnPadding, 7px 12px)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON ]['border-radius'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnBorderRadius, 3px)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON_HOVER ]['background-color'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnHoverBg, transparent)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON_HOVER ]['color'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnHoverColor)',
+ ];
+
+ $mod_key_secondary = Config::MODS_BUTTON_SECONDARY_STYLE;
+ $default_secondary = Mods::get_alternative_mod_default( Config::MODS_BUTTON_SECONDARY_STYLE );
+ $secondary_values = get_theme_mod( $mod_key_secondary, $default_secondary );
+
+ if ( ! isset( $secondary_values['type'] ) || $secondary_values['type'] !== 'outline' ) {
+ return;
+ }
+
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON ]['border-width'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnBorderWidth, 3px)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON ]['border-color'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnHoverColor)',
+ ];
+ $this->_subscribers[ Config::CSS_SELECTOR_FORM_BUTTON_HOVER ]['border-color'] = [
+ 'key' => 'neve_form_button_type',
+ 'override' => 'var(--secondaryBtnHoverColor)',
+ ];
+ }
+
+ /**
+ * Add form buttons selectors to the Buttons selector.
+ *
+ * @param string $selector the CSS selector received from the filter.
+ *
+ * @return string
+ */
+ public function add_form_buttons( $selector ) {
+ return ( $selector . ', form input[type="submit"], form button[type="submit"]' );
+ }
+
+ /**
+ * Add form buttons hover selectors to the Buttons selector.
+ *
+ * @param string $selector the CSS selector received from the filter.
+ *
+ * @return string
+ */
+ public function add_form_buttons_hover( $selector ) {
+ return ( $selector . ', form input[type="submit"]:hover, form button[type="submit"]:hover' );
+ }
+
+ /**
+ * Add css for blog meta.
+ */
+ public function setup_blog_meta() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_blog_meta_legacy();
+
+ return;
+ }
+
+ $rules = [
+ '--avatarSize' => [
+ Dynamic_Selector::META_KEY => Config::MODS_ARCHIVE_POST_META_AUTHOR_AVATAR_SIZE,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_DEFAULT => '{ "mobile": 20, "tablet": 20, "desktop": 20 }',
+ ],
+ ];
+
+ $rules_single = [
+ '--avatarSize' => [
+ Dynamic_Selector::META_KEY => Config::MODS_SINGLE_POST_META_AUTHOR_AVATAR_SIZE,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ Dynamic_Selector::META_DEFAULT => Mods::get( 'neve_author_avatar_size', '{ "mobile": 20, "tablet": 20, "desktop": 20 }' ),
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-meta-list',
+ 'rules' => $rules,
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.single .nv-meta-list',
+ 'rules' => $rules_single,
+ ];
+ }
+
+ /**
+ * Add css for blog meta.
+ */
+ public function setup_blog_meta_legacy() {
+
+ $meta_key = Config::MODS_ARCHIVE_POST_META_AUTHOR_AVATAR_SIZE;
+ if ( is_singular( 'post' ) ) {
+ $meta_key = Config::MODS_SINGLE_POST_META_AUTHOR_AVATAR_SIZE;
+ }
+
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => '.nv-meta-list .meta.author img.photo',
+ Dynamic_Selector::KEY_RULES => [
+ Config::CSS_PROP_HEIGHT => [
+ Dynamic_Selector::META_KEY => $meta_key,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ Config::CSS_PROP_WIDTH => [
+ Dynamic_Selector::META_KEY => $meta_key,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Setup legacy form field styles.
+ */
+ private function setup_legacy_form_fields_style() {
$this->_subscribers[ Config::CSS_SELECTOR_FORM_INPUTS_WITH_SPACING ] = [
Config::CSS_PROP_MARGIN_BOTTOM => [
Dynamic_Selector::META_KEY => Config::MODS_FORM_FIELDS_SPACING,
@@ -712,93 +1129,198 @@ private function setup_form_fields_style() {
],
Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_GENERAL,
];
- }
- /**
- * Setup Form Buttons Type
- */
- private function setup_form_buttons() {
+ /**
+ * Form buttons.
+ */
$form_buttons_type = get_theme_mod( 'neve_form_button_type', 'primary' );
if ( $form_buttons_type === 'primary' ) {
- add_filter(
- 'neve_selectors_' . Config::CSS_SELECTOR_BTN_PRIMARY_NORMAL,
- array(
- $this,
- 'add_form_buttons',
- ),
- 10,
- 1
- );
- add_filter(
- 'neve_selectors_' . Config::CSS_SELECTOR_BTN_PRIMARY_PADDING,
- array(
- $this,
- 'add_form_buttons',
- ),
- 10,
- 1
- );
+ add_filter( 'neve_selectors_' . Config::CSS_SELECTOR_BTN_PRIMARY_NORMAL, [ $this, 'add_form_buttons' ] );
+ add_filter( 'neve_selectors_' . Config::CSS_SELECTOR_BTN_PRIMARY_PADDING, [ $this, 'add_form_buttons' ] );
add_filter(
'neve_selectors_' . Config::CSS_SELECTOR_BTN_PRIMARY_HOVER,
- array(
+ [
$this,
'add_form_buttons_hover',
- ),
- 10,
- 1
+ ]
);
return;
}
- add_filter(
- 'neve_selectors_' . Config::CSS_SELECTOR_BTN_SECONDARY_NORMAL,
- array(
- $this,
- 'add_form_buttons',
- ),
- 10,
- 1
- );
- add_filter(
- 'neve_selectors_' . Config::CSS_SELECTOR_BTN_SECONDARY_PADDING,
- array(
- $this,
- 'add_form_buttons',
- ),
- 10,
- 1
- );
- add_filter(
- 'neve_selectors_' . Config::CSS_SELECTOR_BTN_SECONDARY_HOVER,
- array(
- $this,
- 'add_form_buttons_hover',
- ),
- 10,
- 1
- );
+
+ add_filter( 'neve_selectors_' . Config::CSS_SELECTOR_BTN_SECONDARY_NORMAL, [ $this, 'add_form_buttons' ] );
+ add_filter( 'neve_selectors_' . Config::CSS_SELECTOR_BTN_SECONDARY_PADDING, [ $this, 'add_form_buttons' ] );
+ add_filter( 'neve_selectors_' . Config::CSS_SELECTOR_BTN_SECONDARY_HOVER, [ $this, 'add_form_buttons_hover' ] );
}
/**
- * Add form buttons selectors to the Buttons selector.
- *
- * @param string $selector the CSS selector received from the filter.
- *
- * @return string
+ * Add css for single post.
*/
- public function add_form_buttons( $selector ) {
- return ( $selector . ', form input[type="submit"], form button[type="submit"], #comments input[type="submit"]' );
+ private function setup_single_post_style() {
+ if ( ! neve_is_new_skin() ) {
+ return;
+ }
+
+ $cover_rules = [
+ '--height' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_HEIGHT,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_AS_JSON => true,
+ Dynamic_Selector::META_SUFFIX => 'responsive_suffix',
+ Dynamic_Selector::META_DEFAULT => '{ "mobile": "400", "tablet": "400", "desktop": "400" }',
+ ],
+ '--padding' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_PADDING,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $this->padding_default( 'cover' ),
+ 'directional-prop' => Config::CSS_PROP_PADDING,
+ ],
+ '--vAlign' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_TITLE_POSITION,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-post-cover',
+ 'rules' => $cover_rules,
+ ];
+
+ $title_rules = [
+ '--color' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_TEXT_COLOR,
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-post-cover .nv-title-meta-wrap',
+ 'rules' => $title_rules,
+ ];
+
+ $boxed_title_rules = [
+ '--padding' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_BOXED_TITLE_PADDING,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $this->padding_default( 'cover' ),
+ 'directional-prop' => Config::CSS_PROP_PADDING,
+ ],
+ '--bgColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_BOXED_TITLE_BACKGROUND,
+ Dynamic_Selector::META_DEFAULT => 'var(--nv-dark-bg)',
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-is-boxed.nv-title-meta-wrap',
+ 'rules' => $boxed_title_rules,
+ ];
+
+ $overlay_rules = [
+ '--bgColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_BACKGROUND_COLOR,
+ ],
+ '--blendMode' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_BLEND_MODE,
+ ],
+ '--opacity' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COVER_OVERLAY_OPACITY,
+ Dynamic_Selector::META_DEFAULT => 50,
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-overlay',
+ 'rules' => $overlay_rules,
+ ];
+
+ $boxed_comments_rules = [
+ '--padding' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COMMENTS_PADDING,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $this->padding_default(),
+ 'directional-prop' => Config::CSS_PROP_PADDING,
+ ],
+ '--bgColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COMMENTS_BACKGROUND_COLOR,
+ ],
+ '--color' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COMMENTS_TEXT_COLOR,
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-is-boxed.nv-comments-wrap',
+ 'rules' => $boxed_comments_rules,
+ ];
+
+ $boxed_comment_form_rules = [
+ '--padding' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COMMENTS_FORM_PADDING,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_DEFAULT => $this->padding_default(),
+ 'directional-prop' => Config::CSS_PROP_PADDING,
+ ],
+ '--bgColor' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COMMENTS_FORM_BACKGROUND_COLOR,
+ ],
+ '--color' => [
+ Dynamic_Selector::META_KEY => Config::MODS_POST_COMMENTS_FORM_TEXT_COLOR,
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-is-boxed.comment-respond',
+ 'rules' => $boxed_comment_form_rules,
+ ];
+
+ $spacing_rules = [
+ '--spacing' => [
+ Dynamic_Selector::META_KEY => Config::MODS_SINGLE_POST_ELEMENTS_SPACING,
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ ];
+
+ $this->_subscribers[] = [
+ 'selectors' => '.nv-single-post-wrap',
+ 'rules' => $spacing_rules,
+ ];
}
/**
- * Add form buttons hover selectors to the Buttons selector.
- *
- * @param string $selector the CSS selector received from the filter.
- *
- * @return string
+ * Setup legacy blog typography.
*/
- public function add_form_buttons_hover( $selector ) {
- return ( $selector . ', form input[type="submit"]:hover, form button[type="submit"]:hover, #comments input[type="submit"]:hover' );
+ private function setup_legacy_blog_typography() {
+ $archive_typography = array(
+ Config::CSS_SELECTOR_ARCHIVE_POST_TITLE => Config::MODS_TYPEFACE_ARCHIVE_POST_TITLE,
+ Config::CSS_SELECTOR_ARCHIVE_POST_EXCERPT => Config::MODS_TYPEFACE_ARCHIVE_POST_EXCERPT,
+ Config::CSS_SELECTOR_ARCHIVE_POST_META => Config::MODS_TYPEFACE_ARCHIVE_POST_META,
+ Config::CSS_SELECTOR_SINGLE_POST_TITLE => Config::MODS_TYPEFACE_SINGLE_POST_TITLE,
+ Config::CSS_SELECTOR_SINGLE_POST_META => Config::MODS_TYPEFACE_SINGLE_POST_META,
+ Config::CSS_SELECTOR_SINGLE_POST_COMMENT_TITLE => Config::MODS_TYPEFACE_SINGLE_POST_COMMENT_TITLE,
+ );
+ foreach ( $archive_typography as $selector => $mod ) {
+ $this->_subscribers[ $selector ] = [
+ Config::CSS_PROP_FONT_SIZE => [
+ Dynamic_Selector::META_KEY => $mod . '.fontSize',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ Config::CSS_PROP_LINE_HEIGHT => [
+ Dynamic_Selector::META_KEY => $mod . '.lineHeight',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => '',
+ ],
+ Config::CSS_PROP_LETTER_SPACING => [
+ Dynamic_Selector::META_KEY => $mod . '.letterSpacing',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ Config::CSS_PROP_FONT_WEIGHT => [
+ Dynamic_Selector::META_KEY => $mod . '.fontWeight',
+ ],
+ Config::CSS_PROP_TEXT_TRANSFORM => $mod . '.textTransform',
+ ];
+ }
}
}
diff --git a/inc/core/styles/generator.php b/inc/core/styles/generator.php
index 4a7db4c85d..462844c0f5 100644
--- a/inc/core/styles/generator.php
+++ b/inc/core/styles/generator.php
@@ -49,23 +49,28 @@ public function generate( $echo = false ) {
if ( $this->context === null ) {
$this->context = Dynamic_Selector::CONTEXT_FRONTEND;
}
+
/**
* Neve try to build the CSS as mobile first.
- * Based on this fact, the general CSS is considere the mobile one.
+ * Based on this fact, the general CSS is considered the mobile one.
*/
$dynamic_selectors = new Dynamic_Selector( $this->_subscribers, $this->context );
- $all_css .= $dynamic_selectors->for_mobile();
- $tablet_css .= $dynamic_selectors->for_tablet();
- $desktop_css .= $dynamic_selectors->for_desktop();
+
+ $all_css .= $dynamic_selectors->for_mobile();
+ $tablet_css .= $dynamic_selectors->for_tablet();
+ $desktop_css .= $dynamic_selectors->for_desktop();
if ( ! empty( $tablet_css ) ) {
$all_css .= sprintf( '@media(min-width: 576px){ %s }', $tablet_css );
}
if ( ! empty( $desktop_css ) ) {
$all_css .= sprintf( '@media(min-width: 960px){ %s }', $desktop_css );
}
+
if ( ! $echo ) {
return $all_css;
}
+
+
echo $all_css; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
diff --git a/inc/core/styles/gutenberg.php b/inc/core/styles/gutenberg.php
index 0340642199..791ea5ae97 100644
--- a/inc/core/styles/gutenberg.php
+++ b/inc/core/styles/gutenberg.php
@@ -17,6 +17,8 @@
* @package Neve\Core\Styles
*/
class Gutenberg extends Generator {
+ use Css_Vars;
+
/**
* Generator constructor.
*/
@@ -27,118 +29,82 @@ public function __construct() {
$this->add_editor_color_palette_styles();
}
-
/**
* Setup typography subscribers.
*/
public function setup_typography() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_typography();
+ return;
+ }
- // Gutenberg Typography.
-
+ $rules = $this->get_typography_rules();
$this->_subscribers[] = [
- Dynamic_Selector::KEY_SELECTOR => '.editor-post-title__block .editor-post-title__input,
- .wp-block h1, h1.wp-block
- .wp-block h2, h2.wp-block
- .wp-block h3, h3.wp-block
- .wp-block h4, h4.wp-block
- .wp-block h5, h5.wp-block
- .wp-block h6, h6.wp-block',
- Dynamic_Selector::KEY_RULES => [
- Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_HEADINGS,
- ],
+ Dynamic_Selector::KEY_SELECTOR => ':root',
+ Dynamic_Selector::KEY_RULES => $rules,
Dynamic_Selector::KEY_CONTEXT => [
Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
];
+ }
+
+ /**
+ * Setup button subscribers.
+ */
+ public function setup_buttons() {
+ if ( ! neve_is_new_skin() ) {
+ $this->setup_legacy_buttons();
+
+ return;
+ }
+
+ $rules = $this->get_button_rules();
$this->_subscribers[] = [
- Dynamic_Selector::KEY_SELECTOR => '.editor-styles-wrapper',
- Dynamic_Selector::KEY_RULES => [
- Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_GENERAL,
- ],
+ Dynamic_Selector::KEY_SELECTOR => ':root',
+ Dynamic_Selector::KEY_RULES => $rules,
Dynamic_Selector::KEY_CONTEXT => [
Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
];
+ }
- $this->_subscribers[] = [
- Dynamic_Selector::KEY_SELECTOR => ' .wp-block,
- [data-type="core/paragraph"] p',
- Dynamic_Selector::KEY_RULES => [
- Config::CSS_PROP_FONT_SIZE => [
- Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.fontSize',
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- Dynamic_Selector::META_SUFFIX => 'px',
- ],
- Config::CSS_PROP_LINE_HEIGHT => [
- Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.lineHeight',
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- Dynamic_Selector::META_SUFFIX => '',
- ],
- Config::CSS_PROP_LETTER_SPACING => [
- Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.letterSpacing',
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- ],
- Config::CSS_PROP_FONT_WEIGHT => [
- Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.fontWeight',
- 'font' => 'mods_' . Config::MODS_FONT_GENERAL,
+ /**
+ * Adds colors from the editor-color-palette theme support.
+ */
+ private function add_editor_color_palette_styles() {
+ $is_new_user = get_option( 'neve_new_user' );
+ $imported_starter_site = get_option( 'neve_imported_demo' );
+ if ( $is_new_user === 'yes' && $imported_starter_site !== 'yes' ) {
+ return;
+ }
+
+ $this->_subscribers['.has-neve-button-color-color'] = [
+ Config::CSS_PROP_COLOR => [
+ Dynamic_Selector::META_KEY => Config::MODS_BUTTON_PRIMARY_STYLE . '.background',
+ Dynamic_Selector::META_IMPORTANT => true,
+ Dynamic_Selector::META_DEFAULT => 'var(--nv-primary-accent)',
+ Dynamic_Selector::KEY_CONTEXT => [
+ Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
- Config::CSS_PROP_TEXT_TRANSFORM => Config::MODS_TYPEFACE_GENERAL . '.textTransform',
- Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_GENERAL,
- ],
- Dynamic_Selector::KEY_CONTEXT => [
- Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
];
- foreach (
- [
- 'neve_h1_typeface_general' => '
- .wp-block h1, h1.wp-block,
- .editor-post-title__block .editor-post-title__input',
- 'neve_h2_typeface_general' => ' .wp-block h2, h2.wp-block',
- 'neve_h3_typeface_general' => '.wp-block h3, h3.wp-block',
- 'neve_h4_typeface_general' => '.wp-block h4, h4.wp-block',
- 'neve_h5_typeface_general' => '.wp-block h5, h5.wp-block',
- 'neve_h6_typeface_general' => '.wp-block h6, h6.wp-block',
- ] as $heading_mod => $heading_selector
- ) {
- $this->_subscribers[] = [
- Dynamic_Selector::KEY_RULES => [
- Config::CSS_PROP_FONT_SIZE => [
- Dynamic_Selector::META_KEY => $heading_mod . '.fontSize',
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- Dynamic_Selector::META_SUFFIX => 'em',
- ],
- Config::CSS_PROP_LINE_HEIGHT => [
- Dynamic_Selector::META_KEY => $heading_mod . '.lineHeight',
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- Dynamic_Selector::META_SUFFIX => '',
- ],
- Config::CSS_PROP_LETTER_SPACING => [
- Dynamic_Selector::META_KEY => $heading_mod . '.letterSpacing',
- Dynamic_Selector::META_IS_RESPONSIVE => true,
- ],
- Config::CSS_PROP_FONT_WEIGHT => [
- Dynamic_Selector::META_KEY => $heading_mod . '.fontWeight',
- 'font' => 'mods_' . Config::MODS_FONT_HEADINGS,
- ],
- Config::CSS_PROP_TEXT_TRANSFORM => $heading_mod . '.textTransform',
- Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_HEADINGS,
- ],
- Dynamic_Selector::KEY_SELECTOR => $heading_selector,
- Dynamic_Selector::KEY_CONTEXT => [
+ $this->_subscribers['.has-neve-button-color-background-color'] = [
+ Config::CSS_PROP_BACKGROUND_COLOR => [
+ Dynamic_Selector::META_KEY => Config::MODS_BUTTON_PRIMARY_STYLE . '.background',
+ Dynamic_Selector::META_IMPORTANT => true,
+ Dynamic_Selector::META_DEFAULT => 'var(--nv-primary-accent)',
+ Dynamic_Selector::KEY_CONTEXT => [
Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
- ];
- }
+ ],
+ ];
}
/**
- * Setup button subscribers.
+ * Setup legacy buttons selectors.
*/
- public function setup_buttons() {
-
-
+ private function setup_legacy_buttons() {
// Gutenberg
$this->_subscribers[] = [
Dynamic_Selector::KEY_SELECTOR => '.wp-block-button.is-style-primary .wp-block-button__link, .wc-block-grid .wp-block-button .wp-block-button__link',
@@ -186,9 +152,7 @@ public function setup_buttons() {
Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
];
-
-
- $this->_subscribers[] = [
+ $this->_subscribers[] = [
Dynamic_Selector::KEY_SELECTOR => '.wp-block-button.is-style-primary .wp-block-button__link, .wc-block-grid .wp-block-button .wp-block-button__link',
Dynamic_Selector::KEY_RULES => [
Config::CSS_PROP_PADDING => [
@@ -219,7 +183,7 @@ public function setup_buttons() {
Dynamic_Selector::CONTEXT_GUTENBERG => true,
],
];
- $this->_subscribers[] = [
+ $this->_subscribers[] = [
Dynamic_Selector::KEY_SELECTOR => '.wp-block-button.is-style-secondary .wp-block-button__link',
Dynamic_Selector::KEY_RULES => [
Config::CSS_PROP_PADDING => [
@@ -253,34 +217,111 @@ public function setup_buttons() {
}
/**
- * Adds colors from the editor-color-palette theme support.
+ * Setup legacy typography.
*/
- private function add_editor_color_palette_styles() {
- $is_new_user = get_option( 'neve_new_user' );
- $imported_starter_site = get_option( 'neve_imported_demo' );
- if ( $is_new_user === 'yes' && $imported_starter_site !== 'yes' ) {
- return;
- }
-
- $this->_subscribers['.has-neve-button-color-color'] = [
- Config::CSS_PROP_COLOR => [
- Dynamic_Selector::META_KEY => Config::MODS_BUTTON_PRIMARY_STYLE . '.background',
- Dynamic_Selector::META_IMPORTANT => true,
- Dynamic_Selector::META_DEFAULT => 'var(--nv-primary-accent)',
- Dynamic_Selector::KEY_CONTEXT => [
- Dynamic_Selector::CONTEXT_GUTENBERG => true,
+ private function setup_legacy_typography() {
+ // Gutenberg Typography.
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => '.editor-post-title__block .editor-post-title__input,
+ .wp-block h1, h1.wp-block
+ .wp-block h2, h2.wp-block
+ .wp-block h3, h3.wp-block
+ .wp-block h4, h4.wp-block
+ .wp-block h5, h5.wp-block
+ .wp-block h6, h6.wp-block',
+ Dynamic_Selector::KEY_RULES => [
+ Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_HEADINGS,
+ ],
+ Dynamic_Selector::KEY_CONTEXT => [
+ Dynamic_Selector::CONTEXT_GUTENBERG => true,
+ ],
+ ];
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => '.editor-styles-wrapper',
+ Dynamic_Selector::KEY_RULES => [
+ Config::CSS_PROP_FONT_FAMILY => [
+ Dynamic_Selector::META_KEY => Config::MODS_FONT_GENERAL,
+ Dynamic_Selector::META_DEFAULT => Mods::get_alternative_mod_default( Config::MODS_FONT_GENERAL ),
],
],
+ Dynamic_Selector::KEY_CONTEXT => [
+ Dynamic_Selector::CONTEXT_GUTENBERG => true,
+ ],
];
- $this->_subscribers['.has-neve-button-color-background-color'] = [
- Config::CSS_PROP_BACKGROUND_COLOR => [
- Dynamic_Selector::META_KEY => Config::MODS_BUTTON_PRIMARY_STYLE . '.background',
- Dynamic_Selector::META_IMPORTANT => true,
- Dynamic_Selector::META_DEFAULT => 'var(--nv-primary-accent)',
- Dynamic_Selector::KEY_CONTEXT => [
- Dynamic_Selector::CONTEXT_GUTENBERG => true,
+
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_SELECTOR => ' .wp-block,
+ [data-type="core/paragraph"] p',
+ Dynamic_Selector::KEY_RULES => [
+ Config::CSS_PROP_FONT_SIZE => [
+ Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.fontSize',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'px',
+ ],
+ Config::CSS_PROP_LINE_HEIGHT => [
+ Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.lineHeight',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => '',
+ ],
+ Config::CSS_PROP_LETTER_SPACING => [
+ Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.letterSpacing',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ Config::CSS_PROP_FONT_WEIGHT => [
+ Dynamic_Selector::META_KEY => Config::MODS_TYPEFACE_GENERAL . '.fontWeight',
+ 'font' => 'mods_' . Config::MODS_FONT_GENERAL,
+ ],
+ Config::CSS_PROP_TEXT_TRANSFORM => Config::MODS_TYPEFACE_GENERAL . '.textTransform',
+ Config::CSS_PROP_FONT_FAMILY => [
+ Dynamic_Selector::META_KEY => Config::MODS_FONT_GENERAL,
+ Dynamic_Selector::META_DEFAULT => Mods::get_alternative_mod_default( Config::MODS_FONT_GENERAL ),
],
],
+ Dynamic_Selector::KEY_CONTEXT => [
+ Dynamic_Selector::CONTEXT_GUTENBERG => true,
+ ],
];
+ foreach (
+ [
+ 'neve_h1_typeface_general' => '
+ .wp-block h1, h1.wp-block,
+ .editor-post-title__block .editor-post-title__input',
+ 'neve_h2_typeface_general' => ' .wp-block h2, h2.wp-block',
+ 'neve_h3_typeface_general' => '.wp-block h3, h3.wp-block',
+ 'neve_h4_typeface_general' => '.wp-block h4, h4.wp-block',
+ 'neve_h5_typeface_general' => '.wp-block h5, h5.wp-block',
+ 'neve_h6_typeface_general' => '.wp-block h6, h6.wp-block',
+ ] as $heading_mod => $heading_selector
+ ) {
+
+ $this->_subscribers[] = [
+ Dynamic_Selector::KEY_RULES => [
+ Config::CSS_PROP_FONT_SIZE => [
+ Dynamic_Selector::META_KEY => $heading_mod . '.fontSize',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => 'em',
+ ],
+ Config::CSS_PROP_LINE_HEIGHT => [
+ Dynamic_Selector::META_KEY => $heading_mod . '.lineHeight',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ Dynamic_Selector::META_SUFFIX => '',
+ ],
+ Config::CSS_PROP_LETTER_SPACING => [
+ Dynamic_Selector::META_KEY => $heading_mod . '.letterSpacing',
+ Dynamic_Selector::META_IS_RESPONSIVE => true,
+ ],
+ Config::CSS_PROP_FONT_WEIGHT => [
+ Dynamic_Selector::META_KEY => $heading_mod . '.fontWeight',
+ 'font' => 'mods_' . Config::MODS_FONT_HEADINGS,
+ ],
+ Config::CSS_PROP_TEXT_TRANSFORM => $heading_mod . '.textTransform',
+ Config::CSS_PROP_FONT_FAMILY => Config::MODS_FONT_HEADINGS,
+ ],
+ Dynamic_Selector::KEY_SELECTOR => $heading_selector,
+ Dynamic_Selector::KEY_CONTEXT => [
+ Dynamic_Selector::CONTEXT_GUTENBERG => true,
+ ],
+ ];
+ }
}
}
diff --git a/inc/customizer/base_customizer.php b/inc/customizer/base_customizer.php
index 3811bf4ac6..de586c05ba 100644
--- a/inc/customizer/base_customizer.php
+++ b/inc/customizer/base_customizer.php
@@ -14,6 +14,7 @@
use Neve\Customizer\Types\Panel;
use Neve\Customizer\Types\Partial;
use Neve\Customizer\Types\Section;
+use HFG\Traits\Core;
use WP_Customize_Manager;
/**
@@ -22,6 +23,8 @@
* @package Neve\Customizer\Abstracts
*/
abstract class Base_Customizer {
+ use Core;
+
/**
* WP_Customize object
*
@@ -165,7 +168,7 @@ private function register_controls() {
$new_control = $this->wpc->add_control( $control->id, $control->control_args );
$control_type = isset( $control->control_args['type'] ) ? $control->control_args['type'] : $new_control->type;
}
- if ( isset( $control->control_args['live_refresh_selector'] ) ) {
+ if ( isset( $control->control_args['live_refresh_selector'] ) && $control->control_args['live_refresh_selector'] !== false ) {
$control_args = array(
'selector' => $control->control_args['live_refresh_selector'],
'id' => $control->id,
@@ -323,4 +326,154 @@ public function change_customizer_object( $type, $id, $property, $value ) {
$object->$property = $value;
}
+
+ /**
+ * Function to add controls that form the boxed layout.
+ *
+ * @param string $id Controls short id.
+ * @param array $settings Controls settings.
+ */
+ public function add_boxed_layout_controls( $id, $settings ) {
+ $this->add_control(
+ new Control(
+ 'neve_' . $id . '_boxed_layout',
+ [
+ 'sanitize_callback' => 'neve_sanitize_checkbox',
+ 'default' => array_key_exists( 'is_boxed_default', $settings ) ? $settings['is_boxed_default'] : false,
+ ],
+ [
+ 'label' => esc_html__( 'Boxed layout', 'neve' ),
+ 'section' => $settings['section'],
+ 'type' => 'neve_toggle_control',
+ 'priority' => $settings['priority'],
+ 'active_callback' => array_key_exists( 'toggle_active_callback', $settings ) ? $settings['toggle_active_callback'] : '__return_true',
+ ],
+ 'Neve\Customizer\Controls\Checkbox'
+ )
+ );
+
+ $padding_live_refresh_settings = [
+ 'responsive' => true,
+ 'directional' => true,
+ 'template' =>
+ $settings['boxed_selector'] . '{
+ padding-top: {{value.top}};
+ padding-right: {{value.right}};
+ padding-bottom: {{value.bottom}};
+ padding-left: {{value.left}};
+ }',
+ ];
+
+ $background_live_refresh_settings = [
+ 'template' =>
+ $settings['boxed_selector'] . '{
+ background-color: {{value}};
+ }',
+
+ ];
+
+ $has_text_color = isset( $settings['has_text_color'] ) ? $settings['has_text_color'] : true;
+ if ( $has_text_color ) {
+ $template = $settings['text_color_css_selector'] . '{ color: {{value}}; }';
+ if ( array_key_exists( 'border_color_css_selector', $settings ) ) {
+ $template .= $settings['border_color_css_selector'] . '{ border-color: {{value}}; }';
+ }
+ $color_live_refresh_settings = [
+ 'template' => $template,
+ ];
+ }
+
+ if ( neve_is_new_skin() ) {
+ $padding_live_refresh_settings = [
+ 'cssVar' => array(
+ 'vars' => '--padding',
+ 'selector' => $settings['boxed_selector'],
+ 'responsive' => true,
+ ),
+ ];
+
+ $background_live_refresh_settings = [
+ 'cssVar' => array(
+ 'vars' => '--bgColor',
+ 'selector' => $settings['boxed_selector'],
+ ),
+ ];
+
+ if ( $has_text_color ) {
+ $color_live_refresh_settings = [
+ 'cssVar' => array(
+ 'vars' => '--color',
+ 'selector' => $settings['boxed_selector'],
+ ),
+ ];
+ }
+ }
+
+ $this->add_control(
+ new Control(
+ 'neve_' . $id . '_boxed_padding',
+ [
+ 'sanitize_callback' => [ $this, 'sanitize_spacing_array' ],
+ 'transport' => $this->selective_refresh,
+ 'default' => array_key_exists( 'padding_default', $settings ) ? $settings['padding_default'] : false,
+ ],
+ [
+ 'label' => esc_html__( 'Section padding', 'neve' ),
+ 'section' => $settings['section'],
+ 'input_attrs' => [
+ 'units' => [ 'em', 'px' ],
+ 'min' => 0,
+ ],
+ 'default' => array_key_exists( 'padding_default', $settings ) ? $settings['padding_default'] : false,
+ 'priority' => $settings['priority'],
+ 'live_refresh_selector' => true,
+ 'live_refresh_css_prop' => $padding_live_refresh_settings,
+ 'active_callback' => array_key_exists( 'active_callback', $settings ) ? $settings['active_callback'] : false,
+ ],
+ '\Neve\Customizer\Controls\React\Spacing'
+ )
+ );
+
+ $this->add_control(
+ new Control(
+ 'neve_' . $id . '_boxed_background_color',
+ [
+ 'sanitize_callback' => 'neve_sanitize_colors',
+ 'transport' => $this->selective_refresh,
+ 'default' => array_key_exists( 'background_default', $settings ) ? $settings['background_default'] : false,
+ ],
+ [
+ 'label' => esc_html__( 'Background color', 'neve' ),
+ 'section' => $settings['section'],
+ 'priority' => $settings['priority'],
+ 'live_refresh_selector' => true,
+ 'live_refresh_css_prop' => $background_live_refresh_settings,
+ 'active_callback' => array_key_exists( 'active_callback', $settings ) ? $settings['active_callback'] : false,
+ ],
+ 'Neve\Customizer\Controls\React\Color'
+ )
+ );
+
+ if ( $has_text_color ) {
+ $this->add_control(
+ new Control(
+ 'neve_' . $id . '_boxed_text_color',
+ [
+ 'sanitize_callback' => 'neve_sanitize_colors',
+ 'transport' => $this->selective_refresh,
+ 'default' => array_key_exists( 'color_default', $settings ) ? $settings['color_default'] : false,
+ ],
+ [
+ 'label' => esc_html__( 'Text color', 'neve' ),
+ 'section' => $settings['section'],
+ 'priority' => $settings['priority'],
+ 'live_refresh_selector' => true,
+ 'live_refresh_css_prop' => $color_live_refresh_settings,
+ 'active_callback' => array_key_exists( 'active_callback', $settings ) ? $settings['active_callback'] : false,
+ ],
+ 'Neve\Customizer\Controls\React\Color'
+ )
+ );
+ }
+ }
}
diff --git a/inc/customizer/controls/react/builder.php b/inc/customizer/controls/react/builder.php
new file mode 100644
index 0000000000..c726129b61
--- /dev/null
+++ b/inc/customizer/controls/react/builder.php
@@ -0,0 +1,48 @@
+builder_type;
+ $json['columnsLayout'] = $this->columns_layout;
+
+ return $json;
+ }
+}
diff --git a/inc/customizer/controls/react/builder_columns.php b/inc/customizer/controls/react/builder_columns.php
new file mode 100644
index 0000000000..b805b96664
--- /dev/null
+++ b/inc/customizer/controls/react/builder_columns.php
@@ -0,0 +1,98 @@
+choices = [
+ 1 => [
+ 'equal' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ ],
+ 2 => [
+ 'equal' => [
+ 'url' => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAA8AFsDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+6KT/AFkn++3/AKEa1MhlABQAUAFABQAUAFABQAUAFAHa6R/yD7f/ALa/+j5ah7jONk/1kn++3/oRqxDKACgAoAKACgAoAKACgAoAKAO10j/kH2//AG1/9Hy1D3GcbJ/rJP8Afb/0I1YhlABQAUAFABQAUAFABQAUAFAHa6R/yD7f/tr/AOj5ah7jODvbhreQ7bW5ud7yZ+zLCdm1hjf5s0P3t3y7d33WzjjNgU/7Rl/6BWp/98Wf/wAmUX8n+H+YfNfj/kH9oy/9ArU/++LP/wCTKL+T/D/MPmvx/wAg/tGX/oFan/3xZ/8AyZRfyf4f5h81+P8AkH9oy/8AQK1P/viz/wDkyi/k/wAP8w+a/H/IP7Rl/wCgVqf/AHxZ/wDyZRfyf4f5h81+P+Qf2jL/ANArU/8Aviz/APkyi/k/w/zD5r8f8g/tGX/oFan/AN8Wf/yZRfyf4f5h81+P+Qf2jL/0CtT/AO+LP/5Mov5P8P8AMPmvx/yD+0Zf+gVqf/fFn/8AJlF/J/h/mHzX4/5D4r6SSRUOnX8QY4Mkq2ojT3YpdO2P91WPtR8n+H+YfP8AP/I9F0j/AJB9v/21/wDR8tQ9wPBviL4v1LwodNbToLGY30t+JvtsVxIFFt9mKeX5FzbYz57792/OFxtwc/YcK5Dg88ljli6mJprDRw7p/V50oX9q6ylz+1o1r29nHlty2u730t85xBm+JylYR4eFCft3WU/bRqSt7NUuXl5KtO1+d3vfpa2t/Mv+Fw+Jv+fHQv8AwG1D/wCWdfYf6g5P/wBBOZ/+DsL/APMZ81/rhmf/AD4wP/grEf8AzUH/AAuHxN/z46F/4Dah/wDLOj/UHJ/+gnM//B2F/wDmMP8AXDM/+fGB/wDBWI/+ag/4XD4m/wCfHQv/AAG1D/5Z0f6g5P8A9BOZ/wDg7C//ADGH+uGZ/wDPjA/+CsR/81B/wuHxN/z46F/4Dah/8s6P9Qcn/wCgnM//AAdhf/mMP9cMz/58YH/wViP/AJqPU/BninUPEXh7UdWvYbOK5s7u7gjS1jnSBkt7G1uULrLcTyFjJO4YrIoKBQApBY/FcQ5LhcpzXCYHDVMROlXoUKs5V505VFKria9GSi6dKnFJRpxavBvmbbbVkvqcmzTEZjl+IxdeFGNSjWrU4xpRnGDVOhSqJyU6k5NuU2naSVrWSd2/LP8AhcPib/nx0L/wG1D/AOWdfa/6g5P/ANBOZ/8Ag7C//MZ8t/rhmf8Az4wP/grEf/NQf8Lh8Tf8+Ohf+A2of/LOj/UHJ/8AoJzP/wAHYX/5jD/XDM/+fGB/8FYj/wCag/4XD4m/58dC/wDAbUP/AJZ0f6g5P/0E5n/4Owv/AMxh/rhmf/PjA/8AgrEf/NQf8Lh8Tf8APjoX/gNqH/yzo/1Byf8A6Ccz/wDB2F/+Yw/1wzP/AJ8YH/wViP8A5qNvw58T9f1jXNM0y5tNHSC9uVhleC3vVmVSrEmNpNQlQNx1aNx7V52b8GZXgMtxmMo18fKrh6LqQjVq4d03JNK0lHCwk1r0lF+Z25bxPj8ZjsNhqtHBxp1qqhJ06dZTSab91yxEop6dYv0PqvSP+Qfb/wDbX/0fLX5g9z7s+XPjZ00D/rtq/wD7YV+l+Hnx5t/gwX54o+H40+HLv8WK/LDng1fpp8GFABQAUAfQnwu/5ErXP+wjqX/pp0+vynjT/kocu/7A8H/6nYo/Q+Fv+RLjf+wnE/8AqJhz57r9WPzwKACgAoA6rwP/AMjboP8A1/p/6A9eHxJ/yIsz/wCwWX/pUT1sj/5G+A/6/r/0mR95aR/yD7f/ALa/+j5a/A3ufrx81fF/TNS1L+xRp2n31+YZtUMwsrS4ujEJPsWwyeRHJs37H2bsbtrYztOP0LgXGYPBzzN4vF4bCqpHCez+sV6VDn5Xiebk9rKPNy80ea17XV90fHcW4XE4mOA+r4eviOSWJ5/Y0qlXl5lQ5ebkjLlvZ2va9nbZnin/AAi/ib/oXdd/8FGof/I9fof9tZP/ANDbLP8Awvwv/wAtPi/7LzP/AKF2O/8ACTEf/Kw/4RfxN/0Luu/+CjUP/kej+2sn/wChtln/AIX4X/5aH9l5n/0Lsd/4SYj/AOVh/wAIv4m/6F3Xf/BRqH/yPR/bWT/9DbLP/C/C/wDy0P7LzP8A6F2O/wDCTEf/ACsP+EX8Tf8AQu67/wCCjUP/AJHo/trJ/wDobZZ/4X4X/wCWh/ZeZ/8AQux3/hJiP/lZ7r8ONO1Cx8I6xbXtjeWdzLf6g8dvdWs9vPIj6ZYxo6RSokjq8iOisqkM6soJKkD804uxeFxOe4CthsTh8RShhcLGdWhWp1acJRxmJlKMp05SjFxjKMmm01GSb0aPuuG8PiKGUYynXoVqNSWIxEo06tKdOclLDUIpxhOKk05JxTSs2mlqmeFf8Iv4m/6F3Xf/AAUah/8AI9fpf9tZP/0Nss/8L8L/APLT4X+y8z/6F2O/8JMR/wDKw/4RfxN/0Luu/wDgo1D/AOR6P7ayf/obZZ/4X4X/AOWh/ZeZ/wDQux3/AISYj/5WH/CL+Jv+hd13/wAFGof/ACPR/bWT/wDQ2yz/AML8L/8ALQ/svM/+hdjv/CTEf/Kw/wCEX8Tf9C7rv/go1D/5Ho/trJ/+htln/hfhf/lof2Xmf/Qux3/hJiP/AJWdN4O8Pa/a+KNFuLnQ9Yt4Ir1Hlnn029hhjUK+WkkkhVEXnqzAe9ePxBmuV1smzGlRzLAVas8PKMKdLGYepUnLmjpGEajlJ+STZ6eTZfj6WaYKpVwOMp04Vk5TqYatCEVZ6ylKCil5to+2tI/5B9v/ANtf/R8tfiT3P1I42T/WSf77f+hGrEMoAKACgAoAKACgAoAKACgAoA7XSP8AkH2//bX/ANHy1D3GAP/Z',
+ ],
+ 'right-third' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ 'left-third' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ ],
+ 3 => [
+ 'equal' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ 'left-half' => [
+ 'url' => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAA8AFoDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+5+tTIKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgCpcXLwFAtpdXO4Ek26wkJjHD+bPEcnPGA3Q5xQMr/2jL/0CtT/AO+LP/5Mov5P8P8AMPmvx/yD+0Zf+gVqf/fFn/8AJlF/J/h/mHzX4/5B/aMv/QK1P/viz/8Akyi/k/w/zD5r8f8AIP7Rl/6BWp/98Wf/AMmUX8n+H+YfNfj/AJB/aMv/AECtT/74s/8A5Mov5P8AD/MPmvx/yD+0Zf8AoFan/wB8Wf8A8mUX8n+H+YfNfj/kH9oy/wDQK1P/AL4s/wD5Mov5P8P8w+a/H/IP7Rl/6BWp/wDfFn/8mUX8n+H+YfNfj/kH9oy/9ArU/wDviz/+TKL+T/D/ADD5r8f8iSK9klkWM6ffwhiQZJVthGuATlil07YOMDCNyR25o+T/AA/zD5/n/kX6BHmPxA8aap4Um0yPTrewmW9iunlN7FcSFTC8Kr5fkXVsACJG3bg5JAwRzn7Lhbh7BZ5Txk8XVxVN4edGMPq86UE1UjUcub2lCrdrkVrOPW9+nzPEGdYrKp4WOHp4earRqyn7aNSTTg4JcvJVp2+J3vfpax55/wALh8Tf8+Ohf+A2of8Ayzr6v/UHJ/8AoJzP/wAHYX/5jPnv9cMz/wCfGB/8FYj/AOag/wCFw+Jv+fHQv/AbUP8A5Z0f6g5P/wBBOZ/+DsL/APMYf64Zn/z4wP8A4KxH/wA1B/wuHxN/z46F/wCA2of/ACzo/wBQcn/6Ccz/APB2F/8AmMP9cMz/AOfGB/8ABWI/+ag/4XD4m/58dC/8BtQ/+WdH+oOT/wDQTmf/AIOwv/zGH+uGZ/8APjA/+CsR/wDNR6Z4H8Xal4m03Vry/gsYpbCQJCtpHPHGwMDS/vBNczsTuAHysnHbPNfHcSZFhMnxeBw+GqYmpDEwcqjrzpSkmqqh7jp0aSSs/tRlr5aH02R5viczw2LrV4UISoSSgqMakYtezcveU6lRvVdGtDzP/hcPib/nx0L/AMBtQ/8AlnX2P+oOT/8AQTmf/g7C/wDzGfM/64Zn/wA+MD/4KxH/AM1B/wALh8Tf8+Ohf+A2of8Ayzo/1Byf/oJzP/wdhf8A5jD/AFwzP/nxgf8AwViP/mo9N8S+LdS0bwjo+vWsFjJeah/ZnnRzxztbL9t0+W6l8pI7mKVdsiBY98z4TIbe2GHx2T5FhMwz3H5ZWqYmOHwv1z2c6U6Uaz+r4qFGHPKdGcHeEm5ctON5Wa5VofTZlm+JweUYPH0oUJVsR9W541I1HSXtsPKrLlUakZK0opRvOVo73ep5l/wuHxN/z46F/wCA2of/ACzr7H/UHJ/+gnM//B2F/wDmM+Z/1wzP/nxgf/BWI/8Amo6Dwt8S9d1zX9O0q7tNIjt7ySVJXt4LxJlCW80o2NJfyoDujUHdG3ykgAHBHl51wflmW5Xi8bQr46dXDwhKEatXDyptyq04PmUMLTk1aTatNa26aHflfE2Px2Pw2Fq0sJGnWlJSdOnWU0o05zXK5V5xWsVvF6X9T3GvzY+4PA/jN/x9aB/173//AKMta/UPD3+Bmn/X3C/+kVj4LjP+LgP+veI/9KpHilfop8SFABQAUAe8/CX/AJAXiP8A67L/AOkclfmPHX/Iyyn/AK9v/wBSIn3vCX+45j/jX/plng1fpx8EFAHvXjv/AJJt4Y/7gP8A6ZrivzDhn/kr84/7qf8A6n0j77Pf+Sbyz/uQ/wDUOoeC1+nnwJ2Xw9/5HLQ/+u9x/wCkVzXz3FX/ACT+Zf8AXql/6kUT2uHv+Rzgf+vlT/0zVPrWvws/WTxP4taVqmpXOiNp2m39+sUF6JWsrO4uhGXktiokMEcgQsFYqGwWAOM4Nfo3AuOwWEo5isXjMLhXOphnBYjEUqLmoxrcziqk48yV1e17XV9z4ri3CYrE1ME8PhsRiFCFdTdGjUqqLcqVlLkjLlvZ2va9nbY8g/4RfxN/0Luu/wDgo1D/AOR6+8/trJ/+htln/hfhf/lp8h/ZeZ/9C7Hf+EmI/wDlYf8ACL+Jv+hd13/wUah/8j0f21k//Q2yz/wvwv8A8tD+y8z/AOhdjv8AwkxH/wArD/hF/E3/AELuu/8Ago1D/wCR6P7ayf8A6G2Wf+F+F/8Alof2Xmf/AELsd/4SYj/5WH/CL+Jv+hd13/wUah/8j0f21k//AENss/8AC/C//LQ/svM/+hdjv/CTEf8Ays9r+GGmalp+ja/Ff6ffWUs0qmGO7tJ7aSUfZXXMaTRozjcQuVB5OOtfnfGeMwmKzDK54bFYbEQp02qk6FelWjB+3i7TlTlJRdtfea012PteGMNicPg8fGvh69CU5pwjWpVKcpL2TV4qcYuWuml9dDxT/hF/E3/Qu67/AOCjUP8A5Hr9E/trJ/8AobZZ/wCF+F/+WnxX9l5n/wBC7Hf+EmI/+Vh/wi/ib/oXdd/8FGof/I9H9tZP/wBDbLP/AAvwv/y0P7LzP/oXY7/wkxH/AMrPa/GemaldfD/w7ZWun31zeQf2J51pBaTzXMPlaTPHL5sEcbSx+XIRHJvQbHIVsMQK/OuHsZhKHFOa4itisNRw9T+0fZ16telTo1OfHU5w5Ks5KEueCco8snzRTkrrU+1zrDYmrw/l1Glh69StD6lz0qdKpOrDkwk4y5qcYuUeWTUZXStJ2dmeKf8ACL+Jv+hd13/wUah/8j1+i/21k/8A0Nss/wDC/C//AC0+K/svM/8AoXY7/wAJMR/8rOt8C6Brtp4r0e5u9F1e1t4ppzLPcabeQwxg2lwoLyyQqiAsyqCzDLEAckV4fEuaZZXyPMKNDMcDWqzp01ClSxeHqVJtV6TajCFRyk0k27J6JvZHrZFl+Po5tg6lXBYulTjOblUqYatCEU6NRJylKCirtpavdpH09X4yfpwUAFABQAUAFABQAUAFABQAUAFABQAA/9k=',
+ ],
+ 'right-half' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ 'center-half' => [
+ 'url' => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAA8AFoDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+5+tTIKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgCpcXLwFAtpdXO4Ek26wkJjHD+bPEcnPGA3Q5xQMr/2jL/0CtT/AO+LP/5Mov5P8P8AMPmvx/yD+0Zf+gVqf/fFn/8AJlF/J/h/mHzX4/5B/aMv/QK1P/viz/8Akyi/k/w/zD5r8f8AIP7Rl/6BWp/98Wf/AMmUX8n+H+YfNfj/AJB/aMv/AECtT/74s/8A5Mov5P8AD/MPmvx/yD+0Zf8AoFan/wB8Wf8A8mUX8n+H+YfNfj/kH9oy/wDQK1P/AL4s/wD5Mov5P8P8w+a/H/IP7Rl/6BWp/wDfFn/8mUX8n+H+YfNfj/kH9oy/9ArU/wDviz/+TKL+T/D/ADD5r8f8iSK9klkWM6ffwhiQZJVthGuATlil07YOMDCNyR25o+T/AA/zD5/n/kX6BHmPxA8aap4Um0yPTrewmW9iunlN7FcSFTC8Kr5fkXVsACJG3bg5JAwRzn7Lhbh7BZ5Txk8XVxVN4edGMPq86UE1UjUcub2lCrdrkVrOPW9+nzPEGdYrKp4WOHp4earRqyn7aNSTTg4JcvJVp2+J3vfpax55/wALh8Tf8+Ohf+A2of8Ayzr6v/UHJ/8AoJzP/wAHYX/5jPnv9cMz/wCfGB/8FYj/AOajuPAXjvV/FOq3VjqFtpsMUGnvdo1nDdRyGRbm2hCsZ7y4UptmYkBA24Kd2AQfm+J+GcBkuCoYnC1cXUnVxUaEliKlGcFB0q1RtKnh6T5r00ruTVm9L2a9zIc9xeaYqrQxFPDQhTw8qqdGFWMnJVKcLNzrVFy2m9Ek7216Pm9c+KfiDTdZ1XToLPRnhsdQu7SJpre9aVo4J3iRpGTUY0LlVBYqiKTnCgcV6+W8FZVjMvwWLq4jMI1MThaFecadXDKCnVpxnJQUsJKSim3ZOUnbdvc83HcU5hhsZisPTo4NwoYirSg5067k405yinJrERTk0tWopX2SMv8A4XD4m/58dC/8BtQ/+Wddv+oOT/8AQTmf/g7C/wDzGcv+uGZ/8+MD/wCCsR/81B/wuHxN/wA+Ohf+A2of/LOj/UHJ/wDoJzP/AMHYX/5jD/XDM/8Anxgf/BWI/wDmoP8AhcPib/nx0L/wG1D/AOWdH+oOT/8AQTmf/g7C/wDzGH+uGZ/8+MD/AOCsR/8ANQf8Lh8Tf8+Ohf8AgNqH/wAs6P8AUHJ/+gnM/wDwdhf/AJjD/XDM/wDnxgf/AAViP/mo9N8S+LdS0bwjo+vWsFjJeah/ZnnRzxztbL9t0+W6l8pI7mKVdsiBY98z4TIbe2GHx2T5FhMwz3H5ZWqYmOHwv1z2c6U6Uaz+r4qFGHPKdGcHeEm5ctON5Wa5VofTZlm+JweUYPH0oUJVsR9W541I1HSXtsPKrLlUakZK0opRvOVo73ep5l/wuHxN/wA+Ohf+A2of/LOvsf8AUHJ/+gnM/wDwdhf/AJjPmf8AXDM/+fGB/wDBWI/+ajoPC3xL13XNf07Sru00iO3vJJUle3gvEmUJbzSjY0l/KgO6NQd0bfKSAAcEeXnXB+WZbleLxtCvjp1cPCEoRq1cPKm3KrTg+ZQwtOTVpNq01rbpod+V8TY/HY/DYWrSwkadaUlJ06dZTSjTnNcrlXnFaxW8Xpf1Pca/Nj7g8D+M3/H1oH/Xvf8A/oy1r9Q8Pf4Gaf8AX3C/+kVj4LjP+LgP+veI/wDSqR4pX6KfEnrXwd/5GHUf+wNL/wCl1jXwvH//ACKsJ/2MIf8AqNiT63g7/kYYj/sCn/6foHC+LP8AkaPEP/YZ1L/0rlr6XIv+RLlX/Yvwn/piB4ebf8jTMP8AsNxP/p2Zz9eqecFABQAUAe9eO/8Akm3hj/uA/wDpmuK/MOGf+Svzj/up/wDqfSPvs9/5JvLP+5D/ANQ6h4LX6efAnZfD3/kctD/673H/AKRXNfPcVf8AJP5l/wBeqX/qRRPa4e/5HOB/6+VP/TNU+ta/Cz9ZPE/i1pWqalc6I2nabf36xQXolays7i6EZeS2KiQwRyBCwViobBYA4zg1+jcC47BYSjmKxeMwuFc6mGcFiMRSouajGtzOKqTjzJXV7XtdX3PiuLcJisTUwTw+GxGIUIV1N0aNSqotypWUuSMuW9na9r2dtjyD/hF/E3/Qu67/AOCjUP8A5Hr7z+2sn/6G2Wf+F+F/+WnyH9l5n/0Lsd/4SYj/AOVnp/wq0fV9O12/m1DStSsYn0mWNJbyxurWNpDeWTCNXniRWcqjMFBLFVY4wDXxnG+YYDF5bhaeFxuExM446E5Qw+Jo1pxgsPiIuTjTnJqN5JczVrtK92j6fhTB4vD46vPEYXE0IPCSipVqFWlFy9tRfKpTjFOVk3ZO9k30ON8T+HfEFx4j12eDQtZmhm1bUJIpodLvZIpY3upWSSORIGR0dSGVlJVgQQSDX0OTZtlVLKctp1czy+nUp4HCwnTqY3DQnCcaMFKM4SqKUZRaacWk09Gjxszy7MKmY46dPAYycJ4vEShOGFryjKMqsmpRkoNSi1qmm01qjC/4RfxN/wBC7rv/AIKNQ/8AkevS/trJ/wDobZZ/4X4X/wCWnD/ZeZ/9C7Hf+EmI/wDlYf8ACL+Jv+hd13/wUah/8j0f21k//Q2yz/wvwv8A8tD+y8z/AOhdjv8AwkxH/wArD/hF/E3/AELuu/8Ago1D/wCR6P7ayf8A6G2Wf+F+F/8Alof2Xmf/AELsd/4SYj/5WH/CL+Jv+hd13/wUah/8j0f21k//AENss/8AC/C//LQ/svM/+hdjv/CTEf8Ays9r8Z6ZqV18P/Dtla6ffXN5B/YnnWkFpPNcw+VpM8cvmwRxtLH5chEcm9BschWwxAr864exmEocU5riK2Kw1HD1P7R9nXq16VOjU58dTnDkqzkoS54JyjyyfNFOSutT7XOsNiavD+XUaWHr1K0PqXPSp0qk6sOTCTjLmpxi5R5ZNRldK0nZ2Z4p/wAIv4m/6F3Xf/BRqH/yPX6L/bWT/wDQ2yz/AML8L/8ALT4r+y8z/wChdjv/AAkxH/ys63wLoGu2nivR7m70XV7W3imnMs9xpt5DDGDaXCgvLJCqICzKoLMMsQByRXh8S5pllfI8wo0MxwNarOnTUKVLF4epUm1XpNqMIVHKTSTbsnom9ketkWX4+jm2DqVcFi6VOM5uVSphq0IRTo1EnKUoKKu2lq92kfT1fjJ+nBQAUAFABQAUAFABQAUAFABQAUAFAAD/2Q==',
+ ],
+ 'center-two-thirds' => [
+ 'url' => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAA8AFoDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+5+tTIKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgCpcXLwFAtpdXO4Ek26wkJjHD+bPEcnPGA3Q5xQMr/2jL/0CtT/AO+LP/5Mov5P8P8AMPmvx/yD+0Zf+gVqf/fFn/8AJlF/J/h/mHzX4/5B/aMv/QK1P/viz/8Akyi/k/w/zD5r8f8AIP7Rl/6BWp/98Wf/AMmUX8n+H+YfNfj/AJB/aMv/AECtT/74s/8A5Mov5P8AD/MPmvx/yD+0Zf8AoFan/wB8Wf8A8mUX8n+H+YfNfj/kH9oy/wDQK1P/AL4s/wD5Mov5P8P8w+a/H/IP7Rl/6BWp/wDfFn/8mUX8n+H+YfNfj/kH9oy/9ArU/wDviz/+TKL+T/D/ADD5r8f8iSK9klkWM6ffwhiQZJVthGuATlil07YOMDCNyR25o+T/AA/zD5/n/kX6BHmPxA8aap4Um0yPTrewmW9iunlN7FcSFTC8Kr5fkXVsACJG3bg5JAwRzn7Lhbh7BZ5Txk8XVxVN4edGMPq86UE1UjUcub2lCrdrkVrOPW9+nzPEGdYrKp4WOHp4earRqyn7aNSTTg4JcvJVp2+J3vfpaxznhb4l67rmv6dpV3aaRHb3kkqSvbwXiTKEt5pRsaS/lQHdGoO6NvlJAAOCPXzrg/LMtyvF42hXx06uHhCUI1auHlTblVpwfMoYWnJq0m1aa1t00PNyvibH47H4bC1aWEjTrSkpOnTrKaUac5rlcq84rWK3i9L+pJ4u+JOuaB4h1DSbO00mW2tPsnlvcwXjzt59jbXL72ivoYziSZgu2NcIFByQWMZDwhluaZVhcdiK+OhWr+3540auHjTXssTWox5VPDVJK8acW7zfvNtWVkrzfiTHYDMcRhKNLCSp0vZcsqtOs5v2lClVfM414RdpTaVorS17vV83/wALh8Tf8+Ohf+A2of8Ayzr1/wDUHJ/+gnM//B2F/wDmM83/AFwzP/nxgf8AwViP/moP+Fw+Jv8Anx0L/wABtQ/+WdH+oOT/APQTmf8A4Owv/wAxh/rhmf8Az4wP/grEf/NQf8Lh8Tf8+Ohf+A2of/LOj/UHJ/8AoJzP/wAHYX/5jD/XDM/+fGB/8FYj/wCag/4XD4m/58dC/wDAbUP/AJZ0f6g5P/0E5n/4Owv/AMxh/rhmf/PjA/8AgrEf/NQf8Lh8Tf8APjoX/gNqH/yzo/1Byf8A6Ccz/wDB2F/+Yw/1wzP/AJ8YH/wViP8A5qD/AIXD4m/58dC/8BtQ/wDlnR/qDk//AEE5n/4Owv8A8xh/rhmf/PjA/wDgrEf/ADUeh/EDxpqnhSbTI9Ot7CZb2K6eU3sVxIVMLwqvl+RdWwAIkbduDkkDBHOflOFuHsFnlPGTxdXFU3h50Yw+rzpQTVSNRy5vaUKt2uRWs49b36fQ8QZ1isqnhY4enh5qtGrKfto1JNODgly8lWnb4ne9+lrHOeFviXruua/p2lXdppEdveSSpK9vBeJMoS3mlGxpL+VAd0ag7o2+UkAA4I9fOuD8sy3K8XjaFfHTq4eEJQjVq4eVNuVWnB8yhhacmrSbVprW3TQ83K+JsfjsfhsLVpYSNOtKSk6dOsppRpzmuVyrzitYreL0v6nuNfmx9weB/Gb/AI+tA/697/8A9GWtfqHh7/AzT/r7hf8A0isfBcZ/xcB/17xH/pVI4b4e/wDI5aH/ANd7j/0iua+k4q/5J/Mv+vVL/wBSKJ4fD3/I5wP/AF8qf+mapP8AEr/kdda/7h3/AKabCs+D/wDkncu/7m//AFOxJfEv/I7xv/ct/wColA4avpTwgoAKACgAoAKAPa/jN/x9aB/173//AKMta/OvD3+Bmn/X3C/+kVj7bjP+LgP+veI/9KpHDfD3/kctD/673H/pFc19JxV/yT+Zf9eqX/qRRPD4e/5HOB/6+VP/AEzVPrWvws/WTxP4taVqmpXOiNp2m39+sUF6JWsrO4uhGXktiokMEcgQsFYqGwWAOM4Nfo3AuOwWEo5isXjMLhXOphnBYjEUqLmoxrcziqk48yV1e17XV9z4ri3CYrE1ME8PhsRiFCFdTdGjUqqLcqVlLkjLlvZ2va9nbY43wLoGu2nivR7m70XV7W3imnMs9xpt5DDGDaXCgvLJCqICzKoLMMsQByRX0HEuaZZXyPMKNDMcDWqzp01ClSxeHqVJtV6TajCFRyk0k27J6JvZHjZFl+Po5tg6lXBYulTjOblUqYatCEU6NRJylKCirtpavdpE3xB0HXL3xfq9zZ6Nq13bS/YPLuLbTryeCTZpllG+yWKF432SIyNtY7XVlOCCBnwrmeW4fIcBRxGYYGhWh9a56VbF4elUjzYzESjzQnUjKPNGUZK6V4tNaNF8QYDHVs3xdWjgsXVpy+r8tSlh61SErYWjF8s4wcXaScXZ6NNPVHGf8Iv4m/6F3Xf/AAUah/8AI9fQf21k/wD0Nss/8L8L/wDLTxv7LzP/AKF2O/8ACTEf/Kw/4RfxN/0Luu/+CjUP/kej+2sn/wChtln/AIX4X/5aH9l5n/0Lsd/4SYj/AOVh/wAIv4m/6F3Xf/BRqH/yPR/bWT/9DbLP/C/C/wDy0P7LzP8A6F2O/wDCTEf/ACsP+EX8Tf8AQu67/wCCjUP/AJHo/trJ/wDobZZ/4X4X/wCWh/ZeZ/8AQux3/hJiP/lYf8Iv4m/6F3Xf/BRqH/yPR/bWT/8AQ2yz/wAL8L/8tD+y8z/6F2O/8JMR/wDKw/4RfxN/0Luu/wDgo1D/AOR6P7ayf/obZZ/4X4X/AOWh/ZeZ/wDQux3/AISYj/5Wev8Axa0rVNSudEbTtNv79YoL0StZWdxdCMvJbFRIYI5AhYKxUNgsAcZwa+D4Fx2CwlHMVi8ZhcK51MM4LEYilRc1GNbmcVUnHmSur2va6vufX8W4TFYmpgnh8NiMQoQrqbo0alVRblSspckZct7O17Xs7bHG+BdA1208V6Pc3ei6va28U05lnuNNvIYYwbS4UF5ZIVRAWZVBZhliAOSK+g4lzTLK+R5hRoZjga1WdOmoUqWLw9SpNqvSbUYQqOUmkm3ZPRN7I8bIsvx9HNsHUq4LF0qcZzcqlTDVoQinRqJOUpQUVdtLV7tI+nq/GT9OCgAoAKACgAoAKACgAoAKACgAoAKAAP/Z',
+ ],
+ ],
+ 4 => [
+ 'equal' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ 'left-half' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ 'right-half' => [
+ 'url' => 'data:image/jpeg;base64,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',
+ ],
+ ],
+ 5 => [
+ 'equal' => [
+ 'url' => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAA8AFsDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+6KT/AFkn++3/AKEa1MhlABQAUAFABQAUAFABQAUAFAHa6R/yD7f/ALa/+j5ah7jONk/1kn++3/oRqxDKACgAoAKACgAoAKACgAoAKAO10j/kH2//AG1/9Hy1D3GcbJ/rJP8Afb/0I1YhlABQAUAFABQAUAFABQAUAFAHa6R/yD7f/tr/AOj5ah7jODvbhreQ7bW5ud7yZ+zLCdm1hjf5s0P3t3y7d33WzjjNgU/7Rl/6BWp/98Wf/wAmUX8n+H+YfNfj/kH9oy/9ArU/++LP/wCTKL+T/D/MPmvx/wAg/tGX/oFan/3xZ/8AyZRfyf4f5h81+P8AkH9oy/8AQK1P/viz/wDkyi/k/wAP8w+a/H/IP7Rl/wCgVqf/AHxZ/wDyZRfyf4f5h81+P+Qf2jL/ANArU/8Aviz/APkyi/k/w/zD5r8f8g/tGX/oFan/AN8Wf/yZRfyf4f5h81+P+Qf2jL/0CtT/AO+LP/5Mov5P8P8AMPmvx/yD+0Zf+gVqf/fFn/8AJlF/J/h/mHzX4/5D4r6SSRUOnX8QY4Mkq2ojT3YpdO2P91WPtR8n+H+YfP8AP/I9F0j/AJB9v/21/wDR8tQ9wPBviL4v1LwodNbToLGY30t+JvtsVxIFFt9mKeX5FzbYz57792/OFxtwc/YcK5Dg88ljli6mJprDRw7p/V50oX9q6ylz+1o1r29nHlty2u730t85xBm+JylYR4eFCft3WU/bRqSt7NUuXl5KtO1+d3vfpa2t/Mv+Fw+Jv+fHQv8AwG1D/wCWdfYf6g5P/wBBOZ/+DsL/APMZ81/rhmf/AD4wP/grEf8AzUdpc+PtYh8D6f4lW200393qj2MkTQ3RtFiU3wDJGLwTCT/Ro8lp2Xl/k5G356jwvgKnEmKyd1sYsNQwUcTCaqUfbubWHbUpPDum4fvpaKknpH3tHf2amf4yGR4fM1Tw3t6uKlQlBwq+xUE6+sY+2U1L93HV1GtXptbi/wDhcPib/nx0L/wG1D/5Z19D/qDk/wD0E5n/AODsL/8AMZ43+uGZ/wDPjA/+CsR/81HaaV4+1i+8JeINemttMW80qeCO3jjhuhbOsrW4YzI148rECVsbJoxwuQcHPz2O4Xy/DZ7leV062MeHx1OpOrOdSi60XBVWvZyWHjBL92r81Oe71WlvZwmf4yvlGYY+dPDKthJwjTjGFVUpKTpp86dZyb9925Zx6fPi/wDhcPib/nx0L/wG1D/5Z19D/qDk/wD0E5n/AODsL/8AMZ43+uGZ/wDPjA/+CsR/81HaeE/H2sa9beIZry20yNtJ0qS+thbQ3SK8qRzsFnEt5MWjzEuRGY2wT844x89nvC+X5ZWymnQrYyax2NjhqzrVKMnGDlSi3T5MPTSnab1kprb3d7+zlOf4zH08xnWp4aLwmFlXp+zhVSlNRqO0+atO8fdWkXF76nF/8Lh8Tf8APjoX/gNqH/yzr6H/AFByf/oJzP8A8HYX/wCYzxv9cMz/AOfGB/8ABWI/+ajrfBXxD1rxJrsemX1rpcUD21xMXtIbtJt0KhlAaa9nTaSfmHlknsRXhcRcKZdlGWzxmGrY2dWNalTUa9ShKnapJptqnhqUrrp71u6Z62ScQ43MsdHDV6WFhTdOpO9KFWM7wSa1nXqRt3935o5m5+LniSG5uIVstDKxTyxqWtr8sVSRlBYjUgCSBzgAZ6AV7NHgTKKlKlUeJzJOdOE2lWwtryim7XwbdrvS7fqeZV4uzKFSpBUMDaE5xV6de9oyaV/9pWumuiNTw58T9f1jXNM0y5tNHSC9uVhleC3vVmVSrEmNpNQlQNx1aNx7VxZvwZleAy3GYyjXx8quHoupCNWrh3Tck0rSUcLCTWvSUX5nVlvE+PxmOw2Gq0cHGnWqqEnTp1lNJpv3XLESinp1i/Q+q9I/5B9v/wBtf/R8tfmD3Puz5c+NnTQP+u2r/wDthX6X4efHm3+DBfnij4fjT4cu/wAWK/LDng1fpp8Gep33/JJtF/7GCX/0LVq+Jw3/ACXOYf8AYqh/6TgT6qv/AMkng/8AsYS/PFnllfbHyp6l4d/5Jv4z/wCvqz/9GWVfFZt/yV/D/wD14r/+k4k+qy7/AJJvOf8Ar7S/OgeW19qfKnqXw4/48fGv/YvTf+ibuviuLv8AeeHP+xrT/wDTmHPquG/4Gd/9i+f/AKRWPLa+1PlT0f4Vf8jdB/1433/ota+R43/5EVT/ALCcN/6Uz6ThX/kbw/68V/8A0lHBX/8Ax/Xn/X3cf+jnr6fDf7th/wDrxS/9NxPBr/x63/X2p/6WzoPA/wDyNug/9f6f+gPXlcSf8iLM/wDsFl/6VE9DI/8Akb4D/r+v/SZH3lpH/IPt/wDtr/6Plr8De5+vHzV8X9M1LUv7FGnaffX5hm1QzCytLi6MQk+xbDJ5EcmzfsfZuxu2tjO04/QuBcZg8HPM3i8XhsKqkcJ7P6xXpUOfleJ5uT2so83LzR5rXtdX3R8dxbhcTiY4D6vh6+I5JYnn9jSqVeXmVDl5uSMuW9na9r2dtmeKf8Iv4m/6F3Xf/BRqH/yPX6H/AG1k/wD0Nss/8L8L/wDLT4v+y8z/AOhdjv8AwkxH/wArPSb3RdYf4Y6TYLpOpNfx65JNJZLYXRu44i2p4le2EXnJGfMjw7IF+dOfmGfkMPmGAjxljsU8dg1hp5bCnDEPE0VQlNLB3hGs5+zlP3Ze6pN+7LTRn0lbBYx8MYTDrCYl1446U5UFQqutGF8T70qfJzqPvR95xt7y11R5t/wi/ib/AKF3Xf8AwUah/wDI9fX/ANtZP/0Nss/8L8L/APLT5v8AsvM/+hdjv/CTEf8Ays9I0HRdYh8AeLbKbSdTivLm5tWt7SSwukuZ1V7Qs0MDRCWUKFYkorAbWz0NfI5nmOX1OKcjxFPHYOeHo0ayq14YmjKjSbjXsqlVTcIN3VlKSvdd0fSYDBYyHD+bUZ4TExrVKtJ06UqFWNWok6N3Cm4KUkrO7inaz7Hm/wDwi/ib/oXdd/8ABRqH/wAj19d/bWT/APQ2yz/wvwv/AMtPm/7LzP8A6F2O/wDCTEf/ACs9I8A6LrFnZ+L1vNJ1O1a50KWG2W5sLqBriUxXQEUAliUyyEsoCR7mJYDHIr5HijMcvxGIyGVDHYOuqOZQqVnRxNGqqUFOg3Oo4TkoQSTfNKy0eujPpMgwWMo0c3VbCYmk6uBlCmqlCrB1J8lVcsFKC55ar3Y3eq01PN/+EX8Tf9C7rv8A4KNQ/wDkevrv7ayf/obZZ/4X4X/5afN/2Xmf/Qux3/hJiP8A5Wd/8NdD1qw8UQ3F9pGqWcAs7xTPd6fd28IZkUKpkmiRAzHhRuye1fL8YZll2JyWpSw2PwWIqvEYeSp0MVQq1Goyd2oU6kpWXV2sup7/AA1gcbQzSFSvg8VRpqjWXPVw9WnC7SsuacFG76K+pxV74Z8SPeXbL4f1xla5nZWXSb8qymVyGUi3IIIOQRwRyK+iw+c5RGhQTzXLU1Rppp47CppqEU006t009GnseJWyzMnWqtZfjmnVqNNYSu005uzT9nqn0ZueDvD2v2vijRbi50PWLeCK9R5Z59NvYYY1CvlpJJIVRF56swHvXm8QZrldbJsxpUcywFWrPDyjCnSxmHqVJy5o6RhGo5Sfkk2d+TZfj6WaYKpVwOMp04Vk5TqYatCEVZ6ylKCil5to+2tI/wCQfb/9tf8A0fLX4k9z9SONk/1kn++3/oRqxDKACgAoAKACgAoAKACgAoAKAO10j/kH2/8A21/9Hy1D3GD/2Q==',
+ ],
+ ],
+ ];
+
+ $this->json['choices'] = $this->choices;
+ $this->json['columnsControl'] = $this->columns_control;
+ }
+}
diff --git a/inc/customizer/controls/react/builder_section.php b/inc/customizer/controls/react/builder_section.php
new file mode 100644
index 0000000000..190acf60b0
--- /dev/null
+++ b/inc/customizer/controls/react/builder_section.php
@@ -0,0 +1,58 @@
+ '',
+ 'builder_type' => '',
+ // 'quickLinks' => [],
+ ];
+
+ /**
+ * Options passed to section.
+ *
+ * @var array
+ */
+ public $options = [];
+
+
+ /**
+ * Gather the parameters passed to client JavaScript via JSON.
+ *
+ * @return array The array to be exported to the client as JSON.
+ */
+ public function json() {
+ $json = parent::json();
+ $json['options'] = wp_parse_args( $this->options, $this->default_options );
+
+ return $json;
+ }
+}
+
+
diff --git a/inc/customizer/controls/react/button_appearance.php b/inc/customizer/controls/react/button_appearance.php
index 54eeb58f53..dd5324fa14 100644
--- a/inc/customizer/controls/react/button_appearance.php
+++ b/inc/customizer/controls/react/button_appearance.php
@@ -25,12 +25,19 @@ class Button_Appearance extends \WP_Customize_Control {
* @var array
*/
public $no_hover = false;
+ /**
+ * Default values.
+ *
+ * @var array
+ */
+ public $default_vals = [];
/**
* Send to JS.
*/
public function to_json() {
parent::to_json();
- $this->json['no_hover'] = $this->no_hover;
+ $this->json['no_hover'] = $this->no_hover;
+ $this->json['defaultVals'] = $this->default_vals;
}
}
diff --git a/inc/customizer/controls/react/heading.php b/inc/customizer/controls/react/heading.php
new file mode 100644
index 0000000000..59462dd920
--- /dev/null
+++ b/inc/customizer/controls/react/heading.php
@@ -0,0 +1,112 @@
+class;
+ $json['accordion'] = $this->accordion;
+ $json['categoryLabel'] = $this->category_label;
+
+ if ( $this->accordion === true ) {
+ $json['classes'] .= ' accordion';
+ }
+
+ $json['style'] = $this->print_style();
+
+ return $json;
+ }
+
+ /**
+ * Render the control.
+ */
+ protected function render() {
+ $id = 'customize-control-' . str_replace( array( '[', ']' ), array( '-', '' ), $this->id );
+ $class = 'customize-control customize-control-' . $this->type;
+ $class .= ' ' . $this->class;
+ if ( $this->accordion ) {
+ $class .= ' accordion';
+ }
+
+ if ( $this->expanded ) {
+ $class .= ' expanded';
+ }
+
+ echo '
';
+ echo '';
+ }
+
+ /**
+ * Print the style for the accordion.
+ */
+ protected function print_style() {
+ $style = '';
+ for ( $i = 1; $i <= $this->controls_to_wrap; $i ++ ) {
+ $style .= '.accordion.' . $this->class . ':not(.expanded)';
+ for ( $j = 1; $j <= $i; $j ++ ) {
+ $style .= ' + li';
+ }
+ if ( $i !== $this->controls_to_wrap ) {
+ $style .= ',';
+ }
+ }
+ $style .= '{max-height: 0;opacity: 0;margin: 0; overflow: hidden; padding:0 !important;}';
+
+ return $style;
+ }
+}
diff --git a/inc/customizer/controls/react/inline_select.php b/inc/customizer/controls/react/inline_select.php
index 1cb6f614d6..7a04ae300a 100644
--- a/inc/customizer/controls/react/inline_select.php
+++ b/inc/customizer/controls/react/inline_select.php
@@ -41,6 +41,12 @@ class Inline_Select extends \WP_Customize_Control {
*/
public $link;
+ /**
+ * Allows listening to other components.
+ *
+ * @var string
+ */
+ public $changes_on;
/**
* Send to JS.
*/
@@ -49,5 +55,6 @@ public function to_json() {
$this->json['options'] = $this->options;
$this->json['defaultVal'] = $this->default;
$this->json['link'] = $this->link;
+ $this->json['changesOn'] = $this->changes_on;
}
}
diff --git a/inc/customizer/controls/react/instructions_control.php b/inc/customizer/controls/react/instructions_control.php
new file mode 100644
index 0000000000..ba55c45b95
--- /dev/null
+++ b/inc/customizer/controls/react/instructions_control.php
@@ -0,0 +1,61 @@
+ '',
+ 'image' => '',
+ 'quickLinks' => [],
+ ];
+
+ /**
+ * Options passed to control.
+ *
+ * @var array
+ */
+ public $options = [];
+
+
+
+ /**
+ * Gather the parameters passed to client JavaScript via JSON.
+ *
+ * @since 4.1.0
+ *
+ * @return array The array to be exported to the client as JSON.
+ */
+ public function json() {
+ $json = parent::json();
+ $json['options'] = wp_parse_args( $this->options, $this->default_options );
+
+ return $json;
+ }
+}
+
+
diff --git a/header-footer-grid/Core/Customizer/Instructions_Section.php b/inc/customizer/controls/react/instructions_section.php
similarity index 51%
rename from header-footer-grid/Core/Customizer/Instructions_Section.php
rename to inc/customizer/controls/react/instructions_section.php
index 1154b4c958..04fc730488 100644
--- a/header-footer-grid/Core/Customizer/Instructions_Section.php
+++ b/inc/customizer/controls/react/instructions_section.php
@@ -5,8 +5,7 @@
*
* @package Neve
*/
-
-namespace HFG\Core\Customizer;
+namespace Neve\Customizer\Controls\React;
/**
* Customizer section.
@@ -17,7 +16,6 @@
* @see WP_Customize_Section
*/
class Instructions_Section extends \WP_Customize_Section {
-
/**
* Type of this section.
*
@@ -30,71 +28,38 @@ class Instructions_Section extends \WP_Customize_Section {
* @var array
*/
public $default_options = [
- 'description' => '',
- 'image' => '',
- 'quickLinks' => [],
+ 'description' => '',
+ 'image' => '',
+ 'quickLinks' => [],
+ 'hadOldBuilder' => false,
+ 'builderMigrated' => false,
];
-
/**
* Options passed to control.
*
* @var array
*/
public $options = [];
-
-
/**
* Gather the parameters passed to client JavaScript via JSON.
*
- * @since 4.1.0
- *
* @return array The array to be exported to the client as JSON.
+ * @since 4.1.0
*/
public function json() {
$json = parent::json();
$json['options'] = wp_parse_args( $this->options, $this->default_options );
-
return $json;
}
-
-
/**
* Render template.
*/
protected function render_template() {
?>
-
- <# if( data.options.description ) { #>
- {{data.options.description}}
- <# } #>
-
- <# if( data.options.quickLinks ) { #>
-
-
-
- <# _.each(data.options.quickLinks, function( args, control ) { #>
- -
-
- {{args.label}}
-
-
- <# }) #>
-
-
- <# } #>
-
- <# if( data.options.image ) { #>
-
-
-
- <# } #>
+ data-slug="{{data.id}}"
+ class="control-section control-section-{{ data.type }} neve-quick-links">
json['presets'] = $this->presets;
+ $this->json['builder'] = $this->builder;
}
}
diff --git a/inc/customizer/controls/react/radio_image.php b/inc/customizer/controls/react/radio_image.php
index e7c41b9272..21304a44b8 100644
--- a/inc/customizer/controls/react/radio_image.php
+++ b/inc/customizer/controls/react/radio_image.php
@@ -25,11 +25,18 @@ class Radio_Image extends \WP_Customize_Control {
* @var array
*/
public $choices = [];
+ /**
+ * Additional arguments passed to JS.
+ *
+ * @var array
+ */
+ public $documentation = [];
/**
* Send to JS.
*/
public function to_json() {
parent::to_json();
- $this->json['choices'] = $this->choices;
+ $this->json['choices'] = $this->choices;
+ $this->json['documentation'] = $this->documentation;
}
}
diff --git a/inc/customizer/controls/react/src/.eslintrc b/inc/customizer/controls/react/src/.eslintrc
new file mode 100644
index 0000000000..b577443159
--- /dev/null
+++ b/inc/customizer/controls/react/src/.eslintrc
@@ -0,0 +1,42 @@
+{
+ "root": true,
+ "extends": [
+ "plugin:@wordpress/eslint-plugin/recommended"
+ ],
+ "ignorePatterns": [
+ "**/vendor/**"
+ ],
+ "rules": {
+ "@wordpress/no-global-event-listener": "off"
+ },
+ "overrides": [
+ {
+ "files": [
+ "*.ts",
+ "*.tsx"
+ ],
+ "extends": [
+ "plugin:@wordpress/eslint-plugin/recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:import/typescript"
+ ],
+ "rules": {
+ "@typescript-eslint/ban-ts-comment": "off",
+ "@wordpress/no-global-event-listener": "off"
+ },
+ "settings": {
+ "import/parsers": {
+ "@typescript-eslint/parser": [
+ ".ts",
+ ".tsx"
+ ]
+ },
+ "import/resolver": {
+ "typescript": {
+ "alwaysTryTypes": true
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/inc/customizer/controls/react/src/@types/classnames.d.ts b/inc/customizer/controls/react/src/@types/classnames.d.ts
new file mode 100644
index 0000000000..c5f9b4742e
--- /dev/null
+++ b/inc/customizer/controls/react/src/@types/classnames.d.ts
@@ -0,0 +1 @@
+declare module 'classnames';
diff --git a/inc/customizer/controls/react/src/@types/customizer-control.d.ts b/inc/customizer/controls/react/src/@types/customizer-control.d.ts
new file mode 100644
index 0000000000..af72a35b88
--- /dev/null
+++ b/inc/customizer/controls/react/src/@types/customizer-control.d.ts
@@ -0,0 +1,16 @@
+import { StringObjectKeys } from './utils';
+type WPCustomizeControlSetting = {
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ set: (value: any) => void;
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ get: () => any;
+ // eslint-disable-next-line no-undef
+ bind: (value: unknown) => void;
+};
+
+export type WPCustomizeControl = {
+ active: () => boolean;
+ id: string;
+ setting: WPCustomizeControlSetting;
+ params: StringObjectKeys;
+};
diff --git a/inc/customizer/controls/react/src/@types/utils.d.ts b/inc/customizer/controls/react/src/@types/utils.d.ts
new file mode 100644
index 0000000000..e4885bbd77
--- /dev/null
+++ b/inc/customizer/controls/react/src/@types/utils.d.ts
@@ -0,0 +1,119 @@
+import { ItemInterface } from 'react-sortablejs';
+import { Dispatch, SetStateAction } from 'react';
+
+interface StringObjectKeys {
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ [key: string]: any;
+}
+
+export type RowTypes = 'top' | 'main' | 'bottom' | 'sidebar';
+export type SlotTypes =
+ | 'left'
+ | 'c-left'
+ | 'center'
+ | 'c-right'
+ | 'right'
+ | 'sidebar';
+
+export type DeviceTypes = string & (keyof BuilderContentInterface | 'tablet');
+
+export type BuilderItemType = {
+ height?: number;
+ id: string;
+ width?: number;
+ x?: number;
+ y?: number;
+};
+
+export interface BuilderRowInterface extends StringObjectKeys {
+ left: BuilderItemType[];
+ 'c-left': BuilderItemType[];
+ center: BuilderItemType[];
+ 'c-right': BuilderItemType[];
+ right: BuilderItemType[];
+}
+
+export interface BuilderRowsInterface extends StringObjectKeys {
+ top: BuilderRowInterface;
+ main: BuilderRowInterface;
+ bottom: BuilderRowInterface;
+ sidebar?: BuilderItemType[];
+}
+
+export interface BuilderContentInterface extends StringObjectKeys {
+ desktop: BuilderRowsInterface;
+ mobile?: BuilderRowsInterface;
+}
+
+type HFGLayoutBuilderComponentProps = {
+ componentSlug: string;
+ description: string | null;
+ elementOrder: number;
+ icon: string;
+ id: string;
+ name: string;
+ previewImage: string | null;
+ section: string;
+ width: number;
+};
+
+type HFGLayoutBuilderRowProps = {
+ description: string;
+ title: string;
+};
+
+type HFGLayoutBuilderProps = {
+ // eslint-disable-next-line camelcase
+ control_id: string;
+ devices: { desktop: 'Desktop'; mobile?: 'Mobile' };
+ id: string;
+ items: {
+ [componentId: string]: HFGLayoutBuilderComponentProps;
+ };
+ panel: string;
+ rows: {
+ [rowId: string]: HFGLayoutBuilderRowProps;
+ };
+ section: string;
+ title: string;
+};
+
+type HFGLayoutBuilder = {
+ [key: string]: HFGLayoutBuilderProps;
+};
+
+type LayoutUpdate = (
+ rowId: string,
+ slotId: string,
+ nextItems: BuilderItemType[]
+) => void;
+
+type RemoveItem = (rowId: string, slotId: string, itemIndex: number) => void;
+
+type BuilderActions = {
+ updateLayout: LayoutUpdate;
+ onDragStart: () => void;
+ onDragEnd: () => void;
+ removeItem: RemoveItem;
+ setDevice: Dispatch
>;
+ setSidebarItems: (value: ItemInterface[]) => void;
+ togglePreviewSidebar: (value: boolean) => void;
+};
+
+declare global {
+ interface Window {
+ wp: StringObjectKeys;
+ NeveReactCustomize: {
+ HFG: HFGLayoutBuilder;
+ instructionalVid: string;
+ nonce: string;
+ hideConditionalHeaderSelector: boolean;
+ dashUpdatesMessage: string;
+ };
+ NeveProReactCustomize: undefined | StringObjectKeys;
+ }
+}
+
+interface BuilderChangeEvent extends Event {
+ detail?: { id: string; value: string | BuilderContentInterface };
+}
diff --git a/inc/customizer/controls/react/src/background/Background.js b/inc/customizer/controls/react/src/background/Background.js
index 7cd791cdcc..7a22040da8 100644
--- a/inc/customizer/controls/react/src/background/Background.js
+++ b/inc/customizer/controls/react/src/background/Background.js
@@ -194,8 +194,8 @@ Background.propTypes = {
type: PropTypes.string,
imageUrl: PropTypes.string,
focusPoint: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number,
+ x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
}),
colorValue: PropTypes.string,
overlayColorValue: PropTypes.string,
diff --git a/inc/customizer/controls/react/src/background/BackgroundComponent.js b/inc/customizer/controls/react/src/background/BackgroundComponent.js
index 23684a97ae..ea19530613 100644
--- a/inc/customizer/controls/react/src/background/BackgroundComponent.js
+++ b/inc/customizer/controls/react/src/background/BackgroundComponent.js
@@ -27,7 +27,7 @@ const BackgroundComponent = ({ control }) => {
};
useEffect(() => {
- global.addEventListener('neve-changed-customizer-value', (e) => {
+ document.addEventListener('neve-changed-customizer-value', (e) => {
if (!e.detail) return false;
if (e.detail.id !== control.id) return false;
updateValue(e.detail.value);
diff --git a/inc/customizer/controls/react/src/builder-columns/BuilderColumns.tsx b/inc/customizer/controls/react/src/builder-columns/BuilderColumns.tsx
new file mode 100644
index 0000000000..4222bf5f62
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-columns/BuilderColumns.tsx
@@ -0,0 +1,31 @@
+/* jshint esversion: 6 */
+import React from 'react';
+// @ts-ignore
+import RadioImage from '../radio-image/RadioImage';
+
+type Props = {
+ label: string;
+ value: string;
+ onChange: (val: string) => void;
+ columns: number;
+ choices: Record>;
+};
+
+const BuilderColumns: React.FC = (props) => {
+ const { label, value, onChange, columns, choices } = props;
+
+ const visibleChoices = choices[columns];
+
+ return (
+
+ {label && {label}}
+
+
+ );
+};
+
+export default BuilderColumns;
diff --git a/inc/customizer/controls/react/src/builder-columns/BuilderColumnsComponent.tsx b/inc/customizer/controls/react/src/builder-columns/BuilderColumnsComponent.tsx
new file mode 100644
index 0000000000..4e0cad49b8
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-columns/BuilderColumnsComponent.tsx
@@ -0,0 +1,63 @@
+/* jshint esversion: 6 */
+import React from 'react';
+import { useEffect, useState } from '@wordpress/element';
+import { WPCustomizeControl } from '../@types/customizer-control';
+import BuilderColumns from './BuilderColumns';
+
+type Props = {
+ control: WPCustomizeControl;
+};
+
+const BuilderColumnsComponent: React.FC = ({ control }) => {
+ const { setting, params } = control;
+ const { label, choices, columnsControl } = params;
+ const [value, setValue] = useState(setting.get());
+ const [columnsNo, setColumnsNo] = useState(1);
+
+ const onChange = (nextValue: string) => {
+ setValue(nextValue);
+ control.setting.set(nextValue);
+ };
+
+ useEffect(() => {
+ window.wp.customize.bind('ready', () => {
+ const colNumber = window.wp.customize
+ .control(columnsControl)
+ .setting.get();
+ setColumnsNo(parseInt(colNumber));
+ });
+
+ window.wp.customize.control(
+ columnsControl,
+ (customizeControl: WPCustomizeControl) => {
+ customizeControl.setting.bind((nextColNumber: string) => {
+ const colNumberInt = parseInt(nextColNumber);
+ const currentValue = setting.get();
+ if (
+ !Object.keys(choices[colNumberInt]).includes(
+ currentValue
+ )
+ ) {
+ const firstValue = Object.keys(
+ choices[colNumberInt]
+ )[0];
+ onChange(firstValue);
+ }
+ setColumnsNo(colNumberInt);
+ });
+ }
+ );
+ });
+
+ return (
+
+ );
+};
+
+export default BuilderColumnsComponent;
diff --git a/inc/customizer/controls/react/src/builder-columns/Control.js b/inc/customizer/controls/react/src/builder-columns/Control.js
new file mode 100644
index 0000000000..3ffb6326d8
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-columns/Control.js
@@ -0,0 +1,10 @@
+/* jshint esversion: 6 */
+import BuilderColumnsComponent from './BuilderColumnsComponent.tsx';
+import { render } from '@wordpress/element';
+import React from 'react';
+
+export const BuilderColumns = wp.customize.Control.extend({
+ renderContent() {
+ render(, this.container[0]);
+ },
+});
diff --git a/inc/customizer/controls/react/src/builder-instructions/Control.js b/inc/customizer/controls/react/src/builder-instructions/Control.js
new file mode 100644
index 0000000000..43327b2b8e
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-instructions/Control.js
@@ -0,0 +1,9 @@
+/* jshint esversion: 6 */
+import { render } from '@wordpress/element';
+import Instructions from './Instructions.tsx';
+
+export const InstructionsControl = wp.customize.Control.extend({
+ renderContent() {
+ render(, this.container[0]);
+ },
+});
diff --git a/inc/customizer/controls/react/src/builder-instructions/HFGMigrationNotice.tsx b/inc/customizer/controls/react/src/builder-instructions/HFGMigrationNotice.tsx
new file mode 100644
index 0000000000..7c1ccf9d79
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-instructions/HFGMigrationNotice.tsx
@@ -0,0 +1,211 @@
+import React from 'react';
+import { __ } from '@wordpress/i18n';
+import apiFetch from '@wordpress/api-fetch';
+import {
+ cancelCircleFilled,
+ rotateRight,
+ starFilled,
+ undo,
+} from '@wordpress/icons';
+import { Button } from '@wordpress/components';
+import { useState, useEffect } from '@wordpress/element';
+import { StringObjectKeys } from '../@types/utils';
+
+interface MigrationResponse {
+ success: boolean;
+}
+
+type Props = {
+ alreadyMigrated: boolean;
+ hadOldBuilder: boolean;
+};
+
+export const HFGMigrationNotice: React.FC = ({
+ alreadyMigrated,
+ hadOldBuilder,
+}) => {
+ const [error, setError] = useState(false);
+ const [isCustomizerSaved, setCustomizerSaved] = useState(true);
+
+ useEffect(() => {
+ if (alreadyMigrated && !hadOldBuilder) {
+ return;
+ }
+ window.wp.customize.bind('ready', () => {
+ window.wp.customize.state('saved').bind((status: boolean) => {
+ setCustomizerSaved(status);
+ });
+ });
+ }, []);
+
+ if (typeof window.NeveProReactCustomize !== 'undefined') {
+ const { whiteLabel } = window.NeveProReactCustomize;
+
+ if (whiteLabel) {
+ return null;
+ }
+ }
+
+ if (!hadOldBuilder) {
+ return null;
+ }
+
+ const { nonce } = window.NeveReactCustomize;
+
+ const getReloadUrl = () => {
+ const location = window.location.href;
+ const currentPanel = window.wp.customize.state('expandedPanel').get();
+
+ if (!currentPanel) return location;
+
+ const panelId = currentPanel.id;
+
+ if (!panelId) return location;
+
+ const url = new URL(location);
+
+ url.searchParams.set('autofocus[panel]', panelId);
+
+ return url.href;
+ };
+
+ const runMigration = (rollback = false, dismiss = false) => {
+ let message = __('Migrating builder data', 'neve');
+
+ if (rollback) {
+ message = __('Rolling back builder', 'neve');
+ }
+
+ if (dismiss) {
+ message = __('Removing old data', 'neve');
+ }
+
+ window.wp.customize.notifications.add(
+ new window.wp.customize.OverlayNotification(
+ 'neve_migrating_builders',
+ {
+ message: message + '...',
+ type: 'success',
+ loading: true,
+ }
+ )
+ );
+
+ const headers: StringObjectKeys = { 'X-WP-Nonce': nonce };
+ if (rollback) {
+ headers.rollback = 'yes';
+ }
+
+ if (dismiss) {
+ headers.dismiss = 'yes';
+ }
+
+ apiFetch({
+ path: '/nv/migration/new_header_builder',
+ method: 'GET',
+ headers,
+ }).then((response: unknown) => {
+ if (!(response as MigrationResponse).success) {
+ window.wp.customize.notifications.remove(
+ 'neve_migrating_builders'
+ );
+ setError(true);
+ return false;
+ }
+ reloadPage();
+ });
+ };
+
+ const reloadPage = () => {
+ const url = getReloadUrl();
+ if (window.location.href === url) {
+ window.location.reload();
+ return false;
+ }
+ window.location.href = url;
+ };
+
+ const renderErrors = () => {
+ return (
+ <>
+ {!isCustomizerSaved && !error && (
+
+ {__(
+ 'You must save the current customizer values before running the migration.',
+ 'neve'
+ )}
+
+ )}
+
+ {error && (
+
+ {__(
+ 'Something went wrong. Please reload the page and try again.',
+ 'neve'
+ )}
+
+ )}
+ >
+ );
+ };
+
+ if (alreadyMigrated && hadOldBuilder) {
+ return (
+ <>
+
+ {__('Want to roll back to the old builder?', 'neve')}
+
+
+ {renderErrors()}
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+ {__(
+ "We've created a new Header/Footer Builder experience! You can always roll back to the old builder from right here.",
+ 'neve'
+ )}
+
+
+
+ {__('Some manual adjustments may be required.', 'neve')}
+
+
+
+ {renderErrors()}
+ >
+ );
+};
diff --git a/inc/customizer/controls/react/src/builder-instructions/Instructions.tsx b/inc/customizer/controls/react/src/builder-instructions/Instructions.tsx
new file mode 100644
index 0000000000..6b7aaf9d02
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-instructions/Instructions.tsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import { __ } from '@wordpress/i18n';
+import { Button } from '@wordpress/components';
+import { WPCustomizeControl } from '../@types/customizer-control';
+import { HFGMigrationNotice } from './HFGMigrationNotice';
+
+type Props = {
+ control: WPCustomizeControl;
+};
+
+const Instructions: React.FC = ({ control }) => {
+ const { params, id } = control;
+ const { options } = params;
+ const { description, quickLinks, builderMigrated, hadOldBuilder } = options;
+
+ const linkKeys = Object.keys(quickLinks);
+
+ const expandEventHeaders = (slug: string): void => {
+ const { type } = window.wp.customize.control(slug).params;
+ if (type === 'neve_customizer_heading') {
+ window.wp.customize.control(slug).triggerExpandHeader();
+ }
+ };
+
+ const focusControl = (slug: string): void => {
+ if (slug === 'toggle_sidebar') {
+ window.wp.customize.previewer.send('header_sidebar_open');
+ return;
+ }
+ window.wp.customize.control(slug).focus({
+ completeCallback: () => {
+ expandEventHeaders(slug);
+ },
+ });
+ };
+
+ return (
+
+ {description &&
{description}
}
+
+ {linkKeys.length && (
+
+
+ {__('Quick Links', 'neve')}
+
+
+ {linkKeys.map((settingSlug, index) => {
+ const { label, icon } = quickLinks[settingSlug];
+ return (
+ -
+
+
+ );
+ })}
+
+ {id === 'hfg_header_layout_section_quick_links' && (
+
+ )}
+
+ )}
+
+ );
+};
+
+export default Instructions;
diff --git a/inc/customizer/controls/react/src/builder-instructions/Section.js b/inc/customizer/controls/react/src/builder-instructions/Section.js
new file mode 100644
index 0000000000..b2f1a3ecac
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder-instructions/Section.js
@@ -0,0 +1,6 @@
+export const InstructionsSection = wp.customize.Section.extend({
+ // No events for this type of section.
+ attachEvents: () => null,
+ // Always make the section active.
+ isContextuallyActive: () => true,
+});
diff --git a/inc/customizer/controls/react/src/builder/BuilderContext.tsx b/inc/customizer/controls/react/src/builder/BuilderContext.tsx
new file mode 100644
index 0000000000..93639da6f5
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/BuilderContext.tsx
@@ -0,0 +1,18 @@
+import { createContext } from '@wordpress/element';
+import { BuilderActions, DeviceTypes } from '../@types/utils';
+import { ItemInterface } from 'react-sortablejs';
+
+type BuilderContext = {
+ actions: BuilderActions;
+ sidebarItems: ItemInterface[];
+ builder: string;
+ currentSection: string;
+ dragging: boolean;
+ hasColumns: boolean;
+ device: DeviceTypes;
+ previewSidebar: boolean;
+};
+
+const BuilderContext = createContext({} as BuilderContext);
+
+export default BuilderContext;
diff --git a/inc/customizer/controls/react/src/builder/Control.js b/inc/customizer/controls/react/src/builder/Control.js
new file mode 100644
index 0000000000..d9a7c644a6
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/Control.js
@@ -0,0 +1,28 @@
+/* jshint esversion: 6 */
+import HFGBuilderComponent from './HFGBuilderComponent.tsx';
+import { render } from '@wordpress/element';
+import React from 'react';
+
+export const BuilderControl = wp.customize.Control.extend({
+ renderContent() {
+ const customizePreview = document.getElementById('customize-controls');
+ const where = document.querySelector(
+ `#accordion-section-${this.params.section}`
+ );
+
+ const builderPortalMount = document.createElement('div');
+ builderPortalMount.classList.add(
+ 'neve-builder-portal-wrap',
+ 'neve-hfg-builder'
+ );
+ customizePreview.appendChild(builderPortalMount);
+
+ render(
+ ,
+ where
+ );
+ },
+});
diff --git a/inc/customizer/controls/react/src/builder/HFGBuilder.tsx b/inc/customizer/controls/react/src/builder/HFGBuilder.tsx
new file mode 100644
index 0000000000..9ca22cb94f
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/HFGBuilder.tsx
@@ -0,0 +1,304 @@
+import React from 'react';
+import {
+ createPortal,
+ lazy,
+ Suspense,
+ useEffect,
+ useState,
+} from '@wordpress/element';
+import { Spinner } from '@wordpress/components';
+
+import {
+ BuilderActions,
+ BuilderContentInterface,
+ BuilderItemType,
+ DeviceTypes,
+ LayoutUpdate,
+ RemoveItem,
+ StringObjectKeys,
+} from '../@types/utils';
+
+import {
+ arraysAreIdentical,
+ getUsedItemsFromItems,
+ ROW_SCHEMA,
+} from './common/utils';
+import { ItemInterface } from 'react-sortablejs';
+import BuilderContext from './BuilderContext';
+
+const Builder = lazy(
+ () => import(/* webpackChunkName: "builder" */ './components/Builder')
+);
+
+const SidebarContent = lazy(
+ () =>
+ import(/* webpackChunkName: "sidebar" */ './components/SidebarContent')
+);
+
+type Props = {
+ hasColumns: boolean;
+ onChange: (nextValue: BuilderContentInterface) => void;
+ value: BuilderContentInterface;
+ builder: string;
+ portalMount: HTMLElement;
+ hidden: boolean;
+};
+
+const HFGBuilder: React.FC = ({
+ hasColumns,
+ builder,
+ onChange,
+ value,
+ hidden,
+ portalMount,
+}) => {
+ const [device, setDevice] = useState('desktop');
+ const [dragging, setDragging] = useState(false);
+ const [previewSidebar, togglePreviewSidebar] = useState(false);
+ const [currentSection, setCurrentSection] = useState('');
+
+ const getSidebarItems = () => {
+ const allItems = window.NeveReactCustomize.HFG[builder].items;
+ const usedItems =
+ value && value[device]
+ ? getUsedItemsFromItems(value[device])
+ : Object.keys(allItems);
+ return Object.keys(allItems)
+ .filter((key) => !usedItems.includes(key))
+ .map((itemId) => {
+ return { id: itemId };
+ })
+ .sort((a, b) => {
+ return a.id < b.id ? -1 : 1;
+ });
+ };
+
+ const [sidebarItems, setSidebarItems] = useState(
+ getSidebarItems()
+ );
+
+ const onDragStart = () => {
+ setDragging(true);
+ };
+ const onDragEnd = () => {
+ setDragging(false);
+ };
+
+ const updateLayout: LayoutUpdate = (row, slot, items) => {
+ onDragEnd();
+
+ if (row === 'sidebar') {
+ updateSidebar(items);
+
+ return false;
+ }
+
+ const nextItems = { ...value[device] };
+
+ // Make sure row exists and has slots.
+ if (!nextItems[row]) {
+ nextItems[row] = ROW_SCHEMA;
+ }
+
+ if (arraysAreIdentical(items, nextItems[row][slot])) {
+ return false;
+ }
+
+ const update = nextItems[row];
+ const updateItems: Array = [];
+
+ if (items.length > 0) {
+ items.forEach((item) => {
+ updateItems.push(item);
+ });
+ }
+
+ if (slot === 'center' && items.length === 0 && !hasColumns) {
+ const sideSlots = ['c-left', 'c-right'];
+ sideSlots.forEach((sideSlot) => {
+ if (!Array.isArray(update[sideSlot])) {
+ return false;
+ }
+ if (update[sideSlot].length < 1) {
+ return false;
+ }
+
+ const nextSlot = sideSlot === 'c-left' ? 'left' : 'right';
+
+ update[sideSlot].forEach((itemToMove: StringObjectKeys) => {
+ if (nextSlot === 'left') {
+ nextItems[row][nextSlot].push(itemToMove);
+ } else {
+ nextItems[row][nextSlot].unshift(itemToMove);
+ }
+ });
+ nextItems[row][sideSlot] = [];
+ });
+ }
+
+ update[slot] = updateItems;
+ nextItems[row][slot] = updateItems;
+
+ const finalValue = { ...value, [device]: nextItems };
+
+ onChange(finalValue);
+ };
+
+ const updateSidebar = (items: BuilderItemType[]) => {
+ if (arraysAreIdentical(items, value[device].sidebar)) {
+ return false;
+ }
+
+ const nextItems = { ...value[device] };
+ const updateItems: Array = [];
+
+ if (items.length > 0) {
+ items.forEach((item) => {
+ updateItems.push(item);
+ });
+ }
+
+ nextItems.sidebar = updateItems;
+ const finalValue = { ...value, [device]: nextItems };
+
+ onChange(finalValue);
+ };
+
+ const removeItem: RemoveItem = (row, slot, indexToRemove) => {
+ const nextItems = { ...value[device] };
+ if (row === 'sidebar') {
+ nextItems[row].splice(indexToRemove, 1);
+ const finalValue = { ...value, [device]: nextItems };
+ onChange(finalValue);
+ return false;
+ }
+
+ nextItems[row][slot].splice(indexToRemove, 1);
+
+ if (
+ slot === 'center' &&
+ nextItems[row][slot].length === 0 &&
+ !hasColumns
+ ) {
+ const sideSlots = ['c-left', 'c-right'];
+ sideSlots.forEach((sideSlot) => {
+ if (!Array.isArray(nextItems[row][sideSlot])) {
+ return false;
+ }
+ if (nextItems[row][sideSlot].length < 1) {
+ return false;
+ }
+
+ const nextSlot = sideSlot === 'c-left' ? 'left' : 'right';
+
+ nextItems[row][sideSlot].forEach(
+ (itemToMove: StringObjectKeys) => {
+ if (nextSlot === 'left') {
+ nextItems[row][nextSlot].push(itemToMove);
+ } else {
+ nextItems[row][nextSlot].unshift(itemToMove);
+ }
+ }
+ );
+ nextItems[row][sideSlot] = [];
+ });
+ }
+ const finalValue = { ...value, [device]: nextItems };
+ onChange(finalValue);
+ };
+
+ /*
+ * Bind the device switchers to the device state.
+ */
+ useEffect(() => {
+ bindDeviceSwitching();
+ }, []);
+
+ /*
+ * Make sure we update the sidebar.
+ */
+ useEffect(() => {
+ setSidebarItems(getSidebarItems());
+ }, [device, value[device]]);
+
+ const bindDeviceSwitching = () => {
+ window.wp.customize.bind('ready', () => {
+ window.wp.customize.previewedDevice.bind(
+ (newDevice: DeviceTypes) => {
+ // No tablet context existent on builders.
+ if (newDevice === 'tablet') {
+ newDevice = 'mobile';
+ }
+
+ // If we don't have a value, don't switch context.
+ if (!value[newDevice]) {
+ return false;
+ }
+
+ setDevice(newDevice);
+ }
+ );
+
+ window.wp.customize
+ .state('expandedSection')
+ .bind((expandedSection: StringObjectKeys) => {
+ if (!expandedSection) {
+ setCurrentSection('');
+ return;
+ }
+
+ if (!expandedSection.id) {
+ setCurrentSection('');
+ return;
+ }
+
+ setCurrentSection(expandedSection.id);
+ });
+ });
+ };
+
+ const actions: BuilderActions = {
+ updateLayout,
+ onDragStart,
+ onDragEnd,
+ removeItem,
+ setDevice,
+ setSidebarItems,
+ togglePreviewSidebar,
+ };
+
+ return (
+
+
+
}>
+ <>
+
+
+
+ {createPortal(
+
,
+ portalMount
+ )}
+ >
+
+
+
+ );
+};
+
+export default HFGBuilder;
diff --git a/inc/customizer/controls/react/src/builder/HFGBuilderComponent.tsx b/inc/customizer/controls/react/src/builder/HFGBuilderComponent.tsx
new file mode 100644
index 0000000000..69dd970d29
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/HFGBuilderComponent.tsx
@@ -0,0 +1,145 @@
+/* jshint esversion: 6 */
+import React from 'react';
+import { useEffect, useState } from '@wordpress/element';
+import { WPCustomizeControl } from '../@types/customizer-control';
+import { BuilderChangeEvent, BuilderContentInterface } from '../@types/utils';
+import { maybeParseJson } from './common/utils';
+import HFGBuilder from './HFGBuilder';
+
+type Props = {
+ control: WPCustomizeControl;
+ portalMount: HTMLElement;
+};
+
+const HFGBuilderComponent: React.FC = ({ control, portalMount }) => {
+ const { setting, params } = control;
+
+ const builder: string = params.builderType;
+ const hasColumns: boolean = params.columnsLayout;
+
+ const [value, setValue] = useState(
+ // @ts-ignore
+ maybeParseJson(setting.get())
+ );
+ const [isHidden, setHidden] = useState(true);
+
+ const onChange = (nextValue: BuilderContentInterface) => {
+ const next = JSON.stringify(nextValue);
+ const prev = setting.get();
+
+ if (next === prev) {
+ return;
+ }
+
+ setValue(nextValue);
+ control.setting.set(next);
+ };
+
+ /**
+ * Toggles builder visibility based on the migration notification.
+ */
+ const bindOverlayNotificationHiding = () => {
+ window.wp.customize.notifications.bind(
+ 'add',
+ (data: { code: string }) => {
+ if (data.code !== 'neve_migrating_builders') {
+ return false;
+ }
+
+ setHidden(true);
+ }
+ );
+
+ window.wp.customize.notifications.bind(
+ 'removed',
+ (data: { code: string }) => {
+ if (data.code !== 'neve_migrating_builders') {
+ return false;
+ }
+
+ setHidden(false);
+ }
+ );
+ };
+
+ /**
+ * Shows builder when its panel is expanded.
+ */
+ const bindShowOnExpand = () => {
+ window.wp.customize
+ .state('expandedPanel')
+ .bind((panel: Record) => {
+ if (panel.id && panel.id === `hfg_${builder}` && isHidden) {
+ setHidden(false);
+ return false;
+ }
+ setHidden(true);
+ });
+ };
+
+ /**
+ * Hides builder when customizer is collapsed.
+ */
+ const bindHideOnPaneCollapse = () => {
+ window.wp.customize.bind('ready', () => {
+ window.wp.customize
+ .state('paneVisible')
+ .bind((nextValue: boolean) => {
+ const currentPanel = window.wp.customize
+ .state('expandedPanel')
+ .get();
+ if (nextValue) {
+ if (
+ currentPanel.id &&
+ currentPanel.id === `hfg_${builder}` &&
+ isHidden
+ ) {
+ setHidden(false);
+ }
+ return false;
+ }
+
+ setHidden(true);
+ });
+ });
+ };
+
+ useEffect(() => {
+ bindShowOnExpand();
+ bindOverlayNotificationHiding();
+ bindHideOnPaneCollapse();
+
+ document.addEventListener(
+ 'neve-changed-builder-value',
+ (e: BuilderChangeEvent) => {
+ const { detail } = e;
+ if (!detail) return false;
+ const { id, value: builderValue } = detail;
+ let actualValue = builderValue;
+
+ if (!actualValue) {
+ actualValue = { ...value };
+ }
+
+ if (!id || `hfg_${id}_layout_v2` !== control.id) return false;
+ onChange(
+ maybeParseJson(actualValue) as BuilderContentInterface
+ );
+ return false;
+ }
+ );
+ }, []);
+
+ return (
+
+ );
+};
+
+export default HFGBuilderComponent;
diff --git a/inc/customizer/controls/react/src/builder/common/utils.ts b/inc/customizer/controls/react/src/builder/common/utils.ts
new file mode 100644
index 0000000000..81dfc3b2a3
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/common/utils.ts
@@ -0,0 +1,71 @@
+import { BuilderRowInterface, BuilderRowsInterface } from '../../@types/utils';
+
+export const slotKeys = ['left', 'c-left', 'center', 'c-right', 'right'];
+
+export const getUsedItemsFromItems = (
+ items: BuilderRowsInterface
+): string[] => {
+ const nextItems: string[] = [];
+ // Get the items currently inside the builder and save them in an array.
+ Object.values(items).forEach((value) => {
+ if (Array.isArray(value) && value.length) {
+ value.forEach((item) => {
+ if (!item.id) {
+ return false;
+ }
+ nextItems.push(item.id);
+ });
+ return false;
+ }
+ if (typeof value === 'object' && value !== null) {
+ Object.values(value).forEach((rowSlotItems) => {
+ if (Array.isArray(rowSlotItems)) {
+ rowSlotItems.forEach((item) => nextItems.push(item.id));
+ }
+ });
+ }
+ });
+ return nextItems;
+};
+
+export const arraysAreIdentical = (
+ array1: Array,
+ array2: Array
+): boolean => {
+ if (array1 === array2) {
+ return true;
+ }
+ if (!Array.isArray(array1) || !Array.isArray(array2)) {
+ return false;
+ }
+ if (array1 === null || array2 === null) {
+ return false;
+ }
+ if (array1.length !== array2.length) {
+ return false;
+ }
+
+ return JSON.stringify(array1) === JSON.stringify(array2);
+};
+
+export const maybeParseJson: (
+ value: unknown
+) => Record | [] | string = (input) => {
+ if (typeof input !== 'string') {
+ return input;
+ }
+ try {
+ JSON.parse(input);
+ } catch (error) {
+ return input;
+ }
+ return JSON.parse(input);
+};
+
+export const ROW_SCHEMA: BuilderRowInterface = {
+ left: [],
+ 'c-left': [],
+ center: [],
+ 'c-right': [],
+ right: [],
+};
diff --git a/inc/customizer/controls/react/src/builder/components/Builder.tsx b/inc/customizer/controls/react/src/builder/components/Builder.tsx
new file mode 100644
index 0000000000..6afc9018cf
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/Builder.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import { BuilderContentInterface, RowTypes } from '../../@types/utils';
+import Row from './Row';
+import ResponsiveSwitches from './ResponsiveSwitches';
+import classnames from 'classnames';
+import { useContext, useEffect } from '@wordpress/element';
+import BuilderContext from '../BuilderContext';
+
+type Props = {
+ value: BuilderContentInterface;
+ hidden: boolean;
+ portalMount: HTMLElement | null;
+};
+
+const Builder: React.FC = ({ value, hidden, portalMount }) => {
+ const { device, builder, hasColumns } = useContext(BuilderContext);
+ const { rows } = window.NeveReactCustomize.HFG[builder];
+ const items = { ...value[device] };
+
+ const preview: HTMLElement | null = document.querySelector(
+ '#customize-preview'
+ );
+
+ // Remove preview offset.
+ useEffect(() => {
+ return () => {
+ if (preview === null) {
+ return;
+ }
+ preview.style.maxHeight = '';
+ preview.style.marginTop = '';
+ };
+ }, []);
+
+ const adaptPreviewHeight = (scroll = false) => {
+ const builderNode: HTMLElement | null =
+ portalMount?.querySelector('.neve-builder') || null;
+
+ if (builderNode === null || preview === null) {
+ return;
+ }
+
+ if (hidden) {
+ preview.style.maxHeight = '';
+ preview.style.marginTop = '';
+ return;
+ }
+
+ const height = builderNode.offsetHeight;
+ preview.style.maxHeight = `calc(100vh - ${height}px)`;
+ preview.style.marginTop = '0';
+
+ if (!scroll) {
+ return;
+ }
+
+ if (builder === 'footer') {
+ setTimeout(() => {
+ window.wp.customize.previewer.send('scroll', 100000000);
+ }, 150);
+
+ return;
+ }
+
+ // Scroll to top if we're using another builder (header, page-header)
+ setTimeout(() => {
+ window.wp.customize.previewer.send('scroll', 0);
+ }, 150);
+ };
+
+ // Offset preview to make space for builder.
+ useEffect(() => {
+ setTimeout(() => {
+ adaptPreviewHeight();
+ }, 100);
+
+ window.wp.customize.previewer.bind(
+ 'selective-refresh-setting-validities',
+ () => {
+ adaptPreviewHeight(false);
+ }
+ );
+ }, [hidden]);
+
+ const builderClasses = classnames('neve-builder', {
+ hide: hidden,
+ 'columns-builder': hasColumns,
+ });
+
+ return (
+
+
+
+ {rows.sidebar && device !== 'desktop' && (
+
+
+
+ )}
+
+ {Object.keys(rows).map((rowId, rowIndex) => {
+ if (rowId === 'sidebar') {
+ return null;
+ }
+
+ return (
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default Builder;
diff --git a/inc/customizer/controls/react/src/builder/components/BuilderHeaderNotification.tsx b/inc/customizer/controls/react/src/builder/components/BuilderHeaderNotification.tsx
new file mode 100644
index 0000000000..e586752034
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/BuilderHeaderNotification.tsx
@@ -0,0 +1,145 @@
+import React from 'react';
+import {
+ createInterpolateElement,
+ useEffect,
+ useState,
+} from '@wordpress/element';
+import { Button, Modal } from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+import { info } from '@wordpress/icons';
+
+type Props = {
+ builderName: string;
+ builder: string;
+};
+
+const BuilderHeaderNotification: React.FC = ({
+ builder,
+ builderName,
+}) => {
+ const [modalVisible, setModalVisible] = useState(false);
+ const [currentHeaderName, setCurrentHeaderName] = useState(null);
+
+ const toggleModal = () => {
+ setModalVisible(!modalVisible);
+ };
+
+ const focusConditionalSelector = () => {
+ window.wp.customize.control('neve_header_conditional_selector').focus();
+ };
+
+ useEffect(() => {
+ document.addEventListener('nv-change-conditional-header', (e) => {
+ // @ts-ignore
+ setCurrentHeaderName(e.detail);
+ });
+ }, []);
+
+ const {
+ dashUpdatesMessage,
+ instructionalVid,
+ hideConditionalHeaderSelector: incompatiblePro,
+ } = window.NeveReactCustomize;
+
+ // Disable conditional headers on old versions of the plugin.
+ useEffect(() => {
+ if (!incompatiblePro || builder !== 'header') {
+ return;
+ }
+
+ const control = window.wp.customize.control(
+ 'neve_header_conditional_selector'
+ );
+
+ if (!control) {
+ return;
+ }
+
+ const sectionToNotify = control.params.section;
+ window.wp.customize.section(sectionToNotify).notifications.add(
+ new window.wp.customize.Notification(
+ 'neve-incompatible-conditional',
+ {
+ type: 'warning',
+ message: dashUpdatesMessage,
+ }
+ )
+ );
+ });
+
+ const renderInstruction = () => {
+ if (builder !== 'header' || !currentHeaderName) {
+ return (
+
+
+ {sprintf(
+ /* translators: %1$s builder name (ie: Header) */
+ __('%1$s Builder', 'neve'),
+ builderName
+ ) + ':'}
+
+ {__(
+ 'Click on any empty space to add components, or existing components to adjust settings.',
+ 'neve'
+ )}
+
+ );
+ }
+
+ return (
+
+ {createInterpolateElement(
+ sprintf(
+ /* translators: %1$s conditional header name (ie: Posts Header, Page Header, 404 Header) */
+ __(
+ 'You are customizing the Header',
+ 'neve'
+ ),
+ currentHeaderName || __('')
+ ),
+ {
+ Button: (
+
+ ),
+ }
+ )}
+
+ );
+ };
+
+ return (
+
+ {renderInstruction()}
+
+ {modalVisible && (
+
+
+
+ )}
+
+ );
+};
+
+export default BuilderHeaderNotification;
diff --git a/inc/customizer/controls/react/src/builder/components/BuilderItem.tsx b/inc/customizer/controls/react/src/builder/components/BuilderItem.tsx
new file mode 100644
index 0000000000..1e4ac780a4
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/BuilderItem.tsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import { Button, Icon } from '@wordpress/components';
+import { closeSmall, cog, dragHandle, settings } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import { RowTypes, SlotTypes } from '../../@types/utils';
+import { useContext } from '@wordpress/element';
+import classnames from 'classnames';
+import BuilderContext from '../BuilderContext';
+
+type Props = {
+ row: RowTypes;
+ slot: SlotTypes;
+ index: number;
+ componentId: string;
+ currentSection: string;
+};
+
+const BuilderItem: React.FC = (props) => {
+ const { componentId, currentSection, slot, row, index } = props;
+ const { actions, builder } = useContext(BuilderContext);
+
+ const itemDetails =
+ window.NeveReactCustomize.HFG[builder].items[componentId];
+
+ if (!itemDetails) {
+ return null;
+ }
+
+ const { name, section } = itemDetails;
+ const { removeItem } = actions;
+
+ const isBlockWidget = section && section.includes('neve_sidebar-widgets');
+
+ const isActive = currentSection === section;
+ const itemSection = window.wp.customize.section(section);
+
+ const focusItemWidget = () => {
+ const newSection = section.replace('neve_', '');
+ window.wp.customize.section(newSection).focus();
+ };
+
+ const focusItemSection = () => {
+ itemSection.focus();
+ };
+
+ const remove = () => {
+ removeItem(row, slot, index);
+ itemSection.expanded(false);
+ };
+
+ const iconSize = 18;
+
+ const itemClasses = classnames('builder-item', { active: isActive });
+
+ return (
+
+
+
{name}
+
+
+ {isBlockWidget && (
+
+ )}
+
+
+
+ );
+};
+
+export default BuilderItem;
diff --git a/inc/customizer/controls/react/src/builder/components/ResponsiveSwitches.tsx b/inc/customizer/controls/react/src/builder/components/ResponsiveSwitches.tsx
new file mode 100644
index 0000000000..29066e281d
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/ResponsiveSwitches.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import classnames from 'classnames';
+
+import { __ } from '@wordpress/i18n';
+import { Button } from '@wordpress/components';
+import { useContext } from '@wordpress/element';
+import { DeviceTypes } from '../../@types/utils';
+import BuilderContext from '../BuilderContext';
+import BuilderHeaderNotification from './BuilderHeaderNotification';
+
+type ButtonProps = {
+ title: string;
+ icon: string;
+ slug: DeviceTypes;
+};
+
+type Props = {
+ device: DeviceTypes;
+};
+
+const ResponsiveSwitches: React.FC = ({ device }) => {
+ const { actions, builder } = useContext(BuilderContext);
+ const { setDevice } = actions;
+ const buttons: ButtonProps[] = [
+ { title: __('Desktop', 'neve'), icon: 'desktop', slug: 'desktop' },
+ { title: __('Mobile', 'neve'), icon: 'smartphone', slug: 'mobile' },
+ ];
+ const { devices, title: builderName } = window.NeveReactCustomize.HFG[
+ builder
+ ];
+
+ const shownButtons = buttons.filter(({ slug }) =>
+ Object.keys(devices).includes(slug)
+ );
+
+ const switchDevice = (nextDevice: DeviceTypes) => {
+ if (device === nextDevice) {
+ return false;
+ }
+ window.wp.customize.previewedDevice(nextDevice);
+ setDevice(nextDevice);
+ };
+
+ return (
+
+
+ {shownButtons.map((button, index) => {
+ const { title, icon, slug } = button;
+ const buttonClasses = classnames('device-switcher', {
+ active: slug === device,
+ });
+ return (
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default ResponsiveSwitches;
diff --git a/inc/customizer/controls/react/src/builder/components/Row.tsx b/inc/customizer/controls/react/src/builder/components/Row.tsx
new file mode 100644
index 0000000000..db22fc08d1
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/Row.tsx
@@ -0,0 +1,255 @@
+import React from 'react';
+import {
+ BuilderItemType,
+ BuilderRowInterface,
+ RowTypes,
+ SlotTypes,
+ StringObjectKeys,
+} from '../../@types/utils';
+import { Button } from '@wordpress/components';
+import { cog } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import { useContext, useEffect, useState } from '@wordpress/element';
+import classnames from 'classnames';
+
+import Slot from './Slot';
+import { WPCustomizeControl } from '../../@types/customizer-control';
+import BuilderContext from '../BuilderContext';
+
+type Props = {
+ items: BuilderRowInterface & BuilderItemType[];
+ rowId: RowTypes;
+};
+
+const Row: React.FC = ({ items, rowId }) => {
+ const {
+ actions,
+ dragging,
+ builder,
+ hasColumns,
+ previewSidebar,
+ } = useContext(BuilderContext);
+ const { updateLayout, togglePreviewSidebar } = actions;
+ const slots: SlotTypes[] = ['left', 'c-left', 'center', 'c-right', 'right'];
+
+ const section = `hfg_${builder}_layout_${rowId}`;
+ const columnsSetting = `${section}_columns_number`;
+ const columnsLayoutSetting = `${section}_columns_layout`;
+
+ const [columns, setColumns] = useState(0);
+ const [colLayout, setColLayout] = useState('equal');
+ const [currentRow, setCurrentRow] = useState(false);
+
+ const focusRowSection = () => {
+ if (rowId === 'sidebar') {
+ toggleThemeSidebar(true);
+ }
+ window.wp.customize.section(section).focus();
+ };
+
+ /**
+ * Check if the section is active when sections are expanded.
+ */
+ useEffect(() => {
+ bindRowSettingsButton();
+
+ if (!hasColumns) {
+ return;
+ }
+
+ bindColumnsSync();
+ }, []);
+
+ const bindRowSettingsButton = () => {
+ window.wp.customize
+ .state('expandedSection')
+ .bind((expandedSection: StringObjectKeys) => {
+ if (!expandedSection || expandedSection.id !== section) {
+ setCurrentRow(false);
+ return false;
+ }
+
+ if (expandedSection.id === section) {
+ setCurrentRow(true);
+ }
+ });
+ };
+
+ const bindColumnsSync = () => {
+ const colNumber = window.wp.customize
+ .control(columnsSetting)
+ .setting.get();
+ setColumns(parseInt(colNumber));
+ setColLayout(
+ window.wp.customize.control(columnsLayoutSetting).setting.get()
+ );
+
+ const syncColNumber = (nextValue: string) => {
+ const parsedColNumber = parseInt(nextValue);
+ slots.forEach((slot, index) => {
+ if (index + 1 > parsedColNumber) {
+ updateLayout(rowId, slot, []);
+ }
+ });
+ setColumns(parsedColNumber);
+ };
+
+ window.wp.customize.control(
+ columnsSetting,
+ (control: WPCustomizeControl) => {
+ control.setting.bind((nextValue: string) => {
+ syncColNumber(nextValue);
+ });
+ }
+ );
+ window.wp.customize.control(
+ columnsLayoutSetting,
+ (control: WPCustomizeControl) => {
+ control.setting.bind((nextValue: string) => {
+ setColLayout(nextValue);
+ });
+ }
+ );
+ };
+
+ useEffect(() => {
+ if (rowId !== 'sidebar') {
+ return;
+ }
+ // Toggle sidebar button when sidebar is toggled from preview.
+ window.wp.customize.previewer.bind(
+ 'neve-toggle-navbar',
+ (e: { status: boolean }) => {
+ const { status } = e;
+ togglePreviewSidebar(status);
+ }
+ );
+
+ // Toggle theme sidebar if it was previously opened on customizer refresh.
+ window.wp.customize.previewer.bind('ready', () => {
+ togglePreviewSidebar(false);
+ });
+ }, []);
+
+ // Toggle sidebar in preview.
+ const toggleThemeSidebar = (status: boolean) => {
+ window.wp.customize.previewer.send(
+ status ? 'header_sidebar_open' : 'header_sidebar_close'
+ );
+ togglePreviewSidebar(status);
+ };
+
+ if (rowId === 'sidebar') {
+ const hasItems = items && items.length > 0;
+ const rowClasses = classnames('row', rowId, { 'has-items': hasItems });
+
+ return (
+
+
+
+ {hasItems && (
+
+
+
+
+
+
+ );
+ }
+
+ const hasCenterItems = items.center && items.center.length > 0;
+ const centerItemsClass =
+ hasCenterItems || hasColumns ? 'has-center' : 'no-center';
+ const centerWrapClass = classnames('slots-wrap', 'slots-center-wrap', {
+ expanded: dragging,
+ });
+ return (
+
+
+
+ {!hasColumns && (
+ <>
+
+ {['left', 'c-left'].map((slotId, index) => {
+ return (
+
+ );
+ })}
+
+
+
+
+
+ {['c-right', 'right'].map((slotId, index) => {
+ return (
+
+ );
+ })}
+
+ >
+ )}
+ {hasColumns && (
+
+ {slots.map((slotId, index) => {
+ const slotClasses = classnames({
+ hide: index >= columns,
+ last: index === columns - 1,
+ single: index === 0 && columns === 1,
+ });
+ return (
+
+ );
+ })}
+
+ )}
+
+
+ );
+};
+
+export default Row;
diff --git a/inc/customizer/controls/react/src/builder/components/SidebarContent.tsx b/inc/customizer/controls/react/src/builder/components/SidebarContent.tsx
new file mode 100644
index 0000000000..9decf62c98
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/SidebarContent.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { ReactSortable } from 'react-sortablejs';
+import { Icon } from '@wordpress/components';
+import { dragHandle } from '@wordpress/icons';
+
+import { __ } from '@wordpress/i18n';
+import { useContext } from '@wordpress/element';
+import BuilderContext from '../BuilderContext';
+
+const SidebarContent: React.FC = () => {
+ const { sidebarItems: items, builder, actions, dragging } = useContext(
+ BuilderContext
+ );
+
+ const { onDragStart, onDragEnd, setSidebarItems } = actions;
+ const allItems = window.NeveReactCustomize.HFG[builder].items;
+
+ return (
+
+
+ {__('Available Components', 'neve')}
+
+ {items && items.length > 0 && (
+
+
{
+ const nextItems = [...next]
+ .map((i) => {
+ return { id: i.id };
+ })
+ .sort((a, b) => {
+ return a.id < b.id ? -1 : 1;
+ });
+
+ setSidebarItems(nextItems);
+ }}
+ >
+ {items.map((item, index) => {
+ const { name } = allItems[item.id];
+ return (
+
+
+ {name}
+
+ );
+ })}
+
+
+ )}
+ {!items.length && (
+
+
+ {__(
+ 'All available components are used inside the builder',
+ 'neve'
+ )}
+
+
+ )}
+
+ );
+};
+
+export default SidebarContent;
diff --git a/inc/customizer/controls/react/src/builder/components/Slot.tsx b/inc/customizer/controls/react/src/builder/components/Slot.tsx
new file mode 100644
index 0000000000..1ac1441960
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/components/Slot.tsx
@@ -0,0 +1,175 @@
+import React, { ChangeEvent } from 'react';
+import { BuilderItemType, RowTypes, SlotTypes } from '../../@types/utils';
+import { ReactSortable } from 'react-sortablejs';
+import BuilderItem from './BuilderItem';
+import classnames from 'classnames';
+import { Button, Popover, Icon } from '@wordpress/components';
+import { useContext, useState } from '@wordpress/element';
+import { close, plus, search } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import BuilderContext from '../BuilderContext';
+
+type Props = {
+ rowId: RowTypes;
+ slotId: SlotTypes;
+ items: BuilderItemType[];
+ className?: string;
+};
+
+const Slot: React.FC = ({ items, slotId, rowId, className }) => {
+ const {
+ currentSection,
+ builder,
+ actions,
+ dragging,
+ sidebarItems,
+ } = useContext(BuilderContext);
+
+ const { updateLayout, onDragStart } = actions;
+ const [popupOpen, setPopupOpen] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+ const slotClasses = classnames('droppable-wrap', slotId, className, {
+ 'has-popover': popupOpen,
+ overflowed: items.length >= 3 && rowId !== 'sidebar',
+ });
+
+ const addItemToSlot = (itemId: string) => {
+ const itemSection =
+ window.NeveReactCustomize.HFG[builder].items[itemId].section;
+ window.wp.customize.section(itemSection).focus();
+ const nextItems = [...items];
+ nextItems.push({ id: itemId });
+ updateLayout(rowId, slotId, nextItems);
+ setPopupOpen(false);
+ };
+
+ const runSearch = (e: ChangeEvent) => {
+ setSearchQuery(e.target.value.toLowerCase());
+ };
+
+ const allItems = window.NeveReactCustomize.HFG[builder].items;
+
+ return (
+
+
{
+ const nextState = newState.map((item) => {
+ const { id } = item;
+ return { id };
+ });
+ updateLayout(rowId, slotId, nextState);
+ }}
+ >
+ {items &&
+ items.length > 0 &&
+ items.map((item, index) => {
+ return (
+
+ );
+ })}
+
+ {!dragging && (
+
+ );
+};
+
+export default Slot;
diff --git a/inc/customizer/controls/react/src/builder/oldData.tsx b/inc/customizer/controls/react/src/builder/oldData.tsx
new file mode 100644
index 0000000000..13106635d0
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/oldData.tsx
@@ -0,0 +1,60 @@
+export const Header = {
+ desktop: {
+ top: [],
+ main: [
+ { x: 0, y: 1, width: 4, height: 1, id: 'logo' },
+ { x: 4, y: 1, width: 8, height: 1, id: 'primary-menu' },
+ ],
+ bottom: [],
+ },
+ mobile: {
+ top: [
+ { x: 0, y: 1, width: 2, height: 1, id: 'button_base' },
+ { x: 8, y: 1, width: 1, height: 1, id: 'header_search_responsive' },
+ { x: 9, y: 1, width: 3, height: 1, id: 'custom_html' },
+ ],
+ main: [
+ { x: 0, y: 1, width: 8, height: 1, id: 'logo' },
+ { x: 8, y: 1, width: 4, height: 1, id: 'nav-icon' },
+ ],
+ bottom: [],
+ sidebar: [{ x: 0, y: 1, width: 8, height: 1, id: 'primary-menu' }],
+ },
+};
+
+export const NewHeader = {
+ desktop: {
+ top: {},
+ main: {
+ left: [{ x: 0, y: 1, width: 4, height: 1, id: 'logo' }],
+ 'c-left': [],
+ center: [{ x: 4, y: 1, width: 8, height: 1, id: 'primary-menu' }],
+ 'c-right': [],
+ right: [],
+ },
+ bottom: {},
+ },
+ mobile: {
+ top: {
+ left: [
+ { x: 0, y: 1, width: 2, height: 1, id: 'button_base' },
+ {
+ x: 8,
+ y: 1,
+ width: 1,
+ height: 1,
+ id: 'header_search_responsive',
+ },
+ { x: 9, y: 1, width: 3, height: 1, id: 'custom_html' },
+ ],
+ },
+ main: {
+ center: [
+ { x: 0, y: 1, width: 8, height: 1, id: 'logo' },
+ { x: 8, y: 1, width: 4, height: 1, id: 'nav-icon' },
+ ],
+ },
+ bottom: {},
+ sidebar: [{ x: 0, y: 1, width: 8, height: 1, id: 'primary-menu' }],
+ },
+};
diff --git a/inc/customizer/controls/react/src/builder/scss/_builder-item.scss b/inc/customizer/controls/react/src/builder/scss/_builder-item.scss
new file mode 100644
index 0000000000..062b024936
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_builder-item.scss
@@ -0,0 +1,97 @@
+@import "vars";
+
+.builder-item {
+ display: flex;
+ align-items: center;
+ background-color: #fff;
+ border: $wp-grey-border;
+ min-width: 90px;
+ white-space: nowrap;
+ cursor: grab;
+ margin: 0 2px;
+ border-radius: 3px;
+ box-sizing: border-box;
+ height: $rows-height - 7;
+ color: $item-text-color;
+ z-index: 1;
+
+ button {
+ padding: 0;
+ min-width: 30px;
+
+ .settings {
+ padding: 5px;
+ }
+
+ .remove {
+ padding: 5px;
+ }
+ }
+
+ &.active {
+ background-color: $wp-blue;
+ color: #fff;
+
+ svg {
+ fill: #fff;
+ }
+ }
+
+ .handle {
+ width: 15px;
+ padding: 0 2px 0 5px;
+ }
+
+ svg {
+ fill: $item-icons-color;
+ }
+
+ .actions {
+ margin-left: auto;
+ }
+
+ &.sortable-ghost {
+ border: $droppable-border;
+ padding-right: 10px;
+ }
+}
+
+.droppable-wrap {
+ &.sidebar {
+ padding: 4px;
+ width: 100%;
+ box-sizing: border-box;
+
+ .builder-item {
+ min-width: unset;
+ max-width: 100%;
+ width: 100%;
+ margin: 0 0 4px;
+
+ .name {
+ max-width: 90%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+ &.overflowed {
+ .builder-item {
+ .name {
+ max-width: 35px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ transition: max-width .2s ease;
+ }
+ &:hover {
+ .name {
+ max-width: 250px;
+ }
+ }
+ }
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_columns-builder.scss b/inc/customizer/controls/react/src/builder/scss/_columns-builder.scss
new file mode 100644
index 0000000000..2497c39a81
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_columns-builder.scss
@@ -0,0 +1,71 @@
+.neve-builder.columns-builder {
+ .row {
+ .slots-wrap {
+ width: 100%;
+
+ .droppable-wrap {
+ box-sizing: border-box;
+
+ &.hide {
+ display: none;
+ }
+
+ &.last .droppable {
+ border: none;
+ }
+
+ .droppable {
+ justify-content: center;
+ flex-direction: column;
+ height: 100%;
+ box-sizing: border-box;
+ align-items: center;
+ padding: 2px 4px;
+
+ .builder-item {
+ margin: 2px 2px;
+ width: 100%;
+ }
+ }
+ }
+
+ &.col-2 {
+ &.right-third .droppable-wrap.left {
+ flex-grow: 2;
+ }
+
+ &.left-third .droppable-wrap.c-left {
+ flex-grow: 2;
+ }
+ }
+
+ &.col-3 {
+ &.right-half .droppable-wrap.center {
+ flex-grow: 2;
+ }
+
+ &.left-half .droppable-wrap.left {
+ flex-grow: 2;
+ }
+
+ &.center-half .droppable-wrap.c-left {
+ flex-grow: 2;
+ }
+
+ &.center-two-thirds .droppable-wrap.c-left {
+ flex-grow: 3;
+ }
+ }
+
+ &.col-4 {
+ &.left-half .droppable-wrap.left {
+ flex-grow: 2;
+ }
+
+ &.right-half .droppable-wrap.c-right {
+ flex-grow: 2;
+ }
+ }
+ }
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_customizer-adjustments.scss b/inc/customizer/controls/react/src/builder/scss/_customizer-adjustments.scss
new file mode 100644
index 0000000000..a69f4e2baf
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_customizer-adjustments.scss
@@ -0,0 +1,13 @@
+ul[id^="sub-accordion-panel-hfg_"] {
+ .control-subsection {
+ display: none !important;
+ }
+
+ li[id*="accordion-section-hfg_"] {
+ display: block !important;
+ }
+
+ #accordion-section-neve_header_presets {
+ margin-top: 20px;
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_instructional-section.scss b/inc/customizer/controls/react/src/builder/scss/_instructional-section.scss
new file mode 100644
index 0000000000..87506df658
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_instructional-section.scss
@@ -0,0 +1,30 @@
+.control-section-hfg_instructions {
+ display: list-item !important;
+
+ .quick-links-inner {
+ padding: 0 15px;
+ }
+}
+
+.quick-links-inner {
+ .quick-links {
+ font-size: 14px;
+ font-weight: 500;
+
+ button {
+ text-decoration: none;
+ }
+ }
+
+ .quick-links-wrap {
+ margin: 10px 0;
+ display: block;
+ background: #fff;
+ border-radius: 3px;
+ padding: 10px 20px;
+
+ span {
+ margin-right: 10px;
+ }
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_items-popover.scss b/inc/customizer/controls/react/src/builder/scss/_items-popover.scss
new file mode 100644
index 0000000000..de6bea250b
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_items-popover.scss
@@ -0,0 +1,69 @@
+.items-popover {
+ box-shadow: 0 3px 30px rgba(25, 30, 35, .1);
+ border-radius: 4px;
+ z-index: 100000;
+
+ .popover-header {
+ display: flex;
+ padding: 5px 10px;
+ position: relative;
+ border-bottom: 1px solid #e1e4e7;
+
+ input {
+ appearance: none;
+ width: 100%;
+ height: 35px;
+ border: 1px solid #8d96a0;
+ outline: none;
+ box-shadow: none;
+ font-size: 13px;
+ margin: 16px;
+ padding: 11px 16px 11px 30px;
+ border-radius: 4px;
+ }
+
+ > svg {
+ position: absolute;
+ fill: #8d96a0;
+ left: 30px;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ }
+
+ .items-popover-content {
+ width: 400px;
+ padding: 10px;
+ overflow-y: auto;
+ max-height: 375px;
+ height: 375px;
+ background-color: #fff;
+ }
+
+ .items-popover-list {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-gap: 10px;
+ }
+
+ .popover-item {
+ width: 100%;
+ height: auto;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ padding: 7px;
+ color: #545d66;
+ line-height: 1.4em;
+ font-size: 13px;
+ &:hover {
+ background: #f3f3f5;
+ }
+ .dashicon {
+ margin: 0 0 10px;
+ }
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_main.scss b/inc/customizer/controls/react/src/builder/scss/_main.scss
new file mode 100644
index 0000000000..99b7fba743
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_main.scss
@@ -0,0 +1,44 @@
+@import "vars";
+
+.neve-hfg-builder {
+ .neve-builder {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ margin-left: 300px;
+ padding: 10px 20px 20px;
+ background-color: $wp-light-grey;
+ border-top: $wp-grey-border;
+ transition: all .18s;
+
+ &.hide {
+ transition: bottom .2s ease;
+ bottom: -100%;
+ }
+
+ @media screen and (min-width: 1667px) {
+ margin-left: 18%;
+ }
+ }
+
+ @import 'rows';
+ @import 'builder-item';
+ @import 'responsive-switches';
+ @import 'sidebar-items';
+ @import 'columns-builder';
+ @import 'items-popover';
+}
+
+@import 'customizer-adjustments';
+@import 'instructional-section';
+
+
+.neve-builder-portal-wrap {
+ z-index: 10;
+ position: relative;
+}
+
+.collapsed .neve-builder {
+ margin-left: 0;
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_responsive-switches.scss b/inc/customizer/controls/react/src/builder/scss/_responsive-switches.scss
new file mode 100644
index 0000000000..d9ca8529f1
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_responsive-switches.scss
@@ -0,0 +1,48 @@
+@import "vars";
+
+.responsive-switches {
+ display: inline-flex;
+ width: auto;
+
+ .device-switcher {
+ border-bottom: 2px solid $wp-grey;
+ border-radius: 0;
+ font-weight: 600;
+ color: $item-text-color;
+ padding: 5px 15px;
+ opacity: .85;
+
+ .dashicons {
+ color: $item-icons-color;
+ }
+
+ &.active {
+ opacity: 1;
+ border-bottom-color: $wp-blue;
+
+ &.dashicons {
+ color: $wp-blue;
+ }
+ }
+ }
+}
+
+.builder-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.builder-instructions {
+ margin-left: 20px;
+ display: flex;
+ align-items: center;
+
+ p {
+ margin: 0;
+ }
+
+ strong {
+ margin-right: 5px;
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_rows.scss b/inc/customizer/controls/react/src/builder/scss/_rows.scss
new file mode 100644
index 0000000000..7de0bfbe0d
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_rows.scss
@@ -0,0 +1,194 @@
+@import 'vars';
+
+.rows-wrapper {
+ display: flex;
+
+ .vertical-rows {
+ margin-right: 10px;
+
+ .row {
+ height: 100%;
+ }
+ }
+
+ .horizontal-rows {
+ flex-grow: 1;
+ }
+}
+
+.row {
+ display: flex;
+ border-radius: 3px;
+ margin-bottom: 10px;
+ background-color: #fff;
+ border: $wp-grey-border;
+ flex-wrap: nowrap;
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ .row-settings {
+ width: 32px;
+ display: flex;
+ padding: 0 2px;
+ min-width: unset;
+ align-items: center;
+ height: auto;
+ flex-grow: 1;
+ justify-content: center;
+ border-right: $wp-grey-border;
+ }
+
+ .inner-row {
+ display: flex;
+ width: 100%;
+ align-items: stretch;
+ }
+
+ .droppable-wrap {
+ flex: 1;
+ position: relative;
+
+ &.right, &.c-left {
+ .droppable {
+ justify-content: flex-end;
+ }
+ }
+
+ &.center {
+ .droppable {
+ justify-content: center;
+ transition: .3s ease;
+ width: auto;
+ }
+ }
+ }
+
+ .droppable {
+ display: flex;
+ padding: 0 2px;
+ align-items: center;
+ min-height: $rows-height;
+ border-right: $droppable-border;
+ flex: 1;
+ }
+
+ &.c-left, &.right {
+ .builder-item:first-child {
+ margin-left: auto;
+ }
+ }
+}
+
+.row.sidebar .row-inner {
+ width: 200px;
+
+ .droppable-wrap {
+ height: 100%;
+ }
+
+ .droppable {
+ flex-direction: column;
+ justify-content: flex-start;
+ height: 100%;
+ border: 0;
+ flex-grow: 1;
+ }
+}
+
+.sidebar-actions {
+ .row-settings {
+ height: 100%;
+ border-radius: 0;
+
+ &:not(:first-child) {
+ border-top: $wp-grey-border;
+ }
+ }
+
+ .dashicon {
+ font-size: 15px;
+ margin: 0 !important;
+ }
+}
+
+.has-items .sidebar-actions {
+ .row-settings {
+ height: 50%;
+ }
+}
+
+.slots-wrap {
+ display: flex;
+ width: 50%;
+
+ &:last-child .droppable-wrap:last-child .droppable {
+ border: 0;
+ }
+
+ &.slots-center-wrap {
+ width: auto;
+ transition: .3s ease;
+
+ &.expanded .droppable:empty {
+ min-width: 150px;
+ border-right: $droppable-border;
+ }
+
+ &:hover .droppable:empty {
+ min-width: 150px;
+ border-right: $droppable-border;
+ }
+ }
+}
+
+.no-center {
+ .center .droppable:empty {
+ border-right: 0;
+ padding: 0;
+ }
+
+ .slots-center-wrap .droppable:empty {
+ min-width: 0;
+ transition: .3s ease;
+ }
+
+ &:hover {
+ .slots-center-wrap .droppable:empty {
+ min-width: 150px;
+ border-right: $droppable-border;
+ }
+ }
+
+ .has-popover.center .droppable:empty {
+ min-width: 150px;
+ border-right: $droppable-border;
+ }
+
+ .c-left, .c-right {
+ display: none;
+ }
+}
+
+.open-popover {
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ opacity: 0;
+ z-index: 0;
+ padding: 0;
+ height: unset;
+ min-width: unset;
+ border-radius: 0;
+ position: absolute;
+ width: 100%;
+}
+
+.droppable-wrap {
+ &:hover, &.has-popover {
+ .open-popover {
+ opacity: .25;
+ }
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_sidebar-items.scss b/inc/customizer/controls/react/src/builder/scss/_sidebar-items.scss
new file mode 100644
index 0000000000..dd1d6840f2
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_sidebar-items.scss
@@ -0,0 +1,37 @@
+@import "vars";
+
+.neve-builder-sidebar-content {
+ padding: 15px;
+
+ .no-components {
+ border: $droppable-border;
+ color: $item-text-color;
+ margin-top: 10px;
+ padding: 15px;
+ }
+}
+
+.sidebar-items {
+ margin-top: 10px;
+
+ .droppable {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ grid-gap: 10px 5px;
+
+ .builder-item {
+ margin: 0;
+ padding-right: 10px;
+
+ svg {
+ flex-shrink: 0;
+ }
+
+ span {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
diff --git a/inc/customizer/controls/react/src/builder/scss/_vars.scss b/inc/customizer/controls/react/src/builder/scss/_vars.scss
new file mode 100644
index 0000000000..7fc5a2948d
--- /dev/null
+++ b/inc/customizer/controls/react/src/builder/scss/_vars.scss
@@ -0,0 +1,9 @@
+$wp-light-grey: #EDEDED;
+$wp-grey: #D9D9D9;
+$wp-grey-border: 1px solid #D9D9D9;
+$wp-blue: #0073AA;
+$droppable-border: 1px dashed #cecece;
+$rows-height: 45px;
+$item-text-color: #555D66;
+$item-icons-color: #7D838A;
+
diff --git a/inc/customizer/controls/react/src/button-appearance/ButtonAppearance.js b/inc/customizer/controls/react/src/button-appearance/ButtonAppearance.js
index 4f651e0919..e561396781 100644
--- a/inc/customizer/controls/react/src/button-appearance/ButtonAppearance.js
+++ b/inc/customizer/controls/react/src/button-appearance/ButtonAppearance.js
@@ -79,6 +79,10 @@ const ButtonAppearance = ({ label, value, onChange, noHover, defaultVals }) => {
settings[optionType]
.controls[controlSlug]
}
+ defaultValue={
+ defaultVals[controlSlug] ||
+ null
+ }
selectedColor={
value[controlSlug]
}
diff --git a/inc/customizer/controls/react/src/button-appearance/ButtonAppearanceComponent.js b/inc/customizer/controls/react/src/button-appearance/ButtonAppearanceComponent.js
index 0d0750459a..9c579b1936 100755
--- a/inc/customizer/controls/react/src/button-appearance/ButtonAppearanceComponent.js
+++ b/inc/customizer/controls/react/src/button-appearance/ButtonAppearanceComponent.js
@@ -6,7 +6,6 @@ import { useState, useEffect } from '@wordpress/element';
const ButtonAppearanceComponent = ({ control }) => {
const controlValue = control.setting.get();
-
const defaultsFromControl = {
borderRadius: {
top: 3,
@@ -43,8 +42,8 @@ const ButtonAppearanceComponent = ({ control }) => {
const defaultVals = control.params.defaultVals
? {
- ...control.params.defaultVals,
...defaultsFromControl,
+ ...control.params.defaultVals,
}
: defaultsFromControl;
@@ -62,7 +61,7 @@ const ButtonAppearanceComponent = ({ control }) => {
const { label, no_hover } = control.params;
useEffect(() => {
- global.addEventListener('neve-changed-customizer-value', (e) => {
+ document.addEventListener('neve-changed-customizer-value', (e) => {
if (!e.detail) return false;
if (e.detail.id !== control.id) return false;
// Migrate border-radius and border-width
diff --git a/inc/customizer/controls/react/src/color/ColorComponent.js b/inc/customizer/controls/react/src/color/ColorComponent.js
index 13901705f6..a8a4300b17 100644
--- a/inc/customizer/controls/react/src/color/ColorComponent.js
+++ b/inc/customizer/controls/react/src/color/ColorComponent.js
@@ -13,7 +13,7 @@ const ColorComponent = ({ control }) => {
};
useEffect(() => {
- global.addEventListener('neve-changed-customizer-value', (e) => {
+ document.addEventListener('neve-changed-customizer-value', (e) => {
if (!e.detail) return false;
if (e.detail.id !== control.id) return false;
updateValues(e.detail.value);
diff --git a/inc/customizer/controls/react/src/common/InlineSelect.js b/inc/customizer/controls/react/src/common/InlineSelect.js
index 1a0a70a7a4..b3db3d01e1 100644
--- a/inc/customizer/controls/react/src/common/InlineSelect.js
+++ b/inc/customizer/controls/react/src/common/InlineSelect.js
@@ -1,10 +1,17 @@
import { SelectControl } from '@wordpress/components';
-const InlineSelect = ({ value, onChange, options, label }) => {
+const InlineSelect = ({
+ value,
+ onChange,
+ options,
+ label,
+ disabled = false,
+}) => {
return (
{label &&
{label}}
{
- const [open, setOpen] = useState(false);
- useEffect(() => {
- if (!openAttr) return false;
- global.addEventListener('DOMContentLoaded', () => {
- const outsideTrigger = document.querySelectorAll(
- `[data-open-nv-modal=${openAttr}]`
- );
- if (!outsideTrigger) return false;
- outsideTrigger.forEach((item) => {
- item.addEventListener('click', (e) => {
- e.preventDefault();
- setOpen(true);
- });
- });
- });
- }, []);
-
- const dismiss = () => {
- setOpen(false);
- };
-
- if (!open && !opened) {
- return null;
- }
-
- return (
- <>
- {trigger}
-
- {children}
-
- >
- );
-};
-
-NeveModal.propTypes = {
- children: PropTypes.element.isRequired,
- opened: PropTypes.bool,
- trigger: PropTypes.element,
- openAttr: PropTypes.string,
- title: PropTypes.string,
-};
-
-export default NeveModal;
diff --git a/inc/customizer/controls/react/src/common/RadioIcons.js b/inc/customizer/controls/react/src/common/RadioIcons.js
index 60e74a5b2c..1bcfa88823 100644
--- a/inc/customizer/controls/react/src/common/RadioIcons.js
+++ b/inc/customizer/controls/react/src/common/RadioIcons.js
@@ -18,10 +18,9 @@ const RadioIcons = ({
return (
{
onChange(type);
}}
@@ -35,7 +34,9 @@ const RadioIcons = ({
{
onChange(type);
@@ -66,7 +67,7 @@ const RadioIcons = ({
RadioIcons.propTypes = {
options: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
- value: PropTypes.string,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
showLabels: PropTypes.bool,
};
diff --git a/inc/customizer/controls/react/src/common/Responsive.js b/inc/customizer/controls/react/src/common/Responsive.js
index 7824de5183..d81bfd6381 100644
--- a/inc/customizer/controls/react/src/common/Responsive.js
+++ b/inc/customizer/controls/react/src/common/Responsive.js
@@ -1,34 +1,43 @@
-/* global CustomEvent */
import { Button, Tooltip, ButtonGroup } from '@wordpress/components';
import { useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
-const ResponsiveControl = ({
- onChange,
- excluded,
- controlLabel,
- hideResponsive,
- children,
-}) => {
- const changeViewType = (device) => {
- wp.customize.previewedDevice(device);
- };
+const ResponsiveControl = (props) => {
+ const {
+ onChange,
+ excluded,
+ controlLabel,
+ hideResponsive,
+ children,
+ } = props;
useEffect(() => {
- // eslint-disable-next-line @wordpress/no-global-event-listener
- document.addEventListener('neveChangedRepsonsivePreview', (e) => {
- onChange(e.detail);
+ window.wp.customize.bind('ready', () => {
+ window.wp.customize.previewedDevice.bind((newDevice) => {
+ onChange(checkExcludedTablet(newDevice));
+ });
});
}, []);
+ const checkExcludedTablet = (device) => {
+ if (!excluded) {
+ return device;
+ }
+
+ if (!excluded.includes(device)) {
+ return device;
+ }
+
+ if (device === 'tablet') {
+ return 'mobile';
+ }
+ };
+
const dispatchViewChange = (device) => {
- changeViewType(device);
- const event = new CustomEvent('neveChangedRepsonsivePreview', {
- detail: device,
- });
- document.dispatchEvent(event);
+ onChange(checkExcludedTablet(device));
+ wp.customize.previewedDevice(device);
};
const devices = {
diff --git a/inc/customizer/controls/react/src/common/svg.js b/inc/customizer/controls/react/src/common/svg.js
index 22b4bef957..4d40482f3c 100644
--- a/inc/customizer/controls/react/src/common/svg.js
+++ b/inc/customizer/controls/react/src/common/svg.js
@@ -257,6 +257,26 @@ const SVG = {
),
+ paletteIconStyle1: (
+
+ ),
+ paletteIconStyle2: (
+
+ ),
+ paletteIconStyle3: (
+
+ ),
+ paletteIconStyle4: (
+
+ ),
mediaAlignLeft: (
)}
- {
- onChoseFont(fontSource, font);
- }}
- inheritDefault={controlParams.default_is_inherit}
- systemFonts={controlParams.system}
- maybeGetTypekit={maybeGetTypekitFont}
- />
+ }>
+ {visible && (
+ {
+ onChoseFont(fontSource, font);
+ }}
+ inheritDefault={controlParams.default_is_inherit}
+ systemFonts={controlParams.system}
+ maybeGetTypekit={maybeGetTypekitFont}
+ />
+ )}
+
>
);
diff --git a/inc/customizer/controls/react/src/heading/Control.js b/inc/customizer/controls/react/src/heading/Control.js
new file mode 100644
index 0000000000..59c0a74f09
--- /dev/null
+++ b/inc/customizer/controls/react/src/heading/Control.js
@@ -0,0 +1,16 @@
+/* jshint esversion: 6 */
+
+import HeadingComponent from './HeadingComponent.js';
+import { render } from '@wordpress/element';
+
+export const HeadingControl = wp.customize.Control.extend({
+ triggerExpandHeader: function triggerExpandHeader() {
+ if (this.container[0].classList.contains('expanded')) {
+ return;
+ }
+ this.container[0].classList.toggle('expanded');
+ },
+ renderContent: function renderContent() {
+ render(, this.container[0]);
+ },
+});
diff --git a/inc/customizer/controls/react/src/heading/Heading.js b/inc/customizer/controls/react/src/heading/Heading.js
new file mode 100644
index 0000000000..bb5a62096f
--- /dev/null
+++ b/inc/customizer/controls/react/src/heading/Heading.js
@@ -0,0 +1,25 @@
+const Toggle = ({ categoryLabel, label, accordion }) => {
+ const expandHeading = (e) => {
+ e.currentTarget.parentElement.classList.toggle('expanded');
+ };
+
+ return (
+ <>
+ {categoryLabel && (
+ {categoryLabel}
+ )}
+
+ {label}
+ {accordion && }
+
+ >
+ );
+};
+
+export default Toggle;
diff --git a/inc/customizer/controls/react/src/heading/HeadingComponent.js b/inc/customizer/controls/react/src/heading/HeadingComponent.js
new file mode 100644
index 0000000000..0538cae89c
--- /dev/null
+++ b/inc/customizer/controls/react/src/heading/HeadingComponent.js
@@ -0,0 +1,30 @@
+/* jshint esversion: 6 */
+import Heading from './Heading';
+import PropTypes from 'prop-types';
+
+const HeadingComponent = ({ control }) => {
+ const { label, categoryLabel, accordion, style } = control.params;
+
+ return (
+ <>
+
+ {style && (
+
+ )}
+ >
+ );
+};
+
+HeadingComponent.propTypes = {
+ control: PropTypes.object.isRequired,
+};
+
+export default HeadingComponent;
diff --git a/inc/customizer/controls/react/src/inline-select/InlineSelectComponent.js b/inc/customizer/controls/react/src/inline-select/InlineSelectComponent.js
index 493a8fb044..b9d6af4ff0 100644
--- a/inc/customizer/controls/react/src/inline-select/InlineSelectComponent.js
+++ b/inc/customizer/controls/react/src/inline-select/InlineSelectComponent.js
@@ -1,13 +1,61 @@
-import { useState } from '@wordpress/element';
+import { useEffect, useState } from '@wordpress/element';
import InlineSelect from '../common/InlineSelect';
const InlineSelectComponent = ({ control }) => {
- const { label, options } = control.params;
+ const { label, options, changesOn } = control.params;
const [value, setValue] = useState(control.setting());
-
- const settings = Object.keys(options).map((key) => {
+ const defaultSettings = Object.keys(options).map((key) => {
return { value: key, label: options[key] };
});
+ const [settings, setSettings] = useState(defaultSettings);
+
+ useEffect(() => {
+ if (
+ typeof changesOn !== 'string' ||
+ changesOn !== 'neve_global_colors'
+ ) {
+ return;
+ }
+
+ window.wp.customize.bind('ready', () => {
+ // Update select settings with current global colors
+ const currentGlobalColors = window.wp.customize
+ .control(changesOn)
+ .setting.get().palettes;
+ const newSettings = Object.keys(currentGlobalColors).map((key) => {
+ return {
+ value: key,
+ label: currentGlobalColors[key].name,
+ };
+ });
+ setSettings(newSettings);
+ });
+
+ // Listen on value change and update select settings with current global colors
+ window.wp.customize.control(changesOn, (customizeControl) => {
+ customizeControl.setting.bind('changed', (nextValue) => {
+ const currentGlobalColors = nextValue.palettes;
+ const newSettings = Object.keys(currentGlobalColors).map(
+ (key) => {
+ return {
+ value: key,
+ label: currentGlobalColors[key].name,
+ };
+ }
+ );
+ if (JSON.stringify(settings) !== JSON.stringify(newSettings)) {
+ setSettings(newSettings);
+
+ const currentValue = newSettings.find(
+ (item) => item.value === value
+ );
+ if (currentValue === undefined) {
+ updateValue('darkMode');
+ }
+ }
+ });
+ });
+ }, [settings, value]);
const updateValue = (newValue) => {
setValue(newValue);
diff --git a/inc/customizer/controls/react/src/non-responsive-spacing/NRSpacingComponent.js b/inc/customizer/controls/react/src/non-responsive-spacing/NRSpacingComponent.js
index f0f21e19eb..cbf68508d6 100755
--- a/inc/customizer/controls/react/src/non-responsive-spacing/NRSpacingComponent.js
+++ b/inc/customizer/controls/react/src/non-responsive-spacing/NRSpacingComponent.js
@@ -28,7 +28,7 @@ const NRSpacingComponent = ({ control }) => {
// Used for outside value changes.
useEffect(() => {
- global.addEventListener('neve-changed-customizer-value', (e) => {
+ document.addEventListener('neve-changed-customizer-value', (e) => {
if (!e.detail) return false;
if (e.detail.id !== control.id) return false;
diff --git a/inc/customizer/controls/react/src/ordering/Ordering.js b/inc/customizer/controls/react/src/ordering/Ordering.js
index 9a2f689bbb..1d2c368a3e 100644
--- a/inc/customizer/controls/react/src/ordering/Ordering.js
+++ b/inc/customizer/controls/react/src/ordering/Ordering.js
@@ -81,6 +81,7 @@ const Ordering = ({
label,
value,
allowsToggle = true,
+ orderHeaderElements,
}) => {
const disabled = Object.keys(components).filter(
(item) => !value.includes(item)
@@ -124,6 +125,7 @@ const Ordering = ({
slug={slug}
onToggle={handleToggle}
allowsToggle={allowsToggle}
+ orderHeaderElements={orderHeaderElements}
/>
)
)}
diff --git a/inc/customizer/controls/react/src/ordering/OrderingComponent.js b/inc/customizer/controls/react/src/ordering/OrderingComponent.js
index 43269b6bce..79d8fb8929 100644
--- a/inc/customizer/controls/react/src/ordering/OrderingComponent.js
+++ b/inc/customizer/controls/react/src/ordering/OrderingComponent.js
@@ -1,27 +1,112 @@
/* jshint esversion: 6 */
-import Ordering from './Ordering';
-import PropTypes from 'prop-types';
+// import Ordering from './Ordering';
+import { lazy, Suspense, useEffect, useState } from '@wordpress/element';
+import { Spinner } from '@wordpress/components';
import { maybeParseJson } from '../common/common';
-import { useState } from '@wordpress/element';
+import PropTypes from 'prop-types';
+
+const Ordering = lazy(() =>
+ import(/* webpackChunkName: "ordering" */ './Ordering')
+);
+import { __ } from '@wordpress/i18n';
const OrderingComponent = ({ control }) => {
const [value, setValue] = useState(maybeParseJson(control.setting.get()));
-
- const { components, label } = control.params;
-
+ const [isVisible, setVisible] = useState(false);
+ const { section, components, label } = control.params;
const updateValue = (newVal) => {
setValue(newVal);
control.setting.set(JSON.stringify(newVal));
};
+ useEffect(() => {
+ window.wp.customize.bind('ready', () => {
+ window.wp.customize
+ .state('expandedSection')
+ .bind((expandedSection) => {
+ if (!expandedSection) {
+ return;
+ }
+
+ if (!expandedSection.id) {
+ return;
+ }
+
+ if (expandedSection.id === section) {
+ setVisible(true);
+ }
+ });
+ });
+ }, []);
+
+ useEffect(() => {
+ if (control.id !== 'neve_layout_single_post_elements_order') {
+ return;
+ }
+
+ window.wp.customize.control(
+ 'neve_post_header_layout',
+ (customizeControl) => {
+ customizeControl.setting.bind((nextVal) => {
+ let newVal = maybeParseJson(control.setting.get());
+ let titleMetaIndex, thumbnailIndex;
+ switch (nextVal) {
+ case 'cover':
+ titleMetaIndex = newVal.indexOf('title-meta');
+ if (titleMetaIndex !== -1) {
+ newVal.splice(titleMetaIndex, 1);
+ }
+
+ thumbnailIndex = newVal.indexOf('thumbnail');
+ if (thumbnailIndex !== -1) {
+ newVal.splice(thumbnailIndex, 1);
+ }
+
+ delete components['title-meta'];
+ delete components.thumbnail;
+ break;
+ case 'normal':
+ if (!components['title-meta']) {
+ components['title-meta'] = __(
+ 'Title & Meta',
+ 'neve'
+ );
+ }
+ if (!components.thumbnail) {
+ components.thumbnail = __('Thumbnail', 'neve');
+ }
+
+ thumbnailIndex = newVal.indexOf('thumbnail');
+ if (thumbnailIndex === -1) {
+ newVal = ['thumbnail'].concat(newVal);
+ }
+
+ titleMetaIndex = newVal.indexOf('title-meta');
+ if (titleMetaIndex === -1) {
+ newVal = ['title-meta'].concat(newVal);
+ }
+
+ break;
+ }
+
+ updateValue(newVal);
+ });
+ }
+ );
+ }, []);
+
return (
-
+ }>
+ {isVisible && (
+
+ )}
+
);
};
diff --git a/inc/customizer/controls/react/src/presets-selector/Control.js b/inc/customizer/controls/react/src/presets-selector/Control.js
index 65f0638a4a..d2a600e998 100644
--- a/inc/customizer/controls/react/src/presets-selector/Control.js
+++ b/inc/customizer/controls/react/src/presets-selector/Control.js
@@ -1,6 +1,6 @@
/* jshint esversion: 6 */
-import PresetsSelectorComponent from './PresetsSelectorComponent';
+import PresetsSelectorComponent from './PresetsSelectorComponent.tsx';
import { render } from '@wordpress/element';
export const PresetsSelectorControl = wp.customize.Control.extend({
renderContent: function renderContent() {
diff --git a/inc/customizer/controls/react/src/presets-selector/PresetsSelector.js b/inc/customizer/controls/react/src/presets-selector/PresetsSelector.tsx
similarity index 64%
rename from inc/customizer/controls/react/src/presets-selector/PresetsSelector.js
rename to inc/customizer/controls/react/src/presets-selector/PresetsSelector.tsx
index e55193e55b..71fa32beb6 100755
--- a/inc/customizer/controls/react/src/presets-selector/PresetsSelector.js
+++ b/inc/customizer/controls/react/src/presets-selector/PresetsSelector.tsx
@@ -1,13 +1,18 @@
/* jshint esversion: 6 */
-import PropTypes from 'prop-types';
-
import { useState } from '@wordpress/element';
import { Tooltip } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
+import React from 'react';
-const PresetsSelector = ({ presets, onSelect }) => {
- const [search, setSearch] = useState('');
+type Preset = { image: string; label: string; setup: string };
+
+type Props = {
+ presets: Preset[];
+ onSelect: (data: string) => void;
+};
+const PresetsSelector: React.FC = ({ presets, onSelect }) => {
+ const [search, setSearch] = useState('');
/**
* You can get the value in the console with this command:
*
@@ -15,7 +20,7 @@ const PresetsSelector = ({ presets, onSelect }) => {
*
* @return {*}
*/
- const filteredPresets = presets.filter((preset) =>
+ const filtered = presets.filter((preset) =>
preset.label.toLowerCase().includes(search.toLowerCase())
);
@@ -30,17 +35,18 @@ const PresetsSelector = ({ presets, onSelect }) => {
}}
/>
- {filteredPresets.length > 0 ? (
- filteredPresets.map((preset, index) => {
+ {filtered.length > 0 ? (
+ filtered.map((preset, index) => {
+ const { image, label, setup } = preset;
return (
-
+
{
e.preventDefault();
- onSelect(preset.setup);
+ onSelect(setup);
}}
>
-
+
);
@@ -52,9 +58,4 @@ const PresetsSelector = ({ presets, onSelect }) => {
);
};
-PresetsSelector.propTypes = {
- presets: PropTypes.array.isRequired,
- onSelect: PropTypes.func.isRequired,
-};
-
export default PresetsSelector;
diff --git a/inc/customizer/controls/react/src/presets-selector/PresetsSelectorComponent.js b/inc/customizer/controls/react/src/presets-selector/PresetsSelectorComponent.js
deleted file mode 100644
index d2baf2bef4..0000000000
--- a/inc/customizer/controls/react/src/presets-selector/PresetsSelectorComponent.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* global CustomEvent */
-import PropTypes from 'prop-types';
-
-import PresetsSelector from './PresetsSelector';
-import { maybeParseJson } from '../common/common';
-
-const PresetsSelectorComponent = ({ control }) => {
- const { presets } = control.params;
-
- const handleClick = (setup) => {
- setup = maybeParseJson(setup);
- if (typeof NeveProReactCustomize === 'undefined') {
- Object.keys(setup).map((themeMod) => {
- if (themeMod === 'hfg_header_layout') {
- wp.customize.control(themeMod).setting.set(setup[themeMod]);
- document.dispatchEvent(
- new CustomEvent('neve-changed-builder-value', {
- detail: {
- value: maybeParseJson(setup[themeMod]),
- id: 'header',
- },
- })
- );
- return false;
- }
- if (!wp.customize.control(themeMod)) return false;
- if (
- ['text', 'textarea', 'select'].includes(
- wp.customize.control(themeMod).params.type
- )
- ) {
- wp.customize.control(themeMod).setting.set(setup[themeMod]);
- return false;
- }
-
- document.dispatchEvent(
- new CustomEvent('neve-changed-customizer-value', {
- detail: {
- value: setup[themeMod] || '',
- id: themeMod,
- },
- })
- );
- return false;
- });
- return false;
- }
- document.dispatchEvent(
- new CustomEvent('neve-preset-changed', {
- detail: {
- themeMods: setup,
- },
- })
- );
- };
-
- return ;
-};
-
-PresetsSelectorComponent.propTypes = {
- control: PropTypes.object.isRequired,
-};
-
-export default PresetsSelectorComponent;
diff --git a/inc/customizer/controls/react/src/presets-selector/PresetsSelectorComponent.tsx b/inc/customizer/controls/react/src/presets-selector/PresetsSelectorComponent.tsx
new file mode 100644
index 0000000000..66c243edf5
--- /dev/null
+++ b/inc/customizer/controls/react/src/presets-selector/PresetsSelectorComponent.tsx
@@ -0,0 +1,55 @@
+/* global CustomEvent */
+import PresetsSelector from './PresetsSelector';
+import { maybeParseJson } from '../builder/common/utils';
+import React from 'react';
+import { WPCustomizeControl } from '../@types/customizer-control';
+
+type Props = {
+ control: WPCustomizeControl;
+};
+
+const PresetsSelectorComponent: React.FC = ({ control }) => {
+ const { presets, builder } = control.params;
+
+ const handleClick = (setup: string) => {
+ const parsedSetup = maybeParseJson(setup) as Record;
+ Object.keys(parsedSetup).forEach((themeMod) => {
+ if (themeMod === builder) {
+ document.dispatchEvent(
+ new CustomEvent('neve-changed-builder-value', {
+ detail: {
+ value: parsedSetup[themeMod],
+ id: 'header',
+ },
+ })
+ );
+
+ return false;
+ }
+
+ const controlInstance = window.wp.customize.control(themeMod);
+
+ if (!controlInstance) return false;
+
+ const { type: controlType } = controlInstance.params;
+
+ if (['text', 'textarea', 'select'].includes(controlType)) {
+ controlInstance.setting.set(parsedSetup[themeMod]);
+ return false;
+ }
+
+ document.dispatchEvent(
+ new CustomEvent('neve-changed-customizer-value', {
+ detail: {
+ value: parsedSetup[themeMod] || '',
+ id: themeMod,
+ },
+ })
+ );
+ });
+ };
+
+ return ;
+};
+
+export default PresetsSelectorComponent;
diff --git a/inc/customizer/controls/react/src/public-path.js b/inc/customizer/controls/react/src/public-path.js
new file mode 100644
index 0000000000..0593eb00ec
--- /dev/null
+++ b/inc/customizer/controls/react/src/public-path.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line camelcase,no-undef
+__webpack_public_path__ = window.NeveReactCustomize.bundlePath;
diff --git a/inc/customizer/controls/react/src/radio-buttons/RadioButtonsComponent.js b/inc/customizer/controls/react/src/radio-buttons/RadioButtonsComponent.js
index b348ae0fb7..2cfcf5981c 100644
--- a/inc/customizer/controls/react/src/radio-buttons/RadioButtonsComponent.js
+++ b/inc/customizer/controls/react/src/radio-buttons/RadioButtonsComponent.js
@@ -11,7 +11,7 @@ const RadioButtonsComponent = ({ control }) => {
const [value, setValue] = useState(control.setting.get());
useEffect(() => {
- global.addEventListener('neve-changed-customizer-value', (e) => {
+ document.addEventListener('neve-changed-customizer-value', (e) => {
if (!e.detail) return false;
if (e.detail.id !== control.id) return false;
updateValue(e.detail.value);
@@ -131,6 +131,25 @@ const RadioButtonsComponent = ({ control }) => {
icon: SVG.cartIconStyle6,
},
};
+ case 'palette_switch':
+ return {
+ contrast: {
+ tooltip: __('Contrast', 'neve'),
+ icon: SVG.paletteIconStyle1,
+ },
+ night: {
+ tooltip: __('Night', 'neve'),
+ icon: SVG.paletteIconStyle2,
+ },
+ toggle: {
+ tooltip: __('Toggle', 'neve'),
+ icon: SVG.paletteIconStyle3,
+ },
+ accessibility: {
+ tooltip: __('Accessibility', 'neve'),
+ icon: SVG.paletteIconStyle4,
+ },
+ };
}
};
const { label, large_buttons, showLabels } = control.params;
diff --git a/inc/customizer/controls/react/src/radio-image/RadioImage.js b/inc/customizer/controls/react/src/radio-image/RadioImage.js
index 05defe42cf..a908b7f966 100644
--- a/inc/customizer/controls/react/src/radio-image/RadioImage.js
+++ b/inc/customizer/controls/react/src/radio-image/RadioImage.js
@@ -1,7 +1,35 @@
import classnames from 'classnames';
import PropTypes from 'prop-types';
+import { ExternalLink } from '@wordpress/components';
+
+const RadioImage = ({ choices, onClick, value, label, documentation }) => {
+ const renderDocumentation = () => {
+ if (typeof window.NeveProReactCustomize !== 'undefined') {
+ const { whiteLabel } = window.NeveProReactCustomize;
+
+ if (whiteLabel) {
+ return null;
+ }
+ }
+
+ if (!documentation) {
+ return null;
+ }
+
+ if (!documentation.link || !documentation.label) {
+ return null;
+ }
+
+ return (
+
+ {documentation.label}
+
+ );
+ };
-const RadioImage = ({ choices, onClick, value, label }) => {
return (
<>
{label && (
@@ -36,6 +64,8 @@ const RadioImage = ({ choices, onClick, value, label }) => {
);
})}
+
+ {renderDocumentation()}
>
);
};
diff --git a/inc/customizer/controls/react/src/radio-image/RadioImageComponent.js b/inc/customizer/controls/react/src/radio-image/RadioImageComponent.js
index 657f3588be..11928fb700 100644
--- a/inc/customizer/controls/react/src/radio-image/RadioImageComponent.js
+++ b/inc/customizer/controls/react/src/radio-image/RadioImageComponent.js
@@ -5,7 +5,7 @@ import { useState } from '@wordpress/element';
const RadioImageComponent = ({ control }) => {
const [value, setValue] = useState(control.setting.get());
- const { choices, label } = control.params;
+ const { choices, label, documentation } = control.params;
const updateValue = (newVal) => {
setValue(newVal);
@@ -15,6 +15,7 @@ const RadioImageComponent = ({ control }) => {
return (
', '' ), + 'type' => 'hidden', + 'default' => '', + ] + ); + + SettingsManager::get_instance()->add( + [ + 'id' => self::DARK_PALETTE_ID, + 'group' => $this->get_id(), + 'tab' => SettingsManager::TAB_GENERAL, + 'transport' => 'refresh', + 'sanitize_callback' => 'wp_filter_nohtml_kses', + 'label' => __( 'Dark Palette', 'neve' ), + 'description' => __( 'Dark Palette', 'neve' ), + 'type' => 'Neve\Customizer\Controls\React\Inline_Select', + 'default' => $dark_palette_default, + 'options' => [ + 'options' => $available_palettes, + 'default' => $dark_palette_default, + 'changes_on' => 'neve_global_colors', + ], + 'section' => $this->section, + ] + ); + + SettingsManager::get_instance()->add( + [ + 'id' => self::TOGGLE_ICON_ID, + 'group' => $this->get_id(), + 'tab' => SettingsManager::TAB_GENERAL, + 'transport' => 'post' . $this->get_class_const( 'COMPONENT_ID' ), + 'sanitize_callback' => 'wp_filter_nohtml_kses', + 'label' => __( 'Select icon', 'neve' ), + 'description' => __( 'Select icon', 'neve' ), + 'type' => 'Neve\Customizer\Controls\React\Radio_Buttons', + 'default' => 'contrast', + 'options' => [ + 'is_for' => 'palette_switch', + ], + 'section' => $this->section, + ] + ); + + $default_size_values = [ + 'mobile' => self::DEFAULT_ICON_SIZE, + 'tablet' => self::DEFAULT_ICON_SIZE, + 'desktop' => self::DEFAULT_ICON_SIZE, + ]; + + SettingsManager::get_instance()->add( + [ + 'id' => self::SIZE_ID, + 'group' => $this->get_id(), + 'tab' => SettingsManager::TAB_GENERAL, + 'transport' => 'post' . $this->get_class_const( 'COMPONENT_ID' ), + 'sanitize_callback' => array( $this, 'sanitize_responsive_int_json' ), + 'label' => __( 'Icon Size', 'neve' ), + 'type' => 'Neve\Customizer\Controls\React\Responsive_Range', + 'default' => $default_size_values, + 'options' => [ + 'input_attrs' => [ + 'step' => 1, + 'min' => 8, + 'max' => 120, + 'defaultVal' => $default_size_values, + 'units' => [ 'px' ], + ], + ], + 'live_refresh_selector' => $this->default_selector . ' div.component-wrap .palette-icon-wrapper svg', + 'live_refresh_css_prop' => array( + 'cssVar' => [ + 'vars' => '--iconSize', + 'responsive' => true, + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], + 'type' => 'svg-icon-size', + 'default' => self::DEFAULT_ICON_SIZE, + ), + 'section' => $this->section, + 'conditional_header' => true, + ] + ); + + SettingsManager::get_instance()->add( + [ + 'id' => self::PLACEHOLDER_ID, + 'group' => $this->get_class_const( 'COMPONENT_ID' ), + 'tab' => SettingsManager::TAB_GENERAL, + 'transport' => 'post' . $this->get_class_const( 'COMPONENT_ID' ), + 'sanitize_callback' => 'wp_filter_nohtml_kses', + 'default' => '', + 'label' => __( 'Label', 'neve' ), + 'options' => [ + 'input_attrs' => array( + 'placeholder' => __( 'Leave blank for no label ...', 'neve' ), + ), + ], + 'type' => 'text', + 'section' => $this->section, + 'conditional_header' => true, + ] + ); + + SettingsManager::get_instance()->add( + [ + 'id' => self::AUTO_ADJUST, + 'group' => $this->get_class_const( 'COMPONENT_ID' ), + 'tab' => SettingsManager::TAB_GENERAL, + 'transport' => 'refresh', + 'sanitize_callback' => 'absint', + 'default' => 0, + 'label' => __( 'Automatically adjust color scheme', 'neve' ), + /* translators: %s: Link to Learn More page. */ + 'description' => sprintf( __( 'Adjust default color scheme based on the user device preferences. %1$sLearn More%2$s.', 'neve' ), '', '(opens in a new tab)' ), + 'type' => 'neve_toggle_control', + 'section' => $this->section, + 'conditional_header' => true, + ] + ); + } + + /** + * Method to add Component css styles. + * + * @param array $css_array An array containing css rules. + * + * @return array + * @since 1.0.0 + * @access public + */ + public function add_style( array $css_array = array() ) { + if ( neve_is_new_skin() ) { + $css_array[] = [ + Dynamic_Selector::KEY_SELECTOR => '.builder-item--' . $this->get_id(), + Dynamic_Selector::KEY_RULES => [ + '--iconSize' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::SIZE_ID, + Dynamic_Selector::META_DEFAULT => '{ "mobile": "' . self::DEFAULT_ICON_SIZE . '", "tablet": "' . self::DEFAULT_ICON_SIZE . '", "desktop": "' . self::DEFAULT_ICON_SIZE . '" }', + Dynamic_Selector::META_SUFFIX => 'px', + Dynamic_Selector::META_IS_RESPONSIVE => true, + ], + ], + ]; + + return parent::add_style( $css_array ); + } + + $selector = '.builder-item--' . $this->get_id() . ' .toggle-palette a.toggle span.icon'; + + $css_array[] = [ + Dynamic_Selector::KEY_SELECTOR => $selector, + Dynamic_Selector::KEY_RULES => [ + Config::CSS_PROP_WIDTH => [ + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::SIZE_ID, + Dynamic_Selector::META_DEFAULT => '{ "mobile": "' . self::DEFAULT_ICON_SIZE . '", "tablet": "' . self::DEFAULT_ICON_SIZE . '", "desktop": "' . self::DEFAULT_ICON_SIZE . '" }', + ], + Config::CSS_PROP_HEIGHT => [ + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::SIZE_ID, + Dynamic_Selector::META_DEFAULT => '{ "mobile": "' . self::DEFAULT_ICON_SIZE . '", "tablet": "' . self::DEFAULT_ICON_SIZE . '", "desktop": "' . self::DEFAULT_ICON_SIZE . '" }', + ], + ], + ]; + + return parent::add_style( $css_array ); + } + + /** + * Render the component + * + * @return void + */ + public function render_component() { + Main::get_instance()->load( 'components/component-palette-switch' ); + } + + +} diff --git a/header-footer-grid/Core/Components/Search.php b/header-footer-grid/Core/Components/Search.php index 04f30bffe9..bb399ac296 100644 --- a/header-footer-grid/Core/Components/Search.php +++ b/header-footer-grid/Core/Components/Search.php @@ -81,6 +81,12 @@ public function add_settings() { 'type' => '\Neve\Customizer\Controls\React\Responsive_Range', 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--height', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'template' => 'body ' . $this->default_selector . ' input[type=search] { @@ -121,6 +127,12 @@ public function add_settings() { 'type' => '\Neve\Customizer\Controls\React\Responsive_Range', 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--formFieldFontSize', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'template' => 'body ' . $this->default_selector . ' input[type=search] { @@ -159,28 +171,26 @@ public function add_settings() { ] ); + $new_skin = neve_is_new_skin(); + + $per_device = $new_skin ? [ + 'top' => 2, + 'right' => 2, + 'bottom' => 2, + 'left' => 2, + ] : [ + 'top' => 1, + 'right' => 1, + 'bottom' => 1, + 'left' => 1, + ]; $default_border_width = [ 'desktop-unit' => 'px', 'tablet-unit' => 'px', 'mobile-unit' => 'px', - 'desktop' => [ - 'top' => 1, - 'right' => 1, - 'bottom' => 1, - 'left' => 1, - ], - 'tablet' => [ - 'top' => 1, - 'right' => 1, - 'bottom' => 1, - 'left' => 1, - ], - 'mobile' => [ - 'top' => 1, - 'right' => 1, - 'bottom' => 1, - 'left' => 1, - ], + 'desktop' => $per_device, + 'tablet' => $per_device, + 'mobile' => $per_device, ]; SettingsManager::get_instance()->add( @@ -203,6 +213,12 @@ public function add_settings() { ], 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--formFieldBorderWidth', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'directional' => true, 'template' => @@ -260,6 +276,12 @@ public function add_settings() { ], 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--formFieldBorderRadius', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'directional' => true, 'template' => @@ -286,6 +308,10 @@ public function add_settings() { 'section' => $this->section, 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--formFieldBgColor', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' input[type=search] { background-color: {{value}} !important; @@ -308,6 +334,13 @@ public function add_settings() { 'section' => $this->section, 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => [ + '--formFieldColor', + '--formFieldBorderColor', + ], + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' input[type=search], body ' . $this->default_selector . ' input::placeholder { color: {{value}}; @@ -325,15 +358,13 @@ public function add_settings() { } /** - * Method to add Component css styles. + * Add legacy style. * - * @param array $css_array An array containing css rules. + * @param array $css_array css array. * * @return array - * @since 1.0.0 - * @access public */ - public function add_style( array $css_array = array() ) { + private function add_legacy_style( $css_array ) { $css_array[] = [ Dynamic_Selector::KEY_SELECTOR => $this->default_selector . ' input[type=submit],' . $this->default_selector . ' .nv-search-icon-wrap', Dynamic_Selector::KEY_RULES => [ @@ -372,6 +403,7 @@ public function add_style( array $css_array = array() ) { if ( ! empty( $fs ) ) { $style = sprintf( 'padding-right:%spx;', $padding ); } + return $style; }, ], @@ -432,6 +464,68 @@ public function add_style( array $css_array = array() ) { return parent::add_style( $css_array ); } + /** + * Method to add Component css styles. + * + * @param array $css_array An array containing css rules. + * + * @return array + * @since 1.0.0 + * @access public + */ + public function add_style( array $css_array = array() ) { + if ( ! neve_is_new_skin() ) { + return $this->add_legacy_style( $css_array ); + } + + $rules = [ + '--height' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_HEIGHT, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_SUFFIX => 'px', + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_HEIGHT ), + ], + '--formFieldFontSize' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_FONT_SIZE, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_SUFFIX => 'px', + ], + '--formFieldBorderWidth' => [ + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_BORDER_WIDTH, + Dynamic_Selector::META_SUFFIX => 'px', + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_BORDER_WIDTH ), + 'directional-prop' => Config::CSS_PROP_BORDER_WIDTH, + ], + '--formFieldBorderRadius' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_BORDER_RADIUS, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_BORDER_RADIUS ), + 'directional-prop' => Config::CSS_PROP_BORDER_RADIUS, + ], + '--formFieldBgColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_BG, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_BG ), + ], + '--formFieldBorderColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_TEXT_COLOR, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_TEXT_COLOR ), + ], + '--formFieldColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_TEXT_COLOR, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_TEXT_COLOR ), + ], + ]; + + $css_array[] = [ + Dynamic_Selector::KEY_SELECTOR => '.builder-item--' . $this->get_id(), + Dynamic_Selector::KEY_RULES => $rules, + ]; + + + return parent::add_style( $css_array ); + } + /** * The render method for the component. * @@ -439,30 +533,19 @@ public function add_style( array $css_array = array() ) { * @access public */ public function render_component() { - add_filter( 'get_search_form', [ $this, 'change_placeholder' ] ); + add_filter( 'nv_search_placeholder', [ $this, 'change_placeholder' ] ); Main::get_instance()->load( 'components/component-search' ); - remove_filter( 'get_search_form', [ $this, 'change_placeholder' ] ); + remove_filter( 'nv_search_placeholder', [ $this, 'change_placeholder' ] ); } /** * Change the form placeholder. * - * @param string $form form markup. + * @param string $placeholder placeholder string. * * @return string */ - public function change_placeholder( $form ) { - $form = ''; - $placeholder = get_theme_mod( $this->get_id() . '_placeholder', __( 'Search for...', 'neve' ) ); - - $form .= ''; - - return $form; + public function change_placeholder( $placeholder ) { + return get_theme_mod( $this->get_id() . '_placeholder', __( 'Search for...', 'neve' ) ); } } diff --git a/header-footer-grid/Core/Components/SearchResponsive.php b/header-footer-grid/Core/Components/SearchResponsive.php index ad1f9a5cad..c7bf7e9137 100644 --- a/header-footer-grid/Core/Components/SearchResponsive.php +++ b/header-footer-grid/Core/Components/SearchResponsive.php @@ -162,6 +162,11 @@ public function add_settings() { ], 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--iconSize', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' a.nv-search.nv-icon > svg { width: {{value}}px; @@ -186,6 +191,10 @@ public function add_settings() { 'section' => $this->section, 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--color', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' a.nv-search.nv-icon > svg { fill: {{value}}; @@ -207,6 +216,10 @@ public function add_settings() { 'section' => $this->section, 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--hoverColor', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' a.nv-search.nv-icon:hover > svg { fill: {{value}}; @@ -245,6 +258,12 @@ public function add_settings() { 'type' => '\Neve\Customizer\Controls\React\Responsive_Range', 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--height', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'template' => 'body ' . $this->default_selector . ' .nv-nav-search .search-form input[type=search] { @@ -285,6 +304,12 @@ public function add_settings() { 'type' => '\Neve\Customizer\Controls\React\Responsive_Range', 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--formFieldFontSize', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'template' => 'body ' . $this->default_selector . ' .nv-nav-search .search-form input[type=search] { @@ -323,28 +348,26 @@ public function add_settings() { ] ); + $new_skin = neve_is_new_skin(); + $per_device = $new_skin ? [ + 'top' => 2, + 'right' => 2, + 'bottom' => 2, + 'left' => 2, + ] : [ + 'top' => 1, + 'right' => 1, + 'bottom' => 1, + 'left' => 1, + ]; + $default_border_width = [ 'desktop-unit' => 'px', 'tablet-unit' => 'px', 'mobile-unit' => 'px', - 'desktop' => [ - 'top' => 1, - 'right' => 1, - 'bottom' => 1, - 'left' => 1, - ], - 'tablet' => [ - 'top' => 1, - 'right' => 1, - 'bottom' => 1, - 'left' => 1, - ], - 'mobile' => [ - 'top' => 1, - 'right' => 1, - 'bottom' => 1, - 'left' => 1, - ], + 'desktop' => $per_device, + 'tablet' => $per_device, + 'mobile' => $per_device, ]; SettingsManager::get_instance()->add( @@ -367,6 +390,12 @@ public function add_settings() { ], 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--formFieldBorderWidth', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'directional' => true, 'template' => @@ -424,6 +453,12 @@ public function add_settings() { ], 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'responsive' => true, + 'vars' => '--formFieldBorderRadius', + 'suffix' => 'px', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'responsive' => true, 'directional' => true, 'template' => @@ -450,6 +485,10 @@ public function add_settings() { 'section' => $this->section, 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--formFieldBgColor', + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' .nv-nav-search .search-form input[type=search] { background-color: {{value}}; @@ -472,6 +511,13 @@ public function add_settings() { 'section' => $this->section, 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => [ + '--formFieldColor', + '--formFieldBorderColor', + ], + 'selector' => '.builder-item--' . $this->get_id(), + ], 'template' => 'body ' . $this->default_selector . ' .nv-nav-search .search-form input[type=search], body ' . $this->default_selector . ' input::placeholder { color: {{value}}; @@ -488,20 +534,15 @@ public function add_settings() { ); } + /** - * Method to add Component css styles. + * Add legacy style. * - * @param array $css_array An array containing css rules. + * @param array $css_array css array. * * @return array - * @since 1.0.0 - * @access public */ - public function add_style( array $css_array = array() ) { - if ( is_admin_bar_showing() ) { - wp_add_inline_style( 'neve-style', 'body.admin-bar .floating .nv-nav-search {margin-top: 32px;}' ); - } - + private function add_legacy_style( $css_array ) { $css_array[] = [ Dynamic_Selector::KEY_SELECTOR => $this->default_selector . ' a.nv-search.nv-icon > svg', Dynamic_Selector::KEY_RULES => [ @@ -568,6 +609,7 @@ public function add_style( array $css_array = array() ) { if ( ! empty( $fs ) ) { $style = sprintf( 'padding-right:%spx;', $padding ); } + return $style; }, ], @@ -646,6 +688,85 @@ public function add_style( array $css_array = array() ) { return parent::add_style( $css_array ); } + /** + * Method to add Component css styles. + * + * @param array $css_array An array containing css rules. + * + * @return array + * @since 1.0.0 + * @access public + */ + public function add_style( array $css_array = array() ) { + if ( is_admin_bar_showing() ) { + wp_add_inline_style( 'neve-style', 'body.admin-bar .floating .nv-nav-search {margin-top: 32px;}' ); + } + + if ( ! neve_is_new_skin() ) { + return $this->add_legacy_style( $css_array ); + } + + + $rules = [ + '--iconSize' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::SIZE_ID, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::SIZE_ID ), + Dynamic_Selector::META_SUFFIX => 'px', + ], + '--color' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::COLOR_ID, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::COLOR_ID ), + ], + '--hoverColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::HOVER_COLOR_ID, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::HOVER_COLOR_ID ), + ], + '--formFieldFontSize' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_FONT_SIZE, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_SUFFIX => 'px', + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_FONT_SIZE ), + ], + '--formFieldBorderWidth' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_BORDER_WIDTH, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_BORDER_WIDTH ), + 'directional-prop' => Config::CSS_PROP_BORDER_WIDTH, + ], + '--formFieldBorderRadius' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_BORDER_RADIUS, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_BORDER_RADIUS ), + 'directional-prop' => Config::CSS_PROP_BORDER_RADIUS, + ], + '--formFieldBgColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_BG, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_BG ), + ], + '--formFieldBorderColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_TEXT_COLOR, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_TEXT_COLOR ), + ], + '--formFieldColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_TEXT_COLOR, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_TEXT_COLOR ), + ], + '--height' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::FIELD_HEIGHT, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::FIELD_HEIGHT ), + Dynamic_Selector::META_SUFFIX => 'px', + ], + ]; + + $css_array[] = [ + Dynamic_Selector::KEY_SELECTOR => $this->default_selector, + Dynamic_Selector::KEY_RULES => $rules, + ]; + + return parent::add_style( $css_array ); + } + /** * The render method for the component. * @@ -653,30 +774,19 @@ public function add_style( array $css_array = array() ) { * @access public */ public function render_component() { - add_filter( 'get_search_form', [ $this, 'change_placeholder' ] ); + add_filter( 'nv_search_placeholder', [ $this, 'change_placeholder' ] ); Main::get_instance()->load( 'components/component-search-responsive' ); - remove_filter( 'get_search_form', [ $this, 'change_placeholder' ] ); + remove_filter( 'nv_search_placeholder', [ $this, 'change_placeholder' ] ); } /** * Change the form placeholder. * - * @param string $form form markup. + * @param string $placeholder placeholder string. * * @return string */ - public function change_placeholder( $form ) { - $form = ''; - $placeholder = get_theme_mod( $this->id . '_placeholder', __( 'Search for...', 'neve' ) ); - - $form .= ''; - - return $form; + public function change_placeholder( $placeholder ) { + return get_theme_mod( $this->id . '_placeholder', __( 'Search for...', 'neve' ) ); } } diff --git a/header-footer-grid/Core/Components/SecondNav.php b/header-footer-grid/Core/Components/SecondNav.php index 23e5d95590..8916147a0d 100644 --- a/header-footer-grid/Core/Components/SecondNav.php +++ b/header-footer-grid/Core/Components/SecondNav.php @@ -82,13 +82,17 @@ public function add_settings() { 'tab' => SettingsManager::TAB_STYLE, 'transport' => 'postMessage', 'sanitize_callback' => 'neve_sanitize_colors', - 'default' => 'var(--nv-text-color)', + 'default' => neve_is_new_skin() ? '' : 'var(--nv-text-color)', 'label' => __( 'Items Color', 'neve' ), 'type' => 'neve_color_control', 'section' => $this->section, 'conditional_header' => $this->get_builder_id() === 'header', 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--color', + 'selector' => '.builder-item--' . $this->get_id(), + ], [ 'selector' => $this->default_typography_selector, 'prop' => 'color', @@ -112,6 +116,10 @@ public function add_settings() { 'conditional_header' => $this->get_builder_id() === 'header', 'live_refresh_selector' => true, 'live_refresh_css_prop' => [ + 'cssVar' => [ + 'vars' => '--hoverColor', + 'selector' => '.builder-item--' . $this->get_id(), + ], [ 'selector' => $this->default_typography_selector . ':after', 'prop' => 'background-color', @@ -218,6 +226,50 @@ public function render_component() { * @return array */ public function add_style( array $css_array = array() ) { + if ( ! neve_is_new_skin() ) { + return $this->add_legacy_style( $css_array ); + } + + $rules = [ + '--color' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::COLOR_ID, + ], + '--hoverColor' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::HOVER_COLOR_ID, + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::HOVER_COLOR_ID ), + ], + '--spacing' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::SPACING, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_SUFFIX => 'px', + Dynamic_Selector::META_DEFAULT => SettingsManager::get_instance()->get_default( $this->get_id() . '_' . self::SPACING ), + ], + '--height' => [ + Dynamic_Selector::META_KEY => $this->get_id() . '_' . self::ITEM_HEIGHT, + Dynamic_Selector::META_IS_RESPONSIVE => true, + Dynamic_Selector::META_SUFFIX => 'px', + Dynamic_Selector::META_DEFAULT => $this->get_default_for_responsive_from_intval( self::ITEM_HEIGHT, 25 ), + ], + ]; + + $css_array[] = [ + Dynamic_Selector::KEY_SELECTOR => '.builder-item--' . $this->get_id(), + Dynamic_Selector::KEY_RULES => $rules, + ]; + + return parent::add_style( $css_array ); + } + + + /** + * Add legacy style. + * + * @param array $css_array the styles css array. + * + * @return array + */ + private function add_legacy_style( array $css_array ) { + $css_array[] = [ Dynamic_Selector::KEY_SELECTOR => '.builder-item--' . $this->get_id() . ' .nav-ul#secondary-menu li > a', Dynamic_Selector::KEY_RULES => [ @@ -272,7 +324,8 @@ public function add_style( array $css_array = array() ) { return ''; } $value = absint( $value ); - return sprintf( 'left:%s;right:%s', -$value / 2 . 'px', -$value / 2 . 'px' ); + + return sprintf( 'left:%s;right:%s', - $value / 2 . 'px', - $value / 2 . 'px' ); }, Dynamic_Selector::META_DEFAULT => $this->get_default_for_responsive_from_intval( self::SPACING, 20 ), ], @@ -304,7 +357,6 @@ public function add_style( array $css_array = array() ) { ], ]; - return parent::add_style( $css_array ); } } diff --git a/header-footer-grid/Core/Customizer.php b/header-footer-grid/Core/Customizer.php index 6036b6f7e7..83494e2c66 100644 --- a/header-footer-grid/Core/Customizer.php +++ b/header-footer-grid/Core/Customizer.php @@ -66,7 +66,7 @@ public function __construct() { } } - if ( is_admin() ) { + if ( ! neve_is_new_builder() ) { add_action( 'customize_controls_enqueue_scripts', array( $this, 'scripts' ) ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'template' ) ); } @@ -77,7 +77,7 @@ public function __construct() { } add_filter( 'body_class', array( $this, 'hfg_body_classes' ) ); - add_filter( 'neve_react_controls_localization', array( $this, 'add_dynamic_tags_options' ) ); + add_filter( 'neve_react_controls_localization', array( $this, 'add_builders_and_dynamic_tags' ) ); } /** @@ -85,9 +85,10 @@ public function __construct() { * * @param array $array the localized array. * - * @return mixed + * @return array */ - public function add_dynamic_tags_options( $array ) { + public function add_builders_and_dynamic_tags( $array ) { + $array['HFG'] = $this->get_builders_data(); $array['dynamicTags']['options'] = Magic_Tags::get_instance()->get_options(); return $array; @@ -242,10 +243,10 @@ public function register( WP_Customize_Manager $wp_customize ) { $builder->customize_register( $wp_customize ); } - $wp_customize->register_section_type( '\HFG\Core\Customizer\Instructions_Section' ); - $wp_customize->register_control_type( '\HFG\Core\Customizer\Instructions_Control' ); + $wp_customize->register_section_type( '\Neve\Customizer\Controls\React\Instructions_Section' ); } + /** * The Customizer templates. * @@ -265,21 +266,21 @@ public function template() {
- ' . __( 'Default', 'neve' ) . ' ' ) ); - ?> -
+ ?> +- + ?>