diff --git a/i18n/en.json b/i18n/en.json index 40129365c..c415fb154 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -43,12 +43,15 @@ "citizen-tagline-ns-help": "Help page", "citizen-tagline-ns-category": "Category page", "citizen-tagline-user-regdate": "Joined $1", - "prefs-citizen-theme-label": "Theme", - "prefs-citizen-theme-option-auto": "Auto", - "prefs-citizen-theme-option-light": "Light", - "prefs-citizen-theme-option-dark": "Dark", "prefs-citizen-fontsize-label": "Font size", "prefs-citizen-pagewidth-label": "Page width", "prefs-citizen-lineheight-label": "Line height", - "prefs-citizen-resetbutton-label": "Reset to default" + "prefs-citizen-resetbutton-label": "Reset to default", + + "skin-theme-name": "Color", + "skin-theme-description": "Reduces the light emitted by device screens.", + "skin-theme-day-label": "Day", + "skin-theme-night-label": "Night", + "skin-theme-os-label": "Automatic", + "skin-theme-exclusion-notice": "This page is always in day mode." } diff --git a/i18n/qqq.json b/i18n/qqq.json index 29a660c76..91fe88715 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -46,12 +46,12 @@ "citizen-tagline-ns-help": "Tagline for pages in help namespace", "citizen-tagline-ns-category": "Tagline for pages in category namespace", "citizen-tagline-user-regdate": "Label for registration date in taglines on userpages", - "prefs-citizen-theme-label": "Tooltip for the theme dropdown in Special:Preferences", - "prefs-citizen-theme-option-auto": "Label for the auto theme option", - "prefs-citizen-theme-option-light": "Label for the light theme option", - "prefs-citizen-theme-option-dark": "Label for the dark theme option", "prefs-citizen-fontsize-label": "Label for the font size settings", "prefs-citizen-pagewidth-label": "Label for the page width settings", "prefs-citizen-lineheight-label": "Label for the line height settings", - "prefs-citizen-resetbutton-label": "Label for the reset button that restore default settings" + "prefs-citizen-resetbutton-label": "Label for the reset button that restore default settings", + "skin-theme-name": "Label for setting that allows you to change theme.", + "skin-theme-description": "Description for theme.", + "skin-theme-day-label": "Label for light theme (standard mode).", + "skin-theme-night-label": "Label for night theme (dark mode)." } diff --git a/includes/Hooks/SkinHooks.php b/includes/Hooks/SkinHooks.php index 105366b62..73a150fcc 100644 --- a/includes/Hooks/SkinHooks.php +++ b/includes/Hooks/SkinHooks.php @@ -67,6 +67,7 @@ public function onBeforePageDisplay( $out, $skin ): void { $script = file_get_contents( MW_INSTALL_PATH . '/skins/Citizen/resources/skins.citizen.scripts/inline.js' ); $script = Html::inlineScript( $script ); + // TODO: Consider turning on cache after this is more stable $script = RL\ResourceLoader::filter( 'minify-js', $script, [ 'cache' => false ] ); $out->addHeadItem( 'skin.citizen.inline', $script ); } diff --git a/includes/Partials/Theme.php b/includes/Partials/Theme.php index 2d66b5507..a9ff2a10a 100644 --- a/includes/Partials/Theme.php +++ b/includes/Partials/Theme.php @@ -25,6 +25,12 @@ namespace MediaWiki\Skins\Citizen\Partials; +const CLIENTPREFS_THEME_MAP = [ + 'auto' => 'os', + 'light' => 'day', + 'dark' => 'night' +]; + /** * Theme switcher partial of Skin Citizen */ @@ -42,7 +48,12 @@ public function setSkinTheme( array &$options ) { // Set theme to site theme $theme = $this->getConfigValue( 'CitizenThemeDefault' ) ?? 'auto'; - // Add HTML class based on theme set + // Legacy class to be deprecated $out->addHtmlClasses( 'skin-citizen-' . $theme ); + + // Add HTML class based on theme set + if ( CLIENTPREFS_THEME_MAP[ $theme ] ) { + $out->addHtmlClasses( 'skin-theme-clientpref-' . $theme ); + } } } diff --git a/resources/skins.citizen.preferences/addPortlet.polyfill.js b/resources/skins.citizen.preferences/addPortlet.polyfill.js new file mode 100644 index 000000000..b972d343b --- /dev/null +++ b/resources/skins.citizen.preferences/addPortlet.polyfill.js @@ -0,0 +1,88 @@ +/** + * TODO: Revisit when we move to MW 1.43 and the interface is more stable + */ + +/** + * Creates default portlet. + * Based on Vector + * + * @param {Element} portlet + * @return {Element} + */ +function addDefaultPortlet( portlet ) { + const ul = portlet.querySelector( 'ul' ); + if ( !ul ) { + return portlet; + } + ul.classList.add( 'citizen-menu__content-list' ); + const label = portlet.querySelector( 'label' ); + if ( label ) { + const labelDiv = document.createElement( 'div' ); + labelDiv.classList.add( 'citizen-menu__heading' ); + labelDiv.innerHTML = label.textContent || ''; + portlet.insertBefore( labelDiv, label ); + label.remove(); + } + let wrapper = portlet.querySelector( 'div:last-child' ); + if ( wrapper ) { + ul.remove(); + wrapper.appendChild( ul ); + wrapper.classList.add( 'citizen-menu__content' ); + } else { + wrapper = document.createElement( 'div' ); + wrapper.classList.add( 'citizen-menu__content' ); + ul.remove(); + wrapper.appendChild( ul ); + portlet.appendChild( wrapper ); + } + portlet.classList.add( 'citizen-menu' ); + return portlet; +} + +/** + * Polyfill for mw.util.addPortlet for < MW 1.42 + * + * @return {Element} + */ +function addPortlet() { + if ( mw.util.addPortlet ) { + return addDefaultPortlet( mw.util.addPortlet ); + } + + return function ( id, label, before ) { + const portlet = document.createElement( 'div' ); + portlet.classList.add( 'mw-portlet', 'mw-portlet-' + id, 'emptyPortlet', + // Additional class is added to allow skins to track portlets added via this mechanism. + 'mw-portlet-js' + ); + portlet.id = id; + if ( label ) { + const labelNode = document.createElement( 'label' ); + labelNode.textContent = label; + portlet.appendChild( labelNode ); + } + const listWrapper = document.createElement( 'div' ); + const list = document.createElement( 'ul' ); + listWrapper.appendChild( list ); + portlet.appendChild( listWrapper ); + if ( before ) { + let referenceNode; + try { + referenceNode = document.querySelector( before ); + } catch ( e ) { + // CSS selector not supported by browser. + } + if ( referenceNode ) { + const parentNode = referenceNode.parentNode; + parentNode.insertBefore( portlet, referenceNode ); + } else { + return null; + } + } + mw.hook( 'util.addPortlet' ).fire( portlet, before ); + return addDefaultPortlet( portlet ); + }; +} + +/** @module addPortlet */ +module.exports = addPortlet; diff --git a/resources/skins.citizen.preferences/clientPreferences.js b/resources/skins.citizen.preferences/clientPreferences.js new file mode 100644 index 000000000..327963b17 --- /dev/null +++ b/resources/skins.citizen.preferences/clientPreferences.js @@ -0,0 +1,348 @@ +/** + * @typedef {Object} ClientPreference + * @property {string[]} options that are valid for this client preference + * @property {string} preferenceKey for registered users. + * @property {string} [type] defaults to radio. Supported: radio, switch + * @property {Function} [callback] callback executed after a client preference has been modified. + */ + +/** + * @typedef {Object} PreferenceOption + * @property {string} label + * @property {string} value + */ + +const addPortlet = require( './addPortlet.polyfill.js' )(); +const clientPrefs = require( './clientPrefs.polyfill.js' )(); + +/** + * Get the list of client preferences that are active on the page, including hidden. + * + * @return {string[]} of active client preferences + */ +function getClientPreferences() { + return Array.from( document.documentElement.classList ).filter( + ( className ) => className.match( /-clientpref-/ ) + ).map( ( className ) => className.split( '-clientpref-' )[ 0 ] ); +} + +/** + * Check if the feature is excluded from the current page. + * + * @param {string} featureName + * @return {boolean} + */ +function isFeatureExcluded( featureName ) { + return document.documentElement.classList.contains( featureName + '-clientpref-excluded' ); +} + +/** + * Get the list of client preferences that are active on the page and not hidden. + * + * @param {Record} config + * @return {string[]} of user facing client preferences + */ +function getVisibleClientPreferences( config ) { + const active = getClientPreferences(); + // Order should be based on key in config.json + return Object.keys( config ).filter( ( key ) => active.indexOf( key ) > -1 ); +} + +/** + * @param {string} featureName + * @param {string} value + * @param {Record} config + */ +function toggleDocClassAndSave( featureName, value, config ) { + const pref = config[ featureName ]; + const callback = pref.callback || ( () => {} ); + clientPrefs.set( featureName, value ); + callback(); +} + +/** + * @param {string} featureName + * @param {string} value + * @return {string} + */ +const getInputId = ( featureName, value ) => `skin-client-pref-${ featureName }-value-${ value }`; + +/** + * @param {string} type + * @param {string} featureName + * @param {string} value + * @return {HTMLInputElement} + */ +function makeInputElement( type, featureName, value ) { + const input = document.createElement( 'input' ); + const name = `skin-client-pref-${ featureName }-group`; + const id = getInputId( featureName, value ); + input.name = name; + input.id = id; + input.type = type; + if ( type === 'checkbox' ) { + input.checked = value === '1'; + } else { + input.value = value; + } + input.setAttribute( 'data-event-name', id ); + return input; +} + +/** + * @param {string} featureName + * @param {string} value + * @return {HTMLLabelElement} + */ +function makeLabelElement( featureName, value ) { + const label = document.createElement( 'label' ); + // eslint-disable-next-line mediawiki/msg-doc + label.textContent = mw.msg( `${ featureName }-${ value }-label` ); + label.setAttribute( 'for', getInputId( featureName, value ) ); + return label; +} + +/** + * Create an element that informs users that a feature is not functional + * on a given page. This message is hidden by default and made visible in + * CSS if a specific exclusion class exists. + * + * @param {string} featureName + * @return {HTMLElement} + */ +function makeExclusionNotice( featureName ) { + const p = document.createElement( 'p' ); + // eslint-disable-next-line mediawiki/msg-doc + const noticeMessage = mw.message( `${ featureName }-exclusion-notice` ); + p.classList.add( 'exclusion-notice', `${ featureName }-exclusion-notice` ); + p.textContent = noticeMessage.text(); + return p; +} + +/** + * @param {Element} parent + * @param {string} featureName + * @param {string} value + * @param {string} currentValue + * @param {Record} config + */ +function appendRadioToggle( parent, featureName, value, currentValue, config ) { + const input = makeInputElement( 'radio', featureName, value ); + // input.classList.add( 'cdx-radio__input' ); + input.classList.add( 'citizen-client-prefs-radio__input' ); + if ( currentValue === value ) { + input.checked = true; + } + + if ( isFeatureExcluded( featureName ) ) { + input.disabled = true; + } + + const icon = document.createElement( 'span' ); + // icon.classList.add( 'cdx-radio__icon' ); + icon.classList.add( 'citizen-client-prefs-radio__icon' ); + const label = makeLabelElement( featureName, value ); + // label.classList.add( 'cdx-radio__label' ); + label.classList.add( 'citizen-client-prefs-radio__label' ); + const container = document.createElement( 'div' ); + // container.classList.add( 'cdx-radio' ); + container.classList.add( 'citizen-client-prefs-radio' ); + container.appendChild( input ); + container.appendChild( icon ); + container.appendChild( label ); + parent.appendChild( container ); + input.addEventListener( 'change', () => { + toggleDocClassAndSave( featureName, value, config ); + } ); +} + +/** + * @param {Element} form + * @param {string} featureName + * @param {HTMLElement} labelElement + * @param {string} currentValue + * @param {Record} config + */ +function appendToggleSwitch( form, featureName, labelElement, currentValue, config ) { + const input = makeInputElement( 'checkbox', featureName, currentValue ); + // input.classList.add( 'cdx-toggle-switch__input' ); + input.classList.add( 'citizen-client-prefs-toggle-switch__input' ); + const switcher = document.createElement( 'span' ); + // switcher.classList.add( 'cdx-toggle-switch__switch' ); + switcher.classList.add( 'citizen-client-prefs-toggle-switch__switch' ); + const grip = document.createElement( 'span' ); + // grip.classList.add( 'cdx-toggle-switch__switch__grip' ); + grip.classList.add( 'citizen-client-prefs-toggle-switch__switch__grip' ); + switcher.appendChild( grip ); + const label = labelElement || makeLabelElement( featureName, currentValue ); + // label.classList.add( 'cdx-toggle-switch__label' ); + label.classList.add( 'citizen-client-prefs-toggle-switch__label' ); + const toggleSwitch = document.createElement( 'span' ); + // toggleSwitch.classList.add( 'cdx-toggle-switch' ); + toggleSwitch.classList.add( 'citizen-client-prefs-toggle-switch' ); + toggleSwitch.appendChild( input ); + toggleSwitch.appendChild( switcher ); + toggleSwitch.appendChild( label ); + input.addEventListener( 'change', () => { + toggleDocClassAndSave( featureName, input.checked ? '1' : '0', config ); + } ); + form.appendChild( toggleSwitch ); +} + +/** + * @param {string} className + * @return {Element} + */ +function createRow( className ) { + const row = document.createElement( 'div' ); + row.setAttribute( 'class', className ); + return row; +} + +/** + * Get the label for the feature. + * + * @param {string} featureName + * @return {MwMessage} + */ +const getFeatureLabelMsg = ( featureName ) => + // eslint-disable-next-line mediawiki/msg-doc + mw.message( `${ featureName }-name` ); + +/** + * adds a toggle button + * + * @param {string} featureName + * @param {Record} config + * @return {Element|null} + */ +function makeControl( featureName, config ) { + const pref = config[ featureName ]; + if ( !pref ) { + return null; + } + const currentValue = clientPrefs.get( featureName ); + // The client preference was invalid. This shouldn't happen unless a gadget + // or script has modified the documentElement. + if ( typeof currentValue === 'boolean' ) { + return null; + } + const row = createRow( '' ); + const form = document.createElement( 'form' ); + const type = pref.type || 'radio'; + switch ( type ) { + case 'radio': + pref.options.forEach( ( value ) => { + appendRadioToggle( form, featureName, value, currentValue, config ); + } ); + break; + case 'switch': { + const labelElement = document.createElement( 'label' ); + labelElement.textContent = getFeatureLabelMsg( featureName ).text(); + appendToggleSwitch( form, featureName, labelElement, currentValue, config ); + break; + } default: + throw new Error( 'Unknown client preference! Only switch or radio are supported.' ); + } + row.appendChild( form ); + + if ( isFeatureExcluded( featureName ) ) { + const exclusionNotice = makeExclusionNotice( featureName ); + row.appendChild( exclusionNotice ); + } + return row; +} + +/** + * @param {Element} parent + * @param {string} featureName + * @param {Record} config + */ +function makeClientPreference( parent, featureName, config ) { + const labelMsg = getFeatureLabelMsg( featureName ); + // If the user is not debugging messages and no language exists, + // exit as its a hidden client preference. + if ( !labelMsg.exists() && mw.config.get( 'wgUserLanguage' ) !== 'qqx' ) { + return; + } else { + const id = `skin-client-prefs-${ featureName }`; + const portlet = addPortlet( id, labelMsg.text() ); + const labelElement = portlet.querySelector( 'label' ); + // eslint-disable-next-line mediawiki/msg-doc + const descriptionMsg = mw.message( `${ featureName }-description` ); + if ( descriptionMsg.exists() ) { + const desc = document.createElement( 'span' ); + desc.classList.add( 'skin-client-pref-description' ); + desc.textContent = descriptionMsg.text(); + if ( labelElement && labelElement.parentNode ) { + labelElement.appendChild( desc ); + } + } + const row = makeControl( featureName, config ); + parent.appendChild( portlet ); + if ( row ) { + const tmp = mw.util.addPortletLink( id, '', '' ); + // create a dummy link + if ( tmp ) { + const link = tmp.querySelector( 'a' ); + if ( link ) { + link.replaceWith( row ); + } + } + } + } +} + +/** + * Fills the client side preference dropdown with controls. + * + * @param {string} selector of element to fill with client preferences + * @param {Record} config + * @return {Promise} + */ +function render( selector, config ) { + const node = document.querySelector( selector ); + if ( !node ) { + return Promise.reject(); + } + return new Promise( ( resolve ) => { + getVisibleClientPreferences( config ).forEach( ( pref ) => { + makeClientPreference( node, pref, config ); + } ); + mw.requestIdleCallback( () => { + resolve( node ); + } ); + } ); +} + +/** + * @param {string} clickSelector what to click + * @param {string} renderSelector where to render + * @param {Record} config + */ +function bind( clickSelector, renderSelector, config ) { + let enhanced = false; + const chk = /** @type {HTMLInputElement} */ ( + document.querySelector( clickSelector ) + ); + if ( !chk ) { + return; + } + if ( chk.checked ) { + render( renderSelector, config ); + enhanced = true; + } else { + chk.addEventListener( 'input', () => { + if ( enhanced ) { + return; + } + render( renderSelector, config ); + enhanced = true; + } ); + } +} +module.exports = { + bind, + toggleDocClassAndSave, + render +}; diff --git a/resources/skins.citizen.preferences/clientPreferences.json b/resources/skins.citizen.preferences/clientPreferences.json new file mode 100644 index 000000000..e0586f2ac --- /dev/null +++ b/resources/skins.citizen.preferences/clientPreferences.json @@ -0,0 +1,6 @@ +{ + "skin-theme": { + "options": [ "os", "day", "night" ], + "preferenceKey": "citizen-theme" + } +} diff --git a/resources/skins.citizen.preferences/clientPrefs.localStorage.js b/resources/skins.citizen.preferences/clientPrefs.polyfill.js similarity index 97% rename from resources/skins.citizen.preferences/clientPrefs.localStorage.js rename to resources/skins.citizen.preferences/clientPrefs.polyfill.js index ef9c1fc23..fb4a0c0a6 100644 --- a/resources/skins.citizen.preferences/clientPrefs.localStorage.js +++ b/resources/skins.citizen.preferences/clientPrefs.polyfill.js @@ -1,5 +1,6 @@ /** - * mw.user.clientPrefs modified to only use localStorage + * Polyfill for mw.user.clientPrefs for < MW 1.42 + * Modified to use localStorage for all users * TODO: Revisit when we move to MW 1.43 and the interface is more stable */ diff --git a/resources/skins.citizen.preferences/skins.citizen.preferences.js b/resources/skins.citizen.preferences/skins.citizen.preferences.js index cc2bc9ddd..2dd71fb91 100644 --- a/resources/skins.citizen.preferences/skins.citizen.preferences.js +++ b/resources/skins.citizen.preferences/skins.citizen.preferences.js @@ -18,7 +18,7 @@ const CLIENTPREFS_THEME_MAP = { dark: 'night' }; -const clientPrefs = require( './clientPrefs.localStorage.js' )(); +const clientPrefs = require( './clientPrefs.polyfill.js' )(); /** * Set the value of the input element @@ -56,7 +56,6 @@ function setIndicator( key, value ) { */ function convertForForm( pref ) { return { - theme: pref.theme, fontsize: Number( pref.fontsize.slice( 0, -1 ) ) / 10 - 8, pagewidth: Number( pref.pagewidth.slice( 0, -2 ) ) / 120 - 6, lineheight: ( pref.lineheight - 1 ) * 10 @@ -105,7 +104,6 @@ function getPref() { }; const pref = { - theme: mw.storage.get( PREFIX_KEY + 'theme' ), fontsize: mw.storage.get( PREFIX_KEY + 'fontsize' ) || initFontSize(), pagewidth: mw.storage.get( PREFIX_KEY + 'pagewidth' ) || rootStyle.getPropertyValue( '--width-layout' ), lineheight: mw.storage.get( PREFIX_KEY + 'lineheight' ) || rootStyle.getPropertyValue( '--line-height' ) @@ -125,17 +123,12 @@ function setPref() { formData = Object.fromEntries( new FormData( document.getElementById( CLASS + '-form' ) ) ), currentPref = convertForForm( getPref() ), newPref = { - theme: formData[ CLASS + '-theme' ], fontsize: Number( formData[ CLASS + '-fontsize' ] ), pagewidth: Number( formData[ CLASS + '-pagewidth' ] ), lineheight: Number( formData[ CLASS + '-lineheight' ] ) }; - if ( currentPref.theme !== newPref.theme ) { - mw.storage.set( PREFIX_KEY + 'theme', newPref.theme ); - clientPrefs.set( 'skin-theme', CLIENTPREFS_THEME_MAP[ newPref.theme ] ); - - } else if ( currentPref.fontsize !== newPref.fontsize ) { + if ( currentPref.fontsize !== newPref.fontsize ) { const formattedFontSize = ( newPref.fontsize + 8 ) * 10 + '%'; mw.storage.set( PREFIX_KEY + 'fontsize', formattedFontSize ); setIndicator( 'fontsize', formattedFontSize ); @@ -166,7 +159,6 @@ function setPref() { * @return {void} */ function resetPref() { - // Do not reset theme as its default value is defined somewhere else const keys = [ 'fontsize', 'pagewidth', 'lineheight' ]; // Remove style @@ -239,15 +231,12 @@ function togglePanel() { toggle = document.getElementById( CLASS + '-toggle' ), panel = document.getElementById( CLASS + '-panel' ), form = document.getElementById( CLASS + '-form' ), - themeOption = document.getElementById( CLASS + '-theme' ), resetButton = document.getElementById( CLASS + '-resetbutton' ); if ( !panel.classList.contains( CLASS_PANEL_ACTIVE ) ) { panel.classList.add( CLASS_PANEL_ACTIVE ); toggle.setAttribute( 'aria-expanded', true ); form.addEventListener( 'input', setPref ); - // Some browser doesn't fire input events when checking radio buttons - themeOption.addEventListener( 'click', setPref ); resetButton.addEventListener( 'click', resetPref ); window.addEventListener( 'click', dismissOnClickOutside ); window.addEventListener( 'keydown', dismissOnEscape ); @@ -255,7 +244,6 @@ function togglePanel() { panel.classList.remove( CLASS_PANEL_ACTIVE ); toggle.setAttribute( 'aria-expanded', false ); form.removeEventListener( 'input', setPref ); - themeOption.removeEventListener( 'click', setPref ); resetButton.removeEventListener( 'click', resetPref ); window.removeEventListener( 'click', dismissOnClickOutside ); window.removeEventListener( 'keydown', dismissOnEscape ); @@ -270,10 +258,6 @@ function togglePanel() { function getMessages() { const keys = [ 'preferences', - 'prefs-citizen-theme-label', - 'prefs-citizen-theme-option-auto', - 'prefs-citizen-theme-option-light', - 'prefs-citizen-theme-option-dark', 'prefs-citizen-fontsize-label', 'prefs-citizen-pagewidth-label', 'prefs-citizen-lineheight-label', @@ -313,19 +297,10 @@ function initPanel( event ) { // TODO: Use ES6 template literals when RL does not screw up multiline const panel = template.render( data ).get()[ 1 ]; - // The priorities is as follow: - // 1. User-set theme (localStorage) - // 2. Site default theme (wgCitizenThemeDefault) - // 3. Fallback to auto - const currentTheme = prefValue.theme || - require( './config.json' ).wgCitizenThemeDefault || - 'auto'; - // Attach panel after button event.currentTarget.parentNode.insertBefore( panel, event.currentTarget.nextSibling ); // Set up initial state - document.getElementById( CLASS + '-theme__input__' + currentTheme ).checked = true; keys.forEach( ( key ) => { setIndicator( key, pref[ key ] ); setInputValue( key, prefValue[ key ] ); @@ -334,6 +309,30 @@ function initPanel( event ) { togglePanel(); event.currentTarget.addEventListener( 'click', togglePanel ); event.currentTarget.removeEventListener( 'click', initPanel ); + + const clientPreferenceSelector = '#citizen-client-prefs'; + const clientPreferenceExists = document.querySelectorAll( clientPreferenceSelector ).length > 0; + if ( clientPreferenceExists ) { + const clientPreferences = require( /** @type {string} */ ( './clientPreferences.js' ) ); + const clientPreferenceConfig = ( require( './clientPreferences.json' ) ); + + // Support legacy skin-citizen-* class + // TODO: Remove it in the future version after sufficient time + clientPreferenceConfig[ 'skin-theme' ].callback = () => { + const LEGACY_THEME_CLASSES = [ + 'skin-citizen-auto', + 'skin-citizen-light', + 'skin-citizen-dark' + ]; + const legacyThemeKey = Object.keys( CLIENTPREFS_THEME_MAP ).find( ( key ) => { + return CLIENTPREFS_THEME_MAP[ key ] === clientPrefs.get( 'skin-theme' ); + } ); + document.documentElement.classList.remove( ...LEGACY_THEME_CLASSES ); + document.documentElement.classList.add( `skin-citizen-${ legacyThemeKey }` ); + }; + + clientPreferences.render( clientPreferenceSelector, clientPreferenceConfig ); + } } /** diff --git a/resources/skins.citizen.preferences/skins.citizen.preferences.less b/resources/skins.citizen.preferences/skins.citizen.preferences.less index 5ef77c06a..ccc37a968 100644 --- a/resources/skins.citizen.preferences/skins.citizen.preferences.less +++ b/resources/skins.citizen.preferences/skins.citizen.preferences.less @@ -17,31 +17,15 @@ width: 100%; } - &__value { - font-weight: var( --font-weight-medium ); - color: var( --color-base--emphasized ); + &__title { + font-size: var( --font-size-x-small ); + color: var( --color-base--subtle ); + letter-spacing: 0.05em; } - } - &-theme { - &-option { - flex-grow: 1; - padding: 0.5rem 1rem; + &__value { font-weight: var( --font-weight-medium ); - text-align: center; - white-space: nowrap; - cursor: pointer; - border: 2px solid var( --border-color-base ); - border-radius: var( --border-radius--medium ); - - &:hover { - border-color: var( --color-primary--hover ); - box-shadow: var( --box-shadow-card ); - } - - &:active { - border-color: var( --color-primary--active ); - } + color: var( --color-base--emphasized ); } } @@ -89,39 +73,6 @@ margin: var( --space-md ) 0; } - &-theme { - &-option { - &-light { - color: ~'hsl( var( --color-primary__h ), 30%, 20% )'; - background: ~'hsl( var( --color-primary__h ), 25%, 94% )'; - } - - &-dark { - color: ~'hsl( var( --color-primary__h ), 10%, 75% )'; - background: ~'hsl( var( --color-primary__h ), 20%, 10% )'; - } - } - - fieldset { - display: flex; - gap: 0.5rem; - width: 100%; - padding: 0; - margin-top: 0.25rem; - } - - // Let label be the radio button - input { - display: none; - - &:checked { - + label { - border-color: var( --color-primary ); - } - } - } - } - &-resetbutton { width: 100%; padding: var( --space-sm ) var( --space-md ); @@ -145,6 +96,66 @@ } } +// New clientPrefs styles +.citizen-client-prefs { + &-radio { + &__input { + // Hide radio button because we use label as button + display: none; + + &:checked { + ~ .citizen-client-prefs-radio__label { + border-color: var( --color-primary ); + } + } + } + + &__label { + display: block; + padding: var( --space-xs ) var( --space-md ); + font-weight: var( --font-weight-medium ); + border: 2px solid var( --border-color-base ); + border-radius: var( --border-radius--medium ); + } + } +} + +#citizen-client-prefs { + .citizen-menu { + &__content { + padding: 0 var( --space-md ); + } + } +} + +#skin-client-prefs-skin-theme { + form { + display: grid; + // This is not the best because it does not adapt to the text length but will revisit later + grid-template-columns: 1fr 1fr; + gap: var( --space-xxs ); + text-align: center; + } + + label { + background: var( --color-surface-0 ); + + &[ for='skin-client-pref-skin-theme-value-day' ] { + // color-base of day theme + color: ~'hsl( var( --color-primary__h ), 30%, 20% )'; + // color-surface-0 of day theme + background: ~'hsl( var( --color-primary__h ), 30%, 96% )'; + } + + &[ for='skin-client-pref-skin-theme-value-night' ] { + // color-base of night theme + color: ~'hsl( var( --color-primary__h ), 25%, 80% )'; + // color-surface-0 of night theme + background: ~'hsl( var( --color-primary__h ), 20%, 10% )'; + } + } +} + @media ( hover: hover ) { .citizen-pref:hover .citizen-pref__button .citizen-ui-icon::before { transform: rotate3d( 0, 0, 1, 90deg ); diff --git a/resources/skins.citizen.preferences/templates/preferences.mustache b/resources/skins.citizen.preferences/templates/preferences.mustache index ed5dc10aa..98774e8bf 100644 --- a/resources/skins.citizen.preferences/templates/preferences.mustache +++ b/resources/skins.citizen.preferences/templates/preferences.mustache @@ -11,21 +11,8 @@