diff --git a/assets/apps/components/src/Controls/ColorControl.js b/assets/apps/components/src/Controls/ColorControl.js
index 9461310745..98a147ef11 100644
--- a/assets/apps/components/src/Controls/ColorControl.js
+++ b/assets/apps/components/src/Controls/ColorControl.js
@@ -19,6 +19,7 @@ import classnames from 'classnames';
const ColorPickerFix = lazy(() => import('../Common/ColorPickerFix'));
const ColorControl = ({
+ slug = null,
label,
selectedColor,
onChange,
@@ -52,7 +53,8 @@ const ColorControl = ({
};
const isGlobal = selectedColor && selectedColor.indexOf('var') > -1;
- const defaultGradient = 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)';
+ const defaultGradient =
+ 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)';
const handleClear = () => {
onChange(defaultValue || '');
@@ -63,7 +65,10 @@ const ColorControl = ({
const wrapClasses = classnames([
'neve-control-header',
'neve-color-component',
- { 'allows-global': !disableGlobal },
+ {
+ 'allows-global': !disableGlobal,
+ [`neve-color-slug-${slug}`]: !!slug,
+ },
]);
const [gradient, setGradient] = useState(selectedColor);
@@ -159,7 +164,9 @@ const ColorControl = ({
{
);
};
+const initStyleBookButton = () => {
+ const headerContainer = document.getElementById('customize-header-actions');
+
+ if (!headerContainer) {
+ return;
+ }
+
+ // Initialize the Style Book state if it doesn't exist
+ if (!wp.customize.state.has('neveStyleBookOpen')) {
+ wp.customize.state.create('neveStyleBookOpen', false);
+ }
+
+ // Create the Style Book button
+ const button = document.createElement('button');
+ button.name = 'neve-style-book';
+ button.id = 'neve-style-book';
+ button.className = 'button-secondary button';
+ button.title = __('Style Book', 'neve');
+ button.innerHTML = `
+
+
${__('Style Book', 'neve')}
+ `;
+
+ // Add click handler
+ button.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ // Toggle the state in customizer
+ const currentState = wp.customize.state('neveStyleBookOpen').get();
+ const newState = !currentState;
+ wp.customize.state('neveStyleBookOpen').set(newState);
+
+ // Send message to preview
+ wp.customize.previewer.send('neve-toggle-style-book', newState);
+ });
+
+ // Append to header container
+ headerContainer.appendChild(button);
+
+ // Restore state when preview is ready
+ wp.customize.previewer.bind('ready', () => {
+ const currentState = wp.customize.state('neveStyleBookOpen').get();
+ if (currentState) {
+ wp.customize.previewer.send('neve-restore-style-book-state', true);
+ }
+ });
+
+ // Listen for state changes from preview
+ wp.customize.previewer.bind('neve-style-book-state-changed', (newState) => {
+ wp.customize.state('neveStyleBookOpen').set(newState);
+ });
+};
+
const initCustomPagesFocus = () => {
const { sectionsFocus } = window.NeveReactCustomize;
if (sectionsFocus !== undefined) {
@@ -355,6 +408,7 @@ window.wp.customize.bind('ready', () => {
initBlogPageFocus();
initSearchCustomizer();
initLocalGoogleFonts();
+ initStyleBookButton();
previewScrollToTopChanges();
});
diff --git a/assets/apps/customizer-controls/src/global-colors/PaletteColors.js b/assets/apps/customizer-controls/src/global-colors/PaletteColors.js
index e7a7f9dbd8..4e5f9e8195 100644
--- a/assets/apps/customizer-controls/src/global-colors/PaletteColors.js
+++ b/assets/apps/customizer-controls/src/global-colors/PaletteColors.js
@@ -52,6 +52,7 @@ const PaletteColors = ({ values, defaults, save }) => {
{
+ const colorControl =
+ window.parent.document.querySelector(
+ '.' + controlId
+ );
+ if (colorControl) {
+ const colorButton =
+ colorControl.querySelector(
+ '.components-button'
+ );
+ if (colorButton) {
+ colorButton.click();
+ }
+ }
+ }, 100);
+ }
+ return;
+ }
+
+ // Regular controls (accordions, buttons, etc.)
+ const control =
+ window.parent.wp.customize.control(controlId);
+
+ // Try to focus if the control has a focus method
+ if (control && typeof control.focus === 'function') {
+ control.focus();
+ }
+
+ // Handle accordion expansion after focusing
+ setTimeout(() => {
+ const controlElement =
+ window.parent.document.getElementById(
+ 'customize-control-' + controlId
+ );
+ if (controlElement) {
+ // Close all other expanded accordions in the same section
+ const section =
+ controlElement.closest('.control-section');
+ if (section) {
+ section
+ .querySelectorAll(
+ '.customize-control.expanded'
+ )
+ .forEach((accordion) => {
+ if (
+ accordion.id !==
+ 'customize-control-' + controlId
+ ) {
+ accordion.classList.remove(
+ 'expanded'
+ );
+ }
+ });
+ }
+
+ // Expand the target accordion if not already expanded
+ if (
+ !controlElement.classList.contains(
+ 'expanded'
+ )
+ ) {
+ const heading =
+ controlElement.querySelector(
+ '.neve-customizer-heading'
+ );
+ if (heading) {
+ heading.click();
+ }
+ }
+ }
+ }, 100);
+ return;
+ } // If data-section is specified or control focus failed, focus on section
+ if (sectionId) {
+ const section =
+ window.parent.wp.customize.section(sectionId);
+ if (section && typeof section.focus === 'function') {
+ section.focus();
+ }
+ }
+ } catch (error) {
+ // Fallback: Try to expand the section if focusing fails
+ try {
+ if (sectionId) {
+ const section =
+ window.parent.wp.customize.section(sectionId);
+ if (section && section.expanded) {
+ section.expanded(true);
+ }
+ }
+ } catch (fallbackError) {
+ // Silent fallback - navigation failed
+ }
+ }
+ }
+ }
+ });
});
/**
diff --git a/assets/scss/customizer-preview.scss b/assets/scss/customizer-preview.scss
index 6cee8648e7..4feef276b0 100644
--- a/assets/scss/customizer-preview.scss
+++ b/assets/scss/customizer-preview.scss
@@ -1,3 +1,6 @@
+@import "components/main/variables";
+@import "components/main/extends";
+
/* Customize Preview */
.edit-row-action {
top: 0;
@@ -78,9 +81,6 @@
.customize-partial-edit-shortcut {
display: none;
}
-
- .builder-item-focus {
- }
}
.footer--row {
@@ -118,3 +118,489 @@
top:unset !important;
}
}
+
+/* Prevent body scroll when Style Book is open */
+body.nv-sb-open {
+ overflow: hidden;
+}
+
+/* Style Book Modal */
+#nv-sb-container {
+ margin: 0 auto;
+ padding: 40px 20px;
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ z-index: 999999;
+ overflow: scroll;
+ background: gray;
+}
+
+/* Close button in top right */
+.nv-sb-close-btn {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ width: 40px;
+ height: 40px;
+ background: rgba(0, 0, 0, 0.8);
+ border: none;
+ border-radius: 50%;
+ color: white;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000000;
+ transition: background-color 0.2s ease;
+
+ .dashicons {
+ font-size: 20px;
+ width: 20px;
+ height: 20px;
+ color: white;
+ }
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.9);
+ }
+
+ &:focus {
+ outline: 2px solid white;
+ outline-offset: 2px;
+ background: rgba(0, 0, 0, 0.9);
+ }
+}
+
+.nv-sb-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 30px;
+ margin: 0 auto;
+ max-width: 956px;
+ margin-bottom: 30px;
+}
+
+.nv-sb-two-col-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 30px;
+}
+
+.nv-sb-section {
+ background: var(--nv-light-bg);
+ padding: 35px;
+ border-radius: 8px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+}
+
+.nv-sb-section.nv-sb-full-section {
+ padding: 40px;
+}
+
+.nv-sb-section-title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 20px;
+ color: var(--nv-text-color);
+}
+
+/* Style Book - Generic clickable items */
+#nv-sb-container .builder-item-focus {
+ position: relative;
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ &:hover {
+ outline: 1px solid #0073aa;
+ outline-offset: -1px;
+ }
+}
+
+/* Color Palette */
+.nv-sb-color-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px;
+}
+
+.nv-sb-color-swatch {
+ border-radius: 6px;
+ box-shadow: 0px 2px 4px color-mix(in srgb, var(--nv-text-color) 20%, transparent);
+ background: var(--nv-light-bg);
+}
+
+.nv-sb-color-box {
+ height: 70px;
+ border-radius: 6px 6px 0 0;
+}
+
+.nv-sb-color-info {
+ padding: 10px;
+}
+
+.nv-sb-color-name {
+ font-weight: 600;
+ font-size: 0.85rem;
+ margin-bottom: 3px;
+}
+
+/* Typography */
+.nv-sb-typography-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 40px;
+ align-items: start;
+ overflow: hidden; /* Prevent grid overflow */
+}
+
+.nv-sb-type-sample {
+ margin-bottom: 30px;
+ overflow: hidden; /* Prevent heading overflow */
+}
+
+.nv-sb-alphabet {
+ line-height: 1.8;
+ word-wrap: break-word;
+ word-break: break-all; /* Break long character sequences */
+ overflow-wrap: break-word;
+ margin: 20px 0;
+ overflow: hidden; /* Prevent alphabet overflow */
+}
+
+/* Typography text content */
+.nv-sb-typography-grid p {
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ hyphens: auto;
+ line-height: 1.6;
+ overflow: hidden; /* Prevent paragraph overflow */
+}
+
+/* Heading elements in typography */
+.nv-sb-type-sample h1,
+.nv-sb-type-sample h2,
+.nv-sb-type-sample h3,
+.nv-sb-type-sample h4,
+.nv-sb-type-sample h5,
+.nv-sb-type-sample h6 {
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* Buttons */
+.nv-sb-button-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-bottom: 25px;
+}
+
+ .nv-sb-btn-primary {
+ @extend %nv-button-primary;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-decoration: none;
+ display: inline-block;
+ }
+
+ .nv-sb-btn-secondary {
+ @extend %nv-button-secondary;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-decoration: none;
+ display: inline-block;
+ }
+
+/* Form Elements */
+.nv-sb-form-container {
+ max-width: 600px;
+}
+
+.nv-sb-form-group {
+ margin-bottom: 20px;
+ position: relative;
+ cursor: pointer;
+ padding: 15px;
+ border-radius: 6px;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background: rgba(0, 115, 170, 0.05);
+ outline: 1px solid #0073aa;
+ outline-offset: -1px;
+ }
+
+ > label {
+ display: block;
+ margin-bottom: 8px;
+ }
+
+ input[type="text"],
+ input[type="email"],
+ input[type="password"],
+ input[type="url"],
+ input[type="tel"],
+ input[type="number"],
+ select,
+ textarea {
+ width: 100%;
+ font-family: inherit;
+ /* Let theme styles handle colors, padding, borders, etc. */
+ }
+
+ textarea {
+ min-height: 100px;
+ resize: vertical;
+ }
+
+ select {
+ cursor: pointer;
+ /* Let theme handle select styling */
+ }
+}
+
+.nv-sb-checkbox-group,
+.nv-sb-radio-group {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.nv-sb-checkbox-label,
+.nv-sb-radio-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: normal !important;
+ margin-bottom: 0 !important;
+ cursor: pointer;
+ padding: 8px 0;
+
+ input[type="checkbox"],
+ input[type="radio"] {
+ width: auto !important;
+ margin: 0;
+ /* Let theme handle input styling */
+ }
+}
+
+
+/* Full Width Section */
+.nv-sb-full-section {
+ grid-column: 1 / -1;
+}
+
+/* Responsive */
+/* Large tablets and small desktops */
+@media (max-width: 1024px) {
+ #nv-sb-container {
+ padding: 30px 15px;
+ }
+
+ .nv-sb-grid {
+ gap: 25px;
+ max-width: 100%;
+ padding: 0 15px;
+ }
+
+ .nv-sb-section {
+ padding: 25px;
+ }
+
+ .nv-sb-section.nv-sb-full-section {
+ padding: 30px;
+ }
+
+ .nv-sb-typography-grid {
+ grid-template-columns: 1fr;
+ gap: 30px;
+ }
+}
+
+/* Large tablets and small desktops */
+@media (max-width: 840px) {
+ .nv-sb-color-grid {
+ gap: 10px;
+ }
+
+ .nv-sb-typography-grid {
+ gap: 25px;
+ }
+}
+
+/* Tablets */
+@media (max-width: 768px) {
+ #nv-sb-container {
+ padding: 20px 10px;
+ }
+
+ .nv-sb-grid {
+ gap: 20px;
+ padding: 0 10px;
+ }
+
+ .nv-sb-two-col-grid {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ }
+
+ .nv-sb-section {
+ padding: 20px;
+ }
+
+ .nv-sb-section.nv-sb-full-section {
+ padding: 25px;
+ }
+
+ .nv-sb-section-title {
+ font-size: 1.3rem;
+ margin-bottom: 15px;
+ }
+
+ .nv-sb-color-grid {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+ }
+
+ .nv-sb-color-box {
+ height: 55px;
+ }
+
+ .nv-sb-color-info {
+ padding: 6px;
+ }
+
+ .nv-sb-color-name {
+ font-size: 0.8rem;
+ }
+
+ .nv-sb-button-group {
+ flex-direction: column;
+ gap: 10px;
+ }
+}
+
+/* Small tablets and large phones */
+@media (max-width: 600px) {
+ .nv-sb-color-grid {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 6px;
+ }
+
+ .nv-sb-color-box {
+ height: 45px;
+ }
+
+ .nv-sb-color-info {
+ padding: 5px;
+ }
+
+ .nv-sb-color-name {
+ font-size: 0.75rem;
+ }
+}
+
+/* Mobile phones */
+@media (max-width: 480px) {
+ .nv-sb-close-btn {
+ top: 15px;
+ right: 15px;
+ width: 35px;
+ height: 35px;
+
+ .dashicons {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+ }
+ }
+
+ .nv-sb-section {
+ padding: 15px;
+ }
+
+ .nv-sb-section.nv-sb-full-section {
+ padding: 20px;
+ }
+
+ .nv-sb-section-title {
+ font-size: 1.2rem;
+ margin-bottom: 12px;
+ }
+
+ .nv-sb-color-grid {
+ grid-template-columns: repeat(2, 1fr);
+ gap: 8px;
+ }
+
+ .nv-sb-color-box {
+ height: 50px;
+ }
+
+ .nv-sb-color-info {
+ padding: 8px;
+ }
+
+ .nv-sb-color-name {
+ font-size: 0.8rem;
+ }
+
+ .nv-sb-typography-grid {
+ gap: 15px;
+ overflow: visible; /* Allow content to flow naturally on mobile */
+ }
+
+ /* Allow headings to wrap on mobile */
+ .nv-sb-type-sample h1,
+ .nv-sb-type-sample h2,
+ .nv-sb-type-sample h3,
+ .nv-sb-type-sample h4,
+ .nv-sb-type-sample h5,
+ .nv-sb-type-sample h6 {
+ white-space: normal;
+ text-overflow: unset;
+ }
+
+ .nv-sb-alphabet {
+ font-size: 1.3rem;
+ line-height: 1.5;
+ margin: 15px 0;
+ word-break: break-word; /* More aggressive breaking on mobile */
+ }
+
+ .nv-sb-btn-primary,
+ .nv-sb-btn-secondary {
+ padding: 10px 20px;
+ font-size: 0.9rem;
+ }
+
+ /* Form Elements on Mobile */
+ .nv-sb-form-group {
+ padding: 12px;
+ margin-bottom: 15px;
+
+ > label {
+ font-size: 0.9rem;
+ margin-bottom: 6px;
+ }
+
+ textarea {
+ min-height: 80px;
+ }
+ }
+
+ .nv-sb-checkbox-group,
+ .nv-sb-radio-group {
+ gap: 8px;
+ }
+
+ .nv-sb-checkbox-label,
+ .nv-sb-radio-label {
+ padding: 6px 0;
+ font-size: 0.9rem;
+ }
+}
diff --git a/composer.json b/composer.json
index 16953a4ef1..1ea7a1b515 100644
--- a/composer.json
+++ b/composer.json
@@ -40,7 +40,7 @@
"phpcs": "phpcs --standard=phpcs.xml -s --runtime-set testVersion 7.0-",
"lint": "composer run-script phpcs",
"phpcs-i": "phpcs -i",
- "phpstan": "phpstan analyse",
+ "phpstan": "phpstan analyse --memory-limit 2G",
"post-install-cmd": [
"[ ! -z \"$GITHUB_ACTIONS\" ] && yarn run bump-vendor || true"
],
diff --git a/e2e-tests/specs/customizer/style-book/style-book.spec.ts b/e2e-tests/specs/customizer/style-book/style-book.spec.ts
new file mode 100644
index 0000000000..8650b37cdd
--- /dev/null
+++ b/e2e-tests/specs/customizer/style-book/style-book.spec.ts
@@ -0,0 +1,125 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Style Book Modal', () => {
+ test.beforeEach(async ({ page }) => {
+ // First, try to go to customizer
+ await page.goto('/wp-admin/customize.php');
+
+ // Wait for customizer to fully load
+ await page.waitForSelector('.wp-full-overlay-sidebar', { state: 'visible' });
+
+ // Wait a bit more for all scripts to initialize
+ await page.waitForTimeout(1000);
+
+ // Open Style Book for all tests
+ await page.getByRole('button', { name: ' Style Book' }).click();
+
+ // Wait for Style Book to appear in the iframe
+ await page
+ .frameLocator('iframe[name="customize-preview-0"]')
+ .locator('#nv-sb-container')
+ .waitFor({ state: 'visible' });
+ });
+
+ test('should open Style Book modal when button is clicked', async ({ page }) => {
+ // Check that the Style Book modal appears (already opened in beforeEach)
+ const styleBookModal = page.frameLocator('iframe[name="customize-preview-0"]').locator('#nv-sb-container');
+ await expect(styleBookModal).toBeVisible();
+
+ // Check that the modal has the correct background overlay
+ await expect(styleBookModal).toHaveCSS('position', 'fixed');
+ await expect(styleBookModal).toHaveCSS('z-index', '999999');
+ });
+
+ test('should display all main sections in Style Book', async ({ page }) => {
+ // Check for all main sections
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ await expect(iframe.locator('.nv-sb-section-title', { hasText: 'Palette Colors' })).toBeVisible();
+ await expect(iframe.locator('.nv-sb-section-title', { hasText: 'Typography' })).toBeVisible();
+ await expect(iframe.locator('.nv-sb-section-title', { hasText: 'Buttons' })).toBeVisible();
+ await expect(iframe.locator('.nv-sb-section-title', { hasText: 'Form Fields' })).toBeVisible();
+ });
+
+ test('should display color swatches with correct structure', async ({ page }) => {
+ // Check color grid exists
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ await expect(iframe.locator('.nv-sb-color-grid')).toBeVisible();
+
+ // Check that color swatches are present
+ const colorSwatches = iframe.locator('.nv-sb-color-swatch');
+ await expect(colorSwatches).toHaveCount(9); // We have 9 color variables defined
+
+ // Check first color swatch structure
+ const firstSwatch = colorSwatches.first();
+ await expect(firstSwatch.locator('.nv-sb-color-box')).toBeVisible();
+ await expect(firstSwatch.locator('.nv-sb-color-name')).toBeVisible();
+
+ // Verify color swatch has clickable class
+ await expect(firstSwatch).toHaveClass(/builder-item-focus/);
+ });
+
+ test('should display typography elements with headings', async ({ page }) => {
+ // Check typography grid exists (Style Book already opened in beforeEach)
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ await expect(iframe.locator('.nv-sb-typography-grid')).toBeVisible();
+
+ // Check all heading levels are present and clickable
+ for (let i = 1; i <= 6; i++) {
+ const heading = iframe.locator(`.nv-sb-type-sample h${i}.builder-item-focus`);
+ await expect(heading).toBeVisible();
+ await expect(heading).toContainText(`Heading ${i}`);
+ }
+
+ // Check paragraph text is present and clickable
+ const paragraphs = iframe.locator('p.builder-item-focus');
+ await expect(paragraphs).toHaveCount(2); // We have 2 paragraphs
+ });
+
+ test('should display form elements with proper structure', async ({ page }) => {
+ // Check form container exists (Style Book already opened in beforeEach)
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ await expect(iframe.locator('.nv-sb-form')).toBeVisible();
+
+ // Check individual form elements
+ await expect(iframe.locator('input[type="text"]')).toBeVisible();
+ await expect(iframe.locator('textarea')).toBeVisible();
+ await expect(iframe.locator('select')).toBeVisible();
+ await expect(iframe.locator('.nv-sb-btn-primary')).toBeVisible();
+ });
+
+ test('should display buttons with proper styling', async ({ page }) => {
+ // Check button group exists (Style Book already opened in beforeEach)
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ await expect(iframe.locator('.nv-sb-button-group')).toBeVisible();
+
+ // Check both button types are present
+ const primaryBtn = iframe.locator('.nv-sb-btn-primary.builder-item-focus');
+ const secondaryBtn = iframe.locator('.nv-sb-btn-secondary.builder-item-focus');
+
+ await expect(primaryBtn).toBeVisible();
+ await expect(secondaryBtn).toBeVisible();
+
+ await expect(primaryBtn).toContainText('Primary Button');
+ await expect(secondaryBtn).toContainText('Secondary Button');
+ });
+
+ test('should close Style Book when close button is clicked', async ({ page }) => {
+ // Click close button (Style Book already opened in beforeEach)
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ await iframe.locator('.nv-sb-close-btn').click();
+
+ // Check that modal is hidden
+ await expect(iframe.locator('#nv-sb-container')).toBeHidden();
+ });
+
+ test('should navigate to customizer sections when elements are clicked', async ({ page }) => {
+ // Click on a color swatch (should navigate to colors section) - Style Book already opened in beforeEach
+ const iframe = page.frameLocator('iframe[name="customize-preview-0"]');
+ const colorSwatch = iframe.locator('.nv-sb-color-swatch.builder-item-focus').first();
+ await colorSwatch.click();
+
+ // Check that we're still in the customizer (Style Book should close and focus section)
+ await page.waitForTimeout(500); // Give time for navigation
+ await expect( page.getByRole('heading', { name: 'Customizing ▸ Global Colors & Background' }).getByText('Customizing ▸ Global') ).toBeVisible();
+ });
+});
diff --git a/inc/core/core_loader.php b/inc/core/core_loader.php
index f82bb0c665..462bcceab9 100644
--- a/inc/core/core_loader.php
+++ b/inc/core/core_loader.php
@@ -97,6 +97,7 @@ private function define_modules() {
'Views\Content_None',
'Views\Content_404',
'Views\Breadcrumbs',
+ 'Views\Style_Book',
'Views\Scroll_To_Top',
'Views\Layouts\Layout_Container',
diff --git a/inc/views/style_book.php b/inc/views/style_book.php
new file mode 100644
index 0000000000..195d766258
--- /dev/null
+++ b/inc/views/style_book.php
@@ -0,0 +1,191 @@
+ [
+ 'name' => __( 'Primary Accent', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-secondary-accent' => [
+ 'name' => __( 'Secondary Accent', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-site-bg' => [
+ 'name' => __( 'Site Background', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-light-bg' => [
+ 'name' => __( 'Light Background', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-dark-bg' => [
+ 'name' => __( 'Dark Background', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-text-color' => [
+ 'name' => __( 'Text Color', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-text-dark-bg' => [
+ 'name' => __( 'Text Dark Background', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-c-1' => [
+ 'name' => __( 'Extra Color 1', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ 'nv-c-2' => [
+ 'name' => __( 'Extra Color 2', 'neve' ),
+ 'section' => 'neve_colors_background_section',
+ ],
+ ];
+
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ $color_data ) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ 'neve_h1_accordion_wrap',
+ 2 => 'neve_h2_accordion_wrap',
+ 3 => 'neve_h3_accordion_wrap',
+ 4 => 'neve_h4_accordion_wrap',
+ 5 => 'neve_h5_accordion_wrap',
+ 6 => 'neve_h6_accordion_wrap',
+ ];
+
+ foreach ( $headings as $level => $control_id ) :
+ ?>
+ class="builder-item-focus" data-section="" data-control="">
+
+ >
+
+
+
+
+
+
+ get( 'Description' ) ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+