I am trying to make an accordion component that is similar to the <details><summary>
HTML elements.
I want to make my own component because I want to use useState()
to control whether my accordion is open or not.
First I tried this:
const WeirdAlAccordion: React.FC<MyProps> = ({
children,
}: MyProps) => {
const scrollingRef = useRef(null);
const [isToggleOpen, setIsToggleOpen] = useState(false);
const scrollMyRef = () => {
// Only scroll into view if it is being opened.
if (!isToggleOpen) {
scrollingRef.current.scrollIntoView({ block: 'start', inline: 'nearest' });
}
};
return (
<>
<div id="my-id" ref={scrollingRef}>
<IonItem>
<HandednessToggle
checked={isToggleOpen}
onClick={() => {
setIsToggleOpen(!isToggleOpen);
scrollMyRef();
}}
label={"My Label"}
// Disable the toggle on click while in review mode.
disabled={isAccordionKeyDisabled}
/>
</IonItem>
{isToggleOpen && children}
</div>
</>
);
};
This scrolls the element into view, but if children
contains a lot of text (for example, two screens' worth of text), the element that is scrolled into view won't be scrolled until it is at the top of the screen-- it could be halfway down the screen, or three-quarters; the placement seems a bit random.
What I want to happen: After clicking the toggle, I want the content to be expanded (children
are shown with isToggleOpen
) and then I want to scroll so that scrollingRef
is at the top of the screen.
According to your request " I want to scroll so that scrollingRef is at the top of the screen", I modified your code to have the desired behaviour.
import { useMemo, useRef, useState } from "react";
import _ from "lodash";
type MyProps = any;
export const WeirdAlAccordion: React.FC<MyProps> = ({ children }: MyProps) => {
const scrollingRef = useRef<HTMLDivElement | null>(null);
const [isToggleOpen, setIsToggleOpen] = useState(false);
const scrollMyRef = () => {
// Only scroll into view if it is being opened.
if (!isToggleOpen && scrollingRef?.current !== null) {
scrollingRef.current.scrollIntoView({
block: "start",
inline: "nearest",
});
}
};
const mimicChildren = useMemo(() => {
const ChildrenBasicArray = Array.from({ length: 10 }, (value, index) => ({
value: index,
id: _.uniqueId(),
}));
return ChildrenBasicArray.map((item, index) => (
<div
key={item.id}
style={{
height: "120px",
backgroundColor: index % 2 === 0 ? "red" : "blue",
}}
>
{item.value}
</div>
));
}, []);
return (
<>
<div style={{ height: "300px" }}>fake element</div>
<div style={{ height: "300px" }}>fake element</div>
<div
style={{
height: isToggleOpen ? "600px" : "20px",
width: "300px",
overflow: isToggleOpen ? "scroll" : "visible",
background: "#ccc",
}}
ref={scrollingRef}
onClick={() => {
setIsToggleOpen(!isToggleOpen);
scrollMyRef();
}}
>
{isToggleOpen && mimicChildren}
</div>
<div style={{ height: "300px" }}>fake element</div>
<div style={{ height: "300px" }}>fake element</div>
<div style={{ height: "300px" }}>fake element</div>
</>
);
};