From 521eed19601978bcaf28a668c9a066de55bed2c7 Mon Sep 17 00:00:00 2001 From: Arnaud De Burchgraeve <99794461+arnaud-dbu@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:46:33 +0200 Subject: [PATCH] refactor accordion component --- tailoff/css/site/components/accordion.css | 370 ++++--------------- tailoff/js/components/accordion.component.ts | 29 +- tailoff/js/site.ts | 5 + templates/jsPlugins/accordion.twig | 239 +----------- 4 files changed, 98 insertions(+), 545 deletions(-) diff --git a/tailoff/css/site/components/accordion.css b/tailoff/css/site/components/accordion.css index 6bf15c3f..2d8d0c8f 100644 --- a/tailoff/css/site/components/accordion.css +++ b/tailoff/css/site/components/accordion.css @@ -1,338 +1,118 @@ +:root { + /* Accordion structure */ + --accordion-transition-duration: 300ms; + --accordion-transition-timing: cubic-bezier(0.4, 0.01, 0.165, 0.99); + + /* Summary styles */ + --summary-background-color: hsl(195, 10%, 90%); + --summary-color: inherit; + --summary-border-radius: 5px; + --summary-padding: 0.5rem 0.5rem 0.5rem 1rem; + --summary-margin: 0.5rem 0; + + /* Open summary styles */ + --summary-open-background-color: hsl(195, 10%, 20%); + --summary-open-color: hsl(195, 10%, 92%); + + /* Focus styles */ + --summary-focus-background-color: hsl(195, 10%, 75%); + --summary-open-focus-background-color: hsl(195, 10%, 10%); + --summary-open-focus-color: hsl(195, 10%, 99%); + + /* Content styles */ + --content-color: #777; + --content-line-height: 1.6; + --content-padding: 0.5rem; +} + +/* Accordion structure */ details { height: var(--collapsed); overflow: hidden; - transition: height 300ms cubic-bezier(0.4, 0.01, 0.165, 0.99); + transition: height var(--accordion-transition-duration) var(--accordion-transition-timing); + font-size: 1rem; + cursor: pointer; } details[open] { height: var(--expanded); - - svg { - @apply transition rotate-180; - } - - [data-accordion-collapsed-text] { - @apply hidden; - } - - &[data-accordion-cutoff] { - summary { - @apply hidden; - } - } } -details:not([open]) { - [data-accordion-expanded-text] { - @apply hidden; - } +details[open] svg { + @apply transition rotate-180; } -summary::marker { - display: none; +details[open] [data-accordion-collapsed-text] { + @apply hidden; } -summary::-webkit-details-marker { - display: none; +details[open][data-accordion-cutoff] summary { + @apply hidden; } -summary:has(svg) { - @apply flex items-center justify-between; +details:not([open]) [data-accordion-expanded-text] { + @apply hidden; } -[data-accordion-collapsed-text], -[data-accordion-expanded-text] { - @apply text-base; -} - -[data-accordion-close] { - @apply px-3 m-2 text-black transition bg-gray-200 hover:bg-gray-300; -} - -[data-accordion-cutoff] { - summary { - @apply px-4 text-base text-white transition bg-black hover:bg-gray-600 w-fit; - } -} - -button, +/* Summary styles */ summary { - background-color: var(--bgc); + background-color: var(--summary-background-color); + color: var(--summary-color); border: 0; - border-radius: 5px; - color: var(--c, inherit); + border-radius: var(--summary-border-radius); list-style-type: none; - margin: 0.5rem 0; + margin: var(--summary-margin); outline: none; - padding-bottom: 0.5rem; - padding-top: 0.5rem; - padding-inline-end: 0.5rem; - padding-inline-start: 1rem; + padding: var(--summary-padding); user-select: none; + font-size: 1.5rem; + list-style: none; } -/* Animated icons */ - -[data-css-icon] { - --animdur: 0.3s; - --loading-animdur: 0.8s; - --animtf: ease-in; - --bdw: 2px; - --bdrs: 50%; - --bgc: transparent; - --c: currentcolor; - --dots-bgc: silver; - --dots-size: 0.5rem; - --icon-size: 1rem; - --size: 2.5rem; - align-items: center; - cursor: pointer; - display: flex; - justify-content: space-between; +summary::marker, +summary::-webkit-details-marker { + display: none; } -[data-css-icon] i { +summary:has(svg) { + display: flex; align-items: center; - background-color: var(--bgc); - border-radius: var(--bdrs); - box-sizing: border-box; - display: inline-flex; - height: var(--size); - justify-content: center; - position: relative; - transition: background-color var(--animdur) var(--animtf); - width: var(--size); -} -[data-css-icon] i::after, -[data-css-icon] i::before { - transform-origin: 50% 50%; - transition: all var(--animdur) var(--animtf); -} - -[data-css-icon*="down"] i::after, -[data-css-icon*="right"] i::after { - background: transparent; - border-color: var(--c); - border-style: solid; - box-sizing: border-box; - content: ""; - display: inline-block; - height: var(--icon-size); - margin: 0; - position: relative; - width: var(--icon-size); -} - -[data-css-icon*="down"] i::after { - border-width: 0 var(--bdw) var(--bdw) 0; - top: calc(0px - (var(--icon-size) / 4)); - transform: rotate(45deg); -} -[data-css-icon*="right"] i::after { - border-width: var(--bdw) var(--bdw) 0 0; - right: calc((var(--icon-size) / 4)); - top: 0; - transform: rotate(45deg); -} - -[data-css-icon*="equals"] i::after, -[data-css-icon*="equals"] i::before, -[data-css-icon*="cross"] i::after, -[data-css-icon*="cross"] i::before, -[data-css-icon*="menu"] i, -[data-css-icon*="menu"] i::after, -[data-css-icon*="menu"] i::before, -[data-css-icon*="plus"] i::after, -[data-css-icon*="plus"] i::before { - /* Width need to be the diagonal of the down-arrow side-length (--size): sqrt(2) * --size. */ - --w: calc(var(--icon-size) * 1.4142135623730950488016887242097); - background: var(--c); - content: ""; - height: var(--bdw); - position: absolute; - width: var(--w); -} - -[data-css-icon*="cross"] i::before, -[data-css-icon*="plus"] i::before { - transform: rotate(90deg); -} - -[data-css-icon*="equals"] i { - --m: 4px; -} - -[data-css-icon*="equals"] i::after { - transform: translateY(var(--m)); -} - -[data-css-icon*="equals"] i::before { - transform: translateY(calc(0px - var(--m))); -} - -[data-css-icon*="dots"], -[data-css-icon*="menu"] { - height: var(--size); -} - -[data-css-icon*="menu"] i { - --bdrs: 0; - --m: 7px; - position: relative; - right: calc((var(--size) - var(--w)) / 2); -} - -[data-css-icon*="menu"] i::after { - top: var(--m); -} - -[data-css-icon*="menu"] i::before { - top: calc(0px - var(--m)); -} - -[data-css-icon*="dots"] i, -[data-css-icon*="dots"] i::after, -[data-css-icon*="dots"] i::before { - animation: dots var(--loading-animdur) infinite alternate; - background-color: var(--c); - border-radius: 50%; - content: ""; - display: inline-block; - height: var(--dots-size); - width: var(--dots-size); -} - -[data-css-icon*="dots"] i { - animation-delay: var(--loading-animdur); - position: relative; - right: calc((var(--size) - var(--dots-size)) / 4); -} - -[data-css-icon*="dots"] i::after { - animation-delay: 0s; - left: calc(0px - (var(--dots-size) * 3)); - position: absolute; -} - -[data-css-icon*="dots"] i::before { - animation-delay: calc(var(--loading-animdur) / 2); - left: calc(0px - (var(--dots-size) * 1.5)); - position: absolute; -} - -[data-css-icon*="spin"] i::after { - animation: spin var(--loading-animdur) infinite linear; - border-radius: 50%; - border: var(--bdw) solid var(--dots-bgc); - border-left: var(--bdw) solid var(--c); - content: ""; - height: var(--icon-size); - transform: translateZ(0); - width: var(--icon-size); -} - -/* State */ - -[open] > summary > [data-css-icon*="cross"] i::after { - transform: rotate(45deg); -} -[open] > summary > [data-css-icon*="cross"] i::before { - transform: rotate(135deg); -} -[open] > summary > [data-css-icon*="down"] i::after { - top: var(--bdw); - transform: rotate(45deg) scale(-1); -} -[open] > summary > [data-css-icon*="right"] i::after { - right: 0; - top: calc(0px - (var(--icon-size) / 4)); - transform: rotate(135deg); -} -[open] > summary > [data-css-icon*="plus"] i::after { - transform: rotate(180deg); -} -[open] > summary > [data-css-icon*="plus"] i::before { - transform: rotate(-0deg); + justify-content: space-between; } -[open] > summary > [data-css-icon*="equals"] i::after { - transform: rotate(-45deg); -} -[open] > summary > [data-css-icon*="equals"] i::before { - transform: rotate(45deg); +/* Open summary styles */ +details[open] > summary { + background-color: var(--summary-open-background-color); + color: var(--summary-open-color); } -[open] > summary > [data-css-icon*="menu"] i { - background-color: transparent; -} -[open] > summary > [data-css-icon*="menu"] i::after { - transform: translateY(calc(0px - var(--m))) rotate(-45deg); -} -[open] > summary > [data-css-icon*="menu"] i::before { - transform: translateY(var(--m)) rotate(45deg); +/* Focus styles */ +summary:focus { + background-color: var(--summary-focus-background-color); } -/* MODIFIERS */ -[data-css-icon*="outline"] i { - border: var(--bdw) solid var(--c); +details[open] > summary:focus { + background-color: var(--summary-open-focus-background-color); + color: var(--summary-open-focus-color); } -[data-css-icon*="fill"] { - --bgc: hsl(195, 10%, 30%); - --c: hsl(195, 10%, 95%); -} -[data-css-icon*="square"] { - --bdrs: 5px; -} - -/* 4 States of summary */ -button, -summary { - --bgc: hsl(195, 10%, 90%); +/* Text styles */ +[data-accordion-collapsed-text], +[data-accordion-expanded-text] { + @apply text-base; } -[open] > summary { - --bgc: hsl(195, 10%, 20%); - --c: hsl(195, 10%, 92%); +[data-accordion-close] { + @apply px-3 m-2 text-black transition bg-gray-200 hover:bg-gray-300; } -button:focus, -summary:focus { - --bgc: hsl(195, 10%, 75%); +[data-accordion-cutoff] summary { + @apply px-4 text-base text-white transition bg-black hover:bg-gray-600 w-fit; } -[open] > summary:focus { - --bgc: hsl(195, 10%, 10%); - --c: hsl(195, 10%, 99%); -} -s [open] > summary:focus > [data-css-icon*="fill"], -[open] > summary > [data-css-icon*="fill"] { - --bgc: hsl(195, 10%, 80%); - --c: hsl(195, 10%, 10%); -} +/* Content styles */ summary + * { - color: #777; - line-height: 1.6; - padding: 0.5rem; -} - -/* Animations */ - -@keyframes dots { - 0% { - background-color: var(--c); - } - 50%, - 100% { - background-color: var(--dots-bgc); - } -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - to { - transform: rotate(1turn); - } + color: var(--content-color); + line-height: var(--content-line-height); + padding: var(--content-padding); } diff --git a/tailoff/js/components/accordion.component.ts b/tailoff/js/components/accordion.component.ts index fabf9d70..1b502372 100644 --- a/tailoff/js/components/accordion.component.ts +++ b/tailoff/js/components/accordion.component.ts @@ -1,4 +1,4 @@ -export class accordionComponent { +export class AccordionComponent { constructor() { this.initAccordions(); } @@ -11,7 +11,7 @@ export class accordionComponent { // If the width of the accordion has changed, reset the height if (width !== entry.contentRect.width) { - accordion.removeAttribute("style"); + accordion.removeAttribute('style'); setHeight(accordion); setHeight(accordion, true); accordion.open = false; @@ -19,11 +19,11 @@ export class accordionComponent { }); }); - const accordions = document.querySelectorAll("details"); + const accordions = document.querySelectorAll('details'); // If the accordion has the data-accordion-animation attribute, observe it accordions.forEach((accordion: HTMLElement) => { - if (accordion.hasAttribute("data-accordion-animation")) { + if (accordion.hasAttribute('data-accordion-animation')) { RO.observe(accordion); } }); @@ -38,38 +38,33 @@ export class accordionComponent { accordion.dataset.width = rect.width.toString(); // Set the height of the accordion content - accordion.style.setProperty( - open ? `--expanded` : `--collapsed`, - `${rect.height}px` - ); + accordion.style.setProperty(open ? `--expanded` : `--collapsed`, `${rect.height}px`); // Add the custom transition duration - const animationDuration = accordion.getAttribute( - "data-accordion-duration" - ); + const animationDuration = accordion.getAttribute('data-accordion-duration'); - if (accordion.hasAttribute("data-accordion-duration")) { + if (accordion.hasAttribute('data-accordion-duration')) { accordion.style.transition = `height ${animationDuration}ms cubic-bezier(0.4, 0.01, 0.165, 0.99)`; } // Add a second close button to the accordion - const closeButton = accordion.querySelector("[data-accordion-close]"); + const closeButton = accordion.querySelector('[data-accordion-close]'); if (closeButton) { - closeButton.addEventListener("click", () => { + closeButton.addEventListener('click', () => { accordion.open = false; }); } }; // Close any open accordions when another is opened - const groupAccordion = document.querySelectorAll("[data-accordion-group]"); + const groupAccordion = document.querySelectorAll('[data-accordion-group]'); groupAccordion.forEach((group: HTMLElement) => { - const accordions = group.querySelectorAll("details"); + const accordions = group.querySelectorAll('details'); accordions.forEach((accordion: HTMLDetailsElement) => { - accordion.addEventListener("toggle", (e) => { + accordion.addEventListener('toggle', (e) => { if ((e.target as any).open) { accordions.forEach((accordion: HTMLDetailsElement) => { if (accordion !== e.target) { diff --git a/tailoff/js/site.ts b/tailoff/js/site.ts index 8d756b4e..efe81028 100644 --- a/tailoff/js/site.ts +++ b/tailoff/js/site.ts @@ -36,6 +36,11 @@ const components = [ className: 'AutocompleteComponent', selector: '[data-s-autocomplete]', }, + { + name: 'accordion', + className: 'AccordionComponent', + selector: 'details', + }, { name: 'chip', className: 'ChipComponent', diff --git a/templates/jsPlugins/accordion.twig b/templates/jsPlugins/accordion.twig index 77bb046e..2c925b91 100644 --- a/templates/jsPlugins/accordion.twig +++ b/templates/jsPlugins/accordion.twig @@ -15,6 +15,8 @@