地図の上の丸いボタンMapControlButtonExperimental

地図や canvas(図を一面に描く画面の部分)の上に浮かせる、アイコンだけの丸いボタンです。拡大・縮小・選んだ地点へ移動・現在地へ移動に使います。オン・使えない・取得中・失敗の状態を、色だけに頼らず見せます。

プレビュー

状態とバリエーション

大きさ

sm は 40px(下限)、default は 44px、lg は 48px。スマートフォンでボタンを縦にいくつも並べるときは sm、タブレットは lg。

オン

オン・オフを切り替えるボタン(現在地を追う など)がオンのとき。ボタン全体が塗られます。読み上げには aria-pressed で伝わります。

使えない

まだ地点を選んでいないなど。薄くなり、理由がツールチップに出ます。

取得中

押して始めた処理(現在地の取得など)の終わりを待っているあいだ。アイコンが明滅し、aria-busy が付きます。端末で「動きを減らす」設定をオンにしている人には明滅しません。

現在地を取得しています

失敗

取得に失敗したとき。注意の色の縁と「!」の印が付き、statusLabel が読み上げられます。

現在地を取得できませんでした

プロパティ

表は横にスクロールできます
プロパティ型初期値説明
labelstring-何をするボタンか。読み上げの名前とツールチップを兼ねます。
childrenReactNode-アイコン。
pressedboolean-オン・オフを切り替えるボタンで、いまオンか。渡さなければ切り替えのボタンになりません。
status"idle" | "busy" | "error""idle"取得中か、失敗したか。
statusLabelstring-取得中・失敗のときに読み上げ、ツールチップに出す言葉。
disabledReasonstring-押せない理由。押せないあいだのツールチップになります。
size"default" | "sm" | "lg""default"44px/40px(下限)/48px。
tooltipSide"top" | "right" | "bottom" | "left""left"ツールチップを出す向き。

使い方

"use client";

import * as React from "react";
import { MapControlButton } from "@gunjo/ui";
import { IconCircleDot, IconMapPin, IconMinus, IconPlus } from "@tabler/icons-react";

export function MapColumn() {
  const [zoom, setZoom] = React.useState(3);
  const [following, setFollowing] = React.useState(false);
  const [status, setStatus] = React.useState<"idle" | "busy" | "error">("idle");
  const picked = null; // 地点を選ぶまでは null

  const locate = () => {
    if (following) return setFollowing(false);
    setStatus("busy");
    navigator.geolocation.getCurrentPosition(
      () => {
        setStatus("idle");
        setFollowing(true);
      },
      () => {
        setStatus("error");
        // 失敗の印は短く。消すのは呼び出し側です
        window.setTimeout(() => setStatus("idle"), 1600);
      },
    );
  };

  return (
    <div className="flex flex-col items-center gap-2">
      <MapControlButton label="拡大" onClick={() => setZoom(zoom + 1)}>
        <IconPlus />
      </MapControlButton>
      <MapControlButton label="縮小" onClick={() => setZoom(zoom - 1)}>
        <IconMinus />
      </MapControlButton>
      {/* 選んだ地点は ◎。現在地のピンと混ぜません */}
      <MapControlButton
        label="選んだ地点へ"
        disabled={picked === null}
        disabledReason="先に地図で地点を選んでください"
      >
        <IconCircleDot />
      </MapControlButton>
      <MapControlButton
        label="現在地へ"
        pressed={following}
        status={status}
        statusLabel={status === "busy" ? "現在地を取得しています" : "現在地を取得できませんでした"}
        onClick={locate}
      >
        <IconMapPin />
      </MapControlButton>
    </div>
  );
}

設計の判断

  • Button の変種にしていません。ふつうのボタンの後ろは、ページの無地の背景です。このボタンの後ろにあるのは海・街・雲の写真です。 どの上でも読めるように、半透明の背景色・後ろのぼかし・細い縁を、コンポーネントの側で持ちます。
  • 状態を色だけに載せません。オンのときはボタン全体を塗り、取得中はアイコンが明滅し、失敗には「!」の印が付きます。
  • 失敗の印を、時間が来たら自動で消す仕組みは持ちません。どれだけ出しておくかは画面ごとに違うので、status を戻すのは、このコンポーネントを使う側のコードです。
  • 押せないボタンにも理由を言わせます。ボタン自体は、押してもキーボードで移動しても反応しません。代わりにボタンを包む枠がキーボードのフォーカスを受け、理由をツールチップに出します。

使用コンポーネント

  • Button

    ふつうのページに置くボタン。後ろに地図はありません。

  • TooltipButton

    ふつうのページに置く、アイコンだけのボタン。取得中・失敗の状態は持たない。

  • LayerMenu

    このボタンから開く、地図に重ねて出すもの(雲・雨など)の選択。

  • ScaleBar

    同じ地図の上で、いまの縮尺を見せる物差し。

いつ・なぜ使うか(UIXHERO)

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