1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

shadcn/ui Tableで上下にスクロールバーを実現する方法

Last updated at Posted at 2025-01-13

1. はじめに

下記のような列が多い テーブル を扱う時に、overflow を用いて、左右に移動させるスクロールバーを表示させることがあると思います。
scroll_1.png

こちらは、shadcn/uiTable を用いて テーブル を表示させており、default でoverflow-autoがついているので、テーブル 下部にスクロールバーが表示されます。
scroll_2.png

テーブル によっては、行が多すぎて、横にスクロールさせたいけど、テーブル の一番下まで行ってスクロールさせるのがめんどくさいというパターンが出てくると思います。
そこで、 shadcn/uiTable を用いて、テーブル上下部に、左右にスクロールできるバーを表示させる方法をまとめました。

2. 前提

shadcn/uiTable を Manual でインストールして使用します。
https://ui.shadcn.com/docs/components/table

3. テーブル上下のスクロールバー実装例

Manual で貼り付けたコードの Table を下記のように修正します。

table.tsx
const Table = React.forwardRef<
  HTMLTableElement,
  React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => {
  // テーブル下部のスクロールバー
  const scrollContainerRef = React.useRef<HTMLDivElement>(null);

  // テーブル上部のスクロールバーが表示されるかどうか
  const [isOverflowing, setIsOverflowing] = React.useState<boolean>(false);

  // テーブル上部のスクロールバー
  const topScrollRef = React.useRef<HTMLDivElement>(null);
  const [tableScrollWidth, setTableScrollWidth] = React.useState<number>(0);
  const [topScrollbarWidth, setTopScrollbarWidth] = React.useState<number>(0);

  // テーブル上部のスクロールバーを更新
  const updateOverflowStatus = React.useCallback(() => {
    const tableElement = (
      ref as React.MutableRefObject<HTMLTableElement | null>
    ).current;
    if (scrollContainerRef.current && tableElement) {
      const container = scrollContainerRef.current;
      const table = tableElement;

      const containerWidth = Math.ceil(container.clientWidth);
      const contentScrollWidth = table.scrollWidth;
      const contentWidth = Math.ceil(table.getBoundingClientRect().width);

      setTableScrollWidth(contentWidth);
      setTopScrollbarWidth(containerWidth);
      setIsOverflowing(contentScrollWidth > containerWidth);
    }
  }, [ref]);

  React.useEffect(() => {
    updateOverflowStatus();

    const resizeObserver = new ResizeObserver(updateOverflowStatus);
    const container = scrollContainerRef.current;
    const tableElement = (
      ref as React.MutableRefObject<HTMLTableElement | null>
    ).current;

    if (container) resizeObserver.observe(container);
    if (tableElement) resizeObserver.observe(tableElement);

    return () => resizeObserver.disconnect();
  }, [updateOverflowStatus]);

  // テーブル下部のスクロールバーとテーブル上部のスクロールバー
  const syncBottomScroll = React.useCallback(() => {
    if (scrollContainerRef.current && topScrollRef.current) {
      topScrollRef.current.scrollLeft = scrollContainerRef.current.scrollLeft;
    }
  }, []);
  const syncTopScroll = React.useCallback(() => {
    if (scrollContainerRef.current && topScrollRef.current) {
      scrollContainerRef.current.scrollLeft = topScrollRef.current.scrollLeft;
    }
  }, []);

  return (
    <div>
      {isOverflowing && (
        <div
          ref={topScrollRef}
          onScroll={syncTopScroll}
          className={`overflow-x-auto`}
          style={{ width: `${topScrollbarWidth}px` }}
        >
          <div
            className="h-4 bg-transparent"
            style={{ width: `${tableScrollWidth}px` }}
          />
        </div>
      )}
      <div
        className="relative w-full overflow-auto"
        onScroll={syncBottomScroll}
        ref={scrollContainerRef}
      >
        <table
          ref={ref}
          className={cn("w-full caption-bottom text-sm", className)}
          {...props}
        />
      </div>
    </div>
  );
});

呼び出し先

export function TableDemo() {
  // refを使用して、Tableを呼び出す
  const tableRef = useRef<HTMLTableElement>(null);

  return (
    <div style={{ padding: "20px" }}>
      <Table className="w-4/5" ref={tableRef}>
        <TableHeader>
          <TableRow>
          .
          .

4. テーブル上下のスクロールバー実装方法

① テーブル上下部のスクロールバー用の状態を定義

テーブル上下部のスクロールバーを制御するために、ref と状態変数を用意します。

// テーブル下部のスクロールバー
const scrollContainerRef = React.useRef<HTMLDivElement>(null);

// テーブル上部のスクロールバーが表示されるかどうか
const [isOverflowing, setIsOverflowing] = React.useState<boolean>(false);

// テーブル上部のスクロールバー
const topScrollRef = React.useRef<HTMLDivElement>(null);
const [tableScrollWidth, setTableScrollWidth] = React.useState<number>(0);
const [topScrollbarWidth, setTopScrollbarWidth] = React.useState<number>(0);

② テーブル上部のスクロールバーを更新

テーブルのサイズが変更されたときに、スクロールバーの表示状態を更新します。テーブル下部のスクロールバーが表示されるときのみ、テーブル上部のスクロールバーを表示させるようにして、テーブル下部の長さと同じ長さのスクロールバーを上部に表示させます。

// テーブル上部のスクロールバーを更新
const updateOverflowStatus = React.useCallback(() => {
  const tableElement = (ref as React.MutableRefObject<HTMLTableElement | null>)
    .current;
  if (scrollContainerRef.current && tableElement) {
    const container = scrollContainerRef.current;
    const table = tableElement;

    const containerWidth = Math.ceil(container.clientWidth);
    const contentScrollWidth = table.scrollWidth;
    const contentWidth = Math.ceil(table.getBoundingClientRect().width);

    setTableScrollWidth(contentWidth);
    setTopScrollbarWidth(containerWidth);
    setIsOverflowing(contentScrollWidth > containerWidth);
  }
}, [ref]);

React.useEffect(() => {
  updateOverflowStatus();

  const resizeObserver = new ResizeObserver(updateOverflowStatus);
  const container = scrollContainerRef.current;
  const tableElement = (ref as React.MutableRefObject<HTMLTableElement | null>)
    .current;

  if (container) resizeObserver.observe(container);
  if (tableElement) resizeObserver.observe(tableElement);

  return () => resizeObserver.disconnect();
}, [updateOverflowStatus]);

③ テーブル上下部のスクロールバーを同期

テーブル上下部のスクロールバーの長さを同期させる関数を用意します。

// テーブル下部のスクロールバーとテーブル上部のスクロールバー
const syncBottomScroll = React.useCallback(() => {
  if (scrollContainerRef.current && topScrollRef.current) {
    topScrollRef.current.scrollLeft = scrollContainerRef.current.scrollLeft;
  }
}, []);
const syncTopScroll = React.useCallback(() => {
  if (scrollContainerRef.current && topScrollRef.current) {
    scrollContainerRef.current.scrollLeft = topScrollRef.current.scrollLeft;
  }
}, []);

④ テーブル表示

定義した ref と状態を使用してテーブルを表示させます。

return (
  <div>
    {isOverflowing && (
      <div
        ref={topScrollRef}
        onScroll={syncTopScroll}
        className={`overflow-x-auto`}
        style={{ width: `${topScrollbarWidth}px` }}
      >
        <div
          className="h-4 bg-transparent"
          style={{ width: `${tableScrollWidth}px` }}
        />
      </div>
    )}
    <div
      className="relative w-full overflow-auto"
      onScroll={syncBottomScroll}
      ref={scrollContainerRef}
    >
      <table
        ref={ref}
        className={cn("w-full caption-bottom text-sm", className)}
        {...props}
      />
    </div>
  </div>
);

⑤ ref を用いて、Table を呼び出す

ref を使用して、Table を呼び出します。

export function TableDemo() {
  // refを使用して、Tableを呼び出す
  const tableRef = useRef<HTMLTableElement>(null);

  return (
    <div style={{ padding: "20px" }}>
      <Table className="w-4/5" ref={tableRef}>
        <TableHeader>
          <TableRow>
          .
          .

下記のようにテーブル上下部にスクロールバーが表示されます。
scroll_3.png

5. さいごに

shadcn/ui を使用したテーブル上下部にスクロールバーを表示させる方法をまとめました。もしかしたら、shadcn/uiScroll-areaを用いた方法がコンパクトにまとめれたのかもしれません。一旦は、shadcn/uiTableのみを用いた方法でまとめました。もっとコンパクトにまとめれる方法がありましたらご指摘していただけると幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?