はじめに

芝浦工業大学 デザイン工学部 1年の関口匠弥 ( @newt239 )です。2024年3月、WebフロントエンドエンジニアとしてAmeba LIFE事業部でインターンに参加しました。

参加した目的

私は昨年6月より、サイバーエージェントが運営する学生向けのコミュニティである「CA Tech Lounge」に所属し、他会員やメンターである社員の方と交流をしながら、技術の研鑽を積んでいます。そのため以前から社員の方々と接する機会が多く、サイバーエージェントという会社自体にも興味を持っていました。

高校生の頃から個人開発に取り組んでいたこともあり、基礎的な実装力は身についていると感じていました。一方でコードの品質や共同開発のスキルといった点について、他者から評価された経験がなかったため、不安を感じていました。

今回、春休みという比較的自由に時間が使えるタイミングでこれらの経験を積みたいと考え、CA Tech JOBに応募しました。

インターンにおけるゴール

インターン生としてAmebaで就業するにあたり、まず目標設定を行いました。私の場合、就業前面談やオンボーディングなどでUIコンポーネントライブラリに興味があるという話をしていたところ、トレーナーからSpindleへ新規コンポーネントを実装するタスクを紹介していただきました。

Spindleとは、Amebaの各プロダクトで「Amebaらしさ」を一貫してユーザーに届けるために開発されているデザインシステムのことです。このデザインシステムはOSSとして公開されており、私自身インターン参加以前から認知していました。そのため、汎用的に利用されるコンポーネントを開発するというタスクは私にとって願ってもない提案でした。

プロダクト固有のコンポーネントを実装

Spindleはすでに様々なプロダクトで利用されているデザインシステムです。そのため、新しくコンポーネントを追加するためには、さまざまな議論や検討を経たうえで、慎重に追加する必要があります。

そこで、まずは単一のプロダクトでのみ利用されるコンポーネントを実装することで、Amebaにおけるコンポーネント設計の感覚を掴むことになりました。

RadioButton

最初に取り組んだのが下図のようなコンポーネントです。アイコンをOptionalでつけることのできるチップが横に並んだデザインで、グループ内で単一の項目を選択することができます。機能としてはラジオボタンに近く、選択状態になるとAmebaのブランドカラーである緑色になります。

通常のラジオボタンと異なるのは、一つも選択されていない状態が許容される点です。このコンポーネントでは選択済みの項目を再度クリックすることで選択を解除できます。

横に3つ選択肢が並んでおり、それぞれアイコンとともに「全員」「男性」「女性」のラベルが付いている。「全員」の選択肢だけ文字色が緑色になっていて、選択状態にあることがわかる

 

このコンポーネントにはRadioButtonGroupという名称をつけ、propsに配列ですべての項目のデータを渡す形で実装しました。

const Wrapper = () => {
    const [value, setValue] = useState<string | null>(“national”);
    return (
      <RadioButtonGroup
        items={[
          { label: "全員", value: "common" },
          { label: "男性", value: "male" },
          { label: "女性", value: "female" }
        ]}
      />
  );
};

 

AmebaのプロダクトではスタイリングにCSS Modulesを採用しています。また、プロダクトで登場する色はすべてameba-color-palette.cssというパッケージで定義されており、ここに存在するCSS変数を指定します。例えば今回であれば、Figma上で選択時の枠線色がBorder/Accent Primaryに指定されていたため、これに対応する–color-border-accent-primaryを指定する、といった具合です。

DonutChart

下のスクリーンショットのようなドーナツグラフを実装しました。

「好きな動物」についてのアンケート結果を示したコンポーネントの使用例。中心がくり抜かれた円グラフで表されており、いぬが260人、ねこが90人、その他が40人であることが図でもテキストでも分かるようになっている

 

グラフの実装はチャートライブラリを利用することも多いですが、今回はそこまで複雑なデザインでないことや、このコンポーネント以外で今のところグラフを描画する予定がないことから、CSSで実装することになりました。

conic-gradientという関数を使うことで、中心点の周りを回りながら色を変化させることができます。DonutChartコンポーネントでは下のような計算をしたうえで、backgroundにconic-gradient(${gradientParts.join(", ")})を適用しました。

const gradientParts = useMemo(() => {
  let accumulatedPercentage = 0;
  return items.map(({ value }, index) => {
    const percentage = (value / total) * 100;
    const start = accumulatedPercentage;
    const end = accumulatedPercentage + percentage;
    accumulatedPercentage = end;
    return `${colors[index]} ${start}% ${end}%`;
  });
}, [items, total]);

この他にも3つほどコンポーネントを実装しました。

Spindleの新規コンポーネント開発

コンポーネントの概要

Spindleへ新しいコンポーネントを追加する場合、まず「Design Doc」を書いてSpindler(社内でSpindleについて知見を持っている方)にレビューをいただく必要があります。

Design Docではそのコンポーネントをどのような用途で利用するのか、どのような使い方をするべきか(またはするべきではないか)、を明確にします。

今回実装することになったのは下図のコンポーネントで、2つの選択肢のうち1つを選択するためのコンポーネントです。スイッチのような操作感を持ち、選択肢を切り替えることができます。RadioやToggleSwitchと異なり、どちらも選択していないニュートラルな状態を持つこともできる点が特徴的です。

ButtonSwitchのデザイン。「できれば」と「必須」の項目を持つ状態でのデザインが表示されており、項目選択時に背景色が緑色になることや、チェックアイコンが表示されることがわかる

コンポーネント名の検討

Design Docの作成にあたり、まずコンポーネントの名前を決める必要が生まれました。

同じようなUIパターンが存在すれば、その名前を使うのが分かりやすいですが、同等のものがない場合は用途や機能をもとに、独自に考える必要があります。

まず、前述したSegmented Controlが機能として近かったため検討しました。しかし、今回のコンポーネントはフォーム要素として利用することを想定しており、特定のオブジェクトの異なる状態を切り替えて表示するために利用するSegmented Controlとは用途が明確に異なるため、却下となりました。

また、複数の項目から一つを選び取る特徴がラジオに近いためRadioButtonも検討しましたが、こちらも既存コンポーネントとの違いが分かりづらいことから、別の案を検討しました。

最終的にはデザイナーさんの提案をもとにButtonSwitchという名前を採用しました。これは、クリックが可能なボタン要素でありつつ、それぞれの項目の選択状態をスイッチのように切り替えることができるという特徴をもとに決めました。

Design Docの作成

Design Docには用途や型定義、使用法、アクセシビリティに関するチェックリストなどを記述します。ここで使用例について、DOだけでなくDO NOTを定義したことが印象的でした。

今回の場合、ラベルテキストについて『「送信」「実行」など、直接的なアクションを想起する動詞は使用しない』ことを要求しています。これにより、コンポーネントの使用者がよりユースケースが分かりやすくなると感じました。

さらにアクセシビリティについては「色だけで選択状態を伝えない」ことや「タップ領域を44px x 44px以上確保している」ことなど、確認漏れが発生しがちな点も含めてリストアップし、チェックリストに追加しました。

ButtonSwitchの実装

Design Docがマージできたら、いよいよ実装に取り掛かります。

ButtonSwitchのStorybookを表示したスクリーンショット。1つのみの選択肢を持てないことや、コンポーネント利用時のサンプルコードに加え、選択肢が2つや3つのときのプレビューが表示されている

ButtonSwitchの機能自体は比較的シンプルですが、アクセシブルな状態を保つためには一工夫が必要です。今回の場合はキーボードのみを利用して操作するユーザーに向けて、矢印キーによる操作でフォーカスや選択状態を切り替えられるようにしています。

具体的には、各項目でキーが押されたとき、そのキーが上下左右のキーであれば前後の項目にフォーカスと選択状態を更新する処理を挟みました。

const handleKeydown = useCallback(
  (e: React.KeyboardEvent<HTMLButtonElement>, index: number) => {
    const focusButton = (offset: number) => {
      const buttonRef =
        buttonsRef.current[
          (options.length + index + offset) % options.length
        ];
      const button = buttonRef.current;
      button?.focus();
      if (onClick) {
        onClick(
          options[(options.length + index + offset) % options.length].value,
        );
      }
    };
    switch (e.key) {
      case 'ArrowUp':
      case 'ArrowLeft': {
        e.preventDefault();
        focusButton(-1);
        break;
      }
      case 'ArrowDown':
      case 'ArrowRight': {
        e.preventDefault();
        focusButton(1);
        break;
      }
    }
  },
  [options, onClick],
);

Web開発に携わる人全員が、アクセシビリティについて考慮したうえで実装することは、理想ではありますが現実としては難しいです。UIコンポーネントライブラリを決められた方法通りに利用するだけで、自然とアクセシブルな状態を保てるようにするのは、より良いWebアプリケーションを開発するための工夫だと感じました。

ameba-color-palette.cssへの色追加

Spindleで利用する色はすべてameba-color-palette.cssというパッケージで管理しているのですが、ボタンをホバーしたときの色について、Figmaで指定されていた変数が定義されていませんでした。このためホバー時のボタンに関する変数を追加するPRを作成しました。

GitHub上でameba-color-palette.cssの差分を表示しているスクリーンショット。Hover Colorsに関する変数が5つ追加されていて、mainブランチにマージ済みであることがわかる

アクセシビリティに関する取り組み

このほか期間中、アクセシビリティについても学びを得ることができました。

Accessibility Yatteiki

Amebaでは毎週木曜日、アクセシビリティチームが集まる定例会があります。

今回、オンボーディングの際にアクセシビリティにも興味があるという話をしたところ、この定例会にお誘いいただきました。

アクセシビリティチームにはSpindlerが集まっているためSpindleについてのお話を伺ったり、後述するa11y-guidelinesのドキュメント更新作業もここで紹介いただきました。

a11y-guidelinesのドキュメント改善

WCAG (Web Content Accessibility Guidelines) というドキュメントがあります。これはW3Cによって策定されているウェブアクセシビリティに関するガイドラインで、ウェブサイトやウェブコンテンツをより多くの人が利用できるようにするための基準として、よく活用されます。

現在はWCAG 2.2が勧告されていて、Web Content Accessibility Guidelines (WCAG) 2.2から誰でも確認できるほか、一つ前のバージョンであるWCAG 2.1であればWeb Content Accessibility Guidelines (WCAG) 2.1 (日本語訳)から完全な邦訳を読むことができます。

しかしこのドキュメントは抽象的な表現が多く、毎回これを参照しながら開発するのはコストがかかります。そこでAmebaでは、Spindleコンポーネントによる具体的な例とともに重要なポイントを書いたAmeba Accesibility Guidelinesを策定し、公開しています。

今回私は「4.1.2 カスタムコントロールの操作性を担保する」というページで使用されていた、具体例とチェック方法の説明文を差し替える作業を行いました。

このページでは当初、SpindleにあるSegmented Controlというコンポーネントを使って具体例が紹介されていました。しかしこのUIパターンはあまり一般的ではなく、名前自体も誰もが知っているとは言えません。そこで、より知名度の高いUIパターンである「タブ」を使った例に差し替えました。

Spindleに実装されているUnderlineTabコンポーネントを使ってカスタムコントロールの操作性について説明しているスクリーンショット。キーボードでフォーカスしたときの挙動や、支援技術にタブであることを伝えるための工夫などが説明されている

その他の活動

Daily Check-ins

毎朝実施する定例ミーティングに参加させていただきました。私が配属されたチームでは、朝の10時15分から30分間でエンジニアのみの定例を行い、その後の15分間でビジネス職やデザイナー職の方々を交えたチーム全体の定例を行っています。

全体定例ではSailboat Retrospectiveという振り返り手法を導入していました。画面上に海と島、ヨットを配置し、ヨットが島にたどり着くために「追い風」となること、逆に障害の原因となる「アンカー」にあたること、などを付箋形式で貼っていく手法で、日々の進捗に応じて更新していくそうです。チームでは毎日ひとりずつ24時間以内にあった嬉しいことを共有し「太陽」の部分に貼り付ける決まりになっていて、定例が淡々と進んでしまうことを防ぐための工夫を感じました。

ランチ・懇親会

フロントエンドエンジニアだけでなく、ビジネス職やマネージャー職の方ともセッティングしていただきました。

キャリアや福利厚生の話に加え、これまでに経験してきた技術についての話を聞くことができました。みなさんありがとうございました。

1on1

配属先がリリース前のプロダクトを扱っているチームだったため、すでに運用されている別のプロダクトのソースコードを読み、疑問に感じた点を質問していました。

ログを収集するためのDatadogやGoogleに構造化されたデータを渡すためのJsonLdなど、個人開発では触れる機会の少ない処理が行われていて、多くの学びが得られました。

むすびにかえて

1ヶ月間のインターンでしたが、参加してみると時間が過ぎるのはあっという間でした。

トレーナーの千葉さんとメンターのpaguさん、そしてアクセシビリティチームの方々には特にお世話になりました。

コードレビューでは普段意識せずに書いていた点も含め丁寧にレビューしていただいたため、今後の開発にも活かせるとても良い経験となりました。ありがとうございました!