diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_accordion-section.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_accordion-section.scss index 28ac713ce..d4894abd8 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_accordion-section.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_accordion-section.scss @@ -2,26 +2,27 @@ margin-left: 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); color: $color-gray-900; display: flex; align-items: center; gap: 16px; - margin: 24px auto; - border: 0; - background: transparent; - cursor: pointer; } - &--content { + &__content { margin: 24px 0 0; } - - &.ppcp--is-open { - .ppcp-r-accordion--icon { - transform: rotate(180deg); - } - } } diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js index 23f01a09c..f5b071945 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js @@ -1,65 +1,72 @@ -import { useEffect } from '@wordpress/element'; import { Icon } from '@wordpress/components'; 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 = '' } ) => ( +
+ { children } +
+); +const DefaultTitleWrapper = ( { children } ) => ( +
{ children }
+); +const DefaultTitle = ( { children } ) => ( + { children } +); +const DefaultAction = ( { children } ) => ( + { children } +); +const DefaultDescription = ( { children } ) => ( +
{ children }
+); const Accordion = ( { title, - initiallyOpen = null, - className = '', 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 = () => { - if ( id && initiallyOpen === null ) { - return window.location.hash === `#${ id }`; - } - return !! initiallyOpen; - }; + const { isOpen, toggleOpen } = useAccordionState( { id, initiallyOpen } ); + const wrapperClasses = classNames( 'ppcp-r-accordion', className, { + 'ppcp--is-open': isOpen, + } ); - const [ isOpen, setIsOpen ] = useState( determineInitialState ); - - 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' ); - } + const icon = isOpen ? chevronUp : chevronDown; return ( -
+
- { isOpen && ( -
{ children }
+ { isOpen && children && ( +
{ children }
) }
); diff --git a/modules/ppcp-settings/resources/js/hooks/useAccordionState.js b/modules/ppcp-settings/resources/js/hooks/useAccordionState.js new file mode 100644 index 000000000..f54018262 --- /dev/null +++ b/modules/ppcp-settings/resources/js/hooks/useAccordionState.js @@ -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 }; +}