🐞 fix: Animate team-grid bio with JS measuring natural height

This commit is contained in:
Keith Solomon
2026-07-25 15:41:46 -05:00
parent 7c1c145846
commit 842818ec7e
4 changed files with 2814 additions and 3856 deletions
+2687 -3845
View File
File diff suppressed because it is too large Load Diff
+8 -11
View File
@@ -21,7 +21,7 @@
&:hover { &:hover {
.team-grid__read-bio::after { .team-grid__read-bio::after {
content: url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20transform%3D%22rotate(0%200%200)%22%3E%3Cpath%20d%3D%22M6.28033%2010.5947C5.98744%2010.3018%205.51256%2010.3018%205.21967%2010.5947C4.92678%2010.8876%204.92678%2011.3624%205.21967%2011.6553L11.4697%2017.9053C11.7626%2018.1982%2012.2374%2018.1982%2012.5303%2017.9053L18.7803%2011.6553C19.0732%2011.3624%2019.0732%2010.8876%2018.7803%2010.5947C18.4874%2010.3018%2018.0126%2010.3018%2017.7197%2010.5947L12%2016.3143L6.28033%2010.5947Z%22%20fill%3D%22%23006196%22%2F%3E%3Cpath%20opacity%3D%221%22%20d%3D%22M6.28033%206.09467C5.98744%205.80178%205.51256%205.80178%205.21967%206.09467C4.92678%206.38756%204.92678%206.86244%205.21967%207.15533L11.4697%2013.4053C11.7626%2013.6982%2012.2374%2013.6982%2012.5303%2013.4053L18.7803%207.15533C19.0732%206.86244%2019.0732%206.38756%2018.7803%206.09467C18.4874%205.80178%2018.0126%205.80178%2017.7197%206.09467L12%2011.8143L6.28033%206.09467Z%22%20fill%3D%22%23006196%22%2F%3E%3C%2Fsvg%3E"); content: url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20transform%3D%22rotate(0%200%200)%22%3E%3Cpath%20d%3D%22M6.28033%2010.5947C5.98744%2010.3018%205.51256%2010.3018%205.21967%2010.5947C4.92678%2010.8876%204.92678%2011.3624%205.21967%2011.6553L11.4697%2017.9053C11.7626%2018.1982%2012.2374%2018.1982%2012.5303%2017.9053L18.7803%2011.6553C19.0732%2011.3624%2019.0732%2010.8876%2018.7803%2010.5947C18.4874%2010.3018%2018.0126%2010.3018%2017.7197%2010.5947L12%2016.3143L6.28033%2010.5947Z%22%20fill%3D%22%23006196%22%2F%3E%3Cpath%20opacity%3D%221%22%20d%3D%22M6.28033%206.09467C5.98744%205.80178%205.51256%205.80178%205.21967%206.09467C4.92678%206.38756%204.92678%206.86244%205.21967%207.15533L11.4697%2013.4053C11.7626%2013.6982%2012.2374%2018.1982%2012.5303%2013.4053L18.7803%207.15533C19.0732%206.86244%2019.0732%206.38756%2018.7803%206.09467C18.4874%205.80178%2018.0126%205.80178%2017.7197%206.09467L12%2011.8143L6.28033%206.09467Z%22%20fill%3D%22%23006196%22%2F%3E%3C%2Fsvg%3E");
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
} }
@@ -42,28 +42,25 @@
.team-grid__read-bio { .team-grid__read-bio {
@apply flex gap-2 justify-center items-center text-cwc-blue-01 text-16px font-bold leading-snug mb-1; @apply flex gap-2 justify-center items-center text-cwc-blue-01 text-16px font-bold leading-snug mb-1;
transition: transform 300ms ease;
&::after { &::after {
content: url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20transform%3D%22rotate(0%200%200)%22%3E%3Cpath%20d%3D%22M6.28033%2010.5947C5.98744%2010.3018%205.51256%2010.3018%205.21967%2010.5947C4.92678%2010.8876%204.92678%2011.3624%205.21967%2011.6553L11.4697%2017.9053C11.7626%2018.1982%2012.2374%2018.1982%2012.5303%2017.9053L18.7803%2011.6553C19.0732%2011.3624%2019.0732%2010.8876%2018.7803%2010.5947C18.4874%2010.3018%2018.0126%2010.3018%2017.7197%2010.5947L12%2016.3143L6.28033%2010.5947Z%22%20fill%3D%22%23006196%22%2F%3E%3Cpath%20opacity%3D%220.5%22%20d%3D%22M6.28033%206.09467C5.98744%205.80178%205.51256%205.80178%205.21967%206.09467C4.92678%206.38756%204.92678%206.86244%205.21967%207.15533L11.4697%2013.4053C11.7626%2013.6982%2012.2374%2013.6982%2012.5303%2013.4053L18.7803%207.15533C19.0732%206.86244%2019.0732%206.38756%2018.7803%206.09467C18.4874%205.80178%2018.0126%205.80178%2017.7197%206.09467L12%2011.8143L6.28033%206.09467Z%22%20fill%3D%22%23006196%22%2F%3E%3C%2Fsvg%3E"); content: url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20transform%3D%22rotate(0%200%200)%22%3E%3Cpath%20d%3D%22M6.28033%2010.5947C5.98744%2010.3018%205.51256%2010.3018%205.21967%2010.5947C4.92678%2010.8876%204.92678%2011.3624%205.21967%2011.6553L11.4697%2017.9053C11.7626%2018.1982%2012.2374%2018.1982%2012.5303%2017.9053L18.7803%2011.6553C19.0732%2011.3624%2019.0732%2010.8876%2018.7803%2010.5947C18.4874%2010.3018%2018.0126%2010.3018%2017.7197%2010.5947L12%2016.3143L6.28033%2010.5947Z%22%20fill%3D%22%23006196%22%2F%3E%3Cpath%20opacity%3D%220.5%22%20d%3D%22M6.28033%206.09467C5.98744%205.80178%205.51256%205.80178%205.21967%206.09467C4.92678%206.38756%204.92678%206.86244%205.21967%207.15533L11.4697%2013.4053C11.7626%2013.6982%2012.2374%2018.1982%2012.5303%2013.4053L18.7803%207.15533C19.0732%206.86244%2019.0732%206.38756%2018.7803%206.09467C18.4874%205.80178%2018.0126%205.80178%2017.7197%206.09467L12%2011.8143L6.28033%206.09467Z%22%20fill%3D%22%23006196%22%2F%3E%3C%2Fsvg%3E");
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
transition: transform 300ms ease;
} }
} }
.team-grid__details[open] .team-grid__read-bio::after {
transform: rotate(180deg);
}
.team-grid__bio-wrap { .team-grid__bio-wrap {
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
transition: max-height 300ms ease; transition: max-height 300ms ease;
} }
.team-grid__details[open] {
.team-grid__bio-wrap { max-height: 1000px; }
.team-grid__read-bio::after {
content: url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20transform%3D%22rotate(0%200%200)%22%3E%3Cpath%20d%3D%22M6.28033%2010.5947C5.98744%2010.3018%205.51256%2010.3018%205.21967%2010.5947C4.92678%2010.8876%204.92678%2011.3624%205.21967%2011.6553L11.4697%2017.9053C11.7626%2018.1982%2012.2374%2018.1982%2012.5303%2017.9053L18.7803%2011.6553C19.0732%2011.3624%2019.0732%2010.8876%2018.7803%2010.5947C18.4874%2010.3018%2018.0126%2010.3018%2017.7197%2010.5947L12%2016.3143L6.28033%2010.5947Z%22%20fill%3D%22%23006196%22%2F%3E%3Cpath%20opacity%3D%221%22%20d%3D%22M6.28033%206.09467C5.98744%205.80178%205.51256%205.80178%205.21967%206.09467C4.92678%206.38756%204.92678%206.86244%205.21967%207.15533L11.4697%2013.4053C11.7626%2013.6982%2012.2374%2013.6982%2012.5303%2013.4053L18.7803%207.15533C19.0732%206.86244%2019.0732%206.38756%2018.7803%206.09467C18.4874%205.80178%2018.0126%205.80178%2017.7197%206.09467L12%2011.8143L6.28033%206.09467Z%22%20fill%3D%22%23006196%22%2F%3E%3C%2Fsvg%3E");
transform: rotate(180deg);
}
}
.team-grid__bio { @apply text-16px font-light leading-snug pt-2 text-center text-balance; } .team-grid__bio { @apply text-16px font-light leading-snug pt-2 text-center text-balance; }
} }
+108
View File
@@ -0,0 +1,108 @@
/**
* Team Grid block — bio expand/collapse animation.
*
* Native <details>/<summary> handles the toggle, but CSS transitions on
* max-height need a concrete target value to animate to. This module
* measures each bio's natural height on open and animates max-height
* from current → measured (open) or current → 0 (close) so the
* transition fires in both directions.
*
* The module is enqueued per-block by the team-grid PHP template. It
* auto-initializes on import: queries the DOM for any existing
* .team-grid__details elements and attaches a toggle handler to each.
*/
const DETAILS_SELECTOR = '.team-grid__details';
const BIO_WRAP_SELECTOR = '.team-grid__bio-wrap';
const setMaxHeight = (el, value) => {
el.style.maxHeight = typeof value === 'number' ? `${value}px` : value;
};
const measureBioHeight = (bioWrap) => {
// Temporarily allow the inner content to render at its natural size so
// scrollHeight reports the real height rather than the clipped height.
const previousMax = bioWrap.style.maxHeight;
const previousOverflow = bioWrap.style.overflow;
setMaxHeight(bioWrap, 'none');
bioWrap.style.overflow = 'visible';
const height = bioWrap.scrollHeight;
bioWrap.style.overflow = previousOverflow;
setMaxHeight(bioWrap, previousMax || '');
return height;
};
const animateOpen = (bioWrap) => {
const target = measureBioHeight(bioWrap);
setMaxHeight(bioWrap, '0px');
// Force a reflow so the browser registers the starting value before
// we change it to the target — otherwise the transition is skipped.
// eslint-disable-next-line no-unused-expressions
bioWrap.offsetHeight;
requestAnimationFrame(() => {
setMaxHeight(bioWrap, `${target}px`);
});
// After the transition completes, set max-height back to 'none' so
// dynamic content (e.g. images loading late) can grow the bio
// without being clipped.
const onEnd = (event) => {
if (event.propertyName !== 'max-height' || event.target !== bioWrap) {
return;
}
bioWrap.removeEventListener('transitionend', onEnd);
setMaxHeight(bioWrap, 'none');
};
bioWrap.addEventListener('transitionend', onEnd);
};
const animateClose = (bioWrap) => {
// Capture the current rendered height so the browser has a concrete
// starting value for the transition. Without this, going from
// 'none' to '0px' snaps instead of animating.
const current = bioWrap.getBoundingClientRect().height;
setMaxHeight(bioWrap, `${current}px`);
// Force a reflow so the browser registers the starting value.
// eslint-disable-next-line no-unused-expressions
bioWrap.offsetHeight;
requestAnimationFrame(() => {
setMaxHeight(bioWrap, '0px');
});
};
const attach = (details) => {
if (details.dataset.teamGridBound === 'true') {
return;
}
details.dataset.teamGridBound = 'true';
const bioWrap = details.querySelector(BIO_WRAP_SELECTOR);
if (!bioWrap) {
return;
}
details.addEventListener('toggle', () => {
if (details.open) {
animateOpen(bioWrap);
} else {
animateClose(bioWrap);
}
});
};
const init = () => {
document.querySelectorAll(DETAILS_SELECTOR).forEach(attach);
};
init();
+11
View File
@@ -17,6 +17,17 @@ if ( empty( $members ) ) {
return; return;
} }
// Enqueue the per-block JS only when this block actually renders.
$js_path = '/views/blocks/team-grid/team-grid.js';
if ( file_exists( get_stylesheet_directory() . $js_path ) ) {
wp_enqueue_script_module(
'cwc-team-grid',
get_stylesheet_directory_uri() . $js_path,
array(),
(string) filemtime( get_stylesheet_directory() . $js_path )
);
}
$modeSingle = ''; $modeSingle = '';
$blockID = wp_unique_id( 'single-' ); $blockID = wp_unique_id( 'single-' );