📦
🩺看護師が医療現場で使う計算ツールの開発に挑戦してみた #6:①ResultBox共通化とUI統一
💡 はじめに
現役看護師として働きながら、Next.js × TypeScript × Tailwind CSS を学習し、
そのアウトプットとして「看護師向け計算ツールアプリ」を開発しています。
このツールは、投薬量・点滴速度・体液バランスなど
医療現場でよく使う計算をまとめた Web アプリです。
現場で使える実用性と、開発者としての再利用性・型安全性の両立を目指し、 各機能をステップごとに公開しています。
🩺 今回のテーマ:ResultBox共通化とUI統一
前回までは、ロジックや型安全性の統一 に重点を置いていました。
今回からは UI・UX改善フェーズ に入り、
各計算結果の表示コンポーネントを統一することに挑戦しました。
🚨 課題
初期段階ではページごとにUI構成がバラバラで、次のような問題がありました。
- 結果の表示位置やスタイルが異なる
- 注意書きのフォーマットが統一されていない
- スマホでは結果が画面外になり見づらい
🧭 解決の方向性
-
ResultBox.tsxを共通コンポーネント化 - 注意・学習文を展開できる Disclosure(@headlessui/react) を採用
- スムーススクロールを導入し、モバイルでも快適に操作可能に
⚙️ 実装構成
app/_components/
├─ ResultBox.tsx ← 計算結果表示(Disclosure組み込み)
├─ HelpBox.tsx ← 注意・説明文用(必要に応じて独立)
└─ scrollToRef.ts ← スムーススクロール共通関数
🧩 ResultBox.tsx(抜粋)
"use client";
import React, { useRef, useEffect, useState } from "react";
import { Disclosure } from "@headlessui/react";
import Image from "next/image";
import { helpTexts } from "@/config/helpTexts";
export const ResultBox = ({ title, results, color = "blue", typeId }: Props) => {
const bg = {
blue: "bg-blue-50 border-blue-200 text-blue-800",
yellow: "bg-yellow-50 border-yellow-200 text-yellow-800",
}[color];
const panelRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
if (isOpen && panelRef.current) {
panelRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
}
}, [isOpen]);
return (
<Disclosure>
{({ open }) => {
useEffect(() => setIsOpen(open), [open]);
return (
<div className={`border rounded p-4 ${bg}`}>
<div className="flex items-start justify-between">
<h3 className="font-semibold">{title}</h3>
{helpText && (
<Disclosure.Button aria-label="注意説明">
<Image src="/icons/help-icon.svg" alt="注意" width={28} height={28} />
</Disclosure.Button>
)}
</div>
<ul className="space-y-2 mt-2">
{results.map((r, i) => (
<li key={i} className="flex items-baseline gap-2">
<span className="text-base">{r.label}:</span>
<strong className="text-2xl">{r.value}</strong>
<span className="text-sm">{r.unit}</span>
</li>
))}
</ul>
{helpText && (
<Disclosure.Panel ref={panelRef} className={`mt-3 w-full p-3 ${bg} rounded text-sm`}>
</Disclosure.Panel>
)}
</div>
);
}}
</Disclosure>
);
};
💡 Disclosureとは?
@headlessui/react に含まれるUIコンポーネントで、
アコーディオン構造を安全かつ最小構成で実現できます。
| 特徴 | 内容 |
|---|---|
| 状態管理(open) | 内部で自動化 |
| ARIA属性 | 自動付与でアクセシビリティ対応 |
| 拡張性 | 任意のHTML要素で利用可能 |
<Disclosure>
<Disclosure.Button>開く/閉じる</Disclosure.Button>
<Disclosure.Panel>展開内容</Disclosure.Panel>
</Disclosure>
✨ スムーススクロールを共通化
scrollToRef.ts に関数を作成して各ページから共通利用。
export const scrollToRef = (ref: React.RefObject<HTMLElement>) => {
if (ref.current) {
ref.current.scrollIntoView({ behavior: "smooth", block: "start" });
}
};
→ scrollToRef(resultRef) のように呼び出すだけで
開閉時や計算実行時に自動スクロールが可能。
🎨 UIデザインの工夫
| 要素 | Tailwind設定 | 目的 |
|---|---|---|
| 数値 | text-2xl | 結果の視認性UP |
| 単位 | text-sm | メインとの差別化 |
| 説明文 | text-sm bg-〇〇-50 | 柔らかい背景で読みやすく |
| アイコン | w-[28px] h-[28px] cursor-pointer | モバイルでも押しやすい |
🧩 Popover vs Disclosure:どちらを使うべき?
以前は注意文を Popover(吹き出し) で表示していましたが、
実際に現場で使うと「長文が見づらい」「誤タップで消える」などの課題がありました。
| 項目 | Popover(吹き出し) | Disclosure(展開型) |
|---|---|---|
| 表示位置 | 浮く(上に重なる) | 下に展開 |
| 表示速度 | 即時 | スクロール連動 |
| 適した内容 | 短文・単位補足 | 長文・学習説明 |
| UX印象 | 軽快 | 安心・安定 |
| 看護現場例 | 「mEqとmmolの違い」 | 「K補正の上限値・投与判断」 |
→ 学習・教育的な内容には Disclosure、
単位や補足には Popover が最適かなと考えました。
🧠 学びのまとめ
- Headless UI の Disclosure は状態管理+ARIA対応済みで安全
- useRef+scrollIntoView でモバイルUXを大幅改善
- カラーパレット統一で一貫した見た目を維持
- 「伝えるUI」と「見せるUI」を使い分ける設計が重要
💬 まとめ
ResultBox の共通化により、
全ての計算ページで統一されたUIを再利用可能になりました。
🚀 次回
スムーススクロールで操作感を高める ― React+TailwindでUX改善!
まだまだ改善していきます!!
Discussion