📦

個人的によく使う、省スペースで表示できて雑然としないUI with TailwindCSS

2024/04/21に公開

こんにちは!@Ryo54388667です!☺️

普段は都内でフロントエンドエンジニアとして業務をしてます!
主にTypeScriptやNext.jsといった技術を触っています。

今回は画面上に省スペースで利用できたり雑然として見えづらいUIを紹介していきます!

📌 背景

「なんかいつの間にか、ごちゃついているなぁ。。」

小規模の機能を追加していった結果、ある時、冷静にふと画面を見ると、こんなことを思った覚えがあります。特に、アジャイル開発をしていると、このような場面に出会うのではないでしょうか?

「散らからないように最初から設計をしていないのが悪い」と言われるとそうなのですが、今後の必要な機能の予測の難しい中で開発していると、アプリのUIがごちゃついてしまうのも無理はないのかなと個人的には感じます。

おそらく、アプリのUIが雑然としてきたタイミングで一度立ち止まって、今後の方針を固めるのが正攻法ではないかと思います。ただ、雑然としたまま方針が決まるのを指をくわえて待つのもできない場合がありますよね!例えば、すでに「使いにくい」「なんかごちゃごちゃしている」などユーザーのコメントをもらっている場合など。

あくまで対処療法ではありますが、そんな時に利用する、画面の省スペースで利用できたり雑然として見えにくいUIを紹介していきますー。個人的なセレクションなので、ベストプラクティスではありません🙏こちらも良いですよー、などご意見いただけると嬉しいです!

📌 個人的によく使うUI

ライブラリバージョン
name version 備考
React 18.2.0
tailwindcss ^3.4.1

1.Tooltip

こちらは定番ですね!
特に、何か注釈などの追加情報をユーザーに知らせる時はよく利用します。要素のホバー時だけ表示できるので、エリアの狭い箇所でも利用できるUIです。ただ、警告などの内容には非推奨な点やモバイルユーザーの考慮を必要とする点はデメリットですかね。

// page.tsx
<div className="mt-10 mx-auto text-center">
 <Tooltip label="ここに説明が入ります。ここに説明が入ります。ここに説明が入ります。ここに説明が入ります。">
   <span className="p-2 bg-gray-100 rounded-full">🗑️</span>
 </Tooltip>
</div>

// Tooltip.tsx
import { ReactNode } from "react"

type TooltipProps = {
  label: string
  children: ReactNode
}

const Tooltip = ({ label, children }: TooltipProps) => {
  return (
    <div className="inline-block group relative">
      <span className="pointer-events-none text-xs md:min-w-max min-w-[80vw] absolute -top-10  rounded bg-orange-300 p-1 -translate-x-1/2 left-1/2 text-white opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-orange-300 before:content-[''] group-hover:opacity-100 group-hover:pointer-events-auto">
        {label}
      </span>
      {children}
    </div>
  )
}
export default Tooltip

2.スライドして表示されるアイコン

個人的にはこれも好きなものです😆
表示するリストにアイコンが含まれる場合、いくつも同様のものが表示されていると雑然とした印象になることがあります。それを防ぐために、ホバー時のみ表示するようにしています。もちろん、こちらのUIもモバイルユーザーには不向きですが。。😇
管理画面のデータテーブルなど、モバイルユーザーを想定しないケースに利用できるかもですね。

<div className="inline-block relative group">
        <p className=" hover:text-blue-700 transition duration-300 ease-in-out z-20 px-1">
          Test text
        </p>
        <div className="cursor-pointer">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="h-5 w-5 absolute right-0 top-0 opacity-0 group-hover:opacity-100 z-10 transition group-hover:translate-x-full duration-300">
            <path fill="gray" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a.999.999 0 0 0 0-1.41l-2.34-2.34a.999.999 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
          </svg>
        </div>
      </div>

3.Inline Editor

別の箇所に編集エリアを確保するのではなく、すでに表示されている箇所と差し替えて編集エリアになるので、省スペースなのかなと思っています。リネームなどの簡易的な編集機能でよく利用されるUIですね!

// page.tsx
<div className="mt-10 flex justify-center items-center">
 <InlineEditor defaultValue="Hello World !!!" />
</div>


//  InlineEditor.tsx
import { FocusEvent, useState } from "react";

type InlineEditorProps = {
  defaultValue: string
}

const InlineEditor = ({ defaultValue }: InlineEditorProps) => {
  const [isEditorMode, setIsEditorMode] = useState(false);
  const [text, setText] = useState(defaultValue);

  const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
    const inputText = e.target.value;
    setIsEditorMode(false);
    if (inputText === text) return
    // DB更新処理、バリデーションなど
    setText(`${inputText} (更新)`);
  }

  if (isEditorMode) {
    return (
      <input
        className="px-2 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-green-300 focus:border-transparent"
        autoFocus
        defaultValue={text}
        onBlur={handleBlur}
      />
    );
  }

  return (
    <>
      <p className="px-4 py-2">{text}</p>
      <button className="px-2 py-1 shadow-md rounded bg-green-200 text-gray hover:opacity-70" onClick={() => setIsEditorMode(true)}>🖊️</button>
    </>
  );
}
export default InlineEditor;

4.Fade

必要な場面のみ表示させたいものがあるときに便利です!
注意をひくという点でも良いですね。一般的にはframer-motionを利用しているのですかね。

// page.tsx
<button className="px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-700" onClick={() => setIsVisible((prev) => !prev)}>
 ボタン
</button>
<Fade isShow={isShow} duration={800} keepDisplayTime={1000}>
 <div className="mt-4 p-4 bg-green-500 text-white rounded">
   ここに説明が入ります。ここに説明が入ります。ここに説明が入ります。ここに説明が入ります。
 </div>
</Fade>


// Fade.tsx
import React, { useState, useEffect } from 'react';

type Props = {
  isShow: boolean;
  children: React.ReactNode;
  duration?: number; // フェードアニメーションの時間(ミリ秒)
  keepDisplayTime?: number; // フェードイン後の表示時間(ミリ秒)
}

const Fade = ({ isShow, children, duration = 500, keepDisplayTime = 1000 }: Props) => {
  const [isVisible, setIsVisible] = useState(false);
  // CSSクラスを動的に更新するためにstateを使用します
  const [opacityClass, setOpacityClass] = useState('opacity-0');

  useEffect(() => {
    // isShowの値が変更された時にアニメーションクラスを更新します
    if (isShow) {
      let timeId = null;
      setIsVisible(true);
      timeId = setTimeout(() => setOpacityClass('opacity-100'), 50); // フェードインをトリガー
      return () => clearTimeout(timeId);
    }
  }, [isShow, duration]);

  const transitionEndHandler = async () => {
    if (!isVisible) return;

    let timeId = null;
    await new Promise(resolve => setTimeout(resolve, keepDisplayTime)); // フェードイン後の表示時間をpropsから設定
    setOpacityClass('opacity-0');
    timeId = setTimeout(() => setIsVisible(false), duration); // フェードアウト時間をpropsから設定
    return () => clearTimeout(timeId)
  }

  return (
    isVisible ? (
      <div
        className={`${opacityClass}`}
        style={{ transitionDuration: `${duration}ms` }}
        onTransitionEnd={transitionEndHandler}
      >
        {children}
      </div>
    ) : null
  )
}

export default Fade;

📌 まとめ

画面上に省スペースで利用できたり雑然として見えにくいUIについて

  1. Tooltip
  2. スライドして表示されるアイコン
  3. Inline Editor
  4. Fade

今回は個人的なセレクションなので入れませんでしたが、アコーディオンやセレクトボックスも省スペースのUIとして便利です!

最後まで読んでいただきありがとうございます!
気ままにつぶやいているので、気軽にフォローをお願いします!🥺
https://twitter.com/Ryo54388667/status/1733434994016862256

Discussion