diff --git a/tailoff/css/site/components/accordion.css b/tailoff/css/site/components/accordion.css new file mode 100644 index 00000000..6bf15c3f --- /dev/null +++ b/tailoff/css/site/components/accordion.css @@ -0,0 +1,338 @@ +details { + height: var(--collapsed); + overflow: hidden; + transition: height 300ms cubic-bezier(0.4, 0.01, 0.165, 0.99); +} + +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; + } +} + +summary::marker { + display: none; +} + +summary::-webkit-details-marker { + display: none; +} + +summary:has(svg) { + @apply flex items-center justify-between; +} + +[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 { + background-color: var(--bgc); + border: 0; + border-radius: 5px; + color: var(--c, inherit); + list-style-type: none; + margin: 0.5rem 0; + outline: none; + padding-bottom: 0.5rem; + padding-top: 0.5rem; + padding-inline-end: 0.5rem; + padding-inline-start: 1rem; + user-select: 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; +} + +[data-css-icon] i { + 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); +} + +[open] > summary > [data-css-icon*="equals"] i::after { + transform: rotate(-45deg); +} +[open] > summary > [data-css-icon*="equals"] i::before { + transform: rotate(45deg); +} + +[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); +} + +/* MODIFIERS */ +[data-css-icon*="outline"] i { + border: var(--bdw) solid var(--c); +} +[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%); +} + +[open] > summary { + --bgc: hsl(195, 10%, 20%); + --c: hsl(195, 10%, 92%); +} + +button:focus, +summary:focus { + --bgc: hsl(195, 10%, 75%); +} + +[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%); +} +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); + } +} diff --git a/tailoff/css/site/main.css b/tailoff/css/site/main.css index 3c7e68d5..cce5a2a9 100644 --- a/tailoff/css/site/main.css +++ b/tailoff/css/site/main.css @@ -8,6 +8,7 @@ @import 'tailwindcss/components'; +@import './components/accordion.css'; @import './components/ajax-search.css'; @import './components/autocomplete.css'; @import './components/button.css'; diff --git a/tailoff/js/components/accordion.component.ts b/tailoff/js/components/accordion.component.ts new file mode 100644 index 00000000..fabf9d70 --- /dev/null +++ b/tailoff/js/components/accordion.component.ts @@ -0,0 +1,84 @@ +export class accordionComponent { + constructor() { + this.initAccordions(); + } + + private initAccordions() { + const RO = new ResizeObserver((entries) => { + return entries.forEach((entry) => { + const accordion = entry.target as HTMLDetailsElement; + const width = parseInt(accordion.dataset.width, 10); + + // If the width of the accordion has changed, reset the height + if (width !== entry.contentRect.width) { + accordion.removeAttribute("style"); + setHeight(accordion); + setHeight(accordion, true); + accordion.open = false; + } + }); + }); + + 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")) { + RO.observe(accordion); + } + }); + + const setHeight = (accordion: HTMLDetailsElement, open = false) => { + accordion.open = open; + + // Get the height of the accordion content + const rect = accordion.getBoundingClientRect(); + + // Set the width of the accordion + accordion.dataset.width = rect.width.toString(); + + // Set the height of the accordion content + accordion.style.setProperty( + open ? `--expanded` : `--collapsed`, + `${rect.height}px` + ); + + // Add the custom transition duration + const animationDuration = accordion.getAttribute( + "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]"); + + if (closeButton) { + closeButton.addEventListener("click", () => { + accordion.open = false; + }); + } + }; + + // Close any open accordions when another is opened + const groupAccordion = document.querySelectorAll("[data-accordion-group]"); + + groupAccordion.forEach((group: HTMLElement) => { + const accordions = group.querySelectorAll("details"); + + accordions.forEach((accordion: HTMLDetailsElement) => { + accordion.addEventListener("toggle", (e) => { + if ((e.target as any).open) { + accordions.forEach((accordion: HTMLDetailsElement) => { + if (accordion !== e.target) { + (accordion as any).open = false; + } + }); + } + }); + }); + }); + } +} diff --git a/tailoff/js/site.ts b/tailoff/js/site.ts index d4175d41..21a69f78 100644 --- a/tailoff/js/site.ts +++ b/tailoff/js/site.ts @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; // Accept HMR as per: https://vitejs.dev/guide/api-hmr.html if (import.meta.hot) { import.meta.hot.accept(() => { - console.log('HMR'); + console.log("HMR"); }); } @@ -238,5 +238,5 @@ components.forEach((component) => { * CSS import * DO NOT REMOVE !! */ -import '../css/site/main.css'; -import '../css/site/ckeditor.css'; +import "../css/site/main.css"; +import "../css/site/ckeditor.css"; diff --git a/templates/jsPlugins/accordion.twig b/templates/jsPlugins/accordion.twig new file mode 100644 index 00000000..77bb046e --- /dev/null +++ b/templates/jsPlugins/accordion.twig @@ -0,0 +1,372 @@ +{% extends '_site/_layout' %} +{% set breadcrumbs = [ + { url: './overview.twig', title: 'Plugins' }, + { url: './accordion.twig', title: 'accordion' } ] +%} +{% block content %} +
Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+Mollit aute ex exercitation incididunt deserunt enim labore veniam proident eu. Nostrud ullamco incididunt ex proident nulla reprehenderit anim et duis ipsum veniam nostrud aliquip. Do consequat cillum veniam irure eiusmod aute occaecat dolor mollit proident consectetur mollit aliqua aliqua occaecat. Amet ad ut non culpa sunt laboris laboris tempor dolor dolor. Mollit ut dolor culpa amet. Amet commodo sit velit. Proident ipsum mollit elit magna occaecat nisi laborum veniam aliquip sit nostrud.
+