diff --git a/app/assets/variables.css b/app/assets/variables.css new file mode 100644 index 0000000..f7c1893 --- /dev/null +++ b/app/assets/variables.css @@ -0,0 +1,4 @@ +:root { + --pos-color-button-primary-background: #008000; + --pos-color-button-primary-hover-background: #00a000; +} \ No newline at end of file diff --git a/app/pos-modules.json b/app/pos-modules.json index c81625a..36b45d7 100644 --- a/app/pos-modules.json +++ b/app/pos-modules.json @@ -1,5 +1,6 @@ { "modules": { - "core": "1.5.2" + "core": "1.5.2", + "common-styling": "1.29.0" } } \ No newline at end of file diff --git a/app/pos-modules.lock.json b/app/pos-modules.lock.json index c81625a..36b45d7 100644 --- a/app/pos-modules.lock.json +++ b/app/pos-modules.lock.json @@ -1,5 +1,6 @@ { "modules": { - "core": "1.5.2" + "core": "1.5.2", + "common-styling": "1.29.0" } } \ No newline at end of file diff --git a/app/views/layouts/application.liquid b/app/views/layouts/application.liquid new file mode 100644 index 0000000..b0d2d3b --- /dev/null +++ b/app/views/layouts/application.liquid @@ -0,0 +1,19 @@ + + + + + + Contact Us Form + + {% render 'modules/common-styling/init', reset: true %} + + + + + {{ content_for_layout }} + + {% liquid + theme_render_rc 'modules/common-styling/toasts' + %} + + diff --git a/app/views/partials/contacts/form.liquid b/app/views/partials/contacts/form.liquid index b06675d..1ee4ce6 100644 --- a/app/views/partials/contacts/form.liquid +++ b/app/views/partials/contacts/form.liquid @@ -1,19 +1,21 @@ -

Contact Us

-
- -
- - - {% if contact.errors.email != blank %} -

{{ contact.errors.email | join: ', ' }}

- {% endif %} -
-
- - {% if contact.errors.body != blank %} -

{{ contact.errors.body | join: ', ' }}

- {% endif %} -
- -
- +

Contact Us

+ +
+ + +
+ + + {% render 'modules/common-styling/forms/error_list', name: 'email', errors: contact.errors.email %} +
+ +
+ + + {% render 'modules/common-styling/forms/error_list', name: 'body', errors: contact.errors.body %} +
+ +
+ +
+
\ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-debug.js b/modules/common-styling/public/assets/js/pos-debug.js new file mode 100644 index 0000000..513f099 --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-debug.js @@ -0,0 +1,32 @@ +/* + initializes pos modules +*/ + + + +// purpose: debuging method that outputs data into the console +// arguments: should the function run (bool) +// id of the module for which the debug data is printed (string) +// textual information about what is happening (string) +// optional data printed and parsed in the console (any) +// ------------------------------------------------------------------------ +pos.modules.debug = (active, moduleId, information, data = '') => { + if(active || pos.debug){ + + if(moduleId === 'event'){ + console.log(`%c⚑%c${information}`, 'padding: .2em .5em; background-color: #000; color: #fff; border-radius: 4px;', 'margin-inline-start: .5em; padding: .2em .5em; background-color: #000; color: #fff; border-radius: 4px;', data); + } else { + + const stringToColor = (string, saturation = 100, lightness = 80) => { + let hash = 0; + for (let i = 0; i < string.length; i++) { + hash = string.charCodeAt(i) + ((hash << 5) - hash); + hash = (hash & hash) * 100; + } + return `hsl(${(hash % 360)}, ${saturation}%, ${lightness}%)`; + } + + console.log(`%c${moduleId}%c ${information}`, `padding: .2em .5em; background-color: ${stringToColor(moduleId)}; border-radius: 4px;`, 'all: revert;', data); + } + } +}; \ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-forms-multiselect.js b/modules/common-styling/public/assets/js/pos-forms-multiselect.js new file mode 100644 index 0000000..9d3f066 --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-forms-multiselect.js @@ -0,0 +1,339 @@ +/* + handles the multiselect input + + usage: + new pos.modules.multiselect(container); +*/ + + + +// purpose: shows the floating alert +// arguments: container with the password input (dom node) +// settings that will overwrite the default ones (object) +// ************************************************************************ +window.pos.modules.multiselect = function(container, settings){ + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: settings that are being used across the module + // ------------------------------------------------------------------------ + module.settings = {}; + // container for the component (dom node) + module.settings.container = container; + // id of the input (string) + module.settings.id = container.id; + // if the popup is opened (bool) + module.settings.opened = false; + // class name to add to container when the popup is opened (string) + module.settings.openedClass = 'pos-form-multiselect-opened'; + // button that will toggle the popup (dom node) + module.settings.toggleButton = container.querySelector('.pos-form-input'); + // available options list (dom node) + module.settings.optionsNode = settings.optionsNode || container.querySelector('.pos-form-multiselect-list'); + // available options (object) + module.settings.availableOptions = {}; + // currently selected options (array) + module.settings.selected = []; + // list of selected items (dom node) + module.settings.selectedListNode = container.querySelector('.pos-form-multiselect-selected-list'); + // template for selected items (dom node) + module.settings.selectedTemplate = container.querySelector('.pos-form-multiselect-selected-template'); + // filter input (dom node) + module.settings.filterInput = container.querySelector('.pos-form-multiselect-filter'); + // class list to add to items that are filtered out + module.settings.filteredClass = 'pos-form-multiselect-list-item-filtered'; + // class name to add to the container if filtering outputs no results + module.settings.noResultsClass = 'pos-form-multiselect-filtered-empty'; + // element that counts the combined number of selected options (dom node) + module.settings.combinedNode = container.querySelector('.pos-form-multiselect-selected-item-combined'); + // button that clears all of the selected options (dom node) + module.settings.clearNode = container.querySelector('.pos-form-multiselect-clear'); + // if you want to enable debug mode (bool) + module.settings.debug = false; + // if at least one option is required to be selected (bool) + module.settings.required = container.hasAttribute('required'); + + + + // purpose: initializes the module + // ------------------------------------------------------------------------ + module.init = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Initializing', module.settings.container); + + // toggle opened class on the container when clicking the input (but not the selected items) + module.settings.toggleButton.addEventListener('keydown', event => { + if(event.key === 'Enter' || event.key === ' '){ + if(event.target.matches('.pos-form-multiselect-clear')){ + event.preventDefault(); + module.clear(); + module.settings.toggleButton.focus(); + } else { + event.preventDefault(); + module.toggle(); + } + } + }); + + module.settings.toggleButton.addEventListener('click', event => { + if(!event.composedPath().some(element => element.classList && element.classList.contains('pos-form-multiselect-selected-item'))){ + event.preventDefault(); + module.toggle(); + } + }); + + // prepare the available options list + module.settings.optionsNode.querySelectorAll('li').forEach(item => { + const value = item.querySelector('[type="checkbox"]').value; + const selected = item.querySelector('[type="checkbox"]').checked; + const label = item.querySelector('label').textContent; + module.settings.availableOptions[value] = { label: label, selected: selected, value: value }; + + // update the selected items with items that are preselected from BE + if(selected){ + module.add(value); + } + }); + + pos.modules.debug(module.settings.debug, module.settings.id, 'Available options prepared', module.settings.availableOptions); + pos.modules.debug(module.settings.debug, module.settings.id, 'Preselected items updated', module.settings.selected); + + // react to chagnes in the options list + module.settings.optionsNode.addEventListener('change', event => { + if(event.target.matches('input[type="checkbox"]')) { + if(module.settings.selected.includes(event.target.value)){ + module.remove(event.target.value); + } else { + module.add(event.target.value); + } + + pos.modules.debug(module.settings.debug, module.settings.id, 'Currently selected items', module.settings.selected); + } + }); + + // react to filtering the list + if(module.settings.filterInput){ + module.settings.filterInput?.addEventListener('input', event => { + module.filter(event.target.value); + }); + } + + // clearing all the options + if(module.settings.clearNode){ + module.settings.clearNode.addEventListener('click', () => { + module.clear(); + }); + } + + // report validity + if(module.settings.optionsNode.querySelector('[type="checkbox"]').form && module.settings.required){ + module.settings.optionsNode.querySelector('[type="checkbox"]').form.addEventListener('submit', event => { + const options = module.settings.optionsNode.querySelectorAll('[type="checkbox"]'); + + if(module.settings.optionsNode.querySelectorAll(':checked').length === 0){ + event.preventDefault(); + module.open(); + module.settings.toggleButton.setAttribute('aria-invalid', true); + options[0].setCustomValidity('At least one option must be choosen'); + options[0].reportValidity(); + } else { + module.settings.toggleButton.removeAttribute('aria-invalid'); + } + }); + } + }; + + + // purpose: opens the popup + // output: adds class to the container and sets the bool variable + // ------------------------------------------------------------------------ + module.open = () => { + module.settings.container.classList.add(module.settings.openedClass); + module.settings.opened = true; + module.settings.toggleButton.setAttribute('aria-expanded', true); + + document.addEventListener('keydown', module.reactToEscape); + document.addEventListener('click', module.reactToClickOutside); + document.addEventListener('focusin', module.reactToFocusOutside); + + pos.modules.debug(module.settings.debug, module.settings.id, 'Popup opened'); + }; + + + // purpose: closes the popup + // output: removes class to the container and sets the bool variable + // ------------------------------------------------------------------------ + module.close = () => { + module.settings.container.classList.remove(module.settings.openedClass); + module.settings.opened = false; + module.settings.toggleButton.setAttribute('aria-expanded', false); + + document.removeEventListener('keydown', module.reactToEscape); + document.removeEventListener('click', module.reactToClickOutside); + document.removeEventListener('focusin', module.reactToFocusOutside); + + // clean the filter input + if(module.settings.filterInput){ + module.settings.filterInput.value = ''; + module.filter(''); + } + + pos.modules.debug(module.settings.debug, module.settings.id, 'Popup closed'); + }; + + +// purpose: event handler that closes the popup when esacpe key is pressed +// arguments: event object (dom event) +// ------------------------------------------------------------------------ + module.reactToEscape = event => { + if(event.key === 'Escape'){ + pos.modules.debug(module.settings.debug, module.settings.id, 'Escape key pressed, closing the multiselect popup'); + + module.close(); + module.settings.toggleButton.focus(); + } + }; + + +// purpose: event handler that closes the popup when clicked outside of it +// arguments: event object (dom event) +// ------------------------------------------------------------------------ + module.reactToClickOutside = event => { + if(!event.composedPath().includes(module.settings.container)){ + pos.modules.debug(module.settings.debug, module.settings.id, 'Clicked outside the multiselect, closing the popup'); + + module.close(); + } + }; + + +// purpose: event handler that closes the popup when clicked outside of it +// arguments: event object (dom event) +// ------------------------------------------------------------------------ + module.reactToFocusOutside = event => { + if(!container.contains(event.target)){ + pos.modules.debug(module.settings.debug, module.settings.id, 'Focused outside the multiselect, closing the popup'); + + module.close(); + } + }; + + + // purpose: toggles the popup + // output: toggles the class on the container and sets the bool variable + // ------------------------------------------------------------------------ + module.toggle = () => { + if(module.settings.opened){ + module.close(); + } else { + module.open(); + } + }; + + + // purpose: add an item to the selected items list + // arguments: value to be added (string) + // output: updates module.settings.selected and updates the UI + // ------------------------------------------------------------------------ + module.add = (value) => { + module.settings.selected.push(value); + pos.modules.debug(module.settings.debug, module.settings.id, `Added to selected items: ${value}`); + + const item = module.settings.selectedTemplate.content.cloneNode(true); + + item.querySelector('.pos-form-multiselect-selected-item-remove').htmlFor = `pos-multiselect-${module.settings.id}-${value}`; + item.querySelector('.pos-form-multiselect-selected-item-remove .pos-button-label').textContent += ` '${module.settings.availableOptions[value].label}'`; + item.querySelector('.pos-form-multiselect-selected-item-label').textContent = module.settings.availableOptions[value].label; + + module.updateCounter(); + module.settings.selectedListNode.append(item); + + pos.modules.debug(module.settings.debug, module.settings.id, `Showed in the input: ${module.settings.availableOptions[value].label}`); + }; + + + // purpose: removes an item from the selected items list + // arguments: value to be removed (string) + // output: updates module.settings.selected and updates the UI + // ------------------------------------------------------------------------ + module.remove = (value) => { + module.settings.selected = module.settings.selected.filter(item => item !== value); + pos.modules.debug(module.settings.debug, module.settings.id, `Removed from selected items: ${value}`); + + module.settings.selectedListNode.querySelector(`.pos-form-multiselect-selected-item-remove[for="pos-multiselect-${module.settings.id}-${value}"]`).closest('.pos-form-multiselect-selected-item').remove(); + pos.modules.debug(module.settings.debug, module.settings.id, `Removed from the input: ${module.settings.availableOptions[value].label}`); + + module.updateCounter(); + }; + + + // purpose: updates the selected number counter (used with combine_selected option) + // output: updates the UI + // ------------------------------------------------------------------------ + module.updateCounter = () => { + if(module.settings.combinedNode){ + module.settings.combinedNode.querySelector('i').textContent = module.settings.selected.length; + } + + pos.modules.debug(module.settings.debug, module.settings.id, `Updated the counter of selected items to ${module.settings.selected.length}`); + }; + + + // purpose: clears all selected items + // output: updates the UI and unchecks all the checkboxes + // ------------------------------------------------------------------------ + module.clear = () => { + module.settings.optionsNode.querySelectorAll('[type="checkbox"]').forEach(element => { + element.checked = false; + }); + module.settings.selected = []; + module.settings.selectedListNode.replaceChildren(); + module.updateCounter(); + + pos.modules.debug(module.settings.debug, module.settings.id, `Cleared all of the selected items`); + }; + + + // purpose: filters dom nodes by given phrase + // arguments: phrase to filter by (string) + // output: adds a class to each item that does not match the phrase + // and adds a class to the container if no results are found + // ------------------------------------------------------------------------ + module.filter = (phrase) => { + phrase = phrase.toLowerCase(); + + const allItems = module.settings.optionsNode.querySelectorAll('li'); + + allItems.forEach(item => { + const label = item.querySelector('label').textContent.toLowerCase(); + if(label.includes(phrase)){ + item.classList.remove(module.settings.filteredClass); + } else { + item.classList.add(module.settings.filteredClass); + } + }); + + if(allItems.length === module.settings.optionsNode.querySelectorAll(`.${module.settings.filteredClass}`).length){ + module.settings.container.classList.add(module.settings.noResultsClass); + } else { + module.settings.container.classList.remove(module.settings.noResultsClass); + } + + pos.modules.debug(module.settings.debug, module.settings.id, `Filtered options by phrase: ${phrase}`); + }; + + + // purpose: checks if at least one option is selected when required + // output: triggers default browser form validation + // ------------------------------------------------------------------------ + module.validate = () => { + const checkboxes = module.settings.optionsNode.querySelectorAll('[type="checkbox"]'); + + checkboxes.setCustomValidity(checkboxes.querySelector('input:checked').length === 0 ? 'Choose one' : ''); + }; + + + + module.init(); + +}; \ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-forms-password.js b/modules/common-styling/public/assets/js/pos-forms-password.js new file mode 100644 index 0000000..84af39b --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-forms-password.js @@ -0,0 +1,121 @@ +/* + handles the password input and its strength meter + + usage: + new window.pos.modules.password(container, settings); +*/ + + + +// purpose: shows the floating alert +// arguments: container with the password input (dom node) +// settings that will overwrite the default ones (object) +// ************************************************************************ +window.pos.modules.password = function(container, settings){ + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: settings that are being used across the module + // ------------------------------------------------------------------------ + module.settings = {}; + // container for the component (dom node) + module.settings.container = container; + // prefix for the class that will indicate the strength of the password (string) + module.settings.classPrefix = 'pos-form-password-strength-' + // password input element (dom node) + module.settings.input = container.querySelector('input[type="password"]'); + // id of the input (string) + module.settings.id = module.settings.input.id; + // current input type switched between 'password' and 'text' (string) + module.settings.type = 'password'; + // value of the input (string) + module.settings.password = settings.password || ''; + // current password strength from 0 to 3 (integer) + module.settings.strength = 0; + // toggle button to show/hide the password (dom node) + module.settings.toggle = container.querySelector('.pos-form-password-toggle'); + // class name to add when the password is shown (string) + module.settings.showClass = 'pos-form-password-shown'; + // if you want to enable debug mode (bool) + module.settings.debug = false; + + + + // purpose: initializes the module + // ------------------------------------------------------------------------ + module.init = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Initializing', module.settings.container); + + module.calculateStrength(module.settings.input.value); + module.updateStrengthMeter(module.settings.strength); + + module.settings.input.addEventListener('input', event => { + module.calculateStrength(event.target.value); + module.updateStrengthMeter(module.settings.strength); + }); + + module.settings.toggle.addEventListener('click', event => { + this.toggleVisibility(); + }); + }; + + + + // purpose: checks how strong the password is and updates the 'strength' variable + // arguments: password to check the strength of (string) + // returns: strength of the password as numeric value from 0 to 3 (integer) + // ------------------------------------------------------------------------ + module.calculateStrength = (password) => { + module.settings.strength = 0; + + if(password.length > 5) { + module.settings.strength++; + } + + if(password.match(/[0-9]/) && password.match(/[A-Z]/)){ + module.settings.strength++; + } + + if(password.match(/[!@#$%^&*(),.?:{}|<>=\+\-_]/)){ + module.settings.strength++; + } + + pos.modules.debug(module.settings.debug, module.settings.id, `Recalculated password strength to ${module.settings.strength}`); + + return module.settings.strength; + }; + + + // purpose: updates the password strength meter + // arguments: calculated strength of the password (integer) + // ------------------------------------------------------------------------ + module.updateStrengthMeter = (strength) => { + module.settings.container.classList.remove(`${module.settings.classPrefix}0`, `${module.settings.classPrefix}1`, `${module.settings.classPrefix}2`, `${module.settings.classPrefix}3`); + module.settings.container.classList.add(`${module.settings.classPrefix}${strength}`); + + pos.modules.debug(module.settings.debug, module.settings.id, `Updated strength meter class to ${module.settings.classPrefix}${strength}`); + }; + + + // purpose: toggles password visibility between masked and plain text + // ------------------------------------------------------------------------ + module.toggleVisibility = () => { + if(module.settings.type === 'password'){ + module.settings.type = 'text'; + module.settings.input.setAttribute('type', 'text'); + pos.modules.debug(module.settings.debug, module.settings.id, 'Showing password in plain text'); + } else { + module.settings.type = 'password'; + module.settings.input.setAttribute('type', 'password'); + pos.modules.debug(module.settings.debug, module.settings.id, 'Masking password'); + } + + module.settings.container.classList.toggle(module.settings.showClass); + }; + + + + module.init(); + +}; \ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-load.js b/modules/common-styling/public/assets/js/pos-load.js new file mode 100644 index 0000000..3aeb314 --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-load.js @@ -0,0 +1,81 @@ +/* + automatically loads content from endpoint + and places in in the container when the trigger is clicked + + usage: new load({ + trigger: [dom node], + endpoint: [string]. + target: [string] + }); +*/ + + + +export function load(userSettings = {}){ + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: settings that are being used across the module + // ------------------------------------------------------------------------ + module.settings = {}; + // module id (string) + module.settings.id = userSettings?.id || `load-${userSettings.target}`; + // element that triggers the loading (dom node) + module.settings.trigger = userSettings?.trigger || null; + // url of the page to load (string) + module.settings.endpoint = userSettings.endpoint; + // selector for the container to load the content into (string) + module.settings.target = userSettings.target; + // do you want to replace or append the content (string) + module.settings.method = userSettings.method || 'replace'; + // trigger to run the loading process (string) + module.settings.triggerType = userSettings.triggerType || 'click'; + // if you want to enable debug mode that logs to console (bool) + module.settings.debug = userSettings.debug || false; + + + // purpose: initializes the module + // ------------------------------------------------------------------------ + module.init = async function(){ + module.settings.trigger.addEventListener(module.settings.triggerType, module.load); + }; + + + // purpose: fetch the data and load it into the container + // output: updates the container content + // ------------------------------------------------------------------------ + module.load = async function(){ + pos.modules.debug(module.settings.debug, module.settings.id, 'Loading frame', module.settings); + + fetch(module.settings.endpoint, { + method: 'GET' + }) + .then(response => response.text()) + .then(html => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Frame loaded successfully', module.settings); + + if(module.settings.method === 'replace'){ + document.querySelector(module.settings.target).innerHTML = html; + pos.modules.debug(module.settings.debug, module.settings.id, 'Replaced the container content with fetched data', module.settings); + } + + document.dispatchEvent(new CustomEvent('pos-frame-loaded', { bubbles: true, detail: { target: module.settings.target, content: html } })); + pos.modules.debug(module.settings.debug, 'event', `pos-frame-loaded`, { target: module.settings.target, content: html }); + }); + }; + + + // purpose: removed event listeners and cleans up the module + // ------------------------------------------------------------------------ + module.destroy = function(){ + module.settings.trigger.removeEventListener('click', module.load); + pos.modules.debug(module.settings.debug, module.settings.id, 'Destroyed module', module.settings); + module.settings = {}; + }; + + + + module.init(); + +}; \ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-popover.js b/modules/common-styling/public/assets/js/pos-popover.js new file mode 100644 index 0000000..89225aa --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-popover.js @@ -0,0 +1,232 @@ +/* + handles focus for popover menus and provides fallback for firefox lacking anchor positioning + + usage: +*/ + + + +// purpose: traps focus inside the popover menu +// arguments: +// ************************************************************************ +window.pos.modules.popover = function(container, userSettings = {}){ + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: settings that are being used across the module + // ------------------------------------------------------------------------ + module.settings = {}; + // notifications container (dom node) + module.settings.container = container || document.querySelector('.pos-popover'); + // popover trigger (dom node) + module.settings.trigger = module.settings.container.querySelector('[popovertarget]'); + // id used to mark the module (string) + module.settings.id = module.settings.trigger.getAttribute('popovertarget'); + // popover content (dom node) + module.settings.popover = module.settings.container.querySelector('[popover]'); + // if the popover is opened (bool) + module.settings.opened = false; + // menu element inside the popover (dom node) + module.settings.menu = module.settings.popover.matches('menu') ? module.settings.popover : module.settings.popover.querySelector('menu'); + // to enable debug mode (bool) + module.settings.debug = (userSettings?.debug) ? userSettings.debug : false; + + + + // purpose: initializes the component + // ------------------------------------------------------------------------ + module.init = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Initializing popover menu', module.settings.container); + + module.settings.popover.addEventListener('beforetoggle', event => { + if(event.newState == 'open'){ + module.settings.opened = true; + pos.modules.debug(module.settings.debug, module.settings.id, 'Popover opened', module.settings.container); + + document.dispatchEvent(new CustomEvent('pos-popover-opened', { bubbles: true, detail: { target: module.settings.popover, id: module.settings.id } })); + pos.modules.debug(module.settings.debug, 'event', 'pos-popover-opened', { target: module.settings.popover, id: module.settings.id }); + + // support keyboard navigation + if(module.settings.menu){ + document.addEventListener('keydown', module.keyboard); + } + } else { + module.settings.opened = false; + pos.modules.debug(module.settings.debug, module.settings.id, 'Popover closed', module.settings.container); + + document.dispatchEvent(new CustomEvent('pos-popover-closed', { bubbles: true, detail: { target: module.settings.popover, id: module.settings.id } })); + pos.modules.debug(module.settings.debug, 'event', 'pos-popover-closed', { target: module.settings.popover, id: module.settings.id }); + + // disable keyboard navigation + if(module.settings.menu){ + document.removeEventListener('keydown', module.keyboard); + } + } + }); + + // if the popover is triggered by keyboard navigation, focus the first element + if(module.settings.menu){ + module.settings.trigger.addEventListener('keyup', event => { + if(event.keyCode === 32 || event.key === 'Enter'){ + if(!module.settings.opened){ + module.settings.popover.addEventListener('toggle', () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Opened using keyboard', module.settings.container); + if(module.settings.opened){ + module.focusFirstMenuItem(); + } + }, { once: true }); + } + } + }); + } + + // if user uses tab to navigate through the menu, close the popover when the focus leaves + if(module.settings.menu){ + function hideWhenOutOfFocus(event){ + if(!module.settings.popover.contains(event.relatedTarget)){ + pos.modules.debug(module.settings.debug, module.settings.id, 'Popover lost focus, closing', module.settings.container); + module.settings.popover.hidePopover(); + } + } + + module.settings.popover.addEventListener('beforetoggle', event => { + if(event.newState == 'open'){ + module.settings.popover.addEventListener('focusout', hideWhenOutOfFocus); + } else { + module.settings.popover.removeEventListener('focusout', hideWhenOutOfFocus); + } + }); + } + + // polyfill for Firefox and Safari lacking support for anchor positioning + // can be deleted from code as is when all browsers support it + if(!('anchorName' in document.documentElement.style)){ + module.settings.popover.addEventListener('toggle', event => { + if(event.newState == 'open'){ + module.positionPopoverFallback(); + window.addEventListener('resize', popoverReposition); + } else { + window.removeEventListener('resize', popoverReposition); + } + }); + + // reposition popover when browser window is resized + let popoverRepositionDebounce; + + function popoverReposition(){ + clearTimeout(popoverRepositionDebounce); + + popoverRepositionDebounce = setTimeout(() => { + module.positionPopoverFallback(); + + pos.modules.debug(module.settings.debug, module.settings.id, 'Browser window resized, repositioning the popover', module.settings.container); + }, 100); + } + } + }; + + + // purpose: handles keyboard navigation + // ------------------------------------------------------------------------ + module.keyboard = event => { + if(event.key === 'ArrowDown'){ + event.preventDefault(); + + if(module.settings.menu.contains(document.activeElement)){ + if(document.activeElement.closest('li').nextElementSibling){ + module.focusNextMenuItem(); + } else { + pos.modules.debug(module.settings.debug, module.settings.id, 'There is no next menu item', module.settings.container); + module.focusFirstMenuItem(); + } + } else { + module.focusFirstMenuItem(); + } + } + + if(event.key === 'ArrowUp'){ + event.preventDefault(); + + if(module.settings.menu.contains(document.activeElement)){ + if(document.activeElement.closest('li').previousElementSibling){ + module.focusPreviousMenuItem(); + } else { + pos.modules.debug(module.settings.debug, module.settings.id, 'There is no previous menu item', module.settings.container); + module.focusLastMenuItem(); + } + } else { + module.focusLastMenuItem(); + } + } + + if(event.key === 'Home'){ + event.preventDefault(); + module.focusFirstMenuItem(); + } + + if(event.key === 'End'){ + event.preventDefault(); + module.focusLastMenuItem(); + } + }; + + + // purpose: focuses first menu item + // ------------------------------------------------------------------------ + module.focusFirstMenuItem = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Focusing first menu item', module.settings.container); + module.settings.menu.querySelector('li:first-child a, li:first-child button').focus(); + }; + + // purpose: focuses last menu item + // ------------------------------------------------------------------------ + module.focusLastMenuItem = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Focusing last menu item', module.settings.container); + module.settings.menu.querySelector('li:last-child a, li:last-child button').focus(); + }; + + // purpose: focuses menu item that is next to the currently focused one + // ------------------------------------------------------------------------ + module.focusNextMenuItem = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Focusing next available menu item', module.settings.container); + document.activeElement.closest('li').nextElementSibling.querySelector('a, button').focus(); + }; + + // purpose: focuses menu item that is previous to the currently focused one + // ------------------------------------------------------------------------ + module.focusPreviousMenuItem = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Focusing previous available menu item', module.settings.container); + document.activeElement.closest('li').previousElementSibling.querySelector('a, button').focus(); + }; + + + // purpose: positions the popover relative to the trigger element + // ------------------------------------------------------------------------ + module.positionPopoverFallback = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'This browser does not support anchor positioning, setting the position manually', module.settings.container); + + const triggerSize = module.settings.trigger.getBoundingClientRect(); + const popoverSize = module.settings.popover.getBoundingClientRect(); + + module.settings.popover.style.position = 'absolute'; + + // position to right + if(triggerSize.left - popoverSize.width > 0){ + module.settings.popover.style.right = window.innerWidth - triggerSize.right + 'px'; + } + // position to left + else if(triggerSize.left + popoverSize.width < window.innerWidth){ + module.settings.popover.style.left = triggerSize.left + 'px'; + } + // position to center + else { + module.settings.popover.style.left = triggerSize.left + (triggerSize.width - popoverSize.width) / 2 + 'px'; + } + }; + + + + module.init(); + +}; \ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-position-popover-polyfill.js b/modules/common-styling/public/assets/js/pos-position-popover-polyfill.js new file mode 100644 index 0000000..4f4ed68 --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-position-popover-polyfill.js @@ -0,0 +1,37 @@ +/* + positions the header popover menus relative to the trigger element + can be removed when all browsers support anchor positioning +*/ + + +// header popovers (dom nodes) +const popovers = document.querySelectorAll('.pos-community-header [popover]'); + +// start by positioning the popovers when page loads +popovers.forEach(popover => { + positionHeaderPopoverFallback(popover, popover.parentElement.querySelector('[popovertarget]')); +}); + +// reposition each popover when browser widnow is resized +let headerPopoverDebounce; +window.addEventListener('resize', () => { + clearTimeout(headerPopoverDebounce); + + headerPopoverDebounce = setTimeout(() => { + popovers.forEach(popover => { + positionHeaderPopoverFallback(popover, popover.parentElement.querySelector('[popovertarget]')); + }); + }, 100); +}); + + +// purpose: sets the absolute position of the popover relative to the trigger element +// arguments: popover container (dom node), +// trigger element that opens the popover (dom node) +// output: sets the left and top position of the popover +// ------------------------------------------------------------------------ +function positionHeaderPopoverFallback(popover, trigger){ + popover.style.position = 'absolute'; + popover.style.right = window.innerWidth - (trigger.getBoundingClientRect()).right + 'px'; + popover.style.top = `${trigger.bottom}px`; +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/js/pos-toast.js b/modules/common-styling/public/assets/js/pos-toast.js new file mode 100644 index 0000000..6710f72 --- /dev/null +++ b/modules/common-styling/public/assets/js/pos-toast.js @@ -0,0 +1,126 @@ +/* + handles showing the floating notifications + + usage: + new window.pos.modules.toast('type', 'message'); + or + let notification = new window.pos.modules.toast([type], [message]) + notification.remove(); + + types: + error + success + info +*/ + + + +// purpose: shows the floating alert +// arguments: type of the message (string) +// the message to show (string) +// settings to overwrite the defaults (object) +// ************************************************************************ +window.pos.modules.toast = function(type, message, userSettings){ + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: settings that are being used across the module + // ------------------------------------------------------------------------ + module.settings = {}; + // notifications container (dom node) + module.settings.container = document.querySelector('#pos-toasts'); + // id used to mark the module (string) + module.settings.id = 'toast' + // the html template to be used for notifications (dom node) + module.settings.template = module.settings.container.querySelector('#pos-toast-template'); + // the selector for the text content in the template (string) + module.settings.contentSelector = '.pos-toast-content'; + // the selector for the button that closes the notification (string) + module.settings.closeSelector = '.pos-toast-close'; + // the notification in dom (dom object) + module.settings.notification = null; + // if you want to overwrite the default autohide (bool) + module.settings.autohide = (userSettings?.autohide !== undefined) ? userSettings.autohide : (type === 'success') ? true : false; + // if you want a delay before the notification appears, miliseconds (int) + module.settings.delay = (userSettings?.delay) ? userSettings.delay : false; + // to enable debug mode (bool) + module.settings.debug = (userSettings?.debug) ? userSettings.debug : false; + + let autoHideTimeout = null; + + + + // purpose: initializes the component + // ------------------------------------------------------------------------ + module.init = () => { + pos.modules.debug(module.settings.debug, module.settings.id, 'Initializing', module.settings.container); + + if(module.settings.delay){ + setTimeout(() => { + module.show(); + }, module.settings.delay); + } else { + module.show(); + } + + // auto hide the message when it is a success + if(module.settings.autohide){ + autoHideTimeout = setTimeout(() => { + module.hide(); + }, (module.settings.debug) ? 700 : 5000); + } + + }; + + + // purpose: shows the notification + // ------------------------------------------------------------------------ + module.show = () => { + // clone the template + module.settings.notification = module.settings.template.content.firstElementChild.cloneNode(true); + + // add class corresponding to the severity + module.settings.notification.classList.add(`pos-toast-${type}`); + + // apply the message to content + module.settings.notification.querySelector(module.settings.contentSelector).innerHTML = message; + + // set the option to close notification when clicking on close button + module.settings.notification.querySelector(module.settings.closeSelector).addEventListener('click', () => { + module.hide(); + }, {once: true}); + + // add the class that will animate the appearing + module.settings.notification.classList.add('pos-toast-loading'); + + // when we append the template to the container we are loosing the reference so we need to get it back + module.settings.notification = module.settings.container.appendChild(module.settings.notification); + + pos.modules.debug(module.settings.debug, module.settings.id, `Showed toast notification, type: ${type}, message: ${message}`); + }; + + + // purpose: hides the notification + // ------------------------------------------------------------------------ + module.hide = () => { + // we don't need the autohide feature anymore + clearTimeout(autoHideTimeout); + + // add a class that will animate removing the node + module.settings.notification.classList.add('pos-toast-unloading'); + + // remove the node from DOM as it's not needed anymore + module.settings.notification.addEventListener('animationend', () => { + module.settings.notification.remove(); + }); + + pos.modules.debug(module.settings.debug, module.settings.id, `Hidden and removed toast notification, type: ${type}, message: ${message}`); + }; + + + module.init(); + +}; + +document.dispatchEvent(new Event('pos-modules-toast-ready')); diff --git a/modules/common-styling/public/assets/style-guide/styleguide.css b/modules/common-styling/public/assets/style-guide/styleguide.css new file mode 100644 index 0000000..8a2ec02 --- /dev/null +++ b/modules/common-styling/public/assets/style-guide/styleguide.css @@ -0,0 +1,641 @@ +/* layout +============================================================================ */ +.styleguide { + max-width: var(--pos-size-page); + margin: 0 auto; + padding: var(--pos-padding-page); + display: grid; + grid-template-columns: min-content minmax(600px, 1fr); + gap: calc(var(--pos-padding-page) * 5); +} + +article + article { + margin-block-start: var(--pos-gap-section-section); +} + +.styleguide-columns { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--pos-gap-card-card); + align-items: start; +} + + .styleguide-columns + .styleguide-columns { + margin-block-start: var(--pos-gap-section-elements); + } + + @media (max-width: 1000px) { + .styleguide-columns { + grid-template-columns: 1fr; + } + } + + +/* common elements +============================================================================ */ +/* code samples */ +code { + font-family: monospace; + font-size: 1rem; +} + +p code { + padding-inline: .3em; + + border-radius: 4px; + background-color: var(--pos-color-highlight-background); + + color: var(--pos-color-highlight-text); +} + +.styleguide-code { + position: relative; + + background-color: var(--pos-color-content-background); + border: 1px solid var(--pos-color-frame); + border-radius: var(--pos-radius-panel); +} + + * + .styleguide-code { + margin-block: var(--pos-gap-text-text); + } + + .styleguide-code:last-child { + margin-block-end: 0; + } + + .styleguide-code pre { + padding: var(--pos-padding-card); + overflow-x: auto; + + white-space: pre; + } + + .styleguide-code pre:focus-visible { + outline: 2px solid var(--pos-color-focused); + outline-offset: 4px; + } + +/* rendered output examples */ +.styleguide-example { + padding: var(--pos-padding-card); + display: block; + + border: 1px dashed var(--pos-color-frame); + border-radius: 1rem; +} + +.styleguide-example:has(+ .styleguide-code) { + border-radius: 1rem 1rem 0 0; +} + +.styleguide-example + .styleguide-code { + margin-block: -1px; + + border-start-start-radius: 0; + border-start-end-radius: 0; +} + +/* copy button */ +.styleguide-copy { + width: 16px; + height: 16px; + position: absolute; + inset-block-start: calc(var(--pos-padding-card) / 4); + inset-inline-end: calc(var(--pos-padding-card) / 4); + + cursor: pointer; + + color: var(--pos-color-content-text-supplementary); +} + + .styleguide-copy:hover { + color: var(--pos-color-interactive-hover); + } + + .styleguide-copy:focus-visible { + outline: 2px solid var(--pos-color-focused); + outline-offset: 4px; + } + + .styleguide-copy .styleguide-button-label { + position: absolute; + inset-inline-start: -100vw; + } + + .styleguide-copy svg { + position: absolute; + inset: 0; + + pointer-events: none; + + fill: currentColor; + + transition: scale .2s ease-in-out; + } + + .styleguide-copy-icon-done { + scale: 0; + } + + .styleguide-copy-done .styleguide-copy-icon-default { + scale: 0; + } + + .styleguide-copy-done .styleguide-copy-icon-done { + scale: 1; + + color: var(--pos-color-confirmation); + } + +/* headings */ +.pos-heading-2, +.pos-heading-3 { + margin-block-end: .2em; +} + +.styleguide-heading-4 { + margin-block-start: 1.5em; + margin-block-end: .4em; +} + +.styleguide-subheading { + margin-block-start: 1.5em; + margin-block-end: -1px; + padding: var(--pos-gap-tag-tag); + display: inline-block; + + border-radius: var(--pos-radius-panel) var(--pos-radius-panel) 0 0; + background-color: var(--pos-color-highlight-background); + + text-transform: uppercase; + font-size: .9rem; + font-weight: 500; + color: var(--pos-color-highlight-text); +} + +/* defiinition list */ +.styleguide-details { + display: grid; + grid-template-columns: max-content 1fr; + gap: .5em var(--pos-gap-section-elements); +} + + .styleguide-details-highlighted { + padding: var(--pos-padding-card); + + border-radius: var(--pos-radius-panel); + background-color: var(--pos-color-highlight-background); + + color: var(--pos-color-light-highlight-text); + } + + .styleguide-details dt { + font-weight: 200; + } + + .styleguide-details dt:after { + content: ':'; + } + + .styleguide-details + .pos-tip { + margin-block-start: 1em; + } + + .styleguide-details i { + font-style: italic; + } + + .styleguide-details i:before { + content: '('; + } + + .styleguide-details i:after { + content: ')'; + } + + * + .styleguide-details { + margin-block-start: var(--pos-gap-section-elements); + } + +/* table with examples */ +.styleguide-examples-table th, +.styleguide-examples-table td { + padding: var(--pos-padding-cell); +} + +.styleguide-examples-table thead th { + padding-block-start: 0; + + text-align: center; +} + +.styleguide-examples-table td { + text-align: center; +} + +.styleguide-examples-table thead th { + font-weight: 500; +} + +.styleguide-examples-table tbody th { + font-weight: 300; + color: var(--pos-color-content-text-supplementary); +} + +.styleguide-examples-table td:first-child, +.styleguide-examples-table th:first-child { + padding-inline-start: 0; +} + +.styleguide-examples-table td:last-child, +.styleguide-examples-table th:last-child { + padding-inline-end: 0; +} + + +/* navigation +============================================================================ */ +.styleguide-navigation { + max-height: 90vh; + position: sticky; + inset-block-start: var(--pos-padding-page);; + overflow: auto; + + white-space: nowrap; +} + +.styleguide-navigation a { + padding: .25em 0; + display: block; + + text-transform: uppercase; + font-size: .9em; + font-weight: 500; + color: var(--pos-color-content-text); +} + + .styleguide-navigation a:hover { + color: var(--pos-color-interactive-hover); + } + + + +/* colors +============================================================================ */ +#colors ul { + padding: var(--pos-padding-card); + + border: 1px dashed var(--pos-color-frame); + border-radius: 0 1rem 1rem; +} + +#colors li:first-child .styleguide-heading-4 { + margin-block-start: 0; +} + +.styleguide-colors { + display: flex; +} + + .styleguide-color-container { + flex-grow: 1; + } + + .styleguide-color { + height: 70px; + } + + .styleguide-color-container:first-child .styleguide-color { + border-radius: var(--pos-radius-panel) 0 0 var(--pos-radius-panel); + } + + .styleguide-color-container:last-child .styleguide-color { + border-radius: 0 var(--pos-radius-panel) var(--pos-radius-panel) 0; + } + + .styleguide-colors + .styleguide-colors { + margin-block-start : var(--pos-gap-card-card); + } + +.styleguide-color { + margin-block-end: .3em; + padding: var(--pos-padding-cell); + display: flex; + flex-grow: 1; + align-items: end; +} + + /* add a frame around color that matches the page background */ + .styleguide-color[style*="--pos-color-page-background"] { + border: 1px dashed var(--pos-color-frame); + } + +.styleguide-color-variable { + padding: .3em .3em; + + background-color: var(--pos-color-content-text); + border-radius: var(--pos-radius-panel); + + font-size: .7rem; + font-family: monospace; + color: var(--pos-color-light-content-inverted-text); +} + +.styleguide-color-hex { + padding-inline-start: var(--pos-padding-cell); + + font-size: .9rem; + font-family: monospace; + color: var(--pos-color-content-text-supplementary) +} + + + +/* common elements +============================================================================ */ +#icons ul { + display: flex; + gap: .8em; + flex-wrap: wrap; + + font-size: .9em; + color: var(--pos-color-content-text-supplementary); +} + +#icons li { + display: flex; + flex-direction: column; + gap: .3em; + align-items: center; + + cursor: pointer; + + transition: color .1s linear; +} + +#icons ul svg { + width: 64px; + height: 64px; + padding: 20px; + + background-color: var(--pos-color-content-background); + border-radius: var(--pos-radius-panel); + + color: var(--pos-color-content-text); + + transition: color .1s linear; +} + +#icons li:not(.styleguide-icon-copied):hover, +#icons li:not(.styleguide-icon-copied):hover svg { + color: var(--pos-color-interactive-hover); +} + +#icons .styleguide-icon-copied, +#icons .styleguide-icon-copied svg { + color: var(--pos-color-confirmation); +} + + + +/* fonts +============================================================================ */ +#fonts p { + margin-block-start: var(--pos-gap-text-text); + + line-height: 1.3em; +} + +.styleguide-fonts-example { + margin-block: 2rem .5rem; + display: flex; + gap: calc(var(--pos-gap-text-text) * 2); + align-items: center; +} + + .styleguide-fonts-example strong { + line-height: 1em; + font-size: 7rem; + } + + .styleguide-fonts-example ul { + white-space: nowrap; + font-size: .9rem; + } + + .styleguide-fonts-example aside { + overflow: hidden; + text-overflow: ellipsis; + color: var(--pos-color-content-text-supplementary); + } + + .styleguide-fonts-example em { + font-size: 2rem; + } + + + +/* headings and text styles +============================================================================ */ +#headings .pos-heading-1, +#headings .pos-heading-2, +#headings .pos-heading-3 { + margin: 0; +} + +.styleguide-typography-swatch { + width: 1.3em; + height: 1.3em; + + border-radius: 50%; +} + +.styleguide-typography-color { + padding: .1em .5em; + + border-radius: var(--pos-radius-panel); + background-color: var(--pos-color-content-background); +} + +.styleguide-typography-content-color { + display: flex; + align-items: center; + gap: .5em; +} + + + +/* links +============================================================================ */ +#interactive-elements .styleguide-columns { + gap: var(--pos-gap-section-section); +} + +.pos-links-example { + padding: var(--pos-padding-card); + display: block; + + border: 1px dashed var(--pos-color-frame); + border-radius: 1rem; +} + + + +/* buttons +============================================================================ */ +#buttons .styleguide-columns { + gap: var(--pos-gap-section-section); +} + +#buttons .styleguide-example { + display: flex; + flex-direction: column; + gap: .5em; +} + +#buttons .styleguide-code + .styleguide-example { + margin-block-start: var(--pos-gap-card-card); +} + +#buttons .styleguide-example picture { + display: flex; + flex-direction: column; +} + +#buttons .pos-tip { + margin-block-start: var(--pos-gap-section-elements); +} + +#buttons .styleguide-examples-table { + width: 100%; + margin-block-start: var(--pos-gap-section-section); +} + + + +/* forms +============================================================================ */ +.styleguide-forms-placeholder-container { + margin-block: var(--pos-gap-text-text); + padding: var(--pos-padding-card); + + border: 2px dashed var(--pos-color-frame); + border-radius: .7rem; +} + + .styleguide-forms-placeholder-container:last-child { + margin-block-end: 0; + } + + .styleguide-forms-placeholder-container.pos-form-simple { + padding-block: calc(var(--pos-padding-card) * 1.65); + } + +.styleguide-forms-placeholder-fieldset { + height: 2.6rem; + + border: 3px dashed var(--pos-color-frame); + border-radius: 1rem; +} + + .styleguide-forms-placeholder-fieldset + .styleguide-forms-placeholder-fieldset { + margin-block-start: var(--pos-gap-text-text) + } + + .styleguide-forms-placeholder-fieldset.pos-form-fieldset-combined { + height: auto; + padding: var(--pos-padding-card); + } + +.styleguide-forms-placeholder-input { + height: 2rem; + display: block; + + background-color: var(--pos-color-highlight-background); + border-radius: var(--pos-radius-input); +} + + .styleguide-forms-placeholder-input + .styleguide-forms-placeholder-input { + margin-block-start: var(--pos-gap-text-text); + } + +.styleguide-forms-placeholder-radio { + height: 1.5rem; + margin-block-start: var(--pos-gap-text-text); + display: flex; + align-items: center; + position: relative; + + background-color: var(--pos-color-highlight-background); + border-radius: var(--pos-radius-input); +} + + .styleguide-forms-placeholder-radio:before { + width: 1.5rem; + height: 1.5rem; + position: absolute; + inset-inline-start: 0; + inset-block-start: 0; + + background-color: var(--pos-color-page-background); + + content: ''; + } + + .styleguide-forms-placeholder-radio:after { + width: 1rem; + height: 1rem; + display: block; + position: relative; + z-index: 1; + + background-color: var(--pos-color-highlight-background); + border-radius: 50%; + + content: ''; + } + +.styleguide-forms-placeholder-button { + width: 100px; + height: 2rem; + margin-block-start: var(--pos-gap-text-text); + + background-color: var(--pos-color-highlight-background); + border-radius: var(--pos-radius-button); +} + + .styleguide-forms-placeholder-button-next { + margin-inline-start: auto; + } + +#forms .styleguide-details + p { + margin-block-start: calc(var(--pos-gap-text-text) / 2); +} + +#text-inputs .styleguide-example, +#text-inputs-textarea .styleguide-example { + display: flex; + flex-direction: column; + gap: .5em; +} + +#text-inputs > :last-child, +#text-inputs-textarea > :last-child { + padding-inline-start: calc(var(--pos-gap-section-section) / 2); +} + +#text-inputs table input, +#text-inputs-textarea table textarea { + width: 150px; +} + + .pos-form-fieldset-combined .styleguide-forms-placeholder-input { + flex-grow: 1; + } + + .pos-form-fieldset-combined .styleguide-forms-placeholder-button { + margin: 0; + + border-inline-start: 3px dotted var(--pos-color-content-background); + } \ No newline at end of file diff --git a/modules/common-styling/public/assets/style-guide/styleguide.js b/modules/common-styling/public/assets/style-guide/styleguide.js new file mode 100644 index 0000000..70ba2ba --- /dev/null +++ b/modules/common-styling/public/assets/style-guide/styleguide.js @@ -0,0 +1,203 @@ +/* + handling various aspects of the style guide + that can be found under /style-guide +*/ + + + +// purpose: the main style guide namespace +// ************************************************************************ +const posStyleGuide = function(){ + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: initializes the module + // ------------------------------------------------------------------------ + module.init = () => { + posStyleGuide.colors(); + }; + + module.init(); + +}; + + + +// purpose: handles the color section +// ************************************************************************ +posStyleGuide.colors = () => { + + // cache 'this' value not to be overwritten later + const module = this; + + // purpose: settings that are being used across the module + // ------------------------------------------------------------------------ + module.settings = {}; + // the container with the colors (dom node) + module.settings.container = document.querySelector('#colors'); + // the list with the properties color (dom nodes) + module.settings.propertiesList = module.settings.container.querySelectorAll('.styleguide-color-container'); + // icons (dom nodes) + module.settings.iconNodes = document.querySelectorAll('#icons svg'); + + + // purpose: initializes module + // ------------------------------------------------------------------------ + module.init = () => { + module.showBackgroundColor(); + module.showFontDetails(); + module.showHeadingsDetails(); + module.copyCode(); + module.wrapIcons(); + module.showIconNames(); + module.copyIcon(); + }; + + + // purpose: convert the color from rgb[a] to hex + // arguments: color in rgb[a] + // returns: color in hex + // ------------------------------------------------------------------------ + function rgbaToHex(color){ + // skip if already in hex + if(color.indexOf('#') != -1) return color; + // skip for more advanced color functions using + if(color.indexOf('rgb') != 0) return false; + + color = color + .replace('rgba', '') + .replace('rgb', '') + .replace('(', '') + .replace(')', ''); + color = color.split(','); + + return '#' + + ( '0' + parseInt(color[0], 10).toString(16) ).slice(-2) + + ( '0' + parseInt(color[1], 10).toString(16) ).slice(-2) + + ( '0' + parseInt(color[2], 10).toString(16) ).slice(-2); + } + + + // purpose: finds the background color and shows it in corresponding place + // ------------------------------------------------------------------------ + module.showBackgroundColor = () => { + module.settings.propertiesList.forEach(element => { + element.querySelector('.styleguide-color-hex').textContent = rgbaToHex(window.getComputedStyle(element.querySelector('.styleguide-color')).getPropertyValue('background-color')) || ''; + }); + }; + + + // purpose: prints the fonts details + // ------------------------------------------------------------------------ + module.showFontDetails = () => { + document.querySelectorAll('#fonts .styleguide-typography-content-family').forEach(element => { + element.textContent = window.getComputedStyle(element.closest('div').querySelector('.styleguide-fonts-example strong')).getPropertyValue('font-family'); + }); + document.querySelectorAll('#fonts .styleguide-typography-content-size').forEach(element => { + element.textContent = window.getComputedStyle(element.closest('div').querySelector('p')).getPropertyValue('font-size'); + }); + }; + + + // purpose: prints the headings details + // ------------------------------------------------------------------------ + module.showHeadingsDetails = () => { + document.querySelectorAll('#headings .styleguide-details, #text-styles .styleguide-details').forEach(element => { + const headingComputedStyle = window.getComputedStyle(element.closest('.styleguide-columns').querySelector('.styleguide-example')); + + element.querySelector('.styleguide-typography-content-family').textContent = headingComputedStyle.getPropertyValue('font-family'); + element.querySelector('.styleguide-typography-content-color').innerHTML = `${headingComputedStyle.getPropertyValue('color')} ${rgbaToHex(headingComputedStyle.getPropertyValue('color'))}`; + element.querySelector('.styleguide-typography-swatch').style.backgroundColor = headingComputedStyle.getPropertyValue('color'); + element.querySelector('.styleguide-typography-content-size').textContent = headingComputedStyle.getPropertyValue('font-size'); + '(' + + ')'; + element.querySelector('.styleguide-typography-content-weight').textContent = headingComputedStyle.getPropertyValue('font-weight'); + element.querySelector('.styleguide-typography-content-lineheight').textContent = headingComputedStyle.getPropertyValue('line-height'); + }); + }; + + + // purpose: adds 'copy code' button to the code examples + // ------------------------------------------------------------------------ + module.copyCode = () => { + // copy button template to be appened to each code examples (dom node) + const copyButton = document.querySelector('#styleguide-copy'); + // class name to toggle when the code is copied (string) + const doneButtonClass = 'styleguide-copy-done'; + + + document.querySelectorAll('.styleguide-code').forEach(element => { + element.appendChild(copyButton.content.cloneNode(true)); + + element.addEventListener('click', event => { + // text to copy to the clipboard (string) + const text = element.parentElement.querySelector('pre code').textContent.trim(); + + // copy code to clipboard + navigator.clipboard.writeText(text).then(() => { + event.target.classList.add(doneButtonClass); + + // remove the class after some time + setTimeout(() => { + event.target.classList.remove(doneButtonClass); + }, 800); + }); + }); + }); + } + + + // purpose: wrap icons in list elements + // ------------------------------------------------------------------------ + module.wrapIcons = () => { + + module.settings.iconNodes.forEach(item => { + let wrapper = document.createElement('li'); + + wrapper.classList.add('flex', 'flex-col', 'items-center', 'cursor-pointer'); + + item.parentNode.insertBefore(wrapper, item); + wrapper.appendChild(item); + }); + + }; + + + // purpose: show icon names + // ------------------------------------------------------------------------ + module.showIconNames = () => { + + module.settings.iconNodes.forEach(item => { + item.insertAdjacentHTML('afterend', item.getAttribute('data-icon')); + }); + + }; + + + // purpose: copies the icon render function to clipboard on click + // ------------------------------------------------------------------------ + module.copyIcon = () => { + module.settings.iconNodes.forEach(icon => { + icon.parentElement.addEventListener('click', () => { + let name = icon.getAttribute('data-icon'); + + navigator.clipboard.writeText(`{% render 'modules/common-styling/icon', icon: '${name}' %}`).then(() => { + icon.parentElement.classList.add('styleguide-icon-copied'); + + setTimeout(() => { + icon.parentElement.classList.remove('styleguide-icon-copied'); + }, 800); + }, () => { + new Error('Could not copy the code to clipboard'); + }); + }); + }); + }; + + + + module.init(); +}; + + + +new posStyleGuide(); \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-avatar.css b/modules/common-styling/public/assets/style/pos-avatar.css new file mode 100644 index 0000000..28273d9 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-avatar.css @@ -0,0 +1,88 @@ +/* + avatar component + + no-image avatar + image avatar +*/ + + + +/* no-image avatar +============================================================================ */ +.pos-avatar { + all: unset; + display: revert; + + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + overflow: hidden; + + border-radius: 50%; + background-color: var(--pos-color-frame); + + line-height: 0; + font-weight: 500; + color: var(--pos-color-content-text-supplementary); +} + +.pos-avatar-xs { + width: 20px; + height: 20px; + + font-size: .6rem; +} + +.pos-avatar-s { + width: 24px; + height: 24px; + + font-size: .7rem; +} + +.pos-avatar-m { + width: 32px; + height: 32px; + + font-size: .85rem; +} + + +.pos-avatar-l { + width: 48px; + height: 48px; + + font-size: 1rem; +} + +.pos-avatar-xl { + width: 94px; + height: 94px; + + font-size: 2rem; +} + +.pos-avatar-xxl { + width: 160px; + height: 160px; + + font-size: 3rem; +} + +.pos-avatar-xxxl { + width: 192px; + height: 192px; + + font-size: 3.5rem; +} + + + +/* image avatar +============================================================================ */ +.pos-avatar img { + border-radius: 50%; +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-button.css b/modules/common-styling/public/assets/style/pos-button.css new file mode 100644 index 0000000..c363753 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-button.css @@ -0,0 +1,147 @@ +/* + button component + + basic + icon + label for screen readers + primary + small +*/ + + + +@layer common-styling { + + /* basic + ============================================================================ */ + .pos-button { + all: unset; + display: revert; + + user-select: none; + -webkit-user-select: none; + } + + .pos-button, + .pos-button * { + box-sizing: border-box; + } + + .pos-button { + padding-inline: var(--pos-padding-button); + height: var(--pos-height-button); + position: relative; + z-index: 1; + display: inline-flex; + justify-content: center; + align-items: center; + gap: .5em; + vertical-align: middle; + cursor: pointer; + + background-color: var(--pos-color-button-secondary-background); + border-width: var(--pos-border-button); + border-style: solid; + border-color: var(--pos-color-button-secondary-frame); + border-radius: var(--pos-radius-button); + outline: none; + + line-height: 1.2em; + font-size: .95rem; + font-weight: 500; + color: var(--pos-color-button-secondary-text); + + transition: all .1s linear; + } + + .pos-button:hover, + .pos-debug-button-hover { + background-color: var(--pos-color-button-secondary-hover-background); + border-color: var(--pos-color-button-secondary-hover-frame); + + color: var(--pos-color-button-secondary-hover-text); + } + + .pos-button:active, + .pos-debug-button-active { + transform: scale(98%); + } + + .pos-button:focus-visible, + .pos-debug-button-focus-visible { + border-color: var(--pos-color-focused); + outline: 2px solid var(--pos-color-focused); + outline-offset: 2px; + } + + .pos-button:disabled { + background-color: var(--pos-color-button-secondary-disabled-background); + border-color: var(--pos-color-button-secondary-disabled-frame); + + color: var(--pos-color-button-secondary-disabled-text); + } + + /* icon */ + .pos-button svg { + width: 1.5rem; + height: 1.5rem; + flex-shrink: 0; + + fill: currentColor; + + transition: fill .2s ease-in-out; + } + + /* label for screen readers */ + .pos-button .pos-label, + .pos-button-label { + position: absolute; + left: -200vw; + top: -100vh; + } + + + + /* primary + ============================================================================ */ + .pos-button-primary { + background-color: var(--pos-color-button-primary-background); + border-color: var(--pos-color-button-primary-frame); + + color: var(--pos-color-button-primary-text); + } + + .pos-button-primary:hover, + .pos-button-primary.pos-debug-button-hover { + background-color: var(--pos-color-button-primary-hover-background); + border-color: var(--pos-color-button-primary-hover-frame); + + color: var(--pos-color-button-primary-hover-text); + } + + .pos-button-primary:active, + .pos-button-primary.pos-debug-button-active { + transform: scale(98%); + } + + .pos-button-primary:disabled { + background-color: var(--pos-color-button-primary-disabled-background); + border-color: var(--pos-color-button-primary-disabled-frame); + + color: var(--pos-color-button-primary-disabled-text); + } + + + /* small + ============================================================================ */ + .pos-button-small { + padding-inline: var(--pos-padding-button); + height: calc(var(--pos-height-button) * 0.682 ); + } + + .pos-button-small svg { + width: 1rem; + height: 1rem; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-card.css b/modules/common-styling/public/assets/style/pos-card.css new file mode 100644 index 0000000..2f2ea81 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-card.css @@ -0,0 +1,113 @@ +/* + card component + + basic + highlighted + content +*/ + + +@layer common-styling { + + /* basic + ============================================================================ */ + .pos-card { + padding: var(--pos-padding-card); + + border-radius: var(--pos-radius-card); + background-color: var(--pos-color-content-background); + } + + + /* highlighted + ============================================================================ */ + .pos-card.pos-card-highlighted { + background-color: var(--pos-color-highlight-background); + } + + + /* content + ============================================================================ */ + .pos-card-content { + display: flex; + flex-direction: column; + overflow: hidden; + } + + .pos-card-content:has(.pos-card-content-permalink:focus-visible) { + outline: 2px solid var(--pos-color-focused); + outline-offset: 2px; + } + + .pos-card-content-permalink { + display: flex; + flex-direction: column; + gap: var(--pos-gap-card-elements); + } + + a.pos-card-content-permalink:focus-visible { + outline: none; + } + + .pos-card-content-title { + margin-block-end: calc(var(--pos-gap-text-text) / 2); + font-weight: 600; + } + + .pos-card-content-image-container { + height: 192px; + margin-block-start: calc(var(--pos-padding-card) * -1); + margin-inline: calc(var(--pos-padding-card) * -1); + display: block; + overflow: hidden; + } + + .pos-card-content-image { + width: 100%; + height: 192px; + object-fit: cover; + + scale: 1; + transition: scale .2s ease-in-out; + } + + .pos-card-content:has(.pos-card-content-permalink:hover) .pos-card-content-image, + .pos-card-content:has(.pos-card-content-permalink:focus-visible) .pos-card-content-image { + scale: 1.02; + } + + .pos-card-content-footer { + margin-block-start: auto; + padding-block-start: var(--pos-gap-card-elements); + display: flex; + justify-content: space-between; + align-items: center; + gap: .5em; + + font-size: .75rem; + color: var(--pos-color-content-text-supplementary); + } + + .pos-card-content-footer ul { + display: flex; + align-items: center; + gap: .667em; + } + + .pos-card-content-footer ul li { + display: flex; + align-items: center; + gap: .25em; + } + + .pos-card-content-footer ul li:not(:first-child) { + padding-inline-start: .5em; + + border-inline-start: 1px solid var(--pos-color-frame); + } + + .pos-card-content-footer ul svg { + width: .75rem; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-config.css b/modules/common-styling/public/assets/style/pos-config.css new file mode 100644 index 0000000..11ad6bf --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-config.css @@ -0,0 +1,838 @@ + +/* + stores configurable variables used for styling + + colors + light mode + general content + interactive elements + browser-related UI elements + forms + utility + dark mode + general content + interactive elements + browser-related UI elements + forms + utility + negotiating colors based on the choosen theme + gradients + fonts + typography + sizes + spacing + button shape + input shape +*/ + + + +/* colors */ +/* ================================================================================ */ + +/* light mode */ +/* -------------------------------------------------------------------------------- */ +:root { + + /* general content */ + /* ---------------------------------------- */ + + /* whole page background */ + --pos-color-light-page-background: #f7f8fa; + /* background color used under the content (grid, cards, panels) */ + --pos-color-light-content-background: #fff; + /* general content text */ + --pos-color-light-content-text: #374151; + /* general content icons (moight be different shade due to contrast) */ + --pos-color-light-content-icon: #405568; + /* sidenotes, metadata, table headers */ + --pos-color-light-content-text-supplementary: #6b7280; + /* prominent text used to highlight important information */ + --pos-color-light-content-text-prominent: #141414; + /* content text when placed on an inverted contrasty background */ + --pos-color-light-content-inverted-text: var(--pos-color-light-content-background); + /* dividers, separators, frames, table rows */ + --pos-color-light-frame: #e2e8f0; + /* highlighted elements background, like a navigation item when it is hovered or panels that need to stand out from the rest of the page like a filters panel */ + --pos-color-light-highlight-background: #eff2f6; + /* highlighted elements text and icons */ + --pos-color-light-highlight-text: var(--pos-color-light-content-text); + /* background of sections of the page that needs to really stand out, like call to actions */ + --pos-color-light-standout-background: #fff; + /* background of sections of the page that needs to really stand out when poitner hovers over */ + --pos-color-light-standout-background-hover: #f7f8fa; + /* text and icons placed on the sections that needs to really stand out */ + --pos-color-light-standout-text: var(--pos-color-light-content-text); + + + + /* interactive elements */ + /* ---------------------------------------- */ + + /* active elements suggesting a possible interaction */ + --pos-color-light-interactive: #2173c4; + /* active elements with possible interaction when a pointer is over them */ + --pos-color-light-interactive-hover: #194f90; + /* active elements with possible interaction when they are pressed */ + --pos-color-light-interactive-active: #1a5f9c; + /* disabled elements that could become or has been interactive */ + --pos-color-light-interactive-disabled: #d9ebfc; + + /* primary action button background */ + --pos-color-light-button-primary-background: #141414; + /* primary action button text and icons */ + --pos-color-light-button-primary-text: #fff; + /* border around primary action button */ + --pos-color-light-button-primary-frame: var(--pos-color-light-button-primary-background); + /* primary action button background when a pointer is over it */ + --pos-color-light-button-primary-hover-background: #374151; + /* primary action button text and icons icons when a pointer is over it */ + --pos-color-light-button-primary-hover-text: #fff; + /* border around primary action button when a pointer is over it */ + --pos-color-light-button-primary-hover-frame: var(--pos-color-light-button-primary-hover-background); + /* primary action button background when it is being pressed */ + --pos-color-light-button-primary-active-background: var(--pos-color-light-button-primary-hover-background); + /* primary action button text and icons icons when it is being pressed */ + --pos-color-light-button-primary-active-text: var(--pos-color-light-button-primary-hover-text); + /* border around primary action button when it is being pressed */ + --pos-color-light-button-primary-active-frame: var(--pos-color-light-button-primary-hover-frame); + /* primary action button background when it is disabled */ + --pos-color-light-button-primary-disabled-background: color-mix(in srgb, var(--pos-color-light-button-primary-background) 30%, transparent 70%); + /* primary action button text and icons when it is disabled */ + --pos-color-light-button-primary-disabled-text: var(--pos-color-light-button-primary-text); + /* border around primary action button when it is disabled */ + --pos-color-light-button-primary-disabled-frame: var(--pos-color-light-button-primary-disabled-background); + + /* secondary action button background */ + --pos-color-light-button-secondary-background: #f7f8fa; + /* secondary action button text and icons */ + --pos-color-light-button-secondary-text: var(--pos-color-light-interactive); + /* border around secondary action button */ + --pos-color-light-button-secondary-frame: #6b7280; + /* secondary action button background when a pointer is over it */ + --pos-color-light-button-secondary-hover-background: #fff; + /* secondary action button text and icons icons when a pointer is over it */ + --pos-color-light-button-secondary-hover-text: var(--pos-color-light-interactive-hover); + /* border around secondary action button when a pointer is over it */ + --pos-color-light-button-secondary-hover-frame: var(--pos-color-light-button-secondary-frame); + /* secondary action button background when it is being pressed */ + --pos-color-light-button-secondary-active-background: var(--pos-color-light-button-secondary-hover-background); + /* secondary action button text and icons icons when it is being pressed */ + --pos-color-light-button-secondary-active-text: var(--pos-color-light-button-secondary-hover-text); + /* border around secondary action button when it is being pressed */ + --pos-color-light-button-secondary-active-frame: var(--pos-color-light-button-secondary-hover-frame); + /* secondary action button background when it is disabled */ + --pos-color-light-button-secondary-disabled-background: color-mix(in srgb, var(--pos-color-light-button-secondary-background) 30%, transparent 70%); + /* secondary action button text and icons when it is disabled */ + --pos-color-light-button-secondary-disabled-text: var(--pos-color-light-interactive-disabled); + /* border around secondary action button when it is disabled */ + --pos-color-light-button-secondary-disabled-frame: color-mix(in srgb, var(--pos-color-light-button-secondary-frame) 30%, transparent 70%); + + + /* colors user for variety of browser-related UI elements */ + /* ---------------------------------------- */ + + /* outline for focused elements */ + --pos-color-light-focused: #2e70c1; + + /* background color for selected text */ + --pos-color-light-selection-background: var(--pos-color-light-focused); + /* foreground color for selected text */ + --pos-color-light-selection-text: #fff; + + + /* forms */ + /* ---------------------------------------- */ + + /* placeholder text in input fields */ + --pos-color-light-input-placeholder: color-mix(in srgb, var(--pos-color-light-input-text) 50%, transparent 50%); + + /* input field background */ + --pos-color-light-input-background: #fff; + /* input field text and icon */ + --pos-color-light-input-text: var(--pos-color-light-content-text); + /* border around input field */ + --pos-color-light-input-frame: #6b7280; + /* input field background when a pointer is over it */ + --pos-color-light-input-hover-background: var(--pos-color-light-input-background); + /* input field text and icon when a pointer is over it */ + --pos-color-light-input-hover-text: var(--pos-color-light-input-text); + /* border around input field when a pointer is over it */ + --pos-color-light-input-hover-frame: var(--pos-color-light-input-frame); + /* input field background when it is being focused */ + --pos-color-light-input-active-background: var(--pos-color-light-input-background); + /* input field text and icon when it is being focused */ + --pos-color-light-input-active-text: var(--pos-color-light-input-text); + /* border around input field when it is being focused */ + --pos-color-light-input-active-frame: var(--pos-color-light-interactive-active); + /* background of disabled input field */ + --pos-color-light-input-disabled-background: var(--pos-color-light-input-background); + /* text and icon of disabled input field */ + --pos-color-light-input-disabled-text: color-mix(in srgb, var(--pos-color-light-input-text) 40%, transparent 60%); + /* border around input field when it is disabled */ + --pos-color-light-input-disabled-frame: color-mix(in srgb, var(--pos-color-light-input-frame) 40%, transparent 60%); + + + /* utility */ + /* ---------------------------------------- */ + + /* errors, actions that can't be undone */ + --pos-color-light-important: #b62324; + --pos-color-light-important-hover: #c73e37; + --pos-color-light-important-disabled: #fccbcb; + /* warnings, actions that might have unwanted consequences */ + --pos-color-light-warning: #f0b357; + --pos-color-light-warning-hover: #fcc064; + --pos-color-light-warning-disabled: #feeaca; + /* confirmations of a successful actions */ + --pos-color-light-confirmation: #167b16; + --pos-color-light-confirmation-hover: #198f6a; + --pos-color-light-confirmation-disabled: #bcddd3; + +} + + +/* dark mode */ +/* -------------------------------------------------------------------------------- */ +:root { + + /* general content */ + /* ---------------------------------------- */ + + /* whole page background */ + --pos-color-dark-page-background: #000; + /* background color used under the content (grid, cards, panels) */ + --pos-color-dark-content-background: #141414; + /* general content text */ + --pos-color-dark-content-text: #b6bdca; + /* general content icons (moight be different shade due to contrast) */ + --pos-color-dark-content-icon: #808691; + /* sidenotes, metadata, table headers */ + --pos-color-dark-content-text-supplementary: #808691; + /* prominent text used to highlight important information */ + --pos-color-dark-content-text-prominent: #fff; + /* content text when placed on an inverted contrasty background */ + --pos-color-dark-content-inverted-text: #000; + /* dividers, separators, frames, table rows */ + --pos-color-dark-frame: rgba(255, 255, 255, 0.1); + /* highlighted elements background, like a navigation item when it is hovered or panels that need to stand out from the rest of the page like a filters panel */ + --pos-color-dark-highlight-background: #0c0c0c; + /* highlighted elements text and icons */ + --pos-color-dark-highlight-text: var(--pos-color-dark-content-text); + /* background of sections of the page that needs to really stand out, like call to actions */ + --pos-color-dark-standout-background: #2173c4; + /* background of sections of the page that needs to really stand out when hovered with a pointer */ + --pos-color-dark-standout-background-hover: #194f90; + /* text and icons placed on the sections that needs to really stand out */ + --pos-color-dark-standout-text: #fff; + + + + + /* interactive elements */ + /* ---------------------------------------- */ + + /* active elements suggesting a possible interaction */ + --pos-color-dark-interactive: #fff; + /* active elements with possible interaction when a pointer is over them */ + --pos-color-dark-interactive-hover: #38d430; + /* active elements with possible interaction when they are pressed */ + --pos-color-dark-interactive-active: #31bd29; + /* disabled elements that could become or has been interactive */ + --pos-color-dark-interactive-disabled: #6b7280; + + /* primary action button background */ + --pos-color-dark-button-primary-background: #fff; + /* primary action button text and icons */ + --pos-color-dark-button-primary-text: #141414; + /* border around primary action button */ + --pos-color-dark-button-primary-frame: var(--pos-color-dark-button-primary-background); + /* primary action button background when a pointer is over it */ + --pos-color-dark-button-primary-hover-background: #b6bdca; + /* primary action button text and icons icons when a pointer is over it */ + --pos-color-dark-button-primary-hover-text: #000; + /* border around primary action button when a pointer is over it */ + --pos-color-dark-button-primary-hover-frame: var(--pos-color-dark-button-primary-hover-background); + /* primary action button background when it is being pressed */ + --pos-color-dark-button-primary-active-background: var(--pos-color-dark-button-primary-hover-background); + /* primary action button text and icons icons when it is being pressed */ + --pos-color-dark-button-primary-active-text: var(--pos-color-dark-button-primary-hover-text); + /* border around primary action button when it is being pressed */ + --pos-color-dark-button-primary-active-frame: var(--pos-color-dark-button-primary-hover-frame); + /* primary action button background when it is disabled */ + --pos-color-dark-button-primary-disabled-background: color-mix(in srgb, var(--pos-color-dark-button-primary-background) 30%, transparent 70%); + /* primary action button text and icons when it is disabled */ + --pos-color-dark-button-primary-disabled-text: var(--pos-color-dark-button-primary-text); + /* border around primary action button when it is disabled */ + --pos-color-dark-button-primary-disabled-frame: color-mix(in srgb, var(--pos-color-dark-button-primary-frame) 30%, transparent 70%); + + /* secondary action button background */ + --pos-color-dark-button-secondary-background: #000; + /* secondary action button text and icons */ + --pos-color-dark-button-secondary-text: #fff; + /* border around secondary action button */ + --pos-color-dark-button-secondary-frame: #9da3ad; + /* secondary action button background when a pointer is over it */ + --pos-color-dark-button-secondary-hover-background: var(--pos-color-dark-button-secondary-background); + /* secondary action button text and icons icons when a pointer is over it */ + --pos-color-dark-button-secondary-hover-text: #fff; + /* border around secondary action button when a pointer is over it */ + --pos-color-dark-button-secondary-hover-frame: #000; + /* secondary action button background when it is being pressed */ + --pos-color-dark-button-secondary-active-background: var(--pos-color-dark-button-secondary-hover-background); + /* secondary action button text and icons icons when it is being pressed */ + --pos-color-dark-button-secondary-active-text: var(--pos-color-dark-button-secondary-hover-text); + /* border around secondary action button when it is being pressed */ + --pos-color-dark-button-secondary-active-frame: var(--pos-color-dark-button-secondary-hover-frame); + /* secondary action button background when it is disabled */ + --pos-color-dark-button-secondary-disabled-background: color-mix(in srgb, var(--pos-color-dark-button-secondary-background) 30%, transparent 70%); + /* secondary action button text and icons when it is disabled */ + --pos-color-dark-button-secondary-disabled-text: color-mix(in srgb, var(--pos-color-dark-button-secondary-text) 30%, transparent 70%); + /* border around secondary action button when it is disabled */ + --pos-color-dark-button-secondary-disabled-frame: color-mix(in srgb, var(--pos-color-dark-button-secondary-frame) 30%, transparent 70%); + + + /* colors user for variety of browser-related UI elements */ + /* ---------------------------------------- */ + + /* outline for focused elements */ + --pos-color-dark-focused: #2173c4; + + /* background color for selected text */ + --pos-color-dark-selection-background: var(--pos-color-dark-focused); + /* foreground color for selected text */ + --pos-color-dark-selection-text: #fff; + + + /* forms */ + /* ---------------------------------------- */ + + /* placeholder text in input fields */ + --pos-color-dark-input-placeholder: color-mix(in srgb, var(--pos-color-dark-input-text) 50%, transparent 50%); + + /* input field background */ + --pos-color-dark-input-background: #0c0c0c; + /* input field text and icon */ + --pos-color-dark-input-text: #9da3ad; + /* border around input field */ + --pos-color-dark-input-frame: #6b7280; + /* input field background when a pointer is over it */ + --pos-color-dark-input-hover-background: var(--pos-color-dark-input-background); + /* input field text and icon when a pointer is over it */ + --pos-color-dark-input-hover-text: var(--pos-color-dark-input-text); + /* border around input field when a pointer is over it */ + --pos-color-dark-input-hover-frame: var(--pos-color-dark-input-frame); + /* input field background when it is being focused */ + --pos-color-dark-input-active-background: var(--pos-color-dark-input-background); + /* input field text and icon when it is being focused */ + --pos-color-dark-input-active-text: var(--pos-color-dark-input-text); + /* border around input field when it is being focused */ + --pos-color-dark-input-active-frame: var(--pos-color-dark-interactive-active); + /* background of disabled input field */ + --pos-color-dark-input-disabled-background: var(--pos-color-dark-input-background); + /* text and icon of disabled input field */ + --pos-color-dark-input-disabled-text: color-mix(in srgb, var(--pos-color-dark-input-text) 40%, transparent 60%); + /* border around input field when it is disabled */ + --pos-color-dark-input-disabled-frame: color-mix(in srgb, var(--pos-color-dark-input-frame) 40%, transparent 60%); + + + /* utility */ + /* ---------------------------------------- */ + + /* errors, actions that can't be undone */ + --pos-color-dark-important: #b62324; + --pos-color-dark-important-hover: #c73e37; + --pos-color-dark-important-disabled: #fccbcb; + /* warnings, actions that might have unwanted consequences */ + --pos-color-dark-warning: #f0b357; + --pos-color-dark-warning-hover: #fcc064; + --pos-color-dark-warning-disabled: #feeaca; + /* confirmations of a successful actions */ + --pos-color-dark-confirmation: #167b16; + --pos-color-dark-confirmation-hover: #198f6a; + --pos-color-dark-confirmation-disabled: #bcddd3; + +} + + +/* negotiating colors based on the choosen theme +/* -------------------------------------------------------------------------------- */ + +:root, +:root.pos-theme-light { + + /* general content */ + /* ---------------------------------------- */ + --pos-color-page-background: var(--pos-color-light-page-background); + --pos-color-content-background: var(--pos-color-light-content-background); + --pos-color-content-text: var(--pos-color-light-content-text); + --pos-color-content-icon: var(--pos-color-light-content-icon); + --pos-color-content-text-supplementary: var(--pos-color-light-content-text-supplementary); + --pos-color-content-text-prominent: var(--pos-color-light-content-text-prominent); + --pos-color-content-inverted-text: var(--pos-color-light-content-inverted-text); + --pos-color-frame: var(--pos-color-light-frame); + --pos-color-highlight-background: var(--pos-color-light-highlight-background); + --pos-color-highlight-text: var(--pos-color-light-content-text); + --pos-color-standout-background: var(--pos-color-light-standout-background); + --pos-color-standout-background-hover: var(--pos-color-light-standout-background-hover); + --pos-color-standout-text: var(--pos-color-light-standout-text); + + + /* interactive elements */ + /* ---------------------------------------- */ + --pos-color-interactive: var(--pos-color-light-interactive); + --pos-color-interactive-hover: var(--pos-color-light-interactive-hover); + --pos-color-interactive-active: var(--pos-color-light-interactive-active); + --pos-color-interactive-disabled: var(--pos-color-light-interactive-disabled); + + --pos-color-button-primary-background: var(--pos-color-light-button-primary-background); + --pos-color-button-primary-text: var(--pos-color-light-button-primary-text); + --pos-color-button-primary-frame: var(--pos-color-light-button-primary-frame); + --pos-color-button-primary-hover-background: var(--pos-color-light-button-primary-hover-background); + --pos-color-button-primary-hover-text: var(--pos-color-light-button-primary-hover-text); + --pos-color-button-primary-hover-frame: var(--pos-color-light-button-primary-hover-frame); + --pos-color-button-primary-active-background: var(--pos-color-light-button-primary-active-background); + --pos-color-button-primary-active-text: var(--pos-color-light-button-primary-active-text); + --pos-color-button-primary-active-frame: var(--pos-color-light-button-primary-active-frame); + --pos-color-button-primary-disabled-background: var(--pos-color-light-button-primary-disabled-background); + --pos-color-button-primary-disabled-text: var(--pos-color-light-button-primary-disabled-text); + --pos-color-button-primary-disabled-frame: var(--pos-color-light-button-primary-disabled-frame); + + --pos-color-button-secondary-background: var(--pos-color-light-button-secondary-background); + --pos-color-button-secondary-text: var(--pos-color-light-button-secondary-text); + --pos-color-button-secondary-frame: var(--pos-color-light-button-secondary-frame); + --pos-color-button-secondary-hover-background: var(--pos-color-light-button-secondary-hover-background); + --pos-color-button-secondary-hover-text: var(--pos-color-light-button-secondary-hover-text); + --pos-color-button-secondary-hover-frame: var(--pos-color-light-button-secondary-hover-frame); + --pos-color-button-secondary-active-background: var(--pos-color-light-button-secondary-active-background); + --pos-color-button-secondary-active-text: var(--pos-color-light-button-secondary-active-text); + --pos-color-button-secondary-active-frame: var(--pos-color-light-button-secondary-active-frame); + --pos-color-button-secondary-disabled-background: var(--pos-color-light-button-secondary-disabled-background); + --pos-color-button-secondary-disabled-text: var(--pos-color-light-button-secondary-disabled-text); + --pos-color-button-secondary-disabled-frame: var(--pos-color-light-button-secondary-disabled-frame); + + + /* colors user for variety of browser-related UI elements */ + /* ---------------------------------------- */ + --pos-color-focused: var(--pos-color-light-focused); + + --pos-color-selection-background: var(--pos-color-light-selection-background); + --pos-color-selection-text: var(--pos-color-light-selection-text); + + + /* forms */ + /* ---------------------------------------- */ + --pos-color-input-placeholder: var(--pos-color-light-input-placeholder); + + --pos-color-input-background: var(--pos-color-light-input-background); + --pos-color-input-text: var(--pos-color-light-input-text); + --pos-color-input-frame: var(--pos-color-light-input-frame); + --pos-color-input-hover-background: var(--pos-color-light-input-hover-background); + --pos-color-input-hover-text: var(--pos-color-light-input-hover-text); + --pos-color-input-hover-frame: var(--pos-color-light-input-hover-frame); + --pos-color-input-active-background: var(--pos-color-light-input-active-background); + --pos-color-input-active-text: var(--pos-color-light-input-active-text); + --pos-color-input-active-frame: var(--pos-color-light-input-active-frame); + --pos-color-input-disabled-background: var(--pos-color-light-input-disabled-background); + --pos-color-input-disabled-text: var(--pos-color-light-input-disabled-text); + --pos-color-input-disabled-frame: var(--pos-color-light-input-disabled-frame); + + + /* utility */ + /* ---------------------------------------- */ + --pos-color-important: var(--pos-color-light-important); + --pos-color-important-hover: var(--pos-color-light-important-hover); + --pos-color-important-disabled: var(--pos-color-light-important-disabled); + --pos-color-warning: var(--pos-color-light-warning); + --pos-color-warning-hover: var(--pos-color-light-warning-hover); + --pos-color-warning-disabled: var(--pos-color-light-warning-disabled); + --pos-color-confirmation: var(--pos-color-light-confirmation); + --pos-color-confirmation-hover: var(--pos-color-light-confirmation-hover); + --pos-color-confirmation-disabled: var(--pos-color-light-confirmation-disabled); + +} + + +:root.pos-theme-dark:where(.pos-theme-darkEnabled) { + + /* general content */ + /* ---------------------------------------- */ + --pos-color-page-background: var(--pos-color-dark-page-background); + --pos-color-content-background: var(--pos-color-dark-content-background); + --pos-color-content-text: var(--pos-color-dark-content-text); + --pos-color-content-icon: var(--pos-color-dark-content-icon); + --pos-color-content-text-supplementary: var(--pos-color-dark-content-text-supplementary); + --pos-color-content-text-prominent: var(--pos-color-dark-content-text-prominent); + --pos-color-content-inverted-text: var(--pos-color-dark-content-inverted-text); + --pos-color-frame: var(--pos-color-dark-frame); + --pos-color-highlight-background: var(--pos-color-dark-highlight-background); + --pos-color-highlight-text: var(--pos-color-dark-content-text); + --pos-color-standout-background: var(--pos-color-dark-standout-background); + --pos-color-standout-background-hover: var(--pos-color-dark-standout-background-hover); + --pos-color-standout-text: var(--pos-color-dark-standout-text); + + + /* interactive elements */ + /* ---------------------------------------- */ + --pos-color-interactive: var(--pos-color-dark-interactive); + --pos-color-interactive-hover: var(--pos-color-dark-interactive-hover); + --pos-color-interactive-active: var(--pos-color-dark-interactive-active); + --pos-color-interactive-disabled: var(--pos-color-dark-interactive-disabled); + + --pos-color-button-primary-background: var(--pos-color-dark-button-primary-background); + --pos-color-button-primary-text: var(--pos-color-dark-button-primary-text); + --pos-color-button-primary-frame: var(--pos-color-dark-button-primary-frame); + --pos-color-button-primary-hover-background: var(--pos-color-dark-button-primary-hover-background); + --pos-color-button-primary-hover-text: var(--pos-color-dark-button-primary-hover-text); + --pos-color-button-primary-hover-frame: var(--pos-color-dark-button-primary-hover-frame); + --pos-color-button-primary-active-background: var(--pos-color-dark-button-primary-active-background); + --pos-color-button-primary-active-text: var(--pos-color-dark-button-primary-active-text); + --pos-color-button-primary-active-frame: var(--pos-color-dark-button-primary-active-frame); + --pos-color-button-primary-disabled-background: var(--pos-color-dark-button-primary-disabled-background); + --pos-color-button-primary-disabled-text: var(--pos-color-dark-button-primary-disabled-text); + --pos-color-button-primary-disabled-frame: var(--pos-color-dark-button-primary-disabled-frame); + + --pos-color-button-secondary-background: var(--pos-color-dark-button-secondary-background); + --pos-color-button-secondary-text: var(--pos-color-dark-button-secondary-text); + --pos-color-button-secondary-frame: var(--pos-color-dark-button-secondary-frame); + --pos-color-button-secondary-hover-background: var(--pos-color-dark-button-secondary-hover-background); + --pos-color-button-secondary-hover-text: var(--pos-color-dark-button-secondary-hover-text); + --pos-color-button-secondary-hover-frame: var(--pos-color-dark-button-secondary-hover-frame); + --pos-color-button-secondary-active-background: var(--pos-color-dark-button-secondary-active-background); + --pos-color-button-secondary-active-text: var(--pos-color-dark-button-secondary-active-text); + --pos-color-button-secondary-active-frame: var(--pos-color-dark-button-secondary-active-frame); + --pos-color-button-secondary-disabled-background: var(--pos-color-dark-button-secondary-disabled-background); + --pos-color-button-secondary-disabled-text: var(--pos-color-dark-button-secondary-disabled-text); + --pos-color-button-secondary-disabled-frame: var(--pos-color-dark-button-secondary-disabled-frame); + + + /* colors user for variety of browser-related UI elements */ + /* ---------------------------------------- */ + --pos-color-focused: var(--pos-color-dark-focused); + + --pos-color-selection-background: var(--pos-color-dark-selection-background); + --pos-color-selection-text: var(--pos-color-dark-selection-text); + + + /* forms */ + /* ---------------------------------------- */ + --pos-color-input-placeholder: var(--pos-color-dark-input-placeholder); + + --pos-color-input-background: var(--pos-color-dark-input-background); + --pos-color-input-text: var(--pos-color-dark-input-text); + --pos-color-input-frame: var(--pos-color-dark-input-frame); + --pos-color-input-hover-background: var(--pos-color-dark-input-hover-background); + --pos-color-input-hover-text: var(--pos-color-dark-input-hover-text); + --pos-color-input-hover-frame: var(--pos-color-dark-input-hover-frame); + --pos-color-input-active-background: var(--pos-color-dark-input-active-background); + --pos-color-input-active-text: var(--pos-color-dark-input-active-text); + --pos-color-input-active-frame: var(--pos-color-dark-input-active-frame); + --pos-color-input-disabled-background: var(--pos-color-dark-input-disabled-background); + --pos-color-input-disabled-text: var(--pos-color-dark-input-disabled-text); + --pos-color-input-disabled-frame: var(--pos-color-dark-input-disabled-frame); + + + /* utility */ + /* ---------------------------------------- */ + --pos-color-important: var(--pos-color-dark-important); + --pos-color-important-hover: var(--pos-color-dark-important-hover); + --pos-color-important-disabled: var(--pos-color-dark-important-disabled); + --pos-color-warning: var(--pos-color-dark-warning); + --pos-color-warning-hover: var(--pos-color-dark-warning-hover); + --pos-color-warning-disabled: var(--pos-color-dark-warning-disabled); + --pos-color-confirmation: var(--pos-color-dark-confirmation); + --pos-color-confirmation-hover: var(--pos-color-dark-confirmation-hover); + --pos-color-confirmation-disabled: var(--pos-color-dark-confirmation-disabled); + +} + + +@media (prefers-color-scheme: dark) { + :root:where(.pos-theme-darkEnabled) { + + /* general content */ + /* ---------------------------------------- */ + --pos-color-page-background: var(--pos-color-dark-page-background); + --pos-color-content-background: var(--pos-color-dark-content-background); + --pos-color-content-text: var(--pos-color-dark-content-text); + --pos-color-content-icon: var(--pos-color-dark-content-icon); + --pos-color-content-text-supplementary: var(--pos-color-dark-content-text-supplementary); + --pos-color-content-text-prominent: var(--pos-color-dark-content-text-prominent); + --pos-color-content-inverted-text: var(--pos-color-dark-content-inverted-text); + --pos-color-frame: var(--pos-color-dark-frame); + + + /* interactive elements */ + /* ---------------------------------------- */ + --pos-color-interactive: var(--pos-color-dark-interactive); + --pos-color-interactive-hover: var(--pos-color-dark-interactive-hover); + --pos-color-interactive-active: var(--pos-color-dark-interactive-active); + --pos-color-interactive-disabled: var(--pos-color-dark-interactive-disabled); + + --pos-color-button-primary-background: var(--pos-color-dark-button-primary-background); + --pos-color-button-primary-text: var(--pos-color-dark-button-primary-text); + --pos-color-button-primary-frame: var(--pos-color-dark-button-primary-frame); + --pos-color-button-primary-hover-background: var(--pos-color-dark-button-primary-hover-background); + --pos-color-button-primary-hover-text: var(--pos-color-dark-button-primary-hover-text); + --pos-color-button-primary-hover-frame: var(--pos-color-dark-button-primary-hover-frame); + --pos-color-button-primary-active-background: var(--pos-color-dark-button-primary-active-background); + --pos-color-button-primary-active-text: var(--pos-color-dark-button-primary-active-text); + --pos-color-button-primary-active-frame: var(--pos-color-dark-button-primary-active-frame); + --pos-color-button-primary-disabled-background: var(--pos-color-dark-button-primary-disabled-background); + --pos-color-button-primary-disabled-text: var(--pos-color-dark-button-primary-disabled-text); + --pos-color-button-primary-disabled-frame: var(--pos-color-dark-button-primary-disabled-frame); + + --pos-color-button-secondary-background: var(--pos-color-dark-button-secondary-background); + --pos-color-button-secondary-text: var(--pos-color-dark-button-secondary-text); + --pos-color-button-secondary-frame: var(--pos-color-dark-button-secondary-frame); + --pos-color-button-secondary-hover-background: var(--pos-color-dark-button-secondary-hover-background); + --pos-color-button-secondary-hover-text: var(--pos-color-dark-button-secondary-hover-text); + --pos-color-button-secondary-hover-frame: var(--pos-color-dark-button-secondary-hover-frame); + --pos-color-button-secondary-active-background: var(--pos-color-dark-button-secondary-active-background); + --pos-color-button-secondary-active-text: var(--pos-color-dark-button-secondary-active-text); + --pos-color-button-secondary-active-frame: var(--pos-color-dark-button-secondary-active-frame); + --pos-color-button-secondary-disabled-background: var(--pos-color-dark-button-secondary-disabled-background); + --pos-color-button-secondary-disabled-text: var(--pos-color-dark-button-secondary-disabled-text); + --pos-color-button-secondary-disabled-frame: var(--pos-color-dark-button-secondary-disabled-frame); + + + /* colors user for variety of browser-related UI elements */ + /* ---------------------------------------- */ + --pos-color-focused: var(--pos-color-dark-focused); + + --pos-color-selection-background: var(--pos-color-dark-selection-background); + --pos-color-selection-text: var(--pos-color-dark-selection-text); + + + /* forms */ + /* ---------------------------------------- */ + --pos-color-input-placeholder: var(--pos-color-dark-input-placeholder); + + --pos-color-input-background: var(--pos-color-dark-input-background); + --pos-color-input-text: var(--pos-color-dark-input-text); + --pos-color-input-frame: var(--pos-color-dark-input-frame); + --pos-color-input-hover-background: var(--pos-color-dark-input-hover-background); + --pos-color-input-hover-text: var(--pos-color-dark-input-hover-text); + --pos-color-input-hover-frame: var(--pos-color-dark-input-hover-frame); + --pos-color-input-active-background: var(--pos-color-dark-input-active-background); + --pos-color-input-active-text: var(--pos-color-dark-input-active-text); + --pos-color-input-active-frame: var(--pos-color-dark-input-active-frame); + --pos-color-input-disabled-background: var(--pos-color-dark-input-disabled-background); + --pos-color-input-disabled-text: var(--pos-color-dark-input-disabled-text); + --pos-color-input-disabled-frame: var(--pos-color-dark-input-disabled-frame); + + + /* utility */ + /* ---------------------------------------- */ + --pos-color-important: var(--pos-color-dark-important); + --pos-color-important-hover: var(--pos-color-dark-important-hover); + --pos-color-important-disabled: var(--pos-color-dark-important-disabled); + --pos-color-warning: var(--pos-color-dark-warning); + --pos-color-warning-hover: var(--pos-color-dark-warning-hover); + --pos-color-warning-disabled: var(--pos-color-dark-warning-disabled); + --pos-color-confirmation: var(--pos-color-dark-confirmation); + --pos-color-confirmation-hover: var(--pos-color-dark-confirmation-hover); + --pos-color-confirmation-disabled: var(--pos-color-dark-confirmation-disabled); + + } +} + + + +/* gradients */ +/* ================================================================================ */ +:root { + /* eased black-to-transparent gradient used as background for text and icons to improve legibility */ + --pos-gradient-legibility: linear-gradient(hsl(0, 0%, 0%) 0%, hsla(0, 0%, 0%, 0.738) 19%, hsla(0, 0%, 0%, 0.541) 34%, hsla(0, 0%, 0%, 0.382) 47%, hsla(0, 0%, 0%, 0.278) 56.5%, hsla(0, 0%, 0%, 0.194) 65%, hsla(0, 0%, 0%, 0.126) 73%, hsla(0, 0%, 0%, 0.075) 80.2%, hsla(0, 0%, 0%, 0.042) 86.1%, hsla(0, 0%, 0%, 0.021) 91%, hsla(0, 0%, 0%, 0.008) 95.2%, hsla(0, 0%, 0%, 0.002) 98.2%, hsla(0, 0%, 0%, 0) 100%); +} + + + +/* fonts */ +/* ================================================================================ */ +:root { + /* default font for the whole page */ + --pos-font-default: system-ui, sans-serif; + /* headings */ + --pos-font-heading: 'Source Code Pro', 'Consolas', monospace; +} + + + +/* typography */ +/* ================================================================================ */ +:root { + /* font size and line height for the hero secion */ + --pos-text-hero: 2rem; + --pos-text-hero-leading: 1.17em; + + /* font size and line height for the first level of headings */ + --pos-text-headline-1: 2.5rem; + --pos-text-headline-1-leading: 1.17em; + + /* font size and line height for the second level of headings */ + --pos-text-headline-2: 1.5rem; + --pos-text-headline-2-leading: 1.17em; + + /* font size and line height for the third level of headings */ + --pos-text-headline-3: 1.25rem; + --pos-text-headline-3-leading: 1.2em; + + /* font size and line height for the fourth level of headings */ + --pos-text-headline-4: 1.125rem; + --pos-text-headline-4-leading: 1.11em; +} + +@media (min-width: 1024px){ + :root { + --pos-text-hero: 3rem; + --pos-text-hero-leading: 1.17em; + + --pos-text-headline-1: 2.5rem; + --pos-text-headline-1-leading: 1.17em; + + --pos-text-headline-2: 1.75rem; + --pos-text-headline-2-leading: 1.17em; + + --pos-text-headline-3: 1.25rem; + --pos-text-headline-3-leading: 1.2em; + + --pos-text-headline-4: 1.125rem; + --pos-text-headline-4-leading: 1.11em; + } +} + +@media (min-width: 1440px) { + :root { + --pos-text-hero: 3.5rem; + --pos-text-hero-leading: 1.14em; + + --pos-text-headline-1: 2.5rem; + --pos-text-headline-1-leading: 1.17em; + + --pos-text-headline-2: 1.88rem; + --pos-text-headline-2-leading: 1.17em; + + --pos-text-headline-3: 1.5rem; + --pos-text-headline-3-leading: 1.33em; + + --pos-text-headline-4: 1.25rem; + --pos-text-headline-4-leading: 1.4em; + } +} + +@media (min-width: 1920px) { + :root { + --pos-text-hero: 4.5rem; + --pos-text-hero-leading: 1.11em; + + --pos-text-headline-1: 3rem; + --pos-text-headline-1-leading: 1.11em; + + --pos-text-headline-2: 1.88rem; + --pos-text-headline-2-leading: 1.17em; + + --pos-text-headline-3: 1.5rem; + --pos-text-headline-3-leading: 1.33em; + + --pos-text-headline-4: 1.25rem; + --pos-text-headline-4-leading: 1.4em; + } +} + + + +/* sizes */ +/* ================================================================================ */ +:root { + /* max width of the page */ + --pos-size-page: 1280px; +} + + + +/* spacing */ +/* ================================================================================ */ +/* missing the values for the different screen sizes but need some clarifications */ +:root { + + /* padding for cards, boxes */ + --pos-padding-card: 24px; + /* padding for cells in a table */ + --pos-padding-cell: 16px; + /* padding around whole page content */ + --pos-padding-page: 16px; + + /* gap between elements in a section, probably not usable as it all depends on the fonts x-heights and so many various factors */ + --pos-gap-section-elements: 1rem; + /* gap between section title and content */ + --pos-gap-title-content: 32px; + /* gap between sections */ + --pos-gap-section-section: 80px; + /* gap between cards */ + --pos-gap-card-card: 16px; + /* gap between text content */ + --pos-gap-text-text: 16px; + /* gap between elements in a card */ + --pos-gap-card-elements: 16px; + /* gap before the footer */ + --pos-gap-content-footer: 40px; + /* gap between buttons of standard size */ + --pos-gap-button-button: 16px; + /* gap between buttons of small size */ + --pos-gap-button-button-small: 8px; + /* gap between tags */ + --pos-gap-tag-tag: .5em .5em; + /* gap between form inputs */ + --pos-gap-input-input: 16px; + +} + + + +/* corners */ +/* ================================================================================ */ +:root { + /* border radius for content panels */ + --pos-radius-panel: 4px; + /* border radius for a popup */ + --pos-radius-popup: 4px; + /* border radius for ????? */ + --pos-radius-card: 4px; + /* border radius for a button */ + --pos-radius-button: 4px; + /* border radius for an input */ + --pos-radius-input: 0; + /* border radius for a tag */ + --pos-radius-tag: 4px; +} + + + +/* input shape */ +/* ================================================================================ */ +:root { + /* horizontal padding for inputs */ + --pos-padding-input: 8px; + /* input heights (should match buttons computed height */ + --pos-height-input: 44px; + /* border width for inputs */ + --pos-border-input: 1px; + /* arrow icon for input when focused or hovered with a pointer */ + --pos-icon-select-arrow-hover: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3Csvg version='1.1' id='Regular' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 12 12' style='enable-background:new 0 0 12 12;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E.st0%7Bfill:none;stroke:rgb(34, 77, 142);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;%7D%3C/style%3E%3Cpath class='st0' d='M11.3,3.4l-5,5c-0.1,0.1-0.4,0.1-0.5,0c0,0,0,0,0,0l-5-5'/%3E%3C/svg%3E"); +} + + + +/* button shape */ +/* ================================================================================ */ +:root { + /* horizontal padding for inputs */ + --pos-padding-button: 8px; + /* input heights (should match buttons computed height */ + --pos-height-button: var(--pos-height-input); + /* border width for inputs */ + --pos-border-button: var(--pos-border-input); +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-forms.css b/modules/common-styling/public/assets/style/pos-forms.css new file mode 100644 index 0000000..fe1693f --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-forms.css @@ -0,0 +1,607 @@ +/* + styling for forms and inputs + + general form + form paragraphs/fieldsets + checkbox + radio + text inputs + textareas + selects + errors + password input + multiselect +*/ + + + +@layer common-styling { + + .pos-form { + + } + + + /* form paragraphs/fieldsets + ============================================================================ */ + .pos-form fieldset, + .pos-form-fieldset { + all: unset; + display: revert; + + box-sizing: border-box; + } + + .pos-form fieldset + fieldset, + .pos-form-fieldset + .pos-form-fieldset { + margin-top: var(--pos-gap-text-text); + } + + .pos-form-simple fieldset:has(label + input), + .pos-form-simple fieldset:has(label + textarea) { + display: flex; + flex-direction: column; + gap: .2em; + } + + .pos-form-simple fieldset:has([type="checkbox"] + label), + .pos-form-simple fieldset:has([type="radio"] + label) { + display: flex; + gap: .5em; + align-items: center; + } + + /* combined fieldset */ + .pos-form .pos-form-fieldset-combined { + display: flex; + align-items: center; + } + + .pos-form-fieldset-combined > * { + margin-inline-start: -1px; + } + + .pos-form-fieldset-combined > *:not(:first-child) { + border-start-start-radius: 0; + border-end-start-radius: 0; + } + + .pos-form-fieldset-combined > *:not(:last-child) { + border-start-end-radius: 0; + border-end-end-radius: 0; + } + + .pos-form-fieldset-combined > :first-child { + margin-inline-start: 0; + } + + + + /* labels + ============================================================================ */ + .pos-form-simple fieldset:has(label + input) label { + margin-inline-start: -.1em; + align-self: start; + } + + .pos-form :has([required]) label:after { + color: var(--pos-color-important); + + content: " *"; + } + + + /* checkbox + ============================================================================ */ + .pos-form [type="checkbox"] { + margin: 0; + box-sizing: border-box; + + transition-property: border-color, background-color, color; + transition-duration: .1s, .1s, .1s; + transition-timing-function: linear; + } + + .pos-form [type="checkbox"]:focus-visible, + .pos-form-checkbox:focus-visible, + .pos-debug-checkbox-focus-visible { + outline: 2px solid var(--pos-color-focused); + outline-offset: 2px; + } + + + /* radio + ============================================================================ */ + .pos-form [type="radio"] { + transition-property: border-color, background-color, color; + transition-duration: .1s, .1s, .1s; + transition-timing-function: linear; + } + + .pos-form [type="radio"]:focus-visible, + .pos-form-radio:focus-visible, + .pos-debug-radio-focus-visible { + box-sizing: border-box; + + outline: 2px solid var(--pos-color-focused); + outline-offset: 2px; + } + + + /* text inputs + ============================================================================ */ + .pos-form-input, + .pos-form [type="text"], + .pos-form [type="email"], + .pos-form [type="password"], + .pos-form [type="url"], + .pos-form textarea { + all: unset; + display: revert; + + height: var(--pos-height-input); + padding-inline: var(--pos-padding-input); + box-sizing: border-box; + + border-radius: var(--pos-radius-input); + border-width: var(--pos-border-input); + border-style: solid; + border-color: var(--pos-color-input-frame); + outline: none; + background-color: var(--pos-color-input-background); + + text-align: start; + color: var(--pos-color-input-text); + + transition-property: border-color, background-color, color; + transition-duration: .1s, .1s, .1s; + transition-timing-function: linear; + } + + .pos-form-input:not(:disabled):hover, + .pos-form [type="text"]:not(:disabled):hover, + .pos-form [type="email"]:not(:disabled):hover, + .pos-form [type="password"]:not(:disabled):hover, + .pos-form [type="url"]:not(:disabled):hover, + .pos-form textarea:not(:disabled):hover, + .pos-debug-form-input-hover { + border-color: var(--pos-color-input-hover-frame); + background-color: var(--pos-color-input-hover-background); + + color: var(--pos-color-input-hover-text); + } + + .pos-form-input:focus-visible, + .pos-form [type="text"]:focus-visible, + .pos-form [type="email"]:focus-visible, + .pos-form [type="password"]:focus-visible, + .pos-form [type="url"]:focus-visible, + .pos-form textarea:focus-visible, + .pos-debug-form-input-focus-visible { + position: relative; + z-index: 2; + + border-color: var(--pos-color-input-active-frame); + background-color: var(--pos-color-input-active-background); + outline: 2px solid var(--pos-color-focused); + + color: var(--pos-color-input-focus-text); + } + + .pos-form-input:disabled, + .pos-form [type="text"]:disabled, + .pos-form [type="email"]:disabled, + .pos-form [type="password"]:disabled, + .pos-form [type="url"]:disabled + .pos-form textarea:disabled { + border-color: var(--pos-color-input-disabled-frame); + background-color: var(--pos-color-input-disabled-background); + + color: var(--pos-color-input-disabled-text); + } + + .pos-form-input::placeholder, + .pos-form [type="text"]::placeholder, + .pos-form [type="email"]::placeholder, + .pos-form [type="password"]::placeholder, + .pos-form [type="url"]::placeholder, + .pos-form textarea::placeholder { + color: var(--pos-color-input-placeholder); + } + + + /* textareas + ============================================================================ */ + .pos-form textarea, + textarea.pos-form-input { + height: auto; + min-height: var(--pos-height-input); + padding-block: var(--pos-padding-input); + } + + .pos-form-simple textarea { + width: 100%; + display: block; + } + + + /* selects + ============================================================================ */ + .pos-form select, + .pos-form-select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + height: var(--pos-height-input); + padding-inline: var(--pos-padding-input) calc(var(--pos-padding-input) * 2 + 1rem); + box-sizing: border-box; + + border-width: var(--pos-border-input); + border-style: solid; + border-color: var(--pos-color-input-frame); + border-radius: var(--pos-radius-input); + background-image: var(--pos-icon-select-arrow); + background-repeat: no-repeat; + background-size: 1rem; + background-position: right var(--pos-padding-input) center; + background-color: var(--pos-color-input-background); + + color: var(--pos-color-input-text); + } + + .pos-form select:not(:disabled):hover, + .pos-form-select:not(:disabled):hover { + border-color: var(--pos-color-input-hover-frame); + background-image: var(--pos-icon-select-arrow-hover); + background-color: var(--pos-color-input-hover-background); + + color: var(--pos-color-input-hover-text); + } + + .pos-form select:focus-visible, + .pos-form-select:focus-visible { + position: relative; + z-index: 1; + + border-color: var(--pos-color-input-active-frame); + outline: 2px solid var(--pos-color-focused); + background-image: var(--pos-icon-select-arrow-hover); + background-color: var(--pos-color-input-active-background); + + color: var(--pos-color-input-focus-text); + } + + .pos-form select:disabled, + .pos-form-select:disabled { + border-color: var(--pos-color-input-disabled-frame); + background-color: var(--pos-color-input-disabled-background); + + color: var(--pos-color-input-disabled-text); + } + + + /* errors + ============================================================================ */ + .pos-form-input[aria-invalid="true"], + .pos-form [type="text"][aria-invalid="true"], + .pos-form [type="email"][aria-invalid="true"], + .pos-form [type="password"][aria-invalid="true"], + .pos-form [type="url"][aria-invalid="true"] { + border-color: var(--pos-color-important); + } + + .pos-form-error { + text-wrap: pretty; + color: var(--pos-color-important); + } + + + /* password input + ============================================================================ */ + .pos-form-password { + display: inline-block; + box-sizing: border-box; + } + + .pos-form-password * { + box-sizing: border-box; + } + + .pos-form-password-input-container { + display: flex; + align-items: center; + } + + /* input */ + .pos-form-password input[type] { + width: 100%; + padding-inline-end: 48px; + } + + /* toggle visibility button */ + .pos-form-password-toggle { + width: var(--pos-height-input); + height: var(--pos-height-input); + margin-inline-start: calc(-1 * var(--pos-height-input)); + position: relative; + z-index: 2; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + + cursor: pointer; + + color: var(--pos-color-content-secondary); + } + + .pos-form-password-toggle:hover { + color: var(--pos-color-interactive-hover); + } + + .pos-form-password-toggle:focus-visible { + outline: 2px solid var(--pos-color-focused); + } + + .pos-form-password-toggle .pos-label { + position: absolute; + left: -100vw; + } + + .pos-form-password-toggle svg { + width: clamp(6px, calc(var(--pos-height-input) - var(--pos-padding-input)), 24px); + + transition: fill .1s linear; + } + + .pos-form-password-toggle-hide { + display: none; + } + + .pos-form-password-shown .pos-form-password-toggle-hide { + display: block; + } + + .pos-form-password-shown .pos-form-password-toggle-show { + display: none; + } + + /* strength meter */ + .pos-form-password .pos-form-password-strength-meter { + height: 4px; + margin-block-start: 5px; + display: flex; + align-items: stretch; + gap: 5px; + } + + .pos-form-password .pos-form-password-strength-meter > * { + flex-grow: 1; + + background-color: var(--pos-color-frame); + + transition: background-color .1s linear; + } + + .pos-form-password-strength-1 .pos-form-password-strength-meter > :first-child { + background-color: var(--pos-color-important); + } + + .pos-form-password-strength-2 .pos-form-password-strength-meter > :nth-child(-n+2) { + background-color: var(--pos-color-warning); + } + + .pos-form-password-strength-3 .pos-form-password-strength-meter > * { + background-color: var(--pos-color-confirmation); + } + + /* strength meter description */ + .pos-form-password-strength-description { + margin-block-start: .5em; + + text-align: start; + font-size: .9em; + } + + .pos-form-password-strength-description span { + display: none; + } + + .pos-form-password-strength-1 .pos-form-password-strength-description-weak { + display: block; + + color: var(--pos-color-important); + } + + .pos-form-password-strength-2 .pos-form-password-strength-description-medium { + display: block; + + color: var(--pos-color-warning); + } + + .pos-form-password-strength-3 .pos-form-password-strength-description-strong { + display: block; + + color: var(--pos-color-confirmation); + } + + + /* multiselect + ============================================================================ */ + .pos-form-multiselect { + min-width: 300px; + display: inline-block; + position: relative; + + user-select: none; + } + + .pos-form-multiselect-input { + display: flex; + align-items: center; + justify-content: space-between; + gap: .5em; + } + + .pos-form-multiselect-input:focus-visible { + position: relative; + z-index: 2; + + outline: 2px solid var(--pos-color-focused); + } + + .pos-form-multiselect-placeholder { + cursor: default; + + color: var(--pos-color-input-placeholder); + } + + .pos-form-multiselect-input:has(li) .pos-form-multiselect-placeholder { + display: none; + } + + /* dropdown arrow */ + .pos-form-multiselect-input-arrow { + width: 1em; + height: 1em; + flex-shrink: 0; + } + + .pos-form-multiselect-input:has(.pos-form-multiselect-selected-list:not(:hover)):hover .pos-form-multiselect-input-arrow, + .pos-form-multiselect-input:focus-visible .pos-form-multiselect-input-arrow { + fill: var(--pos-color-interactive-hover); + } + + /* selected items */ + .pos-form-multiselect-selected-list { + display: flex; + gap: var(--pos-gap-tag-tag); + overflow: hidden; + } + + .pos-form-multiselect-selected-item { + flex-grow: 0; + padding-inline: .3em; + padding-block: .2em; + display: flex; + align-items: center; + overflow: hidden; + gap: .2em; + + background-color: var(--pos-color-page-background); + border-radius: var(--pos-radius-tag); + + white-space: nowrap; + } + + .pos-form-multiselect-selected-item-label { + overflow: hidden; + leading-trim: both; + text-overflow: ellipsis; + line-height: 1em; + } + + .pos-form-multiselect-selected-item-remove { + width: 1.1em; + padding: .25em; + flex-shrink: 0; + display: flex; + align-items: center; + line-height: 0; + cursor: pointer; + + color: var(--pos-color-content-text-supplementary); + } + + .pos-form-multiselect-selected-item-remove:hover { + color: var(--pos-color-interactive-hover); + } + + .pos-form-multiselect-selected-item-remove:focus-visible { + outline: 2px solid var(--pos-color-focused); + } + + /* multiline version */ + .pos-form-multiselect-multiline .pos-form-multiselect-input { + height: auto; + min-height: var(--pos-height-input); + padding-block: var(--pos-padding-input); + } + + .pos-form-multiselect-multiline .pos-form-multiselect-selected-list { + flex-wrap: wrap; + } + + /* combined version */ + .pos-form-multiselect-selected-item-combined { + display: none; + } + + .pos-form-multiselect-combine:has(.pos-form-multiselect-selected-item:nth-of-type(2)) .pos-form-multiselect-selected-item-combined { + display: flex; /* show the combined counter when more than two items are selectd */ + } + + .pos-form-multiselect-combine:has(.pos-form-multiselect-selected-item:nth-of-type(2)) .pos-form-multiselect-selected-list { + display: none; /* hide the selected list items when there are more than two selected */ + } + + /* list popup */ + .pos-form-multiselect-list-container { + display: none; + flex-direction: column; + position: absolute; + inset: 100% 0 auto 0; + z-index: 1; + + border: 1px solid var(--pos-color-input-frame); + border-block-start-width: 0; + background-color: var(--pos-color-input-background); + + text-align: start; + } + + .pos-form-multiselect-opened .pos-form-multiselect-list-container { + display: flex; + } + + .pos-form-multiselect-list { + max-height: 200px; + overflow-y: auto; + } + + .pos-form-multiselect-list li { + min-height: calc(var(--pos-height-input) * 0.75); + padding: var(--pos-padding-input); + display: flex; + align-items: center; + gap: .3em; + } + + /* filter input */ + .pos-form-multiselect-filter { + border: 0; + } + + .pos-form-multiselect-filter + .pos-form-multiselect-list { + border-block-start: 1px solid var(--pos-color-input-frame); + } + + .pos-form-multiselect-filter-noresults { + display: none; + } + + .pos-form-multiselect-filtered-empty .pos-form-multiselect-filter-noresults { + min-height: calc(var(--pos-height-input) * 0.75); + padding: var(--pos-padding-input); + display: block; + + font-style: italic; + color: var(--pos-color-content-text-supplementary); + } + + .pos-form-multiselect-list .pos-form-multiselect-list-item-filtered { + display: none; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-page.css b/modules/common-styling/public/assets/style/pos-page.css new file mode 100644 index 0000000..2252039 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-page.css @@ -0,0 +1,50 @@ +/* + defines global page structure and looks + + page background and font + utility classes +*/ + + + +@layer common-styling { + + /* page background and font + ============================================================================ */ + + .pos-app body { + min-height: 100%; + min-height: 100dvh; + padding: var(--pos-padding-page); + display: flex; + flex-direction: column; + + background-color: var(--pos-color-page-background); + + text-rendering: optimizeLegibility; + font-family: var(--pos-font-default); + color: var(--pos-color-content-text); + } + + .pos-app ::selection { + background-color: var(--pos-color-selection-background); + + color: var(--pos-color-selection-text); + } + + + /* utility classes + ============================================================================ */ + .pos-text-end { + text-align: end; + } + + .pos-text-start { + text-align: start; + } + + .pos-text-center { + text-align: center; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-pagination.css b/modules/common-styling/public/assets/style/pos-pagination.css new file mode 100644 index 0000000..5a92588 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-pagination.css @@ -0,0 +1,34 @@ +/* + numbered pagination +*/ + + + +@layer common-styling { + + .pos-pagination { + margin-block-start: var(--pos-gap-card-card); + display: flex; + justify-content: end; + } + + .pos-pagination ul { + display: flex; + gap: calc(var(--pos-gap-text-text) / 2); + } + + .pos-pagination a { + min-width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + + border-radius: var(--pos-radius-button); + } + + .pos-pagination a[aria-current="true"] { + border: 1px solid var(--pos-color-interactive); + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-popover.css b/modules/common-styling/public/assets/style/pos-popover.css new file mode 100644 index 0000000..e32b4b9 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-popover.css @@ -0,0 +1,100 @@ +/* + popover menu that floats around the button that triggers it's visiblity + + popover position + opened state + menu inside the popover +*/ + + + +@layer common-styling { + + /* popover position + ============================================================================ */ + .pos-popover [popovertarget] { + position: relative; + } + + .pos-popover [popover] { + min-width: 100px; + max-width: 600px; + margin-block-start: 1rem; + position-area: block-end span-inline-start; + position-try-fallbacks: flip-inline; + overflow: hidden; + + border-radius: var(--pos-radius-panel); + background-color: var(--pos-color-content-background); + } + + @media (max-width: 600px) { + .pos-popover [popover] { + width: calc(100vw - var(--pos-padding-page) * 2) !important; + position-area: none; + position: absolute; + inset-block-start: anchor(bottom); + inset-inline-start: var(--pos-padding-page); + } + } + + + /* opened state + ============================================================================ */ + .pos-popover:has(:popover-open) { + z-index: 10; + } + + .pos-popover:has(:popover-open) [popovertarget]:after { + width: 16px; + height: 11px; + position: absolute; + top: calc(100% + 6px); + left: calc(50% - 8px); + + clip-path: polygon(50% 0%, 0% 100%, 100% 100%); + background-color: var(--pos-color-interactive); + + content: ''; + } + + + /* menu inside the popover + ============================================================================ */ + .pos-popover menu li a, + .pos-popover menu li button { + width: 100%; + padding: 1rem .75rem; + display: flex; + gap: .5em; + align-items: center; + + cursor: pointer; + + white-space: nowrap; + color: var(--pos-color-interactive); + + transition-property: color, background-color; + transition-duration: .1s; + transition-timing-function: linear; + } + + .pos-popover menu li a:hover, + .pos-popover menu li button:hover, + .pos-popover menu li a:focus-visible, + .pos-popover menu li button:focus-visible { + outline: none; + border-radius: 0; + background-color: var(--pos-color-button-primary-background); + + color: var(--pos-color-button-primary-text); + } + + .pos-popover menu svg { + width: 1.2em; + height: 1.2em; + + color: inherit; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-reset.css b/modules/common-styling/public/assets/style/pos-reset.css new file mode 100644 index 0000000..7c31db7 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-reset.css @@ -0,0 +1,85 @@ +/* + resets default browser styling and fixes some browser-specific issues + based on https://elad2412.github.io/the-new-css-reset/ +*/ + + + +@layer common-styling { + + :where(.pos-app) *:where(:not(html, iframe, canvas, img, svg, video, audio, input, select):not(svg *, symbol *)) { + all: unset; + display: revert; + } + + :where(.pos-app) *, + :where(.pos-app) *::before, + :where(.pos-app) *::after { + box-sizing: border-box; + } + + :where(.pos-app) a, + :where(.pos-app) button { + cursor: revert; + } + + :where(.pos-app) label { + cursor: pointer; + } + + :where(.pos-app) ol, :where(.pos-app) ul, :where(.pos-app) menu { + list-style: none; + } + + :where(.pos-app) img { + max-width: 100%; + height: auto; + } + + :where(.pos-app) table { + border-collapse: collapse; + } + + :where(.pos-app) input, :where(.pos-app) textarea { + -webkit-user-select: auto; + } + + :where(.pos-app) textarea { + white-space: revert; + } + + :where(.pos-app) meter { + -webkit-appearance: revert; + appearance: revert; + } + + :where(.pos-app) ::placeholder { + color: unset; + } + + :where(.pos-app) :where([hidden]) { + display: none; + } + + :where(.pos-app) :where([contenteditable]:not([contenteditable="false"])) { + -moz-user-modify: read-write; + -webkit-user-modify: read-write; + overflow-wrap: break-word; + -webkit-line-break: after-white-space; + -webkit-user-select: auto; + } + + :where(.pos-app) :where([draggable="true"]) { + -webkit-user-drag: element; + } + + :where(.pos-app) q:before, + :where(.pos-app) q:after { + display: none; + } + + :where(pre) { + white-space: pre-wrap; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-table.css b/modules/common-styling/public/assets/style/pos-table.css new file mode 100644 index 0000000..7c685e8 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-table.css @@ -0,0 +1,125 @@ +/* + responsive table component +*/ + + + +@layer common-styling { + + .pos-table { + width: 100%; + } + + @media (min-width: 801px) { + .pos-table { + display: table; + } + } + + /* header */ + .pos-table header { + text-transform: uppercase; + color: var(--pos-color-content-text-supplementary); + } + + @media (min-width: 801px) { + .pos-table header { + display: table-row; + } + + .pos-table header > * { + padding-inline: var(--pos-padding-cell); + padding-block: calc(var(--pos-padding-cell) / 2); + display: table-cell; + + font-size: .9rem; + } + + .pos-table header > :first-child { + padding-inline-start: 0; + } + + .pos-table header > :last-child { + padding-inline-end: 0; + } + } + + @media (max-width: 800px) { + .pos-table header { + display: none; + } + } + + /* content */ + @media (min-width: 801px) { + .pos-table-content { + display: table-row-group; + } + + .pos-table-content > ul { + display: table-row; + } + + .pos-table-content > ul > li { + padding: var(--pos-padding-cell); + display: table-cell; + } + + .pos-table-content > ul > li:first-child { + padding-inline-start: 0; + } + + .pos-table-content > ul > li:last-child { + padding-inline-end: 0; + } + + .pos-table-content > ul:not(:first-child) li { + border-block-start: 1px solid var(--pos-color-frame); + } + + .pos-table-content:not(.pos-card) > ul:last-child li { + padding-block-end: 0; + } + } + + /* content, card variant */ + @media (min-width: 801px) { + .pos-table-content.pos-card > ul:first-child > li:first-child { + border-top-left-radius: var(--pos-radius-card); + } + + .pos-table-content.pos-card > ul:first-child > li:last-child { + border-top-left-radius: var(--pos-radius-card); + } + + .pos-table-content.pos-card > ul > li:first-child { + padding-inline-start: var(--pos-padding-cell); + } + + .pos-table-content.pos-card > ul > li:last-child { + padding-inline-end: var(--pos-padding-cell); + } + } + + /* text formatting */ + @media (min-width: 801px) { + .pos-table-number { + text-align: end; + } + + .pos-table-content-heading { + display: none; + } + } + + @media (max-width: 800px) { + .pos-table-content-heading { + color: var(--pos-color-content-text-supplementary); + } + + .pos-table-content-heading:after { + content: ': ' + } + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-tag.css b/modules/common-styling/public/assets/style/pos-tag.css new file mode 100644 index 0000000..80f80d3 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-tag.css @@ -0,0 +1,39 @@ +/* + tag / chip component + + tag list + basic + clickable +*/ + + +@layer common-styling { + + /* tag list + ============================================================================ */ + .pos-tags-list { + display: flex; + flex-wrap: wrap; + gap: var(--pos-gap-tag-tag); + } + + /* basic + ============================================================================ */ + .pos-tag { + padding: .33em 1rem; + + border-radius: var(--pos-radius-tag); + background-color: var(--pos-color-page-background); + + text-transform: uppercase; + font-size: .75rem; + } + + + /* clickable + ============================================================================ */ + .pos-card.pos-card-highlighted { + background-color: var(--pos-color-highlight-background); + } + + } \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-toast.css b/modules/common-styling/public/assets/style/pos-toast.css new file mode 100644 index 0000000..7f17ca6 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-toast.css @@ -0,0 +1,136 @@ +/* + toast messages + + container + message + variants + close button +*/ + + + +@layer common-styling { + + /* container + ============================================================================ */ + .pos-toasts { + width: 300px; + position: fixed; + inset-block-end: var(--pos-padding-page); + inset-inline-start: var(--pos-padding-page); + display: flex; + flex-direction: column; + gap: var(--pos-gap-card-card); + } + + .pos-toast * { + box-sizing: border-box; + } + + + /* message + ============================================================================ */ + .pos-toast { + padding: calc(var(--pos-padding-cell) / 2) var(--pos-padding-cell); + display: flex; + justify-content: space-between; + + border-width: 1px; + border-style: solid; + border-color: var(--pos-color-content-background); + border-radius: var(--pos-radius-panel); + background-color: var(--pos-color-content-background); + + text-box-trim: trim-both; + } + + .pos-toast-loading { + overflow: hidden; + + animation-name: pos-toast-in; + animation-duration: .6s; + animation-timing-function: ease-in-out; + } + + @keyframes pos-toast-in { + from { + max-height: 0; + } + to { + max-height: 400px; + } + } + + .pos-toast-unloading { + animation-name: pos-toast-out; + animation-duration: .3s; + animation-timing-function: ease-in-out; + } + + @keyframes pos-toast-out { + 0% { + max-height: 400px; + + translate: 0 0; + } + + 100% { + max-height: 0; + + translate: -400px 0; + } + } + + + /* variants + ============================================================================ */ + .pos-toast-success .pos-toast { + border-color: var(--pos-color-confirmation); + background-color: var(--pos-color-confirmation-disabled); + } + + .pos-toast-error .pos-toast { + border-color: var(--pos-color-important); + background-color: var(--pos-color-important-disabled); + } + + .pos-toast-info .pos-toast { + border-color: var(--pos-color-warning); + background-color: var(--pos-color-warning-disabled); + } + + + /* close button + ============================================================================ */ + .pos-toast-close { + margin-inline-end: calc(var(--pos-padding-cell) / -2); + padding: 0 .5em; + align-self: start; + + cursor: pointer; + + transition: color .1s linear + } + + .pos-toast-close .label { + position: absolute; + left: -100vw; + } + + .pos-toast-success .pos-toast-close { + color: var(--pos-color-confirmation); + } + + .pos-toast-error .pos-toast-close { + color: var(--pos-color-important); + } + + .pos-toast-info .pos-toast-close { + color: var(--pos-color-warning); + } + + .pos-toast-close:hover { + color: var(--pos-color-content-text); + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/assets/style/pos-typography.css b/modules/common-styling/public/assets/style/pos-typography.css new file mode 100644 index 0000000..68ec298 --- /dev/null +++ b/modules/common-styling/public/assets/style/pos-typography.css @@ -0,0 +1,174 @@ +/* + defines fonts, paragraphs and headings + + icons + links + headings + sidenotes + increasing text legibility + long text +*/ + + + +@layer common-styling { + + /* icons + ============================================================================ */ + .pos-icon { + fill: currentColor; + } + + + /* links + ============================================================================ */ + :where(.pos-app) a { + color: var(--pos-color-interactive); + + transition: color .1s linear; + } + + :where(.pos-app) a:hover, + .pos-debug-link-hover { + color: var(--pos-color-interactive-hover); + } + + :where(.pos-app) a:active, + .pos-debug-link-active { + color: var(--pos-color-interactive-active) + } + + :where(.pos-app) a:not(.pos-button):focus-visible, + .pos-debug-link-focus { + border-radius: var(--pos-radius-button); + outline: 2px solid var(--pos-color-focused); + outline-offset: 2px; + + color: var(--pos-color-interactive-hover); + } + + :where(.pos-app) a svg { + width: 1em; + height: 1em; + display: inline-block; + + fill: currentColor; + } + + /* hidden text label */ + :where(.pos-app) a .pos-label { + position: absolute; + inset-inline-start: -100vw; + } + + + /* headings + ============================================================================ */ + .pos-heading-1 { + margin-inline-start: -.2ex; + display: block; + + font-family: var(--pos-font-heading); + font-size: 3rem; + font-weight: 500; + } + + .pos-heading-2 { + margin-inline-start: -.1ex; + display: block; + + font-family: var(--pos-font-heading); + font-size: 2rem; + font-weight: 500; + } + + .pos-heading-3 { + margin-inline-start: -.05ex; + display: block; + + font-family: var(--pos-font-heading); + font-size: 1.5rem; + font-weight: 400; + } + + .pos-heading-4 { + display: block; + + font-family: var(--pos-font-heading); + font-size: 1.25rem; + font-weight: 400; + } + + + /* sidenotes + ============================================================================ */ + .pos-supplementary { + color: var(--pos-color-content-text-supplementary); + } + + + /* tips + ============================================================================ */ + .pos-tip { + margin-block-start: .5rem; + position: relative; + display: flex; + gap: .5em; + + color: var(--pos-color-content-text-supplementary); + } + + .pos-tip svg { + width: 1.2rem; + height: 1.2rem; + position: relative; + top: 3px; + flex-shrink: 0; + } + + + /* increasing text legibility + ============================================================================ */ + .pos-increaseLegibility { + position: relative; + } + + .pos-increaseLegibility:before { + position: absolute; + inset-block-start: 0; + inset-inline: 0; + inset-block-end: 0; + opacity: .5; + + border-start-end-radius: inherit; + border-start-start-radius: inherit; + background-image: var(--pos-gradient-legibility); + + content: ''; + } + + .pos-increaseLegibility > * { + position: relative; + z-index: 2; + } + + + /* long text + ============================================================================ */ + .pos-prose .pos-heading-3 { + margin-block-end: .5rem; + } + + .pos-prose ol { + margin-inline-start: 1.2em; + + list-style-type: decimal; + } + + .pos-prose ul { + margin-inline-start: 1.2em; + + list-style-type: disc; + } + +} \ No newline at end of file diff --git a/modules/common-styling/public/translations/en.yml b/modules/common-styling/public/translations/en.yml new file mode 100644 index 0000000..d4a299e --- /dev/null +++ b/modules/common-styling/public/translations/en.yml @@ -0,0 +1,15 @@ +en: + toast: + close: Dismiss notification + password: + weak: Weak password + medium: Fair password + strong: Strong password + toggle_visibility: Toggle password visibility + form: + select: Select + type_to_filter: Type to filter the list + no_filter_results: Nothing matches your search + clear_selection: Clear selection + deselect: Deselect + at_least_one_option: At least one option must be choosen \ No newline at end of file diff --git a/modules/common-styling/public/views/layouts/style-guide.liquid b/modules/common-styling/public/views/layouts/style-guide.liquid new file mode 100644 index 0000000..5df7352 --- /dev/null +++ b/modules/common-styling/public/views/layouts/style-guide.liquid @@ -0,0 +1,26 @@ + + + + + + + + Style Guide and Components + + {% render 'modules/common-styling/init', reset: true %} + + + + + + + + + {{ content_for_layout }} + + {% liquid + theme_render_rc 'modules/common-styling/toasts' + %} + + + \ No newline at end of file diff --git a/modules/common-styling/public/views/pages/style-guide.liquid b/modules/common-styling/public/views/pages/style-guide.liquid new file mode 100644 index 0000000..5209b48 --- /dev/null +++ b/modules/common-styling/public/views/pages/style-guide.liquid @@ -0,0 +1,1746 @@ +--- +layout: 'modules/common-styling/style-guide' +--- + +
+ + + +
+ + +
+

Initlialization

+
+
+

All of the following CSS (except CSS custom properties) are scoped to container that uses pos-app class. You can use that class either on the root html tag of your app or just limit the scope to any container.

+{% capture code %}{% raw %} + +… +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+ +

Dark mode

+

To enable dark mode add the pos-theme-darkEnabled class to the same container. Dark theme setting is based on the user's system settings or can be forced by adding a pos-theme-dark class to your app container.

+{% capture code %}{% raw %} + +… +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+
Initialize common styling class
pos-app
+
Enable automatic dark mode class
pos-theme-darkEnabled
+
Manually turn on dark theme class
pos-theme-dark
+
+
+
+ + + +
+

Colors

+ +

General content

+
    +
  • +

    Page background

    +
    +
    +
    + --pos-color-page-background +
    + # +
    +
    +
  • +
  • +

    Content background

    +
    +
    +
    + --pos-color-content-background +
    + # +
    +
    +
  • +
  • +

    Content text & icons

    +
    +
    +
    --pos-color-content-text
    + # +
    +
    +
    --pos-color-content-icon
    + # +
    +
    +
    --pos-color-content-text-supplementary
    + # +
    +
    +
    --pos-color-content-text-prominent
    + # +
    +
    +
  • +
  • +

    Borders & separators

    +
    +
    +
    --pos-color-frame
    + # +
    +
    +
  • +
  • +

    Highlighted elements

    +
    +
    +
    --pos-color-highlight-background
    + # +
    +
    +
    --pos-color-highlight-text
    + # +
    +
    +
  • +
  • +

    Standout sections, call to actions

    +
    +
    +
    --pos-color-standout-background
    + # +
    +
    +
    --pos-color-standout-background-hover
    + # +
    +
    +
    --pos-color-standout-text
    + # +
    +
    +
  • +
+ +

Interactive elements

+
    +
  • +

    Links

    +
    +
    +
    --pos-color-interactive
    + # +
    +
    +
    --pos-color-interactive-hover
    + # +
    +
    +
    --pos-color-interactive-active
    + # +
    +
    +
    --pos-color-interactive-disabled
    + # +
    +
    +
  • + +
  • +

    Primary buttons

    +
    +
    +
    --pos-color-button-primary-background
    + # +
    +
    +
    --pos-color-button-primary-frame
    + # +
    +
    +
    --pos-color-button-primary-text
    + # +
    +
    +
    +
    +
    --pos-color-button-primary-hover-background
    + # +
    +
    +
    --pos-color-button-primary-hover-frame
    + # +
    +
    +
    --pos-color-button-primary-hover-text
    + # +
    +
    +
    +
    +
    --pos-color-button-primary-active-background
    + # +
    +
    +
    --pos-color-button-primary-active-frame
    + # +
    +
    +
    --pos-color-button-primary-active-text
    + # +
    +
    +
    +
    +
    --pos-color-button-primary-disabled-background
    + # +
    +
    +
    --pos-color-button-primary-disabled-frame
    + # +
    +
    +
    --pos-color-button-primary-disabled-text
    + # +
    +
    +
  • + +
  • +

    Secondary buttons

    +
    +
    +
    --pos-color-button-secondary-background
    + # +
    +
    +
    --pos-color-button-secondary-frame
    + # +
    +
    +
    --pos-color-button-secondary-text
    + # +
    +
    +
    +
    +
    --pos-color-button-secondary-hover-background
    + # +
    +
    +
    --pos-color-button-secondary-hover-frame
    + # +
    +
    +
    --pos-color-button-secondary-hover-text
    + # +
    +
    +
    +
    +
    --pos-color-button-secondary-active-background
    + # +
    +
    +
    --pos-color-button-secondary-active-frame
    + # +
    +
    +
    --pos-color-button-secondary-active-text
    + # +
    +
    +
    +
    +
    --pos-color-button-secondary-disabled-background
    + # +
    +
    +
    --pos-color-button-secondary-disabled-frame
    + # +
    +
    +
    --pos-color-button-secondary-disabled-text
    + # +
    +
    +
  • +
+ +

Browser UI

+
    +
  • +

    Focused elements highlight

    +
    +
    +
    --pos-color-focused
    + # +
    +
    +
  • +
  • +

    Text selection highlight

    +
    +
    +
    --pos-color-selection-background
    + # +
    +
    +
    --pos-color-selection-text
    + # +
    +
    +
  • +
+ +

Forms

+
    +
  • +

    Placeholder text

    +
    +
    +
    --pos-color-input-placeholder
    + # +
    +
    +
  • +
  • +

    Input field

    +
    +
    +
    --pos-color-input-background
    + # +
    +
    +
    --pos-color-input-frame
    + # +
    +
    +
    --pos-color-input-text
    + # +
    +
    +
    +
    +
    --pos-color-input-hover-background
    + # +
    +
    +
    --pos-color-input-hover-frame
    + # +
    +
    +
    --pos-color-input-hover-text
    + # +
    +
    +
    +
    +
    --pos-color-input-active-background
    + # +
    +
    +
    --pos-color-input-active-frame
    + # +
    +
    +
    --pos-color-input-active-text
    + # +
    +
    +
    +
    +
    --pos-color-input-disabled-background
    + # +
    +
    +
    --pos-color-input-disabled-frame
    + # +
    +
    +
    --pos-color-input-disabled-text
    + # +
    +
    +
  • +
+ +

Utility

+
    +
  • +

    Statuses

    +
    +
    +
    --pos-color-important
    + # +
    +
    +
    --pos-color-important-hover
    + # +
    +
    +
    --pos-color-important-disabled
    + # +
    +
    +
    +
    +
    --pos-color-warning
    + # +
    +
    +
    --pos-color-warning-hover
    + # +
    +
    +
    --pos-color-warning-disabled
    + # +
    +
    +
    +
    +
    --pos-color-confirmation
    + # +
    +
    +
    --pos-color-confirmation-hover
    + # +
    +
    +
    --pos-color-confirmation-disabled
    + # +
    +
    +
  • +
+ +
+ + + +
+

Gradients and shadows

+ +

Increasing text legibility over na image

+

When using text on an image you might need to increase the text legibility and ensure the contrast between those two stays high. For that you may use an eased gradient available through the css custom property: --pos-gradient-legibility or a pre-defined class pos-increaseLegibility. Be caresul with the later though as it uses relative positioning and may break your layout in some cases.

+
+
Class
pos-increaseLegibility
+
Properties
--pos-gradient-legibility
+
+ + +

The quick brown fox

+
+
+ + + +
+

Icons

+{% capture code %}{% raw %} +{% render 'modules/common-styling/icon', icon: 'dashDown' %} +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
    + {% render 'modules/common-styling/icon', icon: 'all' %} +
+
+ + + +
+

Fonts

+ +
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. In euismod aliquet nisi euismod eleifend. Phasellus justo tellus, aliquet ac aliquam ut, dictum eu augue.

+

Nullam vitae ex sed ligula convallis suscipit. Maecenas et neque facilisis.

+ + + Aa +
    +
  • Light
  • +
  • Regular
  • +
  • Medium
  • +
  • Semi Bold
  • +
  • Bold
  • +
+ +
+
+
Property
--pos-font-default
+
Font family
+
Default font size
+
+
+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. In euismod aliquet nisi euismod eleifend. Phasellus justo tellus, aliquet ac aliquam ut, dictum eu augue.

+

Nullam vitae ex sed ligula convallis suscipit. Maecenas et neque facilisis.

+ + + Aa +
    +
  • Light
  • +
  • Regular
  • +
  • Medium
  • +
  • Semi Bold
  • +
  • Bold
  • +
+ +
+
+
Property
--pos-font-heading
+
Font family
+
Default font size
+
+
+
+ +
+ + + +
+

Headings

+ +

Heading 1

+
+
+
Class
pos-heading-1
+
Font family
+
Color
+
Size
+
Weight
+
Line height
+
+
+ + The quick brown fox jumps over the lazy dog + +{% capture code %}{% raw %} +

The quick brown fox jumps over the lazy dog

+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ +

Heading 2

+
+
+
Class
pos-heading-2
+
Font family
+
Color
+
Size
+
Weight
+
Line height
+
+
+ + The quick brown fox jumps over the lazy dog + +{% capture code %}{% raw %} +

The quick brown fox jumps over the lazy dog

+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ +

Heading 3

+
+
+
Class
pos-heading-3
+
Font family
+
Color
+
Size
+
Weight
+
Line height
+
+
+ + The quick brown fox jumps over the lazy dog + +{% capture code %}{% raw %} +

The quick brown fox jumps over the lazy dog

+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ +

Heading 4

+
+
+
Class
pos-heading-4
+
Font family
+
Color
+
Size
+
Weight
+
Line height
+
+
+ + The quick brown fox jumps over the lazy dog + +{% capture code %}{% raw %} +

The quick brown fox jumps over the lazy dog

+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ +
+ + + +
+

Text styles

+

Sidenote

+
+
+
Class
pos-supplementary
+
Font family
+
Color
+
Size
+
Weight
+
Line height
+
+
+ + The quick brown fox jumps over the lazy dog + +{% capture code %}{% raw %} +The quick brown fox jumps over the lazy dog +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ +

Tip

+
+
+
Class
pos-tip
+
Font family
+
Color
+
Size
+
Weight
+
Line height
+
+
+ + {% render 'modules/common-styling/tip', content: 'The quick brown fox jumps over the lazy dog' %} + + {% capture code %}{% raw %} + {% render 'modules/common-styling/tip', content: 'The quick brown fox jumps over the lazy dog' %} + {% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ + +
+

Links

+ +
+ +
+ + {% render 'modules/common-styling/tip', content: 'When overwriting the <a> classes, please remember to also overwrite the debug classes used in the style guide: pos-debug-link-hover, pos-debug-link-active, pos-debug-link-focus.' %} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TextIconText with icon
DefaultLink Link Link
HoverLink Link Link
ActiveLink Link Link
FocusedLink Link Link
+
+ + + + +
+ +

Buttons

+ +
+
+
+

Default

+ + + +
+
Class
pos-button
+
+
+{% capture code %}{% raw %} + +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+

Primary

+ + + +
+
Class
pos-button pos-button-primary
+
+
+{% capture code %}{% raw %} + +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+ +
+
+

Default small

+ + + +
+
Class
pos-button pos-button-small
+
+
+{% capture code %}{% raw %} + +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+

Primary small

+ + + +
+
Class
pos-button pos-button-small pos-button-small
+
+
+{% capture code %}{% raw %} + +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ + {% render 'modules/common-styling/tip', content: 'When overwriting the <button> classes, please remember to also overwrite the debug classes used in the style guide: pos-debug-button-hover, pos-debug-button-active, pos-debug-button-focus-visible.' %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DefaultPrimaryDefault smallPrimary small
Enabled
Hover
Active
Focused
Disabled
Icon + + + + + + + +
LinkLinkLinkLinkLink
+ +
+ + + +
+

Forms

+

There are two ways for styling form controlls. You can add a pos-form class to a container and make all the child inputs styled automatically or you can add one of the following classes to any single input to style it separately.

+ +

Basic example

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+{% capture code %}{% raw %} +
+
+ + + {% render 'modules/common-styling/forms/error_list', name: 'styleguide-example-error', errors: errors['styleguide-form-example-a'] %} +
+
+ + + {% render 'modules/common-styling/forms/error_list', name: 'styleguide-example-error', errors: errors['styleguide-form-example-b'] %} +
+
+ + + {% render 'modules/common-styling/forms/error_list', name: 'styleguide-example-error', errors: errors['styleguide-form-example-c'] %} +
+
+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+ + +

Containers

+ +
+ +
+
+
+
+
+
+
+
+
Class
pos-form
+
+

Used for complex forms that needs more manual customized styling. No automatic labels styling, no automatic spacing between elements.

+
+ +
+
+
+
+
+
+
+
Class
pos-form pos-form-simple
+
+

Used for simple forms that can be styled automatically. Styles the labels and spacing between items as well. You can just throw this class onto the container and forget about styling each separate control.

+
+ +
+ +

Rows

+ +
+
+
+
Class
pos-form-fieldset
+
Properties
--pos-gap-text-text
+
+{% capture code %}{% raw %} +
+ +
+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+
+
+
+
+ +
+
+
+
Class
pos-form-fieldset-combined
+
+{% capture code %}{% raw %} +
+ +
+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+
+
+
+
+
+
+ +

Labels

+ +
+ {% render 'modules/common-styling/tip', content: 'Labels that are placed in a fieldset that has a reqired input will automatically be marked with an asterisk.' %} +
+
+
+
+ + +
+
+
+ {% capture code %}{% raw %} + + {% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ +
+ +
+

Radio

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+

Checkbox

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + +

Text inputs

+ +
+ +
+
+ +
+
Class
pos-form-input
+
+
+{% capture code %}{% raw %} + +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+ + {% render 'modules/common-styling/tip', content: 'When overwriting the <input> classes, please remember to also overwrite the debug classes used in the style guide: pos-debug-form-input-hover, pos-debug-form-input-focus-visible.' %} +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlaceholderFilled
Default
Hover
Focused
Disabled
Error
+
+ +
+ +
+ +
+
+ +
+
Class
pos-form-input
+
+
+ {% capture code %}{% raw %} + + {% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+ + {% render 'modules/common-styling/tip', content: 'When overwriting the <input> classes, please remember to also overwrite the debug classes used in the style guide: pos-debug-form-input-hover, pos-debug-form-input-focus-visible.' %} +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlaceholderFilled
Default
Hover
Focused
Disabled
Error
+
+
+ + +

Password input

+ +
+
+
+
name
Input name attribute string
+
id
Input id attribute string
+
value
Current input value string
+
class
Class list added to input container string
+
meter
If you want to show the password strength meter bool
+
+ {% render 'modules/common-styling/tip', content: 'Strong passwords consists of small and capitalized letters, numbers, special signs and are at least 6 characters long. Remember to provide clear instructios for your users.' %} +
+
+
+ {% render 'modules/common-styling/forms/password', name: 'styleguide-form-password-test', id: 'styleguide-form-password-test', value: '123456', meter: true %} +
+{% capture code %}{% raw %} +{% render 'modules/common-styling/forms/password', + name: 'styleguide-form-password-test', + value: '123', + id: 'styleguide-form-password-test', + meter: true +%} +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ + +

Select

+ +
+
+
+
class
pos-form-select
+
+
+
+ + + +
+
+ + +

Multiselect

+ +
+
+
+
id
Unique ID for the input string
+
list
+
+ an array of objects with items to show, must include 'value' and 'label' array
+ [ { value: 'item1value', label: 'Item 1 label' }, { value: 'item2value', label: 'Item 2 label' } ] +
+
selected
+
+ array with selected values (the same as in the 'list') array
+ [ 'item2value' ] +
+
form
the <form> element that the multiselect corresponds to string
+
name
the name="" property for the multiselect checkboxes string
+
required
at least one option is required bool
+
combine_selected
if you want to combine selected items into a single element ('2 selected' instead of displaying names) bool
+
multiline
if you want the list to extend vertically if there are more items than fit the single line bool
+
showFilter
allow to filter the list of options with a text input bool
+
placeholder
translation key for the main select input placeholder string
+
placeholder_filter
translation key for the filter input placeholder string
+
placeholder_empty
translation key shown when the filter brings no results string
+
+
+
+
+ {% liquid + assign example_list = '' | split: '' + + for i in (i..10) + assign value = 'value' | append: i + assign label = 'Label for value ' | append: i + assign example_item = '{}' | parse_json | hash_merge: value: value, label: label + assign example_list = example_list | add_to_array: example_item + assign selected = '["value0", "value5", "value6"]' | parse_json + endfor + %} + {% render 'modules/common-styling/forms/multiselect', name: 'styleguide-form-multiselect-test-1', id: 'styleguide-form-multiselect-test-1', list: example_list, showFilter: true, combine_selected: true %} + {% render 'modules/common-styling/forms/multiselect', name: 'styleguide-form-multiselect-test-2', id: 'styleguide-form-multiselect-test-2', list: example_list, showFilter: true %} + {% render 'modules/common-styling/forms/multiselect', name: 'styleguide-form-multiselect-test-3', id: 'styleguide-form-multiselect-test-3', list: example_list, showFilter: false, multiline: true %} + {% render 'modules/common-styling/forms/multiselect', name: 'styleguide-form-multiselect-test-4', id: 'styleguide-form-multiselect-test-4', list: example_list, selected: selected, showFilter: true, combine_selected: true %} +
+{% capture code %}{% raw %} +{% render 'modules/common-styling/forms/multiselect', + name: 'styleguide-form-multiselect-test', + id: 'styleguide-form-multiselect-test' +%} +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ + +

Error handling

+ +

There are two partials that can be helpful when dealing with forms validation. One can be added to the input itself to handle usability code and the other can output the error message.

+ + {% liquid + assign errors = '{ "styleguide-example-error": ["This is a field with two errors", "This is the second error"] }' | parse_json + %} +
+ + {% render 'modules/common-styling/forms/error_list', name: 'styleguide-example-error', errors: errors['styleguide-example-error'] %} +{% capture code %}{% raw %} + + +{% render 'modules/common-styling/forms/error_list', name: 'styleguide-example-error', errors: errors['styleguide-example-error'] %} +{% endraw %}{% endcapture %} +
+
+
{{ code | lstrip | rstrip }}
+
+ + +
+ + + + +
+

Boxes

+ +
+
+
+
The quick brown fox jumps over the lazy dog
+
+ {% capture code %}{% raw %} +
+ {% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
class
pos-card
+
props
--pos-padding-card, --pos-radius-card, --pos-color-content-background
+
+
+
+
+
The quick brown fox jumps over the lazy dog
+
+ {% capture code %}{% raw %} +
+ {% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
class
pos-card pos-card-highlighted
+
props
--pos-padding-card, --pos-radius-card, --pos-color-highlight-background
+
+
+
+ +

Content card

+
+
+ {% render 'modules/common-styling/content/card', url: '/', image: 'https://picsum.photos/1000/400', title: 'Lorem ipsum dolor sit amet', content: 'Quisque vel velit mi. Proin malesuada iaculis viverra. Vestibulum tristique sollicitudin rhoncus. Vivamus sollicitudin nisi in lorem gravida aliquam.', footer: '
  • Item
  • Item
Aside item' %} +{% capture code %}{% raw %} +{% render 'modules/common-styling/content/card', url: '/', image: 'https://picsum.photos/1000/400', title: 'Title', content: 'Content', footer: '
  • Item
  • Item
Aside item' %} +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+ {% render 'modules/common-styling/content/card', url: '/', image: 'https://picsum.photos/1000/400?random=2', title: 'Lorem ipsum dolor sit amet', content: 'Quisque vel velit mi. Proin malesuada iaculis viverra. Vestibulum tristique sollicitudin rhoncus. Vivamus sollicitudin nisi in lorem gravida aliquam.', footer: 'Cras lacinia lorem', highlighted: true %} +{% capture code %}{% raw %} +{% render 'modules/common-styling/content/card', url: '/', image: 'https://picsum.photos/1000/400', title: 'Title', content: 'Content', footer: 'Footer', highlighted: true %} +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+
+ + + +
+

Tables

+ +
+
+{% capture code %}{% raw %} +
+
+
Column 1
+
Column 2
+
Column 3
+
+
+
    +
  • + Column 1 + Content 1 +
  • +
  • + Column 1 + Content 2 +
  • +
  • + Column 3 + 321 +
  • +
+
+
+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+
+
+
+
Column 1
+
Column 2
+
Column 3
+
+
+
    +
  • + Column 1 + Content 1 +
  • +
  • + Column 1 + Content 2 +
  • +
  • + Column 3 + 321 +
  • +
+
    +
  • + Column 1 + Content 2 +
  • +
  • + Column 2 + Content 2 +
  • +
  • + Column 3 + 123 +
  • +
+
+
+
+
+
class
pos-table
+
props
--pos-padding-cell
+
+
+
+ +
+
+{% capture code %}{% raw %} +
+
+
Column 1
+
Column 2
+
Column 3
+
+
+
    +
  • + Column 1 + Content 1 +
  • +
  • + Column 1 + Content 2 +
  • +
  • + Column 3 + 321 +
  • +
+
+
+{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+
+
+
+
+
+
Column 1
+
Column 2
+
Column 3
+
+
+
    +
  • + Column 1 + Content 1 +
  • +
  • + Column 1 + Content 2 +
  • +
  • + Column 3 + 321 +
  • +
+
    +
  • + Column 1 + Content 2 +
  • +
  • + Column 2 + Content 2 +
  • +
  • + Column 3 + 123 +
  • +
+
+
+
+
+
class
pos-table
+
props
--pos-padding-cell
+
+
+
+ +
+ + + +
+

Toasts

+ +
+
+

A standard platformOS way of showing toast notifications would be to store and get the messages in the session.

+

Adding the following code to your application `layout` file will initialize the module:

+{% capture code %}{% raw %} +{% liquid + function flash = 'modules/core/commands/session/get', key: 'sflash' + if context.location.pathname != flash.from or flash.force_clear + function _ = 'modules/core/commands/session/clear', key: 'sflash' + endif + theme_render_rc 'modules/common-styling/toasts', params: flash +%} +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+ +

Then, you can use the following JavaScript to show a message on page:

+{% capture code %}{% raw %} +new pos.modules.toast('[severity]', '[message]'); +{% endraw %}{% endcapture %} +
+
{{ code | lstrip | rstrip }}
+
+ +
+
severity
how important the message is - error, success, info string
+
message
user-readable message for the toast notification string
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + + + + +
+ +
+ + + +
+ +
+ The quick brown fox jumps over the lazy dog +
+
+ +
+ +
+ The quick brown fox jumps over the lazy dog +
+
\ No newline at end of file diff --git a/modules/common-styling/public/views/partials/content/card.liquid b/modules/common-styling/public/views/partials/content/card.liquid new file mode 100644 index 0000000..d995b0c --- /dev/null +++ b/modules/common-styling/public/views/partials/content/card.liquid @@ -0,0 +1,47 @@ +{% comment %} + + content card with an image, title and a description + + url - (string) where the card should link to + image - (object or URL) platformOS generated object with image versions or just an URL to specific image + title - (string) card title that will be linked + content - (string) card description + footer - (string) secondary related content + + highlighted - (bool) should the card be distinguished among other cards + +{% endcomment %} + + + +
+ + {% if url != blank %} + + {% endif %} + {% if image.versions %} + + + + + {% elsif image %} + + + + {% endif %} + {% if title %} +

{{ title }}

+ {% endif %} + {% if url != blank %} +
+ {% endif %} + + {{ content | html_safe }} + + {% if footer %} + + {% endif %} + +
\ No newline at end of file diff --git a/modules/common-styling/public/views/partials/forms/error_input_handler.liquid b/modules/common-styling/public/views/partials/forms/error_input_handler.liquid new file mode 100644 index 0000000..4e830a5 --- /dev/null +++ b/modules/common-styling/public/views/partials/forms/error_input_handler.liquid @@ -0,0 +1,3 @@ +{% if errors %} + aria-invalid="true" aria-describedby="pos-form-{{ name }}-error" +{% endif %} \ No newline at end of file diff --git a/modules/common-styling/public/views/partials/forms/error_list.liquid b/modules/common-styling/public/views/partials/forms/error_list.liquid new file mode 100644 index 0000000..02f878d --- /dev/null +++ b/modules/common-styling/public/views/partials/forms/error_list.liquid @@ -0,0 +1,7 @@ +{% if errors %} + +{% endif %} \ No newline at end of file diff --git a/modules/common-styling/public/views/partials/forms/hcaptcha.liquid b/modules/common-styling/public/views/partials/forms/hcaptcha.liquid new file mode 100644 index 0000000..da174f8 --- /dev/null +++ b/modules/common-styling/public/views/partials/forms/hcaptcha.liquid @@ -0,0 +1,13 @@ +{% if context.constants.VERIFY_HCAPTCHA == "true" %} +
+ +
+ + {% #render 'theme/simple/field_error', errors: object.errors.hcaptcha %} +
+ {% elsif context.environment == 'staging' %} + + + + {% endif %} + \ No newline at end of file diff --git a/modules/common-styling/public/views/partials/forms/multiselect.liquid b/modules/common-styling/public/views/partials/forms/multiselect.liquid new file mode 100644 index 0000000..c379c28 --- /dev/null +++ b/modules/common-styling/public/views/partials/forms/multiselect.liquid @@ -0,0 +1,94 @@ +{% comment %} + Multiselect input component + + Arguments: + - id (string) unique ID of the input + + - list (array, required) an array of objects with items to show, must include 'value' and 'label' + [ { value: 'item1value', label: 'Item 1 label' }, { value: 'item2value', label: 'Item 2 label' } ] + - selected (array) array with selected values (the same as in the 'list') + [ 'item2value' ] + + - form (string) the
element that the multiselect corresponds to + - name (string) the name="" property for the multiselect checkboxes (no need for adding [] at the end) + - required (bool, false) at least one option is required + + - combine_selected (bool, false) if you want to combine selected items into a single element ('2 selected' instead of displaying names) + - multiline (bool, false) if you want the list to extend vertically if there are more items than fit the single line + - showFilter (bool, false) allow to filter the list of options with a text input + + - placeholder (string, default) translation key for the main select input placeholder + - placeholder_filter (string, default) translation key for the filter input placeholder + - placeholder_empty (string, default) translation key shown when the filter brings no results +{% endcomment %} + + + +{% unless list %} + +{% endunless %} + + + +{% liquid + assign list = list | default: '[]' + + if selected[0] + assign selected = selected | default: '[]' + endif + + assign view = view | default: 'list' + + assign placeholder = placeholder | default: 'modules/common-styling/form.select' + assign placeholder_filter = placeholder_filter | default: 'modules/common-styling/form.type_to_filter' + assign placeholder_empty = placeholder_empty | default: 'modules/common-styling/form.no_filter_results' +%} + +
+ + + +
+ {% if showFilter %} + + {% endif %} +
    + {% for item in list %} +
  • + +
  • + {% endfor %} +
+ {% if showFilter %} + {{ placeholder_empty | t }} + {% endif %} +
+ +
\ No newline at end of file diff --git a/modules/common-styling/public/views/partials/forms/password.liquid b/modules/common-styling/public/views/partials/forms/password.liquid new file mode 100644 index 0000000..4ea51be --- /dev/null +++ b/modules/common-styling/public/views/partials/forms/password.liquid @@ -0,0 +1,51 @@ +{% comment %} + + password input with an optional strength meter + + arguments: + name - form input name (string) + id - input id (string) + value - value in the input (string) + class - class list added to the input container (string) + meter - if you want the password weakness to be shown (bool) + +{% endcomment %} + + + +
+ +
+ {% comment %} input {% endcomment %} + + + {% comment %} show/hide password toggle {% endcomment %} + +
+ + {% comment %} strength meter {% endcomment %} + {% if meter %} +
+ +
+ +
+ {{ 'modules/common-styling/password.weak' | t }} + {{ 'modules/common-styling/password.medium' | t }} + {{ 'modules/common-styling/password.strong' | t }} +
+ {% endif %} + +
\ No newline at end of file diff --git a/modules/common-styling/public/views/partials/icon.liquid b/modules/common-styling/public/views/partials/icon.liquid new file mode 100644 index 0000000..2219439 --- /dev/null +++ b/modules/common-styling/public/views/partials/icon.liquid @@ -0,0 +1,113 @@ +{% comment %} + + List of icons in SVG format + + Params: + - icon name (string) + - class (string, optional) + +{% endcomment %} + + +{% capture attrs %} + viewBox="0 0 24 24" + fill="none" + class="pos-icon {{ class }}" + focusable="false" + role="img" + xmlns="http://www.w3.org/2000/svg" +{% endcapture %} + + +{% case icon %} + + {% when 'plus', 'all' %} + + + {% when 'x', 'all' %} + + + {% when 'dashUp', 'all' %} + + + {% when 'dashDown', 'all' %} + + + {% when 'dashRight', 'all' %} + + + {% when 'dashLeft', 'all' %} + + + {% when 'check', 'all' %} + + + {% when 'dots', 'all' %} + + + {% when 'menu', 'all' %} + + + {% when 'expand', 'all' %} + + + {% when 'eye', 'all' %} + + + {% when 'eyeStriked', 'all' %} + + + {% when 'search' or 'all' %} + + + {% when 'bell', 'all' %} + + + {% when 'mail', 'all' %} + + + {% when 'messagesTyping', 'all' %} + + + {% when 'dashboard', 'all' %} + + + {% when 'groups', 'all' %} + + + {% when 'binocular', 'all' %} + + + {% when 'calendar', 'all' %} + + + {% when 'user', 'all' %} + + + {% when 'users', 'all' %} + + + {% when 'userAdd', 'all' %} + + + {% when 'userRemove', 'all' %} + + + {% when 'cog', 'all' %} + + + {% when 'bookmarksDocument', 'all' %} + + + {% when 'leave', 'all' %} + + + + + + {% else %} + + +{% endcase %} \ No newline at end of file diff --git a/modules/common-styling/public/views/partials/init.liquid b/modules/common-styling/public/views/partials/init.liquid new file mode 100644 index 0000000..126f0fb --- /dev/null +++ b/modules/common-styling/public/views/partials/init.liquid @@ -0,0 +1,92 @@ +{% if reset %} + +{% endif %} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/common-styling/public/views/partials/pagination.liquid b/modules/common-styling/public/views/partials/pagination.liquid new file mode 100644 index 0000000..99bcd43 --- /dev/null +++ b/modules/common-styling/public/views/partials/pagination.liquid @@ -0,0 +1,79 @@ +{% comment %} + + numbered pagination with arrows if the number of pages is large + + count - (int) number of pages in total + maxVisible - (int) maximum number of pages to show on the list, more will can be navigated with arrows + active - (int) currently viewed page + url - (string) query string for the pagination + +{% endcomment %} + + + +{% liquid + assign count = count | default: 10 + assign maxVisible = maxVisible | default: 10 + assign url = url | default: '?page=' + + if count > maxVisible + assign limit = maxVisible | divided_by: 2 | round + assign startPage = active | minus: limit + assign endPage = active | plus: limit + endif + + if count <= maxVisible + assign startPage = 1 + assign endPage = count + endif + + if startPage < 1 + assign startPage = 1 + assign endPage = maxVisible + endif + + if endPage > count + assign startPage = count | minus: maxVisible | plus: 1 + assign endPage = count + endif + + assign leftArrow = startPage | minus: 1 + assign rightArrow = endPage | plus: 1 +%} + + + diff --git a/modules/common-styling/public/views/partials/tip.liquid b/modules/common-styling/public/views/partials/tip.liquid new file mode 100644 index 0000000..1fbe12a --- /dev/null +++ b/modules/common-styling/public/views/partials/tip.liquid @@ -0,0 +1,8 @@ +
+ +
+ {% liquid + print content + %} +
+
\ No newline at end of file diff --git a/modules/common-styling/public/views/partials/toasts.liquid b/modules/common-styling/public/views/partials/toasts.liquid new file mode 100644 index 0000000..64351ad --- /dev/null +++ b/modules/common-styling/public/views/partials/toasts.liquid @@ -0,0 +1,35 @@ +{% liquid + assign autohide = autohide | default: params.autohide + assign delay = delay | default: params.delay | default: 1000 + assign message = message | default: params.message | default: null + assign severity = severity | default: params.severity +%} + + + + \ No newline at end of file diff --git a/modules/common-styling/public/views/partials/user/avatar.liquid b/modules/common-styling/public/views/partials/user/avatar.liquid new file mode 100644 index 0000000..9943329 --- /dev/null +++ b/modules/common-styling/public/views/partials/user/avatar.liquid @@ -0,0 +1,39 @@ +{% liquid + assign size = size | default: params.size | default: 'md' + assign class = class | default: params.class + assign name = name | default: params.name + assign imageSrc = imageSrc | default: params.imageSrc + + assign names = name | split: " " + + case size + when 'xs' + assign dimensions = 20 + when 'sm' + assign dimensions = 24 + when 'md' + assign dimensions = 32 + when 'lg' + assign dimensions = 48 + when 'xl' + assign dimensions = 94 + when '2xl' + assign dimensions = 160 + when '3xl' + assign dimensions = 192 + endcase +%} + +{% if imageSrc == blank %} + +
+ {{ names[0] | slice: 0 }}{{ names[1] | slice: 0 }} +
+ +{% else %} + +
+ +
+ +{% endif %} diff --git a/modules/common-styling/public/views/partials/user/card.liquid b/modules/common-styling/public/views/partials/user/card.liquid new file mode 100644 index 0000000..163c3d6 --- /dev/null +++ b/modules/common-styling/public/views/partials/user/card.liquid @@ -0,0 +1,38 @@ +{% liquid + assign name = first_name | append: ' ' | append: last_name +%} + + +
+ + + {% render 'modules/common-styling/user/avatar', size: 'xxl', name: name, imageSrc: imageSrc %} + {{ name }} + + +
+ {% if job_title != blank %} + {{ job_title }} + {% endif %} + {% if employer != blank %} + {{ 'modules/community/app.at' | t }} {{ employer }}{% if location != blank %}, {{ location }} {% endif %} + {% endif %} +
+ + + +
diff --git a/modules/common-styling/template-values.json b/modules/common-styling/template-values.json new file mode 100644 index 0000000..5e38b0b --- /dev/null +++ b/modules/common-styling/template-values.json @@ -0,0 +1,7 @@ +{ + "name": "platformOS common styling", + "machine_name": "common-styling", + "type": "module", + "version": "1.29.0", + "dependencies": {} +} \ No newline at end of file