diff --git a/libs/blocks/graybox/graybox.css b/libs/blocks/graybox/graybox.css index 996d6fe560e..b115fefcc40 100644 --- a/libs/blocks/graybox/graybox.css +++ b/libs/blocks/graybox/graybox.css @@ -323,107 +323,3 @@ border-radius: 5px; } } - -/* Spectrum Switch */ -.spectrum-Switch { - display: inline-flex; - align-items: flex-start; - position: relative; - margin: auto; - min-block-size: var(--mod-switch-height, var(--spectrum-switch-min-height)); - max-inline-size: 100%; - vertical-align: top; -} - -.spectrum-Switch.gb-toggle-disabled { - pointer-events: none; - opacity: 0.5; -} - -/* .gb-toggle-disabled .spectrum-Switch { - pointer-events: none; -} */ - -.spectrum-Switch-input { - margin: 0; - box-sizing: border-box; - padding: 0; - position: absolute; - inline-size: 100%; - block-size: 100%; - inset-block-start: 0; - inset-inline-start: 0; - opacity: 0; - z-index: 1; - cursor: pointer; -} - -.spectrum-Switch-switch { - display: inline-block; - box-sizing: border-box; - position: relative; - inline-size: 26px; - margin-block: 9px; - margin-inline: 0; - flex-grow: 0; - flex-shrink: 0; - vertical-align: middle; - transition: background 0.16s ease-in-out, border 0.16s ease-in-out; - block-size: 14px; - inset-inline-start: 0; - inset-inline-end: 0; - border-radius: 7px; - background-color: rgb(225 225 225); -} - -.spectrum-Switch-switch::after, .spectrum-Switch-switch::before { - display: block; - position: absolute; - content: ""; - inset-block-start: 0; - inset-inline-start: 0; -} - -.spectrum-Switch-switch::before { - box-sizing: border-box; - transition: background 0.16s ease-in-out, border 0.16s ease-in-out, transform 0.13s ease-in-out, box-shadow 0.16s ease-in-out; - inline-size: 14px; - block-size: 14px; - border-width: 2px; - border-radius: 7px; - border-style: solid; - background-color: rgb(248 248 248); - border-color: rgb(113 113 113); -} - -.spectrum-Switch-switch::after { - border-radius: 11px; - inset-inline-end: 0; - inset-block-end: 0; - margin: 0; - transition: opacity 0.16s ease-out, margin 0.16s ease-out; -} - -.spectrum-Switch-label { - color: rgb(41 41 41); - margin-inline: 10px; - margin-block-start: 6px; - margin-block-end: 0; - font-size: 14px; - line-height: 1.3; - transition: color 0.16s ease-in-out; -} - -.spectrum-Switch-input:checked+.spectrum-Switch-switch::before { - transform: translate(calc(26px - 100%)); - border-color: rgb(80 80 80); -} - -.spectrum-Switch:hover .spectrum-Switch-input+.spectrum-Switch-switch::before { - border-color: rgb(80 80 80); - box-shadow: none; -} - -.spectrum-Switch-input:checked+.spectrum-Switch-switch { - background-color: rgb(41 41 41); -} diff --git a/libs/blocks/graybox/graybox.js b/libs/blocks/graybox/graybox.js index cfced9e67f2..7579409999b 100644 --- a/libs/blocks/graybox/graybox.js +++ b/libs/blocks/graybox/graybox.js @@ -1,4 +1,4 @@ -import { createTag, getMetadata } from '../../utils/utils.js'; +import { createTag, getMetadata, loadStyle, getConfig } from '../../utils/utils.js'; import { getModal, closeModal } from '../modal/modal.js'; import { iphoneFrame, ipadFrame } from './mobileFrames.js'; @@ -323,6 +323,8 @@ const openDeviceModal = async (e) => { }; const createGrayboxOverlayToggle = (grayboxMenu) => { + const { base } = getConfig(); + loadStyle(`${base}/blocks/graybox/switch.css`); const switchDiv = createTag('div', { class: 'spectrum-Switch' }, null, { parent: grayboxMenu }); const input = createTag('input', { type: 'checkbox', class: 'spectrum-Switch-input', id: 'gb-overlay-toggle' }, null, { parent: switchDiv }); createTag('span', { class: 'spectrum-Switch-switch' }, null, { parent: switchDiv }); diff --git a/libs/blocks/graybox/switch.css b/libs/blocks/graybox/switch.css new file mode 100644 index 00000000000..0b3000a39ff --- /dev/null +++ b/libs/blocks/graybox/switch.css @@ -0,0 +1,109 @@ +.toggle-switch { + position: absolute; + top: 15px; + right: 25px; +} + +.toggle-switch .spectrum-Switch:first-child { + margin-right: 15px; +} + +/* Spectrum Switch */ +.spectrum-Switch { + display: inline-flex; + align-items: flex-start; + position: relative; + margin: auto; + min-block-size: var(--mod-switch-height, var(--spectrum-switch-min-height)); + max-inline-size: 100%; + vertical-align: top; +} + +.spectrum-Switch.gb-toggle-disabled { + pointer-events: none; + opacity: 0.5; +} + +.spectrum-Switch-input { + margin: 0; + box-sizing: border-box; + padding: 0; + position: absolute; + inline-size: 100%; + block-size: 100%; + inset-block-start: 0; + inset-inline-start: 0; + opacity: 0; + z-index: 1; + cursor: pointer; +} + +.spectrum-Switch-switch { + display: inline-block; + box-sizing: border-box; + position: relative; + inline-size: 26px; + margin-block: 9px; + margin-inline: 0; + flex-grow: 0; + flex-shrink: 0; + vertical-align: middle; + transition: background 0.16s ease-in-out, border 0.16s ease-in-out; + block-size: 14px; + inset-inline-start: 0; + inset-inline-end: 0; + border-radius: 7px; + background-color: rgb(225 225 225); +} + +.spectrum-Switch-switch::after, .spectrum-Switch-switch::before { + display: block; + position: absolute; + content: ""; + inset-block-start: 0; + inset-inline-start: 0; +} + +.spectrum-Switch-switch::before { + box-sizing: border-box; + transition: background 0.16s ease-in-out, border 0.16s ease-in-out, transform 0.13s ease-in-out, box-shadow 0.16s ease-in-out; + inline-size: 14px; + block-size: 14px; + border-width: 2px; + border-radius: 7px; + border-style: solid; + background-color: rgb(248 248 248); + border-color: rgb(113 113 113); +} + +.spectrum-Switch-switch::after { + border-radius: 11px; + inset-inline-end: 0; + inset-block-end: 0; + margin: 0; + transition: opacity 0.16s ease-out, margin 0.16s ease-out; +} + +.spectrum-Switch-label { + color: rgb(41 41 41); + margin-inline: 10px; + margin-block-start: 6px; + margin-block-end: 0; + font-size: 14px; + line-height: 1.3; + transition: color 0.16s ease-in-out; +} + +.spectrum-Switch-input:checked+.spectrum-Switch-switch::before { + transform: translate(calc(26px - 100%)); + border-color: rgb(80 80 80); +} + +.spectrum-Switch:hover .spectrum-Switch-input+.spectrum-Switch-switch::before { + border-color: rgb(80 80 80); + box-shadow: none; +} + +.spectrum-Switch-input:checked+.spectrum-Switch-switch { + background-color: rgb(41 41 41); +} diff --git a/libs/blocks/ost/ost.js b/libs/blocks/ost/ost.js index 9121fdc29f6..8aa490be6f1 100644 --- a/libs/blocks/ost/ost.js +++ b/libs/blocks/ost/ost.js @@ -1,5 +1,7 @@ import ctaTextOption from './ctaTextOption.js'; -import { getConfig, getLocale, getMetadata, loadScript, loadStyle } from '../../utils/utils.js'; +import { + getConfig, getLocale, getMetadata, loadScript, loadStyle, createTag, +} from '../../utils/utils.js'; export const AOS_API_KEY = 'wcms-commerce-ims-user-prod'; export const CHECKOUT_CLIENT_ID = 'creative'; @@ -14,6 +16,17 @@ const OST_STYLE_URL = 'https://mas.adobe.com/studio/ost/index.css'; export const WCS_ENV = 'PROD'; export const WCS_API_KEY = 'wcms-commerce-ims-ro-user-cc'; export const WCS_LANDSCAPE = 'PUBLISHED'; +export const WCS_LANDSCAPE_DRAFT = 'DRAFT'; +export const LANDSCAPE_URL_PARAM = 'commerce.landscape'; +export const DEFAULTS_URL_PARAM = 'commerce.defaults'; + +function isMasDefaultsEnabled() { + const searchParameters = new URLSearchParams(window.location.search); + const defaults = searchParameters.get(DEFAULTS_URL_PARAM) || 'on'; + return defaults === 'on'; +} + +const masDefaultsEnabled = isMasDefaultsEnabled(); /** * Maps Franklin page metadata to OST properties. @@ -31,8 +44,10 @@ const priceDefaultOptions = { exclusive: false, }; -const updateParams = (params, key, value) => { - if (value !== priceDefaultOptions[key]) { +const updateParams = (params, key, value, cs) => { + let defaultValue = priceDefaultOptions[key]; + if (key === 'seat') defaultValue = cs !== 'INDIVIDUAL'; + if (value !== defaultValue) { params.set(key, value); } }; @@ -78,7 +93,7 @@ export const createLinkMarkup = ( forceTaxExclusive, } = options; updateParams(params, 'term', displayRecurrence); - updateParams(params, 'seat', displayPerUnit); + updateParams(params, 'seat', displayPerUnit, masDefaultsEnabled ? offer.customer_segment : null); updateParams(params, 'tax', displayTax); updateParams(params, 'old', displayOldPrice); updateParams(params, 'exclusive', forceTaxExclusive); @@ -102,18 +117,22 @@ export async function loadOstEnv() { const commerceEnv = searchParameters.get('commerce.env'); if (wcsLandscape || commerceEnv) { if (wcsLandscape) { - searchParameters.set('commerce.landscape', wcsLandscape); + searchParameters.set(LANDSCAPE_URL_PARAM, wcsLandscape); searchParameters.delete('wcsLandscape'); } if (commerceEnv?.toLowerCase() === 'stage') { - searchParameters.set('commerce.landscape', 'DRAFT'); + searchParameters.set(LANDSCAPE_URL_PARAM, WCS_LANDSCAPE_DRAFT); searchParameters.delete('commerce.env'); } window.history.replaceState({}, null, `${window.location.origin}${window.location.pathname}?${searchParameters.toString()}`); } /* c8 ignore next */ const { initService, loadMasComponent, getMasLibs, getMiloLocaleSettings, MAS_COMMERCE_SERVICE } = await import('../merch/merch.js'); - await initService(true, { 'allow-override': 'true' }); + const attributes = { 'allow-override': 'true' }; + if (isMasDefaultsEnabled) { + attributes['data-mas-ff-defaults'] = 'on'; + } + await initService(true, attributes); // Load commerce.js based on masLibs parameter await loadMasComponent(MAS_COMMERCE_SERVICE); @@ -131,7 +150,7 @@ export async function loadOstEnv() { const defaultPlaceholderOptions = Object.fromEntries([ ['term', 'displayRecurrence', 'true'], - ['seat', 'displayPerUnit', 'true'], + ['seat', 'displayPerUnit', masDefaultsEnabled ? null : 'true'], ['tax', 'displayTax'], ['old', 'displayOldPrice'], ].map(([key, targetKey, defaultValue = false]) => { @@ -178,7 +197,7 @@ export async function loadOstEnv() { window.history.replaceState({}, null, newURL); const environment = searchParameters.get('env') ?? WCS_ENV; - const landscape = searchParameters.get('commerce.landscape') ?? WCS_LANDSCAPE; + const landscape = searchParameters.get(LANDSCAPE_URL_PARAM) ?? WCS_LANDSCAPE; const owner = searchParameters.get('owner'); const referrer = searchParameters.get('referrer'); const repo = searchParameters.get('repo'); @@ -265,6 +284,41 @@ export async function loadOstEnv() { }; } +function addToggleSwitch(container, label, checked, onChange) { + const switchDiv = createTag('div', { class: 'spectrum-Switch' }, null, { parent: container }); + const input = createTag('input', { type: 'checkbox', class: 'spectrum-Switch-input', id: 'gb-overlay-toggle' }, null, { parent: switchDiv }); + createTag('span', { class: 'spectrum-Switch-switch' }, null, { parent: switchDiv }); + createTag('label', { class: 'spectrum-Switch-label', for: 'gb-overlay-toggle' }, label, { parent: switchDiv }); + input.checked = checked; + input.addEventListener('change', onChange); + return input; +} + +export function addToggleSwitches(el, ostEnv, masDefEnabled, windowObj) { + const { base } = getConfig(); + loadStyle(`${base}/blocks/graybox/switch.css`); + const toggleContainer = createTag('span', { class: 'toggle-switch' }, null, { parent: el }); + const inputLandscape = addToggleSwitch(toggleContainer, 'Draft landscape offer', ostEnv.landscape === WCS_LANDSCAPE_DRAFT, (e) => { + const url = new URL(window.location.href); + if (e.target.checked) { + url.searchParams.set(LANDSCAPE_URL_PARAM, WCS_LANDSCAPE_DRAFT); + } else { + url.searchParams.delete(LANDSCAPE_URL_PARAM); + } + windowObj.location.href = url.toString(); + }); + const inputDefaults = addToggleSwitch(toggleContainer, 'MAS defaults', masDefEnabled, (e) => { + const url = new URL(window.location.href); + if (!e.target.checked) { + url.searchParams.set(DEFAULTS_URL_PARAM, 'off'); + } else { + url.searchParams.delete(DEFAULTS_URL_PARAM); + } + windowObj.location.href = url.toString(); + }); + return [inputLandscape, inputDefaults]; +} + export default async function init(el) { el.innerHTML = '
'; @@ -281,6 +335,7 @@ export default async function init(el) { ...ostEnv, rootElement: el.firstElementChild, }); + addToggleSwitches(el, ostEnv, masDefaultsEnabled, window); } if (ostEnv.aosAccessToken) { diff --git a/test/blocks/ost/ost.test.html.js b/test/blocks/ost/ost.test.html.js index cfe743f9d81..bd87e0da727 100644 --- a/test/blocks/ost/ost.test.html.js +++ b/test/blocks/ost/ost.test.html.js @@ -1,7 +1,7 @@ import { expect } from '@esm-bundle/chai'; - +import { delay } from '../../helpers/waitfor.js'; import { mockOstDeps, unmockOstDeps } from './mocks/ost-utils.js'; -import { DEFAULT_CTA_TEXT, createLinkMarkup } from '../../../libs/blocks/ost/ost.js'; +import { DEFAULT_CTA_TEXT, createLinkMarkup, addToggleSwitches } from '../../../libs/blocks/ost/ost.js'; const perpM2M = { offer_id: 'aeb0bf53517d46e89a1b039f859cf573', @@ -306,3 +306,89 @@ describe('OST: merch link creation', () => { }); }); }); +describe('OST: toggle switches', () => { + it('change toggle switch landscape from PUBLISHED to DRAFT', async () => { + const el = document.createElement('div'); + const ostEnv = { landscape: 'PUBLISHED' }; + const windowObj = { location: {} }; + const cbs = addToggleSwitches(el, ostEnv, true, windowObj); + const cbLandscape = cbs[0]; + const cbLDefaults = cbs[1]; + + expect(cbLandscape.checked).to.be.false; + expect(cbLDefaults.checked).to.be.true; + + cbLandscape.checked = true; + cbLandscape.dispatchEvent(new Event('change')); + await delay(100); + + const url = new URL(windowObj.location.href); + expect(url.searchParams.get('commerce.landscape')).to.equal('DRAFT'); + expect(url.searchParams.get('commerce.defaults')).to.be.null; + }); + it('change toggle switch defaults on to off', async () => { + const el = document.createElement('div'); + const ostEnv = { landscape: 'PUBLISHED' }; + const windowObj = { location: {} }; + const cbs = addToggleSwitches(el, ostEnv, true, windowObj); + const cbLandscape = cbs[0]; + const cbLDefaults = cbs[1]; + + expect(cbLandscape.checked).to.be.false; + expect(cbLDefaults.checked).to.be.true; + + cbLDefaults.checked = false; + cbLDefaults.dispatchEvent(new Event('change')); + await delay(100); + + const url = new URL(windowObj.location.href); + expect(url.searchParams.get('commerce.landscape')).to.be.null; + expect(url.searchParams.get('commerce.defaults')).to.equal('off'); + }); + it('change toggle switch landscape from DRAFT to PUBLISHED', async () => { + const originalSearch = window.location.search; + window.history.replaceState({}, null, `${window.location.pathname}?commerce.landscape=DRAFT&commerce.defaults=off`); + const el = document.createElement('div'); + const ostEnv = { landscape: 'DRAFT' }; + const windowObj = { location: {} }; + const cbs = addToggleSwitches(el, ostEnv, false, windowObj); + const cbLandscape = cbs[0]; + const cbLDefaults = cbs[1]; + + expect(cbLandscape.checked).to.be.true; + expect(cbLDefaults.checked).to.be.false; + + cbLandscape.checked = false; + cbLandscape.dispatchEvent(new Event('change')); + await delay(100); + + const url = new URL(windowObj.location.href); + expect(url.searchParams.get('commerce.landscape')).to.be.null; + expect(url.searchParams.get('commerce.defaults')).to.equal('off'); + + window.history.replaceState({}, null, `${window.location.pathname}${originalSearch}`); + }); + it('change toggle switch defaults off to on', async () => { + const originalSearch = window.location.search; + window.history.replaceState({}, null, `${window.location.pathname}?commerce.landscape=DRAFT&commerce.defaults=off`); + const el = document.createElement('div'); + const ostEnv = { landscape: 'DRAFT' }; + const windowObj = { location: {} }; + const cbs = addToggleSwitches(el, ostEnv, false, windowObj); + const cbLandscape = cbs[0]; + const cbLDefaults = cbs[1]; + + expect(cbLandscape.checked).to.be.true; + expect(cbLDefaults.checked).to.be.false; + + cbLDefaults.checked = true; + cbLDefaults.dispatchEvent(new Event('change')); + await delay(100); + + const url = new URL(windowObj.location.href); + expect(url.searchParams.get('commerce.landscape')).to.equal('DRAFT'); + expect(url.searchParams.get('commerce.defaults')).to.be.null; + + window.history.replaceState({}, null, `${window.location.pathname}${originalSearch}`); + }); +});