♻️ Make generic Accordion more generic

This commit is contained in:
Philipp Stracker 2024-12-09 14:03:07 +01:00
parent 7146163301
commit 1b557f1619
No known key found for this signature in database
3 changed files with 106 additions and 59 deletions

View file

@ -2,26 +2,27 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
&--title { &__toggler {
display: block;
cursor: pointer;
background: transparent;
border: 0;
box-shadow: none;
padding: 0;
margin: 24px auto;
}
&__title-wrapper {
@include font(14, 32, 450); @include font(14, 32, 450);
color: $color-gray-900; color: $color-gray-900;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
margin: 24px auto;
border: 0;
background: transparent;
cursor: pointer;
} }
&--content { &__content {
margin: 24px 0 0; margin: 24px 0 0;
} }
&.ppcp--is-open {
.ppcp-r-accordion--icon {
transform: rotate(180deg);
}
}
} }

View file

@ -1,65 +1,72 @@
import { useEffect } from '@wordpress/element';
import { Icon } from '@wordpress/components'; import { Icon } from '@wordpress/components';
import { chevronDown, chevronUp } from '@wordpress/icons'; import { chevronDown, chevronUp } from '@wordpress/icons';
import { useState } from 'react'; import classNames from 'classnames';
import { useAccordionState } from '../../hooks/useAccordionState';
// Provide defaults for all layout components so the generic version just works.
const DefaultHeader = ( { children, className = '' } ) => (
<div className={ `ppcp-r-accordion__header ${ className }`.trim() }>
{ children }
</div>
);
const DefaultTitleWrapper = ( { children } ) => (
<div className="ppcp-r-accordion__title-wrapper">{ children }</div>
);
const DefaultTitle = ( { children } ) => (
<span className="ppcp-r-accordion__title">{ children }</span>
);
const DefaultAction = ( { children } ) => (
<span className="ppcp-r-accordion__action">{ children }</span>
);
const DefaultDescription = ( { children } ) => (
<div className="ppcp-r-accordion__description">{ children }</div>
);
const Accordion = ( { const Accordion = ( {
title, title,
initiallyOpen = null,
className = '',
id = '', id = '',
children, initiallyOpen = null,
description = '',
children = null,
className = '',
// Layout components can be overridden by the caller
Header = DefaultHeader,
TitleWrapper = DefaultTitleWrapper,
Title = DefaultTitle,
Action = DefaultAction,
Description = DefaultDescription,
} ) => { } ) => {
const determineInitialState = () => { const { isOpen, toggleOpen } = useAccordionState( { id, initiallyOpen } );
if ( id && initiallyOpen === null ) { const wrapperClasses = classNames( 'ppcp-r-accordion', className, {
return window.location.hash === `#${ id }`; 'ppcp--is-open': isOpen,
} } );
return !! initiallyOpen;
};
const [ isOpen, setIsOpen ] = useState( determineInitialState ); const icon = isOpen ? chevronUp : chevronDown;
useEffect( () => {
const handleHashChange = () => {
if ( id && window.location.hash === `#${ id }` ) {
setIsOpen( true );
}
};
window.addEventListener( 'hashchange', handleHashChange );
return () => {
window.removeEventListener( 'hashchange', handleHashChange );
};
}, [ id ] );
const toggleOpen = ( ev ) => {
setIsOpen( ! isOpen );
ev?.preventDefault();
return false;
};
const wrapperClasses = [ 'ppcp-r-accordion' ];
if ( className ) {
wrapperClasses.push( className );
}
if ( isOpen ) {
wrapperClasses.push( 'ppcp--is-open' );
}
return ( return (
<div className={ wrapperClasses.join( ' ' ) } id={ id }> <div className={ wrapperClasses } { ...( id && { id } ) }>
<button <button
onClick={ toggleOpen }
className="ppcp-r-accordion--title"
type="button" type="button"
className="ppcp-r-accordion__toggler"
onClick={ toggleOpen }
> >
<span>{ title }</span> <Header>
<Icon icon={ isOpen ? chevronUp : chevronDown } /> <TitleWrapper>
<Title>{ title }</Title>
<Action>
<Icon icon={ icon } />
</Action>
</TitleWrapper>
{ description && (
<Description>{ description }</Description>
) }
</Header>
</button> </button>
{ isOpen && ( { isOpen && children && (
<div className="ppcp-r-accordion--content">{ children }</div> <div className="ppcp-r-accordion__content">{ children }</div>
) } ) }
</div> </div>
); );

View file

@ -0,0 +1,39 @@
import { useEffect, useState } from '@wordpress/element';
const checkIfCurrentTab = ( id ) => {
return id && window.location.hash === `#${ id }`;
};
const determineInitialState = ( id, initiallyOpen ) => {
if ( initiallyOpen !== null ) {
return initiallyOpen;
}
return checkIfCurrentTab( id );
};
export function useAccordionState( { id = '', initiallyOpen = null } ) {
const [ isOpen, setIsOpen ] = useState(
determineInitialState( id, initiallyOpen )
);
useEffect( () => {
const handleHashChange = () => {
if ( checkIfCurrentTab( id ) ) {
setIsOpen( true );
}
};
window.addEventListener( 'hashchange', handleHashChange );
return () => {
window.removeEventListener( 'hashchange', handleHashChange );
};
}, [ id ] );
const toggleOpen = ( ev ) => {
setIsOpen( ! isOpen );
ev?.preventDefault();
return false;
};
return { isOpen, toggleOpen };
}