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 cb55a449..30ce512a 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 62efeb0b..b2a81a59 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 e9c5a98e..ed32fbde 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 00000000..47199e11
--- /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 329a76f7..d651b12a 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