🍊

[Output]Reactとstyled-componentsで簡易的なアコーディオンを作ってみた

2024/07/10に公開

ReactとCSSinJSの学習として、アコーディオンを簡易的に作ってみました。

結論

色んな入門動画を見ながら一通りReactを触っていましたが、実際に自分で一から作るとなると中々うまくはいきませんでした。

使用言語

  • React
  • TypeScript
  • styled-components

実際のコード

Accordion.tsx
import { ReactNode, useLayoutEffect, useRef, useState } from "react";
import styled from "styled-components";

interface AccordionInterface {
  heading: string;
  children: ReactNode;
  initialOpen: boolean;
}

const Div_Accordion = styled.div`
  border: 1px solid #000;
  padding: 10px;
  border-radius: 8px;
`;

const Summary_AccordionHead = styled.summary`
  cursor: pointer;
`;

const Div_AccordionContents = styled.div<{
  $isOpen: boolean;
  $maxHeight: number;
}>`
  transition: max-height 0.5s ease;
  overflow: hidden;
  max-height: ${(props) => (props.$isOpen ? `${props.$maxHeight}px` : "0")};
`;

const Accordion = (props: AccordionInterface) => {
  const contentsRef = useRef<HTMLDivElement>(null);
  const [isOpen, setIsOpen] = useState(props.initialOpen);
  const [maxHeight, setMaxHeight] = useState<number>(0);

  useLayoutEffect(() => {
    if (contentsRef.current) {
      setMaxHeight(contentsRef.current.scrollHeight);
    }
  }, [isOpen]);

  return (
    <Div_Accordion>
      <Summary_AccordionHead
        onClick={() => {
          setIsOpen(!isOpen);
        }}
      >
        {props.heading}
      </Summary_AccordionHead>
      <Div_AccordionContents
        $maxHeight={maxHeight}
        $isOpen={isOpen}
        ref={contentsRef}
      >
        <div>{props.children}</div>
      </Div_AccordionContents>
    </Div_Accordion>
  );
};

export default Accordion;

やりのこしていること

  • initialOpenをpropsでtrueを渡すと、はじめから開いている状態になりますが、アニメーションが付与されてページ高さがスクロール中や初期表示時に変わる可能性があります。これをinitialOpenがtrueのときはアニメーションなしで表示できるようにしたい。

参考記事

React(Next.js)で滑らかな開閉アニメーションとアクセシビリティを考慮したアコーディオンを実装する
https://zenn.dev/lclco/articles/02a815b78f7fd3

Discussion