Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions blocks/accordion/accordion.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/* =========================
Accordion Container
========================= */

.accordion {
width: 100%;
margin: 2rem 0;
}

/* =========================
Accordion Item
========================= */

.accordion-item {
overflow: hidden;
}

/* =========================
Accordion Header (Button)
========================= */

.accordion-header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.5rem;
background-color: #d6ecff;
border: none;
text-align: left;
cursor: pointer;
font-family: var(--heading-font-family, inherit);
font-size: 1.125rem;
font-weight: 600;
color: var(--text-color, #333);
transition: background-color 0.2s ease, color 0.2s ease;
position: relative;
}

/* Change to lighter color on hover */
.accordion-header:hover {
background-color: #e8f4ff;
}

/* Ensure closed accordions always keep light blue background */
.accordion-item:not(.is-open) .accordion-header {
background-color: #d6ecff !important;
}

.accordion-item:not(.is-open) .accordion-header:hover {
background-color: #e8f4ff !important;
}

.accordion-item:not(.is-open) .accordion-header:focus {
background-color: #d6ecff !important;
}

.accordion-item:not(.is-open) .accordion-header:active {
background-color: #d6ecff !important;
}

.accordion-header:focus {
outline: 2px solid var(--link-color, #0066cc);
outline-offset: -2px;
}

.accordion-header:focus:not(:focus-visible) {
outline: none;
}

/* Opened accordion styling */
.accordion-item.is-open .accordion-header {
color: var(--text-color, #333);
}

/* =========================
Accordion Icon
========================= */

.accordion-icon {
flex-shrink: 0;
width: 1.5rem;
height: 1.5rem;
margin-left: 1rem;
position: relative;
transition: transform 0.3s ease;
}

.accordion-icon::before,
.accordion-icon::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
background-color: var(--text-color, #333);
transition: background-color 0.2s ease;
}

.accordion-icon::before {
width: 12px;
height: 2px;
transform: translate(-50%, -50%);
}

.accordion-icon::after {
width: 2px;
height: 12px;
transform: translate(-50%, -50%);
transition: transform 0.3s ease, opacity 0.3s ease;
}

.accordion-item.is-open .accordion-icon::after {
transform: translate(-50%, -50%) rotate(90deg);
opacity: 0;
}

/* Icon color remains default for closed accordions */

/* =========================
Accordion Content
========================= */

.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
padding: 0 1.5rem;
}

.accordion-item.is-open .accordion-content {
padding-bottom: 1.5rem;
padding-top: 0.5rem;
}

.accordion-content > * {
margin: 0.75rem 0;
}

.accordion-content > *:first-child {
margin-top: 0;
}

.accordion-content > *:last-child {
margin-bottom: 0;
}

/* =========================
Content Styling
========================= */

.accordion-content h4 {
margin-top: 1.5rem;
margin-bottom: 0.75rem;
font-size: 1rem;
font-weight: 600;
}

.accordion-content h4:first-child {
margin-top: 0;
}

.accordion-content ul {
margin: 0.75rem 0;
padding-left: 1.5rem;
}

.accordion-content li {
margin: 0.5rem 0;
line-height: 1.6;
}

.accordion-content strong {
font-weight: 600;
}

/* =========================
Responsive Design
========================= */

@media (width >= 768px) {
.accordion-header {
padding: 1.5rem 2rem;
font-size: 1.25rem;
}

.accordion-content {
padding-left: 2rem;
padding-right: 2rem;
}

.accordion-item.is-open .accordion-content {
padding-bottom: 2rem;
}
}

/* =========================
Reduced Motion
========================= */

@media (prefers-reduced-motion: reduce) {
.accordion-content,
.accordion-icon,
.accordion-icon::after {
transition: none;
}
}
88 changes: 88 additions & 0 deletions blocks/accordion/accordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
export default function decorate(block) {
const items = [...block.children];

Check failure on line 3 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
items.forEach((item, index) => {
// Add accordion item class
item.classList.add('accordion-item');

Check failure on line 7 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Get title and content divs
const [titleDiv, contentDiv] = [...item.children];

Check failure on line 10 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
if (!titleDiv || !contentDiv) return;

Check failure on line 12 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Create button for title
const button = document.createElement('button');
button.className = 'accordion-header';
button.type = 'button';
button.id = `accordion-header-${index}`;
button.setAttribute('aria-expanded', index === 0 ? 'true' : 'false');
button.setAttribute('aria-controls', `accordion-content-${index}`);

Check failure on line 20 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Move title content to button
while (titleDiv.firstChild) {
button.appendChild(titleDiv.firstChild);
}

Check failure on line 25 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Add icon
const icon = document.createElement('span');
icon.className = 'accordion-icon';
icon.setAttribute('aria-hidden', 'true');
button.appendChild(icon);

Check failure on line 31 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Setup content div
contentDiv.className = 'accordion-content';
contentDiv.id = `accordion-content-${index}`;
contentDiv.setAttribute('role', 'region');
contentDiv.setAttribute('aria-labelledby', `accordion-header-${index}`);

Check failure on line 37 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Set initial state (first item open by default)
if (index === 0) {
item.classList.add('is-open');
contentDiv.style.maxHeight = `${contentDiv.scrollHeight}px`;
} else {
contentDiv.style.maxHeight = '0';
}

Check failure on line 45 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Replace title div with button
item.replaceChild(button, titleDiv);

Check failure on line 48 in blocks/accordion/accordion.js

View workflow job for this annotation

GitHub Actions / build

Trailing spaces not allowed
// Click handler
button.addEventListener('click', () => {
const isOpen = item.classList.contains('is-open');

// Close all items (for accordion behavior)
items.forEach((otherItem) => {
if (otherItem !== item) {
otherItem.classList.remove('is-open');
const otherContent = otherItem.querySelector('.accordion-content');
const otherButton = otherItem.querySelector('.accordion-header');
if (otherContent) {
otherContent.style.maxHeight = '0';
}
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
}
});

// Toggle current item
if (isOpen) {
item.classList.remove('is-open');
contentDiv.style.maxHeight = '0';
button.setAttribute('aria-expanded', 'false');
} else {
item.classList.add('is-open');
contentDiv.style.maxHeight = `${contentDiv.scrollHeight}px`;
button.setAttribute('aria-expanded', 'true');
}
});

// Update max-height when content changes (for dynamic content)
const resizeObserver = new ResizeObserver(() => {
if (item.classList.contains('is-open')) {
contentDiv.style.maxHeight = `${contentDiv.scrollHeight}px`;
}
});
resizeObserver.observe(contentDiv);
});
}
Loading
Loading