From 83d11b7363d4e09f37f3f8a456dcfeb1346d9ec5 Mon Sep 17 00:00:00 2001 From: Niklan Date: Thu, 28 Aug 2025 21:37:08 +0500 Subject: [PATCH] feat(theme): collapse code blocks by default --- .../components/ui/code-block/code-block.twig | 8 +- .../ui/code-block/code-block.component.yml | 2 + .../components/ui/code-block/code-block.css | 47 +++++++++++ .../components/ui/code-block/code-block.js | 84 +++++++++++++++++++ assets/translation/ru.po | 2 + 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 app/themes/laszlo/components/ui/code-block/code-block.js diff --git a/app/modules/niklan/components/ui/code-block/code-block.twig b/app/modules/niklan/components/ui/code-block/code-block.twig index cb55a449a..30ce512ae 100644 --- a/app/modules/niklan/components/ui/code-block/code-block.twig +++ b/app/modules/niklan/components/ui/code-block/code-block.twig @@ -6,9 +6,13 @@ {% do pre_attributes.setAttribute('data-highlight', highlighted_lines) %} {% do pre_attributes.setAttribute('data-highlighted-line-class', 'code-block__highlighted') %} {% endif %} - +{% do attributes.setAttribute('data-selector', 'code-block') %} +{% do attributes.setAttribute('data-collapsed-class', 'code-block--collapsed') %} +{% do attributes.setAttribute('data-collapsed-height', 415) %} +{% do attributes.setAttribute('data-collapse-button-class', 'code-block__button') %} + {% if heading %}
{{ heading }}
{% endif %} - {{ code }} + {{ code }} diff --git a/app/themes/laszlo/components/ui/code-block/code-block.component.yml b/app/themes/laszlo/components/ui/code-block/code-block.component.yml index 62efeb0b6..b2a81a595 100644 --- a/app/themes/laszlo/components/ui/code-block/code-block.component.yml +++ b/app/themes/laszlo/components/ui/code-block/code-block.component.yml @@ -24,3 +24,5 @@ props: libraryOverrides: dependencies: - niklan/hljs + - core/once + - core/drupal diff --git a/app/themes/laszlo/components/ui/code-block/code-block.css b/app/themes/laszlo/components/ui/code-block/code-block.css index e9c5a98e6..ed32fbde7 100644 --- a/app/themes/laszlo/components/ui/code-block/code-block.css +++ b/app/themes/laszlo/components/ui/code-block/code-block.css @@ -1,5 +1,7 @@ @layer component.ui { .code-block { + --collapse-height: 60px; + display: flex; flex-direction: column; border-radius: var(--spacing-4); @@ -9,6 +11,36 @@ .code-block__main { position: relative; + + &::after { + position: absolute; + right: 1px; + bottom: 1px; + left: 1px; + overflow: hidden; + height: var(--collapse-height); + content: ""; + transition: opacity 0.2s; + pointer-events: none; + opacity: 0; + border-top: 1px solid var(--color-outline-variant); + border-radius: 0 0 var(--spacing-1) var(--spacing-1); + background: color-mix( + in sRGB, + var(--color-secondary-fixed), + transparent 50% + ); + backdrop-filter: blur(var(--spacing-1)); + } + } + + .code-block--collapsed .code-block__main::after { + opacity: 1; + } + + .code-block--collapsed code { + overflow: hidden; + max-height: 415px; } .code-block__title { @@ -18,4 +50,19 @@ color: var(--color-on-surface); font: var(--typography-title-medium); } + + .code-block__button { + all: unset; + position: absolute; + z-index: 10; + right: 0; + bottom: 0; + left: 0; + height: var(--collapse-height); + cursor: pointer; + text-align: center; + text-transform: uppercase; + color: var(--color-on-secondary-fixed); + font: var(--typography-label-large); + } } diff --git a/app/themes/laszlo/components/ui/code-block/code-block.js b/app/themes/laszlo/components/ui/code-block/code-block.js new file mode 100644 index 000000000..47199e118 --- /dev/null +++ b/app/themes/laszlo/components/ui/code-block/code-block.js @@ -0,0 +1,84 @@ +((Drupal, once) => { + + const observers = new WeakMap(); + const DEFAULT_CONFIG = { + collapsedClass: 'is-collapsed', + baseHeight: 400, + buttonMargin: 60, + observerRootMargin: '200px 0px' + }; + + const createButton = (block, { buttonClass, ariaLabel }) => { + const button = Object.assign(document.createElement('button'), { + className: buttonClass, + ariaExpanded: 'false', + textContent: Drupal.t('Expand'), + ariaLabel: ariaLabel || Drupal.t('Expand code block') + }); + + const toggle = () => { + block.classList.remove(block.dataset.collapsedClass); + button.remove(); + }; + + return Object.assign(button, { toggle }); + }; + + const handleIntersection = (block, entries) => { + entries + .filter(entry => entry.isIntersecting) + .forEach(() => { + const observer = observers.get(block); + observer?.unobserve(block); + + const { + collapsedClass = DEFAULT_CONFIG.collapsedClass, + collapsedHeight = DEFAULT_CONFIG.baseHeight, + buttonMargin = DEFAULT_CONFIG.buttonMargin, + collapseButtonClass: buttonClass + } = block.dataset; + + const codeContent = block.querySelector('[data-code]'); + const contentHeight = codeContent?.scrollHeight ?? 0; + const calculatedHeight = contentHeight - parseInt(buttonMargin, 10); + + if (calculatedHeight <= parseInt(collapsedHeight, 10)) { + block.classList.remove(collapsedClass); + return; + } + + const button = createButton(block, { + buttonClass, + ariaLabel: codeContent?.dataset.buttonLabel + }); + + button.addEventListener('click', button.toggle); + codeContent?.parentNode.append(button); + }); + }; + + const initCodeBlock = block => { + const observer = new IntersectionObserver( + entries => handleIntersection(block, entries), + { rootMargin: DEFAULT_CONFIG.observerRootMargin } + ); + + observers.set(block, observer); + observer.observe(block); + }; + + Drupal.behaviors.codeBlock = { + attach(context) { + once('code-block', '[data-selector="code-block"]', context) + .forEach(initCodeBlock); + }, + detach(context) { + once.remove('code-block', '[data-selector="code-block"]', context) + .forEach(block => { + observers.get(block)?.disconnect(); + observers.delete(block); + }); + } + }; + +})(Drupal, once); diff --git a/assets/translation/ru.po b/assets/translation/ru.po index 329a76f7d..d651b12ad 100644 --- a/assets/translation/ru.po +++ b/assets/translation/ru.po @@ -498,3 +498,5 @@ msgstr "Копировать" msgid "Copied!" msgstr "Скопировано!" +msgid "Expand code block" +msgstr "Раскрыть блок кода" \ No newline at end of file