Style MapLibre controls and popups, follow the page theme

This commit is contained in:
codecalm committed 2026-10-04 13:23:28 +02:00
1 parent 8a2998ba47
commit 5b2cddc30b
9 files changed
+237 -14

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@tabler/core": minor
"@tabler/preview": patch
"@tabler/docs": patch
---
Added MapLibre GL styles to `tabler-vendors.css`: map controls, popups and attribution, in light and dark mode.
+6 -6
View File
@@ -196,19 +196,19 @@
},
{
"path": "./dist/css/tabler-vendors.css",
"maxSize": "4 kB"
"maxSize": "4.5 kB"
},
{
"path": "./dist/css/tabler-vendors.min.css",
"maxSize": "3.5 kB"
},
{
"path": "./dist/css/tabler-vendors.rtl.css",
"maxSize": "4 kB"
},
{
"path": "./dist/css/tabler-vendors.rtl.css",
"maxSize": "4.5 kB"
},
{
"path": "./dist/css/tabler-vendors.rtl.min.css",
"maxSize": "3.5 kB"
"maxSize": "4 kB"
},
{
"path": "./dist/js/tabler.js",
+1
View File
@@ -4,6 +4,7 @@
@forward 'vendor/tom-select';
@forward 'vendor/apexcharts';
@forward 'vendor/jsvectormap';
@forward 'vendor/maplibre';
@forward 'vendor/dropzone';
@forward 'vendor/fslightbox';
@forward 'vendor/plyr';
@@ -90,6 +90,7 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre
"--tblr-secondary",
"--tblr-secondary-bg",
"--tblr-shadow-card",
"--tblr-shadow-color",
"--tblr-shadow-dropdown",
"--tblr-shadow-input",
"--tblr-signal-color",
+133
View File
@@ -0,0 +1,133 @@
@use '../config' as *;
.maplibregl-map {
font-family: inherit;
font-size: $h5-font-size;
line-height: $line-height-base;
}
// Controls: zoom, compass, fullscreen, locate
.maplibregl-ctrl-group {
overflow: hidden;
background: var(--bg-surface);
@include border-radius(var(--border-radius));
&:not(:empty) {
box-shadow:
0 0 0 var(--border-width) var(--border-color),
#{$card-box-shadow};
}
button {
width: 2rem;
height: 2rem;
+ button {
border-top: var(--border-width) var(--border-style) var(--border-color);
}
&:focus:focus-visible {
outline-offset: calc(-1 * var(--focus-ring-width));
box-shadow: none;
@include focus-ring();
}
}
}
.maplibregl-ctrl button:not(:disabled):hover,
.maplibregl-ctrl button:not(:disabled):active {
background-color: var(--bg-surface-secondary);
}
// Attribution
.maplibregl-ctrl.maplibregl-ctrl-attrib {
color: var(--secondary);
background-color: color-transparent(var(--bg-surface), 0.7);
a {
color: inherit;
}
}
.maplibregl-ctrl-attrib.maplibregl-compact {
color: var(--secondary);
background-color: var(--bg-surface);
}
.maplibregl-ctrl-attrib-button:focus {
box-shadow: none;
@include focus-ring();
}
.maplibregl-ctrl-scale {
color: var(--body-color);
background-color: color-transparent(var(--bg-surface), 0.75);
border-color: var(--body-color);
}
// Popup
.maplibregl-popup-content {
padding: 0.75rem 1rem;
color: var(--body-color);
background: var(--bg-surface);
box-shadow:
0 0 0 var(--border-width) var(--border-color),
#{$box-shadow-md};
@include border-radius(var(--border-radius));
}
// Room for the close button, which MapLibre pins to the right edge in both directions
.maplibregl-popup-content:has(.maplibregl-popup-close-button) {
/* rtl:ignore */
padding-right: 2.25rem;
}
.maplibregl-popup-close-button {
width: 1.5rem;
height: 1.5rem;
padding: 0;
margin: 0.375rem;
font-size: 1rem;
line-height: 1;
color: var(--secondary);
@include border-radius(var(--border-radius));
&:hover {
color: var(--body-color);
background-color: var(--bg-surface-secondary);
}
}
.maplibregl-popup-anchor-top,
.maplibregl-popup-anchor-top-left,
.maplibregl-popup-anchor-top-right {
.maplibregl-popup-tip {
border-bottom-color: var(--bg-surface);
}
}
.maplibregl-popup-anchor-bottom,
.maplibregl-popup-anchor-bottom-left,
.maplibregl-popup-anchor-bottom-right {
.maplibregl-popup-tip {
border-top-color: var(--bg-surface);
}
}
.maplibregl-popup-anchor-left .maplibregl-popup-tip {
border-right-color: var(--bg-surface);
}
.maplibregl-popup-anchor-right .maplibregl-popup-tip {
border-left-color: var(--bg-surface);
}
// The control icons are black SVG images
@if $enable-dark-mode {
@include color-mode(dark) {
.maplibregl-ctrl-icon,
.maplibregl-ctrl-attrib-button {
@include autodark-image();
}
}
}
+54
View File
@@ -40,6 +40,13 @@ Or include it from a CDN. You need two files: the stylesheet and the library.
<script src="${site.cdnUrl}/dist/libs/maplibre-gl/dist/maplibre-gl.js"></script>`}
/>
Tabler styles the map controls, popups and attribution to match the interface, in light and dark mode. These styles are in `tabler-vendors.css`, so load it after the MapLibre stylesheet:
<Code
lang="html"
code={`<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler-vendors.min.css" />`}
/>
## Usage
### Markup
@@ -115,6 +122,53 @@ cities.forEach(function (coords) {
The `color` option is written to the `fill` attribute of the marker icon. A CSS variable works there, so `var(--tblr-primary)` keeps the markers in your theme color, including after a theme change.
### Popups
Attach a popup to a marker with `setPopup`. It opens when the marker is clicked. Click a marker on the map above to see one.
```js
new maplibregl.Marker({ color: 'var(--tblr-primary)' })
.setLngLat([16.366667, 48.2])
.setPopup(new maplibregl.Popup({ offset: 32 }).setText('Vienna'))
.addTo(map)
```
Use `setHTML` instead of `setText` for richer content. Only pass HTML you trust, as it is not escaped.
### Controls
Add the zoom and compass buttons with `NavigationControl`. They go to the top right corner by default.
```js
map.addControl(new maplibregl.NavigationControl())
```
MapLibre also has `FullscreenControl`, `GeolocateControl` and `ScaleControl`. Pass a corner as the second argument, for example `'bottom-left'`.
### Dark mode
A map style does not change with the page theme on its own. Pick the style from the `data-bs-theme` attribute, and set it again when the attribute changes:
```js
const themeStyle = function () {
const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark'
return 'https://tiles.openfreemap.org/styles/' + (dark ? 'dark' : 'liberty')
}
const map = new maplibregl.Map({
container: 'map-simple',
style: themeStyle(),
zoom: 13,
center: [13.4049, 52.518827],
})
new MutationObserver(function () {
map.setStyle(themeStyle())
}).observe(document.documentElement, { attributeFilter: ['data-bs-theme'] })
```
Markers and popups stay on the map when the style changes. The maps on this page follow the theme this way.
### Attribution
MapLibre adds the credit for the map data in the corner of the map. OpenFreeMap requires it, so do not hide it.
+6 -1
View File
@@ -12,15 +12,20 @@ import CaptureScript from '@shared/components/CaptureScript.astro'
window.tabler_map ??= {}
function initMap() {
const themeStyle = () => `https://tiles.openfreemap.org/styles/${document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'liberty'}`
const map = new maplibregl.Map({
container: 'map-fullsize',
style: 'https://tiles.openfreemap.org/styles/liberty',
style: themeStyle(),
zoom: 12,
center: [13.4049, 52.518827],
})
map.addControl(new maplibregl.NavigationControl())
// Change the map style when the page theme changes
new MutationObserver(() => map.setStyle(themeStyle())).observe(document.documentElement, { attributeFilter: ['data-bs-theme'] })
window.tabler_map['map-fullsize'] = map
}
+26 -6
View File
@@ -1,5 +1,6 @@
---
// MapLibre GL map on OpenFreeMap tiles: no account and no access token.
// The map follows the page theme: `style` in light mode, `darkStyle` in dark.
// Renders nothing unless maps[mapId] exists.
import maps from '@data/maps.json';
import CaptureScript from '../CaptureScript.astro';
@@ -18,6 +19,10 @@ type MapData = {
card?: boolean;
/** OpenFreeMap style name: liberty (default), bright, positron, dark or fiord */
style?: string;
/** style used in dark mode, `dark` by default */
darkStyle?: string;
/** adds the zoom and compass buttons */
controls?: boolean;
zoom?: number;
center?: [number, number];
markers?: { name?: string; center: [number, number] }[];
@@ -26,10 +31,13 @@ type MapData = {
const data = (maps as unknown as Record<string, MapData>)[mapId];
const mapContainerId = `map-${mapId}`;
const style = `https://tiles.openfreemap.org/styles/${data?.style ?? 'liberty'}`;
const styleUrl = (name: string) => `https://tiles.openfreemap.org/styles/${name}`;
const style = styleUrl(data?.style ?? 'liberty');
const darkStyle = styleUrl(data?.darkStyle ?? 'dark');
const controls = data?.controls ?? false;
const zoom = data?.zoom ?? 13;
const center = data?.center ? [data.center[1], data.center[0]] : [13.4049, 52.518827];
const markers = (data?.markers ?? []).map((marker) => [marker.center[1], marker.center[0]]);
const markers = (data?.markers ?? []).map((marker) => ({ name: marker.name, coords: [marker.center[1], marker.center[0]] }));
const ratioClass = data?.ratio ?? ratio ?? '16x9';
---
@@ -47,21 +55,33 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
data && (
<CaptureScript>
<!-- BEGIN MAP -->
<script is:inline define:vars={{ mapContainerId, style, zoom, center, markers }}>
<script is:inline define:vars={{ mapContainerId, style, darkStyle, controls, zoom, center, markers }}>
window.tabler_map ??= {};
function initMap() {
const themeStyle = () => (document.documentElement.getAttribute('data-bs-theme') === 'dark' ? darkStyle : style);
const map = new maplibregl.Map({
container: mapContainerId,
style,
style: themeStyle(),
zoom,
center,
});
markers.forEach((coords) => {
new maplibregl.Marker({ color: 'var(--tblr-primary)' }).setLngLat(coords).addTo(map);
if (controls) {
map.addControl(new maplibregl.NavigationControl());
}
markers.forEach((marker) => {
new maplibregl.Marker({ color: 'var(--tblr-primary)' })
.setLngLat(marker.coords)
.setPopup(new maplibregl.Popup({ offset: 32 }).setText(marker.name))
.addTo(map);
});
// Change the map style when the page theme changes
new MutationObserver(() => map.setStyle(themeStyle())).observe(document.documentElement, { attributeFilter: ['data-bs-theme'] });
window.tabler_map[mapContainerId] = map;
}
+3 -1
View File
@@ -1,6 +1,7 @@
{
"simple": {
"title": "Simple map"
"title": "Simple map",
"controls": true
},
"light": {
"title": "Light map style",
@@ -73,6 +74,7 @@
],
"zoom": 1,
"style": "bright",
"darkStyle": "fiord",
"card": true
}
}