mapcn
shadcn/ui 互換 React マップコンポーネントライブラリ — MapLibre GL ベース
mapcn は MapLibre GL をレンダリングエンジンに持つ、shadcn/ui 互換のマップコンポーネントライブラリ。コアコンポーネント全体が map.tsx という 1 ファイルに集約されており、npx shadcn add でプロジェクトにコピーして自由に改変できる。ライト/ダークテーマ自動切替・マーカー・ポップアップ・ルート・クラスタリングをゼロコンフィグで使えるのが売り。
Next.js 16 (App Router) + TypeScript 5 + Tailwind CSS 4 で構築されたドキュメントサイトも同梱。shadcn レジストリスキーマに準拠した registry.json を公開しており、public/r/ にビルド済み JSON が配置される。
components/ui/map.tsx として手元に置き、自由に改変できる。依存は maplibre-gl と lucide-react のみ。
styles prop や blank prop で OpenStreetMap タイル・MapTiler 等へ差し替え可能。
shadcn が初期化済みのプロジェクトで以下を実行する。
# mapcn の map コンポーネントをプロジェクトに追加
npx shadcn add https://mapcn.dev/r/map.json
# インストール後の配置先
# → components/ui/map.tsx
依存パッケージの maplibre-gl も自動でインストールされる(shadcn の dependencies フィールドから解決)。
npm run dev # 開発サーバー起動(Next.js)
npm run build # Next.js プロダクションビルド
npm run registry:build # public/r/ へレジストリ JSON を生成
# → shadcn build -o ./public/r
# → fix-registry-imports.ts でパス書き換え
registry:build は npx shadcn build -o ./public/r のあと、scripts/fix-registry-imports.ts で @/registry/map → @/components/ui/map へ import パスを書き換える。
マップインスタンスは React Context で全子コンポーネントに共有される。マーカーは MapLibre の DOM 要素を React Portal のマウント先として使うことで、宣言的 JSX と MapLibre のネイティブ API を統合している。
(レンダリングエンジン)"] --> B["Map
(Context Provider)"] B --> C["MapMarker
(Portal)"] B --> D["MapControls
(ズーム/コンパス)"] B --> E["MapRoute
(GeoJSON LineString)"] B --> E2["MapArc
(曲線アーク)"] B --> E3["MapGeoJSON
(GeoJSON レイヤー)"] B --> F["MapClusterLayer
(ポイントクラスター)"] B --> G["MapPopup
(独立ポップアップ)"] C --> C1["MarkerContent"] C --> C2["MarkerPopup"] C --> C3["MarkerTooltip"] C --> C4["MarkerLabel"] style A fill:#0891b2,color:#fff,stroke:none style B fill:#0369a1,color:#fff,stroke:none style C fill:#0e7490,color:#fff,stroke:none style D fill:#155e75,color:#fff,stroke:none style E fill:#164e63,color:#fff,stroke:none style E2 fill:#134e4a,color:#fff,stroke:none style E3 fill:#115e59,color:#fff,stroke:none style F fill:#083344,color:#fff,stroke:none style G fill:#0c4a6e,color:#fff,stroke:none style C1 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec style C2 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec style C3 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec style C4 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec
(定義)"] --> B["npx shadcn build"] B --> J["public/r/*.json
(ビルド済み)"] J --> F["fix-registry-imports.ts
(パス書き換え)"] F --> U["ユーザープロジェクト
components/ui/map.tsx"] style R fill:#92400e,color:#fff,stroke:none style B fill:#b45309,color:#fff,stroke:none style J fill:#d97706,color:#fff,stroke:none style F fill:#ca8a04,color:#fff,stroke:none style U fill:#a16207,color:#fff,stroke:none
- Single File Component: 全コンポーネントを
map.tsx1 ファイルに集約。shadcn のコピペ哲学と一致し依存を最小化。 - React Portal でのマーカーレンダリング: MapLibre の DOM 要素を Portal のマウント先として使い、仮想 DOM とネイティブ API を統合。
- Controlled / Uncontrolled 両対応:
viewport + onViewportChangeでコントロールドモード。internalUpdateRefフラグで無限ループを防止。 - テーマ自動検出:
MutationObserverでdocument.documentElementの class 変更を監視し、next-themes などと自動連携。 - スタイルロード競合対策:
isStyleLoadedを別フラグで管理し、100ms のバッファでレースコンディションを防止。 - blank ベースマップ:
blankprop でタイルなし透明キャンバスを使い、choropleth やアークなどデータ可視化専用レイアウトを構築。 - MapGeoJSON / MapArc: GeoJSON ソース・レイヤーを宣言的に追加。アークは二次ベジェ曲線で日付変更線をまたぐ経路も短い方へ描画。
すべて 1 ファイルに収録。インストール後は components/ui/map.tsx から import する。
forwardRef で MapRef(生の MapLibre インスタンス)を公開。すべての子コンポーネントはこの中に置く。Map コンテキストから mapInstance と isLoaded を取得するフック。カスタムレイヤーや直接 API 操作に使う。showZoom / showLocate などの props でオン/オフを切り替える。curvature で曲率、hoverPaint でホバー強調。日付変更線をまたぐ経路は短い方へ描画。blank マップ上の choropleth やカスタムデータレイヤー向け。クリック・ホバーイベント対応。| Prop | 型 | 説明 |
|---|---|---|
theme | "light" | "dark" | テーマを強制指定。省略時はシステム設定を自動検出。 |
styles | { light?, dark? } | カスタムタイルスタイル URL。デフォルトは Carto Positron / Dark Matter。 |
blank | boolean | タイルなし透明ベースマップ。MapGeoJSON / MapArc などデータ可視化向け。styles 指定時は無視。 |
projection | ProjectionSpecification | { type: "globe" } で 3D 地球儀モード。 |
viewport | Partial<MapViewport> | コントロールドモード用ビューポート(center, zoom, bearing, pitch)。 |
onViewportChange | (v: MapViewport) => void | ビューポート変更時のコールバック。 |
loading | boolean | ローディングスピナーの表示制御。 |
import { Map, MapMarker, MarkerContent, MapControls } from "@/components/ui/map";
export function BasicMap() {
return (
<Map className="h-[400px] w-full" zoom={10} center={[139.69, 35.68]}>
<MapMarker longitude={139.69} latitude={35.68}>
<MarkerContent>
<div className="rounded-full bg-primary text-primary-foreground p-1 text-xs">
東京
</div>
</MarkerContent>
</MapMarker>
<MapControls showZoom showLocate />
</Map>
);
}
import { useState } from "react";
import { Map, MapViewport } from "@/components/ui/map";
export function ControlledMap() {
const [viewport, setViewport] = useState<MapViewport>({
center: [139.69, 35.68],
zoom: 10,
bearing: 0,
pitch: 0,
});
return (
<Map
className="h-[400px] w-full"
viewport={viewport}
onViewportChange={setViewport}
/>
);
}
import { Map, MapRoute, useMap } from "@/components/ui/map";
function FlyToButton() {
const { mapInstance } = useMap();
const handleFly = () => {
mapInstance?.flyTo({ center: [135.5, 34.69], zoom: 12, duration: 1500 });
};
return <button onClick={handleFly}>大阪へ飛ぶ</button>;
}
export function RouteMap() {
const coords: [number, number][] = [
[139.69, 35.68], [137.00, 35.30], [135.50, 34.69],
];
return (
<Map className="h-[400px] w-full" zoom={7} center={[137.5, 35.4]}>
<MapRoute coordinates={coords} color="#0891b2" width={3} />
<FlyToButton />
</Map>
);
}
shadcn レジストリに登録されたブロック(完全なページコンポーネント)。npx shadcn add でプロジェクトに追加できる。
overview-card + breakdown-card の 2 カードレイアウト。use-world-data で GeoJSON を取得。projection: "globe" で 3D 地球儀モード。ズーム依存のスタイリングで密度を可視化。- mapcn.dev — 公式ドキュメントサイト(コンポーネント API・ブロックプレビュー)
- mapcn.dev/r/map.json — shadcn add 用レジストリ JSON
- MapLibre GL JS — レンダリングエンジン(MIT ライセンス)
- shadcn/ui — コンポーネント配布モデルの仕様元
- CARTO Basemaps — デフォルトタイルの利用条件