地点の詳細PlacePanelExperimental

地図で選んだ1か所の詳細を出す欄です(スマートフォンでは画面の下、広い画面では地図の横に置きます)。値が届くまで、値の場所には読み込み中の仮の枠(骨組み)を出します。

プレビュー

体感
湿度
風
雲量
座標
N35.69° E139.75°
地方太陽時
11:27
上は値が届くたびに行を足す作り方、下は最初から全部の行を出す作り方です。

状態とバリエーション

骨組み

調べているあいだ。項目名と、すぐに分かる値(座標)は出ていて、届くのを待つ値だけが骨組みです。aria-busy が付きます。端末で「動きを減らす」設定をオンにしている人には明滅しません。

体感
湿度
座標
N35.69° E139.75°

値が入った

値が入った状態です。

千代田区・東京都・日本

22.4°C薄曇り
体感
24.8°C
湿度
80%
座標
N35.69° E139.75°

取れなかった

取得に失敗した値は「—」で確定し、明滅をやめます。

千代田区・東京都・日本

——
体感
—
湿度
—
座標
N35.69° E139.75°

プロパティ

表は横にスクロールできます
プロパティ型初期値説明
titleReactNode | null | undefined-地点の名前。調べているあいだは undefined、取れなかったら null。
headline{ value, caption? }-大きく出す1つの値と、横の一言。
fieldsPlacePanelField[]-出す値の全部。最初から全部渡します。
fields[].valueReactNode | null | undefined-undefined は読み込み中、null は取れなかった。
columns1 | 22値の列数。狭いときは1列になります。
onClose() => void-閉じるボタン。渡さなければ出ません。
closeLabelstring"Close"閉じるボタンの読み上げ名。
labelstring"Place details"欄の読み上げ名。
titleAs"h2" | "h3" | "h4""h2"地点の名前を、どの階層の見出し(h2〜h4)にするか。

使い方

"use client";

import * as React from "react";
import { PlacePanel } from "@gunjo/ui";

type Current = { temperature_2m?: number; relative_humidity_2m?: number };

export function PickedPlace() {
  const [name, setName] = React.useState<string | null | undefined>(undefined);
  const [now, setNow] = React.useState<Current | null | undefined>(undefined);

  React.useEffect(() => {
    setName("千代田区・東京都・日本");
    fetch("https://api.open-meteo.com/v1/forecast?latitude=35.69&longitude=139.75&current=temperature_2m,relative_humidity_2m")
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((j) => setNow(j.current ?? null))
      // 取れなかったら null。骨組みは「—」で止まります
      .catch(() => setNow(null));
  }, []);

  // undefined は読み込み中、null は取れなかった
  const pick = <T,>(v: T | undefined) => (now === undefined ? undefined : v ?? null);

  return (
    <div className="relative h-[32rem] overflow-hidden rounded-lg bg-muted">
      <PlacePanel
        className="absolute inset-x-0 bottom-0"
        label="地点の詳細"
        closeLabel="閉じる"
        title={name}
        headline={{ value: pick(now?.temperature_2m != null ? `${now.temperature_2m}°C` : undefined) }}
        fields={[
          { id: "hum", label: "湿度", value: pick(now?.relative_humidity_2m != null ? `${now.relative_humidity_2m}%` : undefined) },
          { id: "pos", label: "座標", value: "N35.69° E139.75°" },
        ]}
        onClose={() => setName(undefined)}
      />
    </div>
  );
}

設計の判断

  • Sheet にしていません。地図を見る人は、欄を出したまま地図を動かしたいので、この欄は後ろの操作を止めません。
  • 3つの状態を、値の中身だけで区別します。状態を別の prop にすると、値と状態が食い違う組み合わせが作れてしまいます。
  • 2列にするかは、欄の幅で決めます。広い画面で地図の横の細い欄に置いたときも、1列に戻ります。

使用コンポーネント

  • Sheet

    画面の手前に重なり、そのあいだ後ろの地図を操作できなくする。

  • Drawer

    指で下へ払って閉じる手前の面。これも後ろを操作できなくする。

  • Skeleton

    読み込み中の仮の枠だけ。この欄の値の場所に使っている。

  • MetadataList

    項目と値の並び。読み込み中の状態を持たない。

いつ・なぜ使うか(UIXHERO)

「いつ・なぜ使うか」の判断は、姉妹サイト UIXHERO の記事で解説しています。