@eeacms/volto-accordion-block
Version:
volto-accordion-block: Volto accordion block
216 lines (196 loc) • 6.89 kB
JSX
import React from 'react';
import { getPanels, accordionBlockHasValue, Icon } from './util';
import { Accordion } from 'semantic-ui-react';
import { withBlockExtensions } from '@plone/volto/helpers//Extensions';
import { useLocation, useHistory } from 'react-router-dom';
import cx from 'classnames';
import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
import AnimateHeight from 'react-animate-height';
import config from '@plone/volto/registry';
import './editor.less';
import AccordionFilter from './AccordionFilter';
const useQuery = (location) => {
const { search } = location;
return React.useMemo(() => new URLSearchParams(search), [search]);
};
const View = (props) => {
const { data, className } = props;
const location = useLocation();
const history = useHistory();
const panels = getPanels(data.data);
const metadata = props.metadata || props.properties;
const diffView =
location?.pathname.slice(
location?.pathname.lastIndexOf('/'),
location?.pathname.length,
) === '/diff';
const [activeIndex, setActiveIndex] = React.useState([]);
const [activePanel, setActivePanel] = React.useState([]);
const [filterValue, setFilterValue] = React.useState('');
const [itemToScroll, setItemToScroll] = React.useState('');
const accordionConfig = config.blocks.blocksConfig.accordion;
const { titleIcons } = accordionConfig;
const iconOnRight = data.right_arrows;
const iconPosition = iconOnRight ? 'rightPosition' : 'leftPosition';
const query = useQuery(location);
const activePanels = query.get('activeAccordion')?.split(',');
const [firstIdFromPanels] = panels[0];
const activePanelsRef = React.useRef(activePanels);
const firstIdFromPanelsRef = React.useRef(firstIdFromPanels);
const addQueryParam = (key, value) => {
const searchParams = new URLSearchParams(location.search);
searchParams.set(key, value);
history.push({
hash: location.hash,
pathname: location.pathname,
search: searchParams.toString(),
});
};
const handleClick = (e, itemProps) => {
const { index, id } = itemProps;
const newIndex =
activeIndex.indexOf(index) === -1
? data.non_exclusive
? [...activeIndex, index]
: [index]
: activeIndex.filter((item) => item !== index);
const newPanel =
activePanel.indexOf(id) === -1
? data.non_exclusive
? [...activePanel, id]
: [id]
: activePanel.filter((item) => item !== id);
handleActiveIndex(newIndex, newPanel);
};
const handleActiveIndex = (index, id) => {
setActiveIndex(index);
setActivePanel(id);
addQueryParam('activeAccordion', id);
};
const handleFilteredValueChange = (value) => {
setFilterValue(value);
};
const scrollToElement = () => {
if (!!activePanels && !!activePanels[0].length) {
let element = document.getElementById(
activePanels[activePanels.length - 1],
);
element.scrollIntoView({ behavior: 'smooth' });
}
};
const isExclusive = (id) => {
return activePanel.includes(id);
};
React.useEffect(() => {
!!activePanelsRef.current &&
setItemToScroll(
activePanelsRef.current[activePanelsRef.current?.length - 1],
);
}, []);
React.useEffect(() => {
if (data.collapsed) {
setActivePanel(activePanelsRef.current || []);
} else {
if (!!activePanelsRef.current && !!activePanelsRef.current[0].length) {
setActivePanel(activePanelsRef.current || []);
} else {
setActivePanel([
firstIdFromPanelsRef.current,
...(activePanelsRef.current || []),
]);
}
}
}, [data.collapsed]);
return (
<div className={cx('accordion-block', className)}>
{data.headline && <h2 className="headline">{data.headline}</h2>}
{data.filtering && (
<AccordionFilter
config={accordionConfig}
data={data}
filterValue={filterValue}
handleFilteredValueChange={handleFilteredValueChange}
/>
)}
{panels
.filter(
(panel) =>
!data.filtering ||
filterValue === '' ||
(filterValue !== '' &&
panel[1].title
?.toLowerCase()
.includes(filterValue.toLowerCase())),
)
.map(([id, panel], index) => {
const active = isExclusive(id);
return accordionBlockHasValue(panel) ? (
<Accordion
key={id}
id={id}
exclusive={!data.exclusive}
className={
data.styles
? data.styles.theme
: accordionConfig?.defaults?.theme
}
{...accordionConfig.options}
>
<React.Fragment>
<Accordion.Title
as={data.title_size}
active={active}
aria-expanded={active}
className={cx('accordion-title', {
'align-arrow-left': !iconOnRight,
'align-arrow-right': iconOnRight,
})}
index={index}
onClick={(e) => handleClick(e, { index, id })}
onKeyDown={(e) => {
if (e.keyCode === 13 || e.keyCode === 32) {
e.preventDefault();
handleClick(e, { index, id });
}
}}
role="button"
tabIndex={0}
>
<Icon
options={titleIcons}
name={
active
? titleIcons.opened[iconPosition]
: titleIcons.closed[iconPosition]
}
/>
<span>{panel?.title}</span>
</Accordion.Title>
<AnimateHeight
animateOpacity
duration={500}
height={active || diffView ? 'auto' : 0}
onTransitionEnd={() => {
if (!!activePanels && id === itemToScroll) {
scrollToElement();
setItemToScroll('');
}
}}
>
<Accordion.Content active={diffView ? true : active}>
<RenderBlocks
{...props}
location={location}
metadata={metadata}
content={panel}
/>
</Accordion.Content>
</AnimateHeight>
</React.Fragment>
</Accordion>
) : null;
})}
</div>
);
};
export default withBlockExtensions(View);