diff --git a/.changelogs/fix-3340-certificate-editor-alignment.yml b/.changelogs/fix-3340-certificate-editor-alignment.yml new file mode 100644 index 0000000000..02796d26eb --- /dev/null +++ b/.changelogs/fix-3340-certificate-editor-alignment.yml @@ -0,0 +1,5 @@ +significance: patch +type: fixed +links: + - "#3340" +entry: Fixed certificate editor sidebar control layout. diff --git a/assets/scss/admin.scss b/assets/scss/admin.scss index 47eb2fda40..28c14a3e36 100644 --- a/assets/scss/admin.scss +++ b/assets/scss/admin.scss @@ -80,6 +80,7 @@ $llms-buttons-theme-inherit: false; @import "admin/metaboxes/metabox-students"; @import "admin/metaboxes/metabox-field-repeater"; @import "admin/metaboxes/builder-launcher"; +@import "admin/certificate-editor"; @import "admin/media-protection"; @import "admin/post-tables/llms_orders"; diff --git a/assets/scss/admin/_certificate-editor.scss b/assets/scss/admin/_certificate-editor.scss new file mode 100644 index 0000000000..37f37ab468 --- /dev/null +++ b/assets/scss/admin/_certificate-editor.scss @@ -0,0 +1,52 @@ +/** + * Certificate / achievement editor document sidebar. + * + * WP 7.0+ bumped Gutenberg TextControl height to 40px. The inner-margin + * labels used a negative top margin sized for the old shorter inputs, which + * then sat on top of the fields. Classic side metaboxes (sync awarded) also + * sat flush against the settings panel, and the full-width Sync button + * overflowed the ~280px complementary area. + */ + +.llms-certificate-doc-settings.components-panel__body { + .components-panel__body-content { + padding-bottom: 16px; + } + + .llms-certificate-sequential-id-control { + margin-bottom: 0; + } + + .llms-certificate-margin-control__label { + display: block; + margin: 4px 0 0 4px; + } +} + +.interface-complementary-area #poststuff, +.editor-sidebar #poststuff, +.edit-post-sidebar #poststuff { + box-sizing: border-box; + min-width: 0; + width: 100%; +} + +#certificate_sync, +#achievement_sync { + margin-top: 16px; + + .inside { + overflow-x: hidden; + } + + .llms-sync-action-wrap { + margin: 1em 0 0; + } + + .sync-action { + box-sizing: border-box; + display: inline-block; + max-width: 100%; + width: auto; + } +} diff --git a/includes/abstracts/llms-abstract-meta-box-user-engagement-sync.php b/includes/abstracts/llms-abstract-meta-box-user-engagement-sync.php index 64757b831a..2771954cd4 100644 --- a/includes/abstracts/llms-abstract-meta-box-user-engagement-sync.php +++ b/includes/abstracts/llms-abstract-meta-box-user-engagement-sync.php @@ -295,8 +295,8 @@ private function sync_action() { ob_start(); ?>

-

- +

+

+
- { getDesc( index ) } + + { getDesc( index ) } +
); } @@ -104,11 +113,17 @@ export default function MarginsControl( { margins } ) { return ( -
- { margins.map( ( margin, index ) => ( ) ) } +
+ { margins.map( ( margin, index ) => ( + + ) ) }
); diff --git a/src/js/admin-certificate-editor/plugin/sequential-id-control.js b/src/js/admin-certificate-editor/plugin/sequential-id-control.js index dd0f92c84a..6091c11e74 100644 --- a/src/js/admin-certificate-editor/plugin/sequential-id-control.js +++ b/src/js/admin-certificate-editor/plugin/sequential-id-control.js @@ -24,18 +24,23 @@ export default function SequentialIdControl( { sequentialId } ) { } return ( - { - setId( val ); - editCertificate( 'sequential_id', val ); - } } - help={ __( 'Used for the {sequential_id} merge code when generating a certificate from this template.', 'lifterlms' ) } - /> +
+ { + setId( val ); + editCertificate( 'sequential_id', val ); + } } + help={ __( + 'Used for the {sequential_id} merge code when generating a certificate from this template.', + 'lifterlms' + ) } + /> +
); } diff --git a/tests/e2e/specs/admin/certificate-editor-layout.spec.js b/tests/e2e/specs/admin/certificate-editor-layout.spec.js new file mode 100644 index 0000000000..6f1ace19d1 --- /dev/null +++ b/tests/e2e/specs/admin/certificate-editor-layout.spec.js @@ -0,0 +1,163 @@ +/** + * Certificate editor document sidebar layout (WP 7.0+) + * + * WP 7.0 raised default Gutenberg TextControl height to 40px. Inner-margin + * labels must sit below those inputs, and the Sync awarded metabox must not + * overflow the document sidebar. + */ + +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +/** + * Create a published certificate template and award it once so the Sync button renders. + * + * @param {import('@wordpress/e2e-test-utils-playwright').RequestUtils} requestUtils Request utils. + * @return {Promise} Template post ID. + */ +async function createTemplateWithAward( requestUtils ) { + const suffix = Date.now(); + const template = await requestUtils.rest( { + method: 'POST', + path: '/llms/v1/certificates', + data: { + title: `Certificate Layout ${ suffix }`, + content: '

Certificate body.

', + status: 'publish', + }, + } ); + + const me = await requestUtils.rest( { + method: 'GET', + path: '/wp/v2/users/me', + } ); + + await requestUtils.rest( { + method: 'POST', + path: '/llms/v1/awarded-certificates', + data: { + student_id: me.id, + certificate_id: template.id, + }, + } ); + + return template.id; +} + +/** + * Open the Certificate Template document sidebar and ensure Settings is expanded. + * + * @param {import('@wordpress/e2e-test-utils-playwright').Editor} editor Editor utils. + * @param {import('@playwright/test').Page} page Playwright page. + * @return {Promise} + */ +async function openCertificateSettings( editor, page ) { + await editor.openDocumentSettingsSidebar(); + + const documentTab = page.getByRole( 'tab', { name: 'Certificate Template' } ); + if ( await documentTab.count() ) { + await documentTab.click(); + } + + const settingsPanel = page.locator( '.llms-certificate-doc-settings' ); + await expect( settingsPanel ).toBeVisible(); + + const toggle = settingsPanel.locator( '.components-panel__body-toggle' ); + if ( await toggle.count() ) { + const expanded = await toggle.getAttribute( 'aria-expanded' ); + if ( expanded === 'false' ) { + await toggle.click(); + } + } +} + +test.describe( 'Admin/CertificateEditorLayout', () => { + + test( 'Margin labels sit below inputs and the Sync panel fits the sidebar', async ( { admin, editor, page, requestUtils } ) => { + await admin.visitAdminPage( '/' ); + + const isWp70 = await page.evaluate( () => document.body.classList.contains( 'llms-wp-version-gte-70' ) ); + test.skip( ! isWp70, 'WP 7.0+ form-control sizing only' ); + + const templateId = await createTemplateWithAward( requestUtils ); + await admin.editPost( templateId ); + + await openCertificateSettings( editor, page ); + + const topInput = page.locator( '#llms-certificate-control--margin--top' ); + await expect( topInput ).toBeVisible(); + + const topLabel = page.locator( '.llms-certificate-margin-control' ).filter( { + has: page.locator( '#llms-certificate-control--margin--top' ), + } ).locator( '.llms-certificate-margin-control__label' ); + + await expect( topLabel ).toHaveText( 'Top' ); + + const overlap = await page.evaluate( () => { + const sides = [ 'top', 'right', 'bottom', 'left' ]; + return sides.map( ( side ) => { + const input = document.querySelector( `#llms-certificate-control--margin--${ side }` ); + const label = input?.closest( '.llms-certificate-margin-control' ) + ?.querySelector( '.llms-certificate-margin-control__label' ); + if ( ! input || ! label ) { + return { side, missing: true }; + } + const inputBox = input.getBoundingClientRect(); + const labelBox = label.getBoundingClientRect(); + return { + side, + inputBottom: inputBox.bottom, + labelTop: labelBox.top, + gap: labelBox.top - inputBox.bottom, + inputHeight: inputBox.height, + }; + } ); + } ); + + for ( const side of overlap ) { + expect( side.missing, `${ side.side } margin control missing` ).toBeFalsy(); + expect( side.inputHeight, `${ side.side } input height` ).toBeGreaterThanOrEqual( 36 ); + expect( side.gap, `${ side.side } label overlaps input` ).toBeGreaterThanOrEqual( 0 ); + } + + const sequentialHelp = page.locator( '.llms-certificate-sequential-id-control .components-base-control__help' ); + await expect( sequentialHelp ).toBeVisible(); + + const syncBox = page.locator( '#certificate_sync' ); + await expect( syncBox ).toBeVisible(); + await syncBox.scrollIntoViewIfNeeded(); + + const syncSpacing = await page.evaluate( () => { + const help = document.querySelector( '.llms-certificate-sequential-id-control .components-base-control__help' ); + const heading = document.querySelector( '#certificate_sync .postbox-header, #certificate_sync h2.hndle' ); + if ( ! help || ! heading ) { + return null; + } + return heading.getBoundingClientRect().top - help.getBoundingClientRect().bottom; + } ); + + expect( syncSpacing ).not.toBeNull(); + expect( syncSpacing ).toBeGreaterThanOrEqual( 12 ); + + const syncButton = syncBox.locator( 'a.sync-action' ); + await expect( syncButton ).toBeVisible(); + + const sidebarFit = await page.evaluate( () => { + const sidebar = document.querySelector( '.interface-complementary-area, .editor-sidebar, .edit-post-sidebar' ); + const button = document.querySelector( '#certificate_sync a.sync-action' ); + if ( ! sidebar || ! button ) { + return null; + } + const sidebarBox = sidebar.getBoundingClientRect(); + const buttonBox = button.getBoundingClientRect(); + return { + scrollOverflow: sidebar.scrollWidth - sidebar.clientWidth, + buttonOverflow: buttonBox.right - sidebarBox.right, + }; + } ); + + expect( sidebarFit ).not.toBeNull(); + expect( sidebarFit.scrollOverflow ).toBeLessThanOrEqual( 1 ); + expect( sidebarFit.buttonOverflow ).toBeLessThanOrEqual( 1 ); + } ); + +} );