Commit 1896acb098e for woocommerce
commit 1896acb098e4d508623cf2a3901cb88eeb9e0d42
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Mon Jun 8 11:58:47 2026 +0100
Align settings UI SDK with settings card design (#65557)
* Align settings UI SDK with settings card design
* Add settings UI unsaved changes modal
* Fix custom settings save navigation protection
---------
Co-authored-by: Ahmed <ahmed.el.azzabi@automattic.com>
diff --git a/packages/js/settings-ui-sdk/changelog/tweak-settings-ui-card-design b/packages/js/settings-ui-sdk/changelog/tweak-settings-ui-card-design
new file mode 100644
index 00000000000..ce3e1503b8f
--- /dev/null
+++ b/packages/js/settings-ui-sdk/changelog/tweak-settings-ui-card-design
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Align Settings UI SDK rendering with the settings card design.
diff --git a/packages/js/settings-ui-sdk/src/settings-ui-page.tsx b/packages/js/settings-ui-sdk/src/settings-ui-page.tsx
index 4a6fd5db543..c44a4940dc7 100644
--- a/packages/js/settings-ui-sdk/src/settings-ui-page.tsx
+++ b/packages/js/settings-ui-sdk/src/settings-ui-page.tsx
@@ -2,7 +2,7 @@
* External dependencies
*/
import { Page } from '@wordpress/admin-ui';
-import { Button, Notice } from '@wordpress/components';
+import { Button, Modal, Notice } from '@wordpress/components';
import {
Component,
createElement,
@@ -10,6 +10,7 @@ import {
useCallback,
useEffect,
useMemo,
+ useRef,
useState,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
@@ -44,6 +45,10 @@ type SaveNotice = {
message: string;
};
+type PendingNavigation = {
+ href: string;
+};
+
const getInitialValues = ( schema: SettingsUISchema ): SettingsValues => {
const values: SettingsValues = {};
@@ -100,6 +105,92 @@ const clearLegacyFormPrompt = () => {
window.onbeforeunload = null;
};
+const shouldPromptForNavigation = ( event: MouseEvent ) => {
+ if (
+ event.defaultPrevented ||
+ event.button !== 0 ||
+ event.metaKey ||
+ event.ctrlKey ||
+ event.shiftKey ||
+ event.altKey
+ ) {
+ return false;
+ }
+
+ const target = event.target;
+
+ if ( ! ( target instanceof Element ) ) {
+ return false;
+ }
+
+ const link = target.closest( 'a[href]' );
+
+ if ( ! ( link instanceof HTMLAnchorElement ) ) {
+ return false;
+ }
+
+ if ( link.target && link.target !== '_self' ) {
+ return false;
+ }
+
+ return Boolean( link.href ) && link.href !== window.location.href;
+};
+
+const getNavigationHref = ( event: MouseEvent ) => {
+ const target = event.target;
+
+ if ( ! ( target instanceof Element ) ) {
+ return undefined;
+ }
+
+ const link = target.closest( 'a[href]' );
+
+ return link instanceof HTMLAnchorElement ? link.href : undefined;
+};
+
+const UnsavedChangesModal = ( {
+ isSaving,
+ onClose,
+ onDiscard,
+ onSave,
+}: {
+ isSaving: boolean;
+ onClose: () => void;
+ onDiscard: () => void;
+ onSave: () => void;
+} ) => {
+ return (
+ <Modal
+ className="wc-settings-ui__unsaved-changes-modal"
+ title={ __( 'You have unsaved changes', 'woocommerce' ) }
+ onRequestClose={ onClose }
+ >
+ <p>
+ { __(
+ "If you leave now, your changes won't be saved.",
+ 'woocommerce'
+ ) }
+ </p>
+ <div className="wc-settings-ui__unsaved-changes-actions">
+ <Button variant="tertiary" onClick={ onDiscard }>
+ { __( 'Discard', 'woocommerce' ) }
+ </Button>
+ <Button
+ variant="primary"
+ type="button"
+ name="save"
+ value={ __( 'Save', 'woocommerce' ) }
+ isBusy={ isSaving }
+ disabled={ isSaving }
+ onClick={ onSave }
+ >
+ { __( 'Save', 'woocommerce' ) }
+ </Button>
+ </div>
+ </Modal>
+ );
+};
+
const GroupHeader = ( { group }: { group: SettingsUIGroup } ) => {
const hasHeaderContent =
group.title || group.description || ( group.actions || [] ).length > 0;
@@ -109,17 +200,19 @@ const GroupHeader = ( { group }: { group: SettingsUIGroup } ) => {
}
return (
- <div className="wc-settings-ui__group-header">
- { group.title ? <h2>{ group.title }</h2> : null }
- { group.description ? (
- <div className="wc-settings-ui__group-description">
- <RawHTML>
- { sanitizeSettingsHtml( group.description ) }
- </RawHTML>
- </div>
- ) : null }
+ <header className="wc-settings-ui__section-header">
+ <div className="wc-settings-ui__section-heading">
+ { group.title ? <h2>{ group.title }</h2> : null }
+ { group.description ? (
+ <div className="wc-settings-ui__section-description">
+ <RawHTML>
+ { sanitizeSettingsHtml( group.description ) }
+ </RawHTML>
+ </div>
+ ) : null }
+ </div>
{ group.actions && group.actions.length > 0 ? (
- <div className="wc-settings-ui__group-actions">
+ <div className="wc-settings-ui__section-actions">
{ group.actions.map( ( action ) => (
<Button
key={ action.id }
@@ -133,7 +226,7 @@ const GroupHeader = ( { group }: { group: SettingsUIGroup } ) => {
) ) }
</div>
) : null }
- </div>
+ </header>
);
};
@@ -256,7 +349,7 @@ const ShellHeader = ( {
isDirty: boolean;
isSaving: boolean;
saveStrategy: SettingsUISaveStrategy;
- onSave: () => void;
+ onSave: () => void | Promise< boolean >;
children: ReactNode;
} ) => {
const shell = schema.shell || {};
@@ -294,22 +387,20 @@ const ShellHeader = ( {
</nav>
) : undefined;
+ const saveButtonLabel = __( 'Save', 'woocommerce' );
+
const actions = showSaveButton ? (
<Button
className="woocommerce-save-button"
variant="primary"
type={ saveButtonType }
name="save"
- value={ __( 'Save changes', 'woocommerce' ) }
+ value={ saveButtonLabel }
disabled={ ! isDirty || isSaving }
isBusy={ isSaving }
- onClick={
- saveStrategy.adapter === 'form_post'
- ? clearLegacyFormPrompt
- : onSave
- }
+ onClick={ onSave }
>
- { __( 'Save changes', 'woocommerce' ) }
+ { saveButtonLabel }
</Button>
) : undefined;
@@ -400,6 +491,9 @@ export const SettingsUIPage = ( {
);
const [ isSaving, setIsSaving ] = useState( false );
const [ saveNotice, setSaveNotice ] = useState< SaveNotice | null >( null );
+ const [ pendingNavigation, setPendingNavigation ] =
+ useState< PendingNavigation | null >( null );
+ const allowNavigationRef = useRef( false );
const context: SettingsFieldContext = useMemo(
() => ( {
page: page || schema.id,
@@ -423,6 +517,7 @@ export const SettingsUIPage = ( {
setInitialValues( nextValues );
setValuesState( nextValues );
setSaveNotice( null );
+ setPendingNavigation( null );
}, [ schema ] );
const setValue = useCallback(
@@ -435,6 +530,33 @@ export const SettingsUIPage = ( {
[]
);
+ const allowNavigation = useCallback( () => {
+ allowNavigationRef.current = true;
+ clearLegacyFormPrompt();
+ }, [] );
+
+ const submitSettingsForm = useCallback( () => {
+ allowNavigation();
+
+ const form = document.getElementById( 'mainform' );
+
+ if ( ! ( form instanceof HTMLFormElement ) ) {
+ return;
+ }
+
+ const saveButton = document.querySelector( '.woocommerce-save-button' );
+
+ if (
+ saveButton instanceof HTMLButtonElement &&
+ saveButton.form === form
+ ) {
+ form.requestSubmit( saveButton );
+ return;
+ }
+
+ form.requestSubmit();
+ }, [ allowNavigation ] );
+
const setValues = useCallback(
( nextValues: Partial< SettingsValues > ) => {
setValuesState( ( currentValues ) => {
@@ -456,7 +578,7 @@ export const SettingsUIPage = ( {
const handleCustomSave = useCallback( async () => {
if ( saveStrategy.adapter !== 'custom' ) {
- return;
+ return false;
}
const handlerName =
@@ -469,7 +591,7 @@ export const SettingsUIPage = ( {
status: 'error',
message: __( 'Unable to save settings.', 'woocommerce' ),
} );
- return;
+ return false;
}
setIsSaving( true );
@@ -493,12 +615,14 @@ export const SettingsUIPage = ( {
result?.notice ||
__( 'Settings saved successfully.', 'woocommerce' ),
} );
+ return true;
} catch ( saveError ) {
const message =
saveError instanceof Error && saveError.message
? saveError.message
: __( 'Unable to save settings.', 'woocommerce' );
setSaveNotice( { status: 'error', message } );
+ return false;
} finally {
setIsSaving( false );
}
@@ -512,6 +636,99 @@ export const SettingsUIPage = ( {
values,
] );
+ useEffect( () => {
+ if ( ! isDirty ) {
+ return;
+ }
+
+ const handleBeforeUnload = ( event: BeforeUnloadEvent ) => {
+ if ( allowNavigationRef.current ) {
+ return;
+ }
+
+ event.preventDefault();
+ event.returnValue = '';
+ };
+
+ window.addEventListener( 'beforeunload', handleBeforeUnload );
+
+ return () => {
+ window.removeEventListener( 'beforeunload', handleBeforeUnload );
+ };
+ }, [ isDirty ] );
+
+ useEffect( () => {
+ if ( ! isDirty ) {
+ return;
+ }
+
+ const handleNavigationClick = ( event: MouseEvent ) => {
+ const target = event.target;
+
+ if (
+ ! ( target instanceof Element ) ||
+ ! target.closest( '.wc-settings-ui-shell' ) ||
+ ! shouldPromptForNavigation( event )
+ ) {
+ return;
+ }
+
+ const href = getNavigationHref( event );
+
+ if ( ! href ) {
+ return;
+ }
+
+ event.preventDefault();
+ setPendingNavigation( { href } );
+ };
+
+ document.addEventListener( 'click', handleNavigationClick, true );
+
+ return () => {
+ document.removeEventListener(
+ 'click',
+ handleNavigationClick,
+ true
+ );
+ };
+ }, [ isDirty ] );
+
+ const handleDiscardNavigation = useCallback( () => {
+ if ( ! pendingNavigation ) {
+ return;
+ }
+
+ allowNavigation();
+ window.location.assign( pendingNavigation.href );
+ }, [ allowNavigation, pendingNavigation ] );
+
+ const handleSavePendingNavigation = useCallback( async () => {
+ if ( ! pendingNavigation ) {
+ return;
+ }
+
+ if ( saveStrategy.adapter === 'form_post' ) {
+ submitSettingsForm();
+ return;
+ }
+
+ if ( saveStrategy.adapter === 'custom' ) {
+ const saved = await handleCustomSave();
+
+ if ( saved ) {
+ allowNavigation();
+ window.location.assign( pendingNavigation.href );
+ }
+ }
+ }, [
+ allowNavigation,
+ handleCustomSave,
+ pendingNavigation,
+ saveStrategy.adapter,
+ submitSettingsForm,
+ ] );
+
const visibleGroups = useMemo(
() =>
Object.values( schema.groups )
@@ -555,8 +772,20 @@ export const SettingsUIPage = ( {
isDirty={ isDirty }
isSaving={ isSaving }
saveStrategy={ saveStrategy }
- onSave={ handleCustomSave }
+ onSave={
+ saveStrategy.adapter === 'form_post'
+ ? submitSettingsForm
+ : handleCustomSave
+ }
>
+ { pendingNavigation ? (
+ <UnsavedChangesModal
+ isSaving={ isSaving }
+ onClose={ () => setPendingNavigation( null ) }
+ onDiscard={ handleDiscardNavigation }
+ onSave={ handleSavePendingNavigation }
+ />
+ ) : null }
{ saveNotice ? (
<Notice
className="wc-settings-ui-shell__notice"
@@ -569,38 +798,50 @@ export const SettingsUIPage = ( {
) : null }
<div className="wc-settings-ui">
{ visibleGroups.map( ( group ) => (
- <section className="wc-settings-ui__group" key={ group.id }>
- <GroupHeader group={ group } />
- <div className="wc-settings-ui__group-panel">
- { group.fields.map( ( field ) => {
- const FieldComponent =
- resolveFieldComponent( field, context ) ||
- NativeSettingsField;
- const value = values[ field.id ];
-
- return (
- <div
- className={ [
- 'wc-settings-ui__field',
- getFieldTypeClassName( field.type ),
- ].join( ' ' ) }
- key={ field.id }
- >
- <FieldComponent
- field={ field }
- value={ value }
- context={ context }
- values={ values }
- initialValues={ initialValues }
- setValue={ setValue }
- setValues={ setValues }
- onChange={ ( nextValue ) =>
- setValue( field.id, nextValue )
- }
- />
- </div>
- );
- } ) }
+ <section
+ className="wc-settings-ui__section"
+ key={ group.id }
+ >
+ <div className="wc-settings-ui__section-card">
+ <GroupHeader group={ group } />
+ <div className="wc-settings-ui__section-fields">
+ { group.fields.map( ( field ) => {
+ const FieldComponent =
+ resolveFieldComponent(
+ field,
+ context
+ ) || NativeSettingsField;
+ const value = values[ field.id ];
+
+ return (
+ <div
+ className={ [
+ 'wc-settings-ui__field',
+ getFieldTypeClassName(
+ field.type
+ ),
+ ].join( ' ' ) }
+ key={ field.id }
+ >
+ <FieldComponent
+ field={ field }
+ value={ value }
+ context={ context }
+ values={ values }
+ initialValues={ initialValues }
+ setValue={ setValue }
+ setValues={ setValues }
+ onChange={ ( nextValue ) =>
+ setValue(
+ field.id,
+ nextValue
+ )
+ }
+ />
+ </div>
+ );
+ } ) }
+ </div>
</div>
</section>
) ) }
diff --git a/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx b/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx
index a37b0d16d72..fe514aec45b 100644
--- a/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx
+++ b/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx
@@ -7,14 +7,20 @@ import { createRoot } from 'react-dom/client';
import type { ReactNode } from 'react';
jest.mock( '@wordpress/admin-ui', () => ( {
- Page: ( { children }: { children: ReactNode } ) => <>{ children }</>,
+ Page: ( {
+ children,
+ className,
+ }: {
+ children: ReactNode;
+ className?: string;
+ } ) => <div className={ className }>{ children }</div>,
} ) );
/**
* Internal dependencies
*/
import { SettingsUIPage } from '../settings-ui-page';
-import { NativeSettingsField } from '../native-fields';
+import { __resetRegistry, registerSettingsExtension } from '../registry';
import type { SettingsUISchema } from '../types';
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
@@ -45,23 +51,86 @@ const expectUnsafeMarkupRemoved = ( container: HTMLElement ) => {
};
describe( 'settings HTML rendering', () => {
- it( 'sanitizes native field help before rendering', () => {
+ afterEach( () => {
+ __resetRegistry();
+ } );
+
+ it( 'renders settings as centered sections and cards', () => {
+ const schema: SettingsUISchema = {
+ id: 'test-page',
+ title: 'Test page',
+ section: 'default',
+ save: { adapter: 'none' },
+ groups: {
+ general: {
+ id: 'general',
+ title: 'General settings',
+ description: 'Configure the basics.',
+ fields: [
+ {
+ id: 'test_field',
+ label: 'Test field',
+ type: 'text',
+ description: 'Shown as field description.',
+ },
+ ],
+ },
+ },
+ };
+
const { container, root } = renderElement(
- <NativeSettingsField
- field={ {
- id: 'test_field',
- label: 'Test field',
- type: 'text',
- description: unsafeDescription,
- } }
- value=""
- onChange={ jest.fn() }
- context={ { page: 'test' } }
- values={ {} }
- initialValues={ {} }
- setValue={ jest.fn() }
- setValues={ jest.fn() }
- />
+ <SettingsUIPage schema={ schema } />
+ );
+
+ expect(
+ container.querySelector( '.wc-settings-ui__section' )
+ ).not.toBeNull();
+ expect(
+ container.querySelector( '.wc-settings-ui__section-card' )
+ ).not.toBeNull();
+ expect(
+ container.querySelector( '.wc-settings-ui__section-fields' )
+ ).not.toBeNull();
+ expect( container.querySelector( '.wc-settings-ui__row' ) ).toBeNull();
+ expect(
+ container.querySelector( '.wc-settings-ui__group-panel' )
+ ).toBeNull();
+ expect(
+ container.querySelector( '.wc-settings-ui__group-header' )
+ ).toBeNull();
+ expect( container.textContent ).toContain( 'General settings' );
+ expect( container.textContent ).toContain( 'Test field' );
+ expect( container.textContent ).toContain(
+ 'Shown as field description.'
+ );
+
+ act( () => root.unmount() );
+ container.remove();
+ } );
+
+ it( 'sanitizes native field descriptions before rendering', () => {
+ const schema: SettingsUISchema = {
+ id: 'test-page',
+ title: 'Test page',
+ section: 'default',
+ save: { adapter: 'none' },
+ groups: {
+ general: {
+ id: 'general',
+ fields: [
+ {
+ id: 'test_field',
+ label: 'Test field',
+ type: 'text',
+ description: unsafeDescription,
+ },
+ ],
+ },
+ },
+ };
+
+ const { container, root } = renderElement(
+ <SettingsUIPage schema={ schema } />
);
expectUnsafeMarkupRemoved( container );
@@ -111,6 +180,189 @@ describe( 'settings HTML rendering', () => {
container.remove();
} );
+ it( 'prompts before navigating away with unsaved changes', () => {
+ const schema: SettingsUISchema = {
+ id: 'test-page',
+ title: 'Test page',
+ section: 'default',
+ save: { adapter: 'form_post' },
+ shell: {
+ navigation: [
+ {
+ id: 'next-page',
+ label: 'Next page',
+ href: 'https://example.com/next',
+ },
+ ],
+ },
+ groups: {
+ general: {
+ id: 'general',
+ fields: [
+ {
+ id: 'test_field',
+ label: 'Test field',
+ type: 'text',
+ value: 'Initial value',
+ },
+ ],
+ },
+ },
+ };
+
+ const { container, root } = renderElement(
+ <SettingsUIPage schema={ schema } />
+ );
+
+ const input = container.querySelector( 'input[type="text"]' );
+ const link = container.querySelector(
+ 'a[href="https://example.com/next"]'
+ );
+
+ expect( input ).not.toBeNull();
+ expect( link ).not.toBeNull();
+
+ act( () => {
+ if ( input instanceof HTMLInputElement ) {
+ const valueSetter = Object.getOwnPropertyDescriptor(
+ HTMLInputElement.prototype,
+ 'value'
+ )?.set;
+
+ valueSetter?.call( input, 'Changed value' );
+ input.dispatchEvent(
+ new Event( 'input', { bubbles: true, cancelable: true } )
+ );
+ }
+ } );
+
+ act( () => {
+ link?.dispatchEvent(
+ new MouseEvent( 'click', {
+ bubbles: true,
+ cancelable: true,
+ button: 0,
+ } )
+ );
+ } );
+
+ expect( document.body.textContent ).toContain(
+ 'You have unsaved changes'
+ );
+ expect( document.body.textContent ).toContain(
+ "If you leave now, your changes won't be saved."
+ );
+ expect( document.body.textContent ).toContain( 'Discard' );
+ expect( document.body.textContent ).toContain( 'Save' );
+
+ act( () => root.unmount() );
+ container.remove();
+ } );
+
+ it( 'keeps unload protection when custom save before navigation fails', async () => {
+ const saveHandler = jest
+ .fn()
+ .mockRejectedValue( new Error( 'Save failed.' ) );
+
+ registerSettingsExtension( {
+ scope: { page: 'test-page', section: 'default' },
+ saveHandlers: {
+ fail: saveHandler,
+ },
+ } );
+
+ const schema: SettingsUISchema = {
+ id: 'test-page',
+ title: 'Test page',
+ section: 'default',
+ save: { adapter: 'custom', handler: 'fail' },
+ shell: {
+ navigation: [
+ {
+ id: 'next-page',
+ label: 'Next page',
+ href: 'https://example.com/next',
+ },
+ ],
+ },
+ groups: {
+ general: {
+ id: 'general',
+ fields: [
+ {
+ id: 'test_field',
+ label: 'Test field',
+ type: 'text',
+ value: 'Initial value',
+ },
+ ],
+ },
+ },
+ };
+
+ const { container, root } = renderElement(
+ <SettingsUIPage schema={ schema } />
+ );
+
+ const input = container.querySelector( 'input[type="text"]' );
+ const link = container.querySelector(
+ 'a[href="https://example.com/next"]'
+ );
+
+ act( () => {
+ if ( input instanceof HTMLInputElement ) {
+ const valueSetter = Object.getOwnPropertyDescriptor(
+ HTMLInputElement.prototype,
+ 'value'
+ )?.set;
+
+ valueSetter?.call( input, 'Changed value' );
+ input.dispatchEvent(
+ new Event( 'input', { bubbles: true, cancelable: true } )
+ );
+ }
+ } );
+
+ act( () => {
+ link?.dispatchEvent(
+ new MouseEvent( 'click', {
+ bubbles: true,
+ cancelable: true,
+ button: 0,
+ } )
+ );
+ } );
+
+ const saveButton = Array.from(
+ document.body.querySelectorAll(
+ '.wc-settings-ui__unsaved-changes-actions button'
+ )
+ ).find( ( button ) => button.textContent === 'Save' );
+
+ await act( async () => {
+ saveButton?.dispatchEvent(
+ new MouseEvent( 'click', {
+ bubbles: true,
+ cancelable: true,
+ button: 0,
+ } )
+ );
+ } );
+
+ const beforeUnloadEvent = new Event( 'beforeunload', {
+ cancelable: true,
+ } );
+
+ window.dispatchEvent( beforeUnloadEvent );
+
+ expect( saveHandler ).toHaveBeenCalledTimes( 1 );
+ expect( beforeUnloadEvent.defaultPrevented ).toBe( true );
+ expect( document.body.textContent ).toContain( 'Save failed.' );
+
+ act( () => root.unmount() );
+ container.remove();
+ } );
+
it( 'sanitizes info fields and group descriptions before rendering', () => {
const schema: SettingsUISchema = {
id: 'test-page',
diff --git a/plugins/woocommerce/changelog/tweak-settings-ui-card-design b/plugins/woocommerce/changelog/tweak-settings-ui-card-design
new file mode 100644
index 00000000000..6b22f8777da
--- /dev/null
+++ b/plugins/woocommerce/changelog/tweak-settings-ui-card-design
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Align Settings UI SDK embed styling with the settings card design.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
index 5911212d033..118d42dd4cc 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
@@ -97,8 +97,10 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
position: static;
}
- .wc-settings-ui-shell:has(.wc-settings-ui-shell__navigation) .admin-ui-page__header {
- border-bottom: 0;
+ .wc-settings-ui-shell {
+ &:has(.wc-settings-ui-shell__navigation) .admin-ui-page__header {
+ border-bottom: 0;
+ }
}
.wc-settings-ui-shell .admin-ui-page__header-title {
@@ -131,7 +133,6 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
}
}
-
.wc-settings-ui-shell__navigation {
border-bottom: 1px solid #f0f0f0;
margin: 0;
@@ -193,72 +194,70 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
}
.wc-settings-ui {
- --wc-settings-ui-panel-width: 681px;
- --wc-settings-ui-sidebar-width: 296px;
+ box-sizing: border-box;
+ margin: 32px auto 48px;
+ max-width: 656px;
+ }
+
+ .wc-settings-ui__section {
+ margin: 0 0 32px;
+ }
+ .wc-settings-ui__section-card {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
box-sizing: border-box;
- color: #1e1e1e;
- font-size: 13px;
- line-height: 20px;
- margin: 48px 48px;
- max-width: calc(var(--wc-settings-ui-sidebar-width) + var(--wc-settings-ui-panel-width) + 24px);
+ padding: 24px;
+ width: 100%;
}
- .wc-settings-ui__group {
+ .wc-settings-ui__section-header {
align-items: flex-start;
display: flex;
gap: 24px;
- margin: 0 0 48px;
+ justify-content: space-between;
+ margin-bottom: 24px;
}
- .wc-settings-ui__group-header {
- flex: 0 0 var(--wc-settings-ui-sidebar-width);
- max-width: var(--wc-settings-ui-sidebar-width);
+ .wc-settings-ui__section-heading {
min-width: 0;
+ }
- h2 {
- color: #070707;
- font-size: 16px;
- font-weight: 600;
- line-height: 24px;
- margin: 0;
- }
-
- .wc-settings-ui__group-description {
- color: #505050;
- font-size: 14px;
- line-height: 20px;
- margin: 4px 0 0;
- max-width: 240px;
+ .wc-settings-ui__section-header h2 {
+ color: #1e1e1e;
+ font-size: 15px;
+ font-weight: 500;
+ line-height: 20px;
+ margin: 0;
+ }
- p {
- margin: 0 0 8px;
- }
+ .wc-settings-ui__section-description {
+ color: #757575;
+ font-size: 13px;
+ line-height: 20px;
+ margin: 8px 0 0;
- p:last-child {
- margin-bottom: 0;
- }
+ p {
+ margin: 0 0 8px;
}
- .wc-settings-ui__group-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 16px;
+ p:last-child {
+ margin-bottom: 0;
}
}
- .wc-settings-ui__group-panel {
- background: #fff;
- border: 1px solid #e0e0e0;
- border-radius: 4px;
+ .wc-settings-ui__section-actions {
+ display: flex;
+ flex: 0 0 auto;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+
+ .wc-settings-ui__section-fields {
display: flex;
- flex: 0 1 var(--wc-settings-ui-panel-width);
flex-direction: column;
- gap: 16px;
- max-width: var(--wc-settings-ui-panel-width);
- padding: 24px;
- width: 100%;
+ gap: 24px;
}
.wc-settings-ui__field {
@@ -266,22 +265,13 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
width: 100%;
}
- .wc-settings-ui__actions {
- margin-left: calc(var(--wc-settings-ui-sidebar-width) + 24px);
- }
-
.wc-settings-ui__info {
- background: #f0f0f1;
+ background: #f6f7f7;
border-radius: 4px;
- color: #505050;
- font-size: 13px;
- line-height: 20px;
padding: 16px;
strong {
- color: #1e1e1e;
display: block;
- font-weight: 500;
margin-bottom: 4px;
}
@@ -300,89 +290,42 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
margin-bottom: 0;
}
- .components-base-control__label {
- color: #1e1e1e;
- display: block;
- font-size: 11px;
- font-weight: 500;
- line-height: 16px;
- margin: 0 0 8px;
- text-transform: uppercase;
- }
-
.components-base-control__help {
- color: #505050;
- font-size: 12px;
- line-height: 16px;
- margin: 8px 0 0;
+ color: #757575;
+ font-size: 13px;
+ line-height: 20px;
+ margin-top: 8px;
}
}
- .wc-settings-ui__control input[type="text"],
- .wc-settings-ui__control input[type="password"],
- .wc-settings-ui__control input[type="datetime-local"],
- .wc-settings-ui__control input[type="date"],
- .wc-settings-ui__control input[type="time"],
- .wc-settings-ui__control input[type="email"],
- .wc-settings-ui__control input[type="url"],
- .wc-settings-ui__control input[type="tel"],
- .wc-settings-ui__control input[type="number"],
- .wc-settings-ui__control select,
- .wc-settings-ui__control textarea {
- border-color: #949494;
- border-radius: 2px;
- color: #1e1e1e;
- font-size: 13px;
- line-height: 20px;
- margin: 0;
- max-width: none;
- min-height: 40px;
- width: 100%;
- }
-
- .wc-settings-ui__control input[type="text"],
- .wc-settings-ui__control input[type="password"],
- .wc-settings-ui__control input[type="datetime-local"],
- .wc-settings-ui__control input[type="date"],
- .wc-settings-ui__control input[type="time"],
- .wc-settings-ui__control input[type="email"],
- .wc-settings-ui__control input[type="url"],
- .wc-settings-ui__control input[type="tel"],
- .wc-settings-ui__control input[type="number"],
- .wc-settings-ui__control select {
- padding: 8px 12px;
- }
-
- .wc-settings-ui__control textarea {
- padding: 10px 12px;
- }
-
- .wc-settings-ui__field--checkbox {
- .components-base-control__field {
- align-items: flex-start;
- display: flex;
- gap: 12px;
+ .wc-settings-ui__unsaved-changes-modal {
+ .components-modal__header {
+ min-height: 72px;
+ padding: 24px 32px 8px;
}
- .components-h-stack {
- align-items: flex-start;
- gap: 12px;
+ .components-modal__header-heading {
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 24px;
}
- .components-checkbox-control__input-container {
- flex: 0 0 auto;
- margin: 0;
+ .components-modal__content {
+ padding: 4px 32px 32px;
}
- .components-checkbox-control__label {
- color: #1e1e1e;
+ p {
font-size: 13px;
line-height: 20px;
+ margin: 0;
}
+ }
- .components-base-control__help {
- margin-left: 32px;
- }
+ .wc-settings-ui__unsaved-changes-actions {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+ margin-top: 24px;
}
@media (max-width: 960px) {
@@ -424,25 +367,20 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
}
.wc-settings-ui {
- margin: 32px 24px;
+ margin: 48px 24px 32px;
max-width: none;
}
- .wc-settings-ui__group {
- display: block;
+ .wc-settings-ui__section-card {
+ padding: 24px;
}
- .wc-settings-ui__group-header {
- margin-bottom: 16px;
- max-width: none;
- }
-
- .wc-settings-ui__group-panel {
- max-width: none;
+ .wc-settings-ui__section-header {
+ display: block;
}
- .wc-settings-ui__actions {
- margin-left: 0;
+ .wc-settings-ui__section-actions {
+ margin-top: 16px;
}
}
}