diff --git a/css/component/color-categorization.css b/css/component/color-categorization.css new file mode 100644 index 0000000..38f8f7b --- /dev/null +++ b/css/component/color-categorization.css @@ -0,0 +1,269 @@ +/** + * Color categorization swatches. + * Color names stay in the control for screen readers. + */ + +.basic-page-color-category-details > summary .basic-page-color-category__selected { + display: none; + align-items: center; + gap: 0.25rem; + margin-inline-start: 0.5rem; +} + +.basic-page-color-category-details:not([open]) > summary .basic-page-color-category__selected:not(:empty) { + display: inline-flex; +} + +.basic-page-color-category__dot { + width: 0.75rem; + height: 0.75rem; + border-radius: 50%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); +} + +.basic-page-color-category__dot--blue { + background-color: #0088ff; +} + +.basic-page-color-category__dot--green { + background-color: #34c759; +} + +.basic-page-color-category__dot--purple { + background-color: #663399; +} + +.basic-page-color-category__dot--pink { + background-color: #ff69b4; +} + +.basic-page-color-category__dot--orange { + background-color: #ff8d28; +} + +.basic-page-color-category__dot--yellow { + background-color: #ffcc00; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.28); +} + +.basic-page-color-category__dot--red { + background-color: #ff383c; +} + +.views-exposed-form .category-column { + width: max-content; + min-width: 4.5rem; + border: var(--input-border-size) solid var(--input-border-color); + border-radius: var(--base-border-radius); + background-color: var(--input-bg-color); +} + +.views-exposed-form .category-column__toggle { + display: flex; + align-items: center; + justify-content: flex-end; + box-sizing: border-box; + min-height: calc(((var(--input-padding-vertical) + var(--input-border-size)) * 2) + var(--input-line-height)); + margin: 0; + gap: 0.35rem; + padding-inline: 0.65rem; + list-style: none; + cursor: pointer; +} + +.views-exposed-form .category-column__selected { + display: none; + align-items: center; + gap: 0.25rem; + margin-inline-end: auto; +} + +.views-exposed-form .category-column:not([open]) .category-column__selected:not(:empty) { + display: flex; +} + +.views-exposed-form .category-column__dot { + width: 0.75rem; + height: 0.75rem; + border-radius: 50%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); +} + +.views-exposed-form .category-column__dot--blue { + background-color: #0088ff; +} + +.views-exposed-form .category-column__dot--green { + background-color: #34c759; +} + +.views-exposed-form .category-column__dot--purple { + background-color: #663399; +} + +.views-exposed-form .category-column__dot--pink { + background-color: #ff69b4; +} + +.views-exposed-form .category-column__dot--orange { + background-color: #ff8d28; +} + +.views-exposed-form .category-column__dot--yellow { + background-color: #ffcc00; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.28); +} + +.views-exposed-form .category-column__dot--red { + background-color: #ff383c; +} + +.views-exposed-form .category-column__toggle::-webkit-details-marker { + display: none; +} + +.views-exposed-form .category-column__toggle::marker { + content: ""; +} + +.views-exposed-form .category-column__caret { + width: 0.875rem; + height: 0.5625rem; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 9'%3e%3cpath fill='none' stroke-width='1.5' d='M1 1l6 6 6-6' stroke='%23545560'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} + +.views-exposed-form .category-column[open] .category-column__caret { + transform: scaleY(-1); +} + +.views-exposed-form .category-column .form-element--type-select-multiple { + display: block; + width: 100%; + min-width: 0; + margin: 0; + padding-inline: 0; + border: 0; + border-block-start: var(--input-border-size) solid var(--input-border-color); + border-radius: 0; + box-shadow: none; + text-align: center; + background-image: none; +} + +.form-item--field-color-categorization-value option, +.form-item--field-color-categorization option:not([value="_none"]) { + height: 1.375rem; + color: transparent; + -webkit-text-fill-color: transparent; + font-size: 0; + line-height: 1.375rem; + background-repeat: no-repeat; + background-position: center; + background-size: 0.875rem 0.875rem; +} + +.form-item--field-color-categorization option:not([value="_none"]) { + background-position: 0.75rem center; +} + +.form-item--field-color-categorization-value option[value="blue"], +.form-item--field-color-categorization option[value="blue"] { + background-image: radial-gradient(circle, #0088ff 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="green"], +.form-item--field-color-categorization option[value="green"] { + background-image: radial-gradient(circle, #34c759 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="purple"], +.form-item--field-color-categorization option[value="purple"] { + background-image: radial-gradient(circle, #663399 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="pink"], +.form-item--field-color-categorization option[value="pink"] { + background-image: radial-gradient(circle, #ff69b4 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="orange"], +.form-item--field-color-categorization option[value="orange"] { + background-image: radial-gradient(circle, #ff8d28 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="yellow"], +.form-item--field-color-categorization option[value="yellow"] { + background-image: radial-gradient(circle, #ffcc00 60%, rgba(0, 0, 0, 0.28) 62%, rgba(0, 0, 0, 0.28) 70%, transparent 72%); +} + +.form-item--field-color-categorization-value option[value="red"], +.form-item--field-color-categorization option[value="red"] { + background-image: radial-gradient(circle, #ff383c 68%, transparent 70%); +} + +.color-categorization { + display: inline-flex; + gap: 0.35rem; + align-items: center; +} + +.color-swatch { + display: inline-block; + width: 0.875rem; + height: 0.875rem; + border-radius: 50%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); + vertical-align: middle; +} + +.color-swatch--blue { + background-color: #0088ff; +} + +.color-swatch--green { + background-color: #34c759; +} + +.color-swatch--purple { + background-color: #663399; +} + +.color-swatch--pink { + background-color: #ff69b4; +} + +.color-swatch--orange { + background-color: #ff8d28; +} + +.color-swatch--yellow { + background-color: #ffcc00; +} + +.color-swatch--red { + background-color: #ff383c; +} + +@media (forced-colors: active) { + .form-item--field-color-categorization-value option, + .form-item--field-color-categorization option { + height: auto; + color: CanvasText; + -webkit-text-fill-color: CanvasText; + font-size: inherit; + background-image: none; + } + + .color-swatch .visually-hidden { + position: static !important; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + clip-path: none; + } +} diff --git a/express_admin.libraries.yml b/express_admin.libraries.yml index 7faea6b..6aa2a8e 100644 --- a/express_admin.libraries.yml +++ b/express_admin.libraries.yml @@ -3,6 +3,12 @@ global-styling: component: css/component/button.css: {} css/component/form-warning.css: {} + css/component/color-categorization.css: {} + js: + js/category-column.js: {} + dependencies: + - core/drupal + - core/once node-form: css: diff --git a/js/category-column.js b/js/category-column.js new file mode 100644 index 0000000..eec6c83 --- /dev/null +++ b/js/category-column.js @@ -0,0 +1,88 @@ +/* global Drupal, once */ +(function (Drupal, once) { + const colors = ['blue', 'green', 'purple', 'pink', 'orange', 'yellow', 'red']; + + // One circle per selected color - w/ accessiblescreen reader naming + function fillDots(select, container, className) { + const names = []; + container.replaceChildren(); + [...select.selectedOptions].forEach((option) => { + if (!colors.includes(option.value)) { + return; + } + const dot = document.createElement('span'); + dot.className = className + ' ' + className + '--' + option.value; + dot.setAttribute('aria-hidden', 'true'); + container.appendChild(dot); + names.push(option.textContent.trim()); + }); + return names; + } + + Drupal.behaviors.expressAdminCategoryColumn = { + attach(context) { + // show selected circles in the Color Category summary + once('basic-page-color-category', '.form-item--field-color-categorization select[multiple]', context).forEach((select) => { + const details = select.closest('details'); + const summary = details ? details.querySelector(':scope > summary') : null; + if (!details || !summary) { + return; + } + + details.classList.add('basic-page-color-category-details'); + const selected = document.createElement('span'); + selected.className = 'basic-page-color-category__selected'; + summary.appendChild(selected); + + const render = () => { + fillDots(select, selected, 'basic-page-color-category__dot'); + }; + + select.addEventListener('change', render); + render(); + }); + + // Content View - wrap the Category multiselect in a caret to look nice, otherwise its long + once('category-column', '.form-item--field-color-categorization-value select[multiple]', context).forEach((select) => { + const item = select.closest('.form-item--field-color-categorization-value'); + const label = item ? item.querySelector('label') : null; + if (!item || !label) { + return; + } + + const labelText = label.textContent.trim(); + const details = document.createElement('details'); + details.className = 'category-column'; + + const summary = document.createElement('summary'); + summary.className = 'category-column__toggle'; + + // Screen reader name for the closed control. + const hidden = document.createElement('span'); + hidden.className = 'visually-hidden'; + hidden.dataset.categoryLabel = labelText; + hidden.textContent = labelText; + + const selected = document.createElement('span'); + selected.className = 'category-column__selected'; + + const caret = document.createElement('span'); + caret.className = 'category-column__caret'; + caret.setAttribute('aria-hidden', 'true'); + + summary.append(hidden, selected, caret); + details.append(summary); + label.insertAdjacentElement('afterend', details); + details.append(select); + + const render = () => { + const names = fillDots(select, selected, 'category-column__dot'); + hidden.textContent = names.length ? labelText + ': ' + names.join(', ') : labelText; + }; + + select.addEventListener('change', render); + render(); + }); + }, + }; +})(Drupal, once); diff --git a/js/node-form.js b/js/node-form.js index 65fb184..4d055be 100644 --- a/js/node-form.js +++ b/js/node-form.js @@ -8,7 +8,7 @@ // check the toggle state of the button const status = button.dataset.status; - // if the toggle status is 'open' + // Toggle toggle if (status == 'open') { main.classList.add('ucb-sidebar-closed'); } else { @@ -25,17 +25,17 @@ const myStatus = button.dataset.status; if (myStatus == 'open') { - // set the data attribute to closed + // Set the data attribute to closed button.dataset.status = 'closed'; - // and switch to the closed icon + // Switch to the closed icon button.innerHTML = closeButton; } else { - // set the data attribute to open + // Set open button.dataset.status = 'open'; - // ans switch to the open icon + // Switch to open icon button.innerHTML = openButton; } } diff --git a/templates/views-view-field--content--field-color-categorization.html.twig b/templates/views-view-field--content--field-color-categorization.html.twig new file mode 100644 index 0000000..dd0d982 --- /dev/null +++ b/templates/views-view-field--content--field-color-categorization.html.twig @@ -0,0 +1,18 @@ +{# +/** + * @file + * Shows each color categorization as a circle + */ +#} +{% set labels = output|render|striptags|trim|split(', ') %} +{% if labels|join %} + + {% for label in labels %} + {% if label %} + + {{ label }} + + {% endif %} + {% endfor %} + +{% endif %}