地点の詳細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°
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| title | ReactNode | null | undefined | - | 地点の名前。調べているあいだは undefined、取れなかったら null。 |
| headline | { value, caption? } | - | 大きく出す1つの値と、横の一言。 |
| fields | PlacePanelField[] | - | 出す値の全部。最初から全部渡します。 |
| fields[].value | ReactNode | null | undefined | - | undefined は読み込み中、null は取れなかった。 |
| columns | 1 | 2 | 2 | 値の列数。狭いときは1列になります。 |
| onClose | () => void | - | 閉じるボタン。渡さなければ出ません。 |
| closeLabel | string | "Close" | 閉じるボタンの読み上げ名。 |
| label | string | "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¤t=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 の記事で解説しています。