News

お知らせ

Information

ヘッドレスUIを用いたコードスニペット

はじめに

当社では開発スピードを早めるために全社員の使えるReact用コードスニペットを用意しています。慣れていない社員のスピードをあげる他、コードリーディング用としても活躍します。
本記事では、どんなスニペットを用意しているのか、どのように確認できるようにしているかなど一例をご紹介します。

環境

本コードスニペットはReactを使用しています。また、Storybookを用意し、どんなスニペットがあるのか、どういった挙動を行うのかなどを確認しやすくしています。
以下のようなライブラリを使用しています。

  • React
  • Storybook
  • React Aria Components
  • Radix UI
  • TailwindCSS

スニペットのリポジトリはGitlab上でホスティングされていますが、Gitlab Pagesを使用し、StorybookをビルドしたものをURLで確認できるようにしています。
スニペットはヘッドレスUIを積極的に用いることで、アクセシビリティに対応すると共にプロジェクト内で実装者による対応差異を減らすようにしています。

スニペットの一例

スニペットの一例として、モーダルコンポーネントをご紹介します。トリガーを押すと、モーダルが出現するといった単純なコンポーネントになっています。
React Aria ComponentsのModal・Dialogコンポーネントをラップしており、アクセシビリティに関しても対応しています。

モーダルコンポーネントをStorybook上で操作する様子

ソースコード

当社でのプロジェクトは、CSS ModulesとTailwindCSSを用いたスタイリング手法が多いため、どちらでも利用できるよう2パターンを用意しています。

TailwindCSS

TSX
import type { ComponentProps } from "react";
import {
  DialogTrigger as AriaDialogTrigger,
  Modal as AriaModal,
  Dialog,
  ModalOverlay,
} from "react-aria-components";

type DialogTriggerProps = Omit<
  ComponentProps<typeof AriaDialogTrigger>,
  "style" | "className"
>;
type ModalOverlayProps = Omit<
  ComponentProps<typeof ModalOverlay>,
  "style" | "className" | "children"
> & {
  children: ComponentProps<typeof Dialog>["children"];
};

function DialogTrigger(props: DialogTriggerProps) {
  return <AriaDialogTrigger {...props} />;
}

function ModalDefault({ children, ...attr }: ModalOverlayProps) {
  return (
    <ModalOverlay
      className="fixed inset-0 z-10 flex h-[var(--visual-viewport-height)] w-full bg-black/50"
      {...attr}
    >
      <AriaModal className="m-auto rounded-lg bg-white shadow-[0_0_4px_rgb(0,0,0,0.25)]">
        <Dialog className="p-4 focus:outline-none">{children}</Dialog>
      </AriaModal>
    </ModalOverlay>
  );
}

export const Modal = Object.assign(ModalDefault, {
  Root: DialogTrigger,
});

また、TailwindCSSの場合、コンポーネントによってはクラス名が非常に長くなるので、

TSX
const buttonClasses = "block w-full break-all rounded-sm border border-current border-solid bg-transparent px-8 py-2 font-bold text-sky-800 text-base transition-colors duration-200 max-md:text-sm";

const buttonHoveredClasses = "hover:bg-sky-100 data-[hovered=true]:bg-sky-100";

const buttonFocusedClasses = "focus:outline-none data-[focus-visible=true]:outline data-[focus-visible=true]:outline-1 data-[focus-visible=true]:outline-indigo-600 data-[focus-visible=true]:outline-offset-1";

const buttonPressedClasses = "data-[pressed=true]:!bg-sky-200";

const buttonDisabledClasses = "disabled:bg-gray-200 disabled:border-gray-300 disabled:text-gray-400 disabled:cursor-not-allowed data-[disabled=true]:bg-gray-200 data-[disabled=true]:border-gray-300 data-[disabled=true]:text-gray-400 data-[disabled=true]:cursor-not-allowed";

のように状態によってクラスをまとめるといった対応も行なっています。

CSS Modules

SCSS
.overlay {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  width: 100%;
  height: var(--visual-viewport-height);
  background: rgb(0 0 0 / 50%);
}

.modal {
  margin: auto;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 0 4px rgb(0 0 0 / 25%);
}

.dialog {
  padding: 16px;
}

.dialog:focus {
  outline: none;
}

使用例が

TSX
default export function Page() {
  return (
    <Modal.Root {...args}>
      <Button>Open Button</Button>
      <Modal>
        {({ close }) => <>Modal Contents</>}
      </Modal>
    </Modal.Root>
  );
}

のようになっており、React Aria ComponentsのButtonコンポーネントを使用することでトリガーに関してもスムーズに対応が可能になっています。

利用するには

実装者はGitlabリポジトリに記載されたソースコードをコピーするか、Storybookからコンポーネントを表示し、「Copy」をクリックすることでソースコードをコピーできます。
実際に挙動が確認できるため、Storybookを見ることを社内ではおすすめしております。

最後に

当社ではこのように実装のサポート等手厚く行っております。ご興味を持っていただけましたら、当社採用情報からご応募いただけますと幸いです。

ご質問等ございましたら、お気軽に当社お問合せよりお問合せください。