From 44ef5d81a4605f9c7ff240512ad162c999c20a90 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 13 Oct 2026 12:16:48 -0600 Subject: [PATCH 1/5] Adds Color Categorization to Basic Page View --- css/component/color-categorization.css | 117 ++++++++++++++++++ express_admin.libraries.yml | 1 + ...tent--field-color-categorization.html.twig | 18 +++ 3 files changed, 136 insertions(+) create mode 100644 css/component/color-categorization.css create mode 100644 templates/views-view-field--content--field-color-categorization.html.twig diff --git a/css/component/color-categorization.css b/css/component/color-categorization.css new file mode 100644 index 0000000..ff2d5d7 --- /dev/null +++ b/css/component/color-categorization.css @@ -0,0 +1,117 @@ +/** + * Color categorization swatches on the content overview View + */ + +.form-item--field-color-categorization-value .form-element--type-select-multiple { + width: 3.5rem; + min-width: 3.5rem; + padding-inline: 0; + text-align: center; +} + +.form-item--field-color-categorization-value option { + 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-value option[value="blue"] { + background-image: radial-gradient(circle, #0088ff 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="green"] { + background-image: radial-gradient(circle, #34c759 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="purple"] { + background-image: radial-gradient(circle, #cb30e0 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="pink"] { + background-image: radial-gradient(circle, #ff2d55 68%, transparent 70%); +} + +.form-item--field-color-categorization-value option[value="orange"] { + background-image: radial-gradient(circle, #ff8d28 68%, transparent 70%); +} + +.form-item--field-color-categorization-value 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"] { + 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: #cb30e0; +} + +.color-swatch--pink { + background-color: #ff2d55; +} + +.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 .form-element--type-select-multiple { + width: auto; + min-width: 0; + } + + .form-item--field-color-categorization-value 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..c048b0f 100644 --- a/express_admin.libraries.yml +++ b/express_admin.libraries.yml @@ -3,6 +3,7 @@ global-styling: component: css/component/button.css: {} css/component/form-warning.css: {} + css/component/color-categorization.css: {} node-form: css: 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 %} From 4f266fd12b1107d0dd940784ea7fc569efd7c88a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 13 Oct 2026 12:37:13 -0600 Subject: [PATCH 2/5] Color circles on Basic Page creation form, change hex codes to look more unique --- css/component/color-categorization.css | 38 ++++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/css/component/color-categorization.css b/css/component/color-categorization.css index ff2d5d7..ad7ebdc 100644 --- a/css/component/color-categorization.css +++ b/css/component/color-categorization.css @@ -1,5 +1,6 @@ /** - * Color categorization swatches on the content overview View + * Color categorization swatches on the content overview and the basic page form. + * Color names stay in the option text for screen readers. */ .form-item--field-color-categorization-value .form-element--type-select-multiple { @@ -9,7 +10,8 @@ text-align: center; } -.form-item--field-color-categorization-value option { +.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; @@ -20,31 +22,42 @@ background-size: 0.875rem 0.875rem; } -.form-item--field-color-categorization-value option[value="blue"] { +.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-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"] { - background-image: radial-gradient(circle, #cb30e0 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"] { - background-image: radial-gradient(circle, #ff2d55 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-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-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-value option[value="red"], +.form-item--field-color-categorization option[value="red"] { background-image: radial-gradient(circle, #ff383c 68%, transparent 70%); } @@ -97,7 +110,8 @@ min-width: 0; } - .form-item--field-color-categorization-value option { + .form-item--field-color-categorization-value option, + .form-item--field-color-categorization option { height: auto; color: CanvasText; -webkit-text-fill-color: CanvasText; From e4187aa1455ada0bf77c617d5bd5b1ca752e221c Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 13 Oct 2026 12:46:03 -0600 Subject: [PATCH 3/5] Fix Color Categorization hex code consistency --- css/component/color-categorization.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/component/color-categorization.css b/css/component/color-categorization.css index ad7ebdc..7d18bc5 100644 --- a/css/component/color-categorization.css +++ b/css/component/color-categorization.css @@ -85,11 +85,11 @@ } .color-swatch--purple { - background-color: #cb30e0; + background-color: #663399; } .color-swatch--pink { - background-color: #ff2d55; + background-color: #ff69b4; } .color-swatch--orange { From 34eab8ebb6597a2b8da090f672bb8596ce9f729e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Oct 2026 15:26:03 -0600 Subject: [PATCH 4/5] Adds color circles w/ accessible tagging only on the Content View and the Node edit form for basic page --- css/component/color-categorization.css | 158 +++++++++++++++++++++++-- express_admin.libraries.yml | 5 + js/category-column.js | 87 ++++++++++++++ js/node-form.js | 10 +- 4 files changed, 245 insertions(+), 15 deletions(-) create mode 100644 js/category-column.js diff --git a/css/component/color-categorization.css b/css/component/color-categorization.css index 7d18bc5..38f8f7b 100644 --- a/css/component/color-categorization.css +++ b/css/component/color-categorization.css @@ -1,13 +1,156 @@ /** - * Color categorization swatches on the content overview and the basic page form. - * Color names stay in the option text for screen readers. + * Color categorization swatches. + * Color names stay in the control for screen readers. */ -.form-item--field-color-categorization-value .form-element--type-select-multiple { - width: 3.5rem; - min-width: 3.5rem; +.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, @@ -105,11 +248,6 @@ } @media (forced-colors: active) { - .form-item--field-color-categorization-value .form-element--type-select-multiple { - width: auto; - min-width: 0; - } - .form-item--field-color-categorization-value option, .form-item--field-color-categorization option { height: auto; diff --git a/express_admin.libraries.yml b/express_admin.libraries.yml index c048b0f..6aa2a8e 100644 --- a/express_admin.libraries.yml +++ b/express_admin.libraries.yml @@ -4,6 +4,11 @@ global-styling: 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..042e05e --- /dev/null +++ b/js/category-column.js @@ -0,0 +1,87 @@ +(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; } } From a852f9cae52aa979b4d8062dd8bcd5b854685348 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 15 Oct 2026 13:57:41 -0600 Subject: [PATCH 5/5] Linter fix --- js/category-column.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/category-column.js b/js/category-column.js index 042e05e..eec6c83 100644 --- a/js/category-column.js +++ b/js/category-column.js @@ -1,3 +1,4 @@ +/* global Drupal, once */ (function (Drupal, once) { const colors = ['blue', 'green', 'purple', 'pink', 'orange', 'yellow', 'red'];