UNPKG

map-gl-style-switcher

Version:

A customizable style switcher control for Mapbox GL JS and MapLibre GL JS

329 lines (260 loc) 10.1 kB
# Map GL Style Switcher [![npm version](https://badge.fury.io/js/map-gl-style-switcher.svg)](https://badge.fury.io/js/map-gl-style-switcher) [![CI](https://github.com/muimsd/map-gl-style-switcher/actions/workflows/ci.yml/badge.svg)](https://github.com/muimsd/map-gl-style-switcher/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Netlify Status](https://api.netlify.com/api/v1/badges/ea3ee6af-161d-46be-9006-9d31ad52da3c/deploy-status)](https://app.netlify.com/projects/map-gl-style-switcher/deploys) <!-- [![Coverage Status](https://codecov.io/gh/muimsd/map-gl-style-switcher/branch/main/graph/badge.svg)](https://codecov.io/gh/muimsd/map-gl-style-switcher) --> A TypeScript control for switching Mapbox GL / MapLibre GL map styles. Easily add a floating style switcher to your map app, with support for multiple styles, images, dark/light themes, and before/after change callbacks. **Available as:** - `StyleSwitcherControl` - Direct IControl implementation for Mapbox/MapLibre GL - `MapGLStyleSwitcher` - React component for react-map-gl integration _Live demo of the style switcher control in action_ **<a href="https://map-gl-style-switcher.netlify.app/" target="_blank">🌐 Live Demo</a>** ## Animated Demo ![Demo GIF](./images/demo.gif) ## Available Styles ![Available Styles](./images/styles.png) ## Features - IControl implementation for Mapbox GL / MapLibre GL - **React component for react-map-gl integration** - Floating style switcher in any corner (via map.addControl position) - Support for multiple map styles with thumbnails - Expand/collapse on hover with smooth animations - Dark/light/auto theme support - RTL text support for Arabic scripts - Configurable display options (show/hide labels and images) - Callbacks for before/after style change - Fully customizable CSS classe's - TypeScript support - Accessibility features (ARIA labels, keyboard navigation) - Comprehensive test coverage ## Install ```sh # Using npm (recommended) npm install map-gl-style-switcher # Using yarn yarn add map-gl-style-switcher # Using pnpm pnpm add map-gl-style-switcher ``` ### Basic Map Instance For React applications where you manage the map instance yourself (e.g., with `useEffect`), use the `useStyleSwitcher` hook: ```tsx import React, { useEffect, useRef } from 'react'; import * as maplibregl from 'maplibre-gl'; import { StyleSwitcherControl, type StyleItem } from 'map-gl-style-switcher'; // Custom hook // import { useStyleSwitcher } from 'map-gl-style-switcher/react'; import 'maplibre-gl/dist/maplibre-gl.css'; import 'map-gl-style-switcher/dist/map-gl-style-switcher.css'; const mapStyles: StyleItem[] = [ { id: 'voyager', name: 'Voyager', image: 'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/voyager.png', styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json', description: 'Voyager style from Carto', }, { id: 'positron', name: 'Positron', image: 'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/positron.png', styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json', description: 'Positron style from Carto', }, { id: 'dark-matter', name: 'Dark Matter', image: 'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/dark.png', styleUrl: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json', description: 'Dark style from Carto', }, { id: 'arcgis-hybrid', name: 'ArcGIS Hybrid', image: 'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/arcgis-hybrid.png', styleUrl: 'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/arcgis_hybrid.json', description: 'Hybrid Satellite style from ESRI', }, { id: 'osm', name: 'OSM', image: 'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/osm.png', styleUrl: 'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/openStreetMap.json', description: 'OSM style', }, ]; export default function App() { const mapContainer = useRef<HTMLDivElement | null>(null) useEffect(() => { if (!mapContainer.current) { console.error('Map container not found!'); return; } const currentStyle = mapStyles[0]; const map = new maplibregl.Map({ container: mapContainer.current, // Use a simple, reliable style first style: currentStyle.styleUrl, // Center on Dubai, UAE center: [55.2708, 25.2048], zoom: 10, }); // Add style switcher control const styleSwitcher = new StyleSwitcherControl({ styles: mapStyles, theme: 'auto', showLabels: true, showImages: true, activeStyleId: currentStyle.id, onBeforeStyleChange: (from, to) => { console.log('Changing style from', from.name, 'to', to.name); }, onAfterStyleChange: (_from, to) => { map.setStyle(to.styleUrl); console.log('Style changed to', to.name); }, }); map.addControl(styleSwitcher, 'bottom-left'); return () => { map.remove(); }; }, []); // Empty dependency array // Add style switcher control to the map // useStyleSwitcher(map.current, { // styles, // theme: 'auto', // position: 'top-right', // onAfterStyleChange: (from, to) => { // if (map.current) { // map.current.setStyle(to.styleUrl); // } // }, // }); return ( <div ref={mapContainer} style={{ width: '100%', height: '500px', }} /> ); } ``` ## Examples ### React Map GL Example [View the maplibre example →](examples/maplibre/) ### React Map GL Example [View the react-map-gl example →](examples/react-map-gl/) ## Configuration Options ```ts interface StyleSwitcherControlOptions { styles: StyleItem[]; // Array of map styles (required) activeStyleId?: string; // Currently active style ID (default: first style) onBeforeStyleChange?: (from: StyleItem, to: StyleItem) => void; // Callback before style change onAfterStyleChange?: (from: StyleItem, to: StyleItem) => void; // Callback after style change showLabels?: boolean; // Show style names (default: true) showImages?: boolean; // Show style thumbnails (default: true) animationDuration?: number; // Animation duration in ms (default: 200) maxHeight?: number; // Max height of expanded list (default: 300) theme?: 'light' | 'dark' | 'auto'; // UI theme (default: 'light') classNames?: Partial<StyleSwitcherClassNames>; // Custom CSS classes rtl?: boolean; // Enable RTL layout (default: false) } interface StyleItem { id: string; // Unique identifier name: string; // Display name image: string; // Thumbnail URL or data URI styleUrl: string; // MapLibre/Mapbox style URL description?: string; // Optional tooltip text } interface StyleSwitcherClassNames { container: string; // Main container class list: string; // Expanded list container class item: string; // Individual style item class itemSelected: string; // Selected style item class itemHideLabel: string; // Hide label utility class dark: string; // Dark theme class light: string; // Light theme class } ``` _Example of different map styles that can be used with the style switcher_ ### Option Details - **`activeStyleId`**: Controls both the initially selected style and what's displayed in the collapsed state - **`showLabels`** & **`showImages`**: At least one must be `true` - **`theme`**: - `'light'`: Light color scheme - `'dark'`: Dark color scheme - `'auto'`: Auto-detect from system preference - **`rtl`**: Enables right-to-left layout for Arabic/Hebrew interfaces ## Customizing CSS Classes You can override all CSS classes used by the style switcher control using the `classNames` option: ```ts import { StyleSwitcherControl } from 'map-gl-style-switcher'; const styleSwitcher = new StyleSwitcherControl({ styles, showLabels: true, showImages: true, classNames: { container: 'my-style-switcher', list: 'my-style-list', item: 'my-style-item', itemSelected: 'my-style-item-selected', itemHideLabel: 'my-style-item-hide-label', dark: 'my-style-dark', light: 'my-style-light', }, }); ``` See the default class names in the `StyleSwitcherControl` source for all available keys. ## Contributing We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change. ### Development Setup 1. **Clone the repository** ```sh git clone https://github.com/muimsd/map-gl-style-switcher cd map-gl-style-switcher ``` 2. **Install dependencies** ```sh npm install ``` 3. **Start development server** ```sh npm run dev ``` 4. **Make your changes** - Follow TypeScript best practices - Maintain backward compatibility when possible - Add tests for new features 5. **Test your changes** ```sh npm run validate # Runs type-check, lint, format-check, and tests ``` 6. **Submit a pull request** ### Guidelines - Use npm for dependency management - Follow TypeScript best practices - Maintain backward compatibility when possible - Add tests for new features - Update documentation as needed - Follow the existing code style - Ensure all checks pass: `npm run validate` ## License MIT License - see [LICENSE](LICENSE) file for details. ## Author **Muhammad Imran Siddique** - GitHub: [@muimsd](https://github.com/muimsd) ## Repository - GitHub: [https://github.com/muimsd/map-gl-style-switcher](https://github.com/muimsd/map-gl-style-switcher) - Issues: [https://github.com/muimsd/map-gl-style-switcher/issues](https://github.com/muimsd/map-gl-style-switcher/issues) --- Made with ❤️ for the MapLibre GL and Mapbox GL community