diff --git a/.changeset/map-markers.md b/.changeset/map-markers.md new file mode 100644 index 000000000..d49fc4b12 --- /dev/null +++ b/.changeset/map-markers.md @@ -0,0 +1,7 @@ +--- +"@tabler/core": minor +"@tabler/preview": minor +"@tabler/docs": patch +--- + +Added `.map-marker` with dot, pin and color variants and `.map-popup-card` for MapLibre maps to `tabler-vendors.css`. diff --git a/core/package.json b/core/package.json index e5d718d4f..7b5aece3d 100644 --- a/core/package.json +++ b/core/package.json @@ -196,19 +196,19 @@ }, { "path": "./dist/css/tabler-vendors.css", - "maxSize": "4.5 kB" + "maxSize": "5 kB" }, { "path": "./dist/css/tabler-vendors.min.css", - "maxSize": "4 kB" - }, - { - "path": "./dist/css/tabler-vendors.rtl.css", "maxSize": "4.5 kB" }, + { + "path": "./dist/css/tabler-vendors.rtl.css", + "maxSize": "5 kB" + }, { "path": "./dist/css/tabler-vendors.rtl.min.css", - "maxSize": "4 kB" + "maxSize": "4.5 kB" }, { "path": "./dist/js/tabler.js", diff --git a/core/scss/_variables.scss b/core/scss/_variables.scss index dd97a60f7..d078ece95 100644 --- a/core/scss/_variables.scss +++ b/core/scss/_variables.scss @@ -1279,6 +1279,19 @@ $ribbon-bookmark-border-size: 1rem !default; $ribbon-bookmark-notch-width: 0.5rem !default; // scss-docs-end ribbon-variables +// scss-docs-start map-marker-variables +$map-marker-size: 1.75rem !default; +$map-marker-dot-size: 0.875rem !default; +$map-marker-bg: var(--primary) !default; +$map-marker-color: var(--primary-fg) !default; +$map-marker-border-width: 2px !default; +$map-marker-border-color: var(--bg-surface) !default; +$map-marker-font-size: $h5-font-size !default; +$map-marker-icon-size: 1rem !default; +$map-marker-shadow: drop-shadow(0 1px 2px rgba($black, 0.3)) !default; +$map-popup-card-width: 16rem !default; +// scss-docs-end map-marker-variables + // Accordion // scss-docs-start accordion-variables $accordion-padding-y: 1rem !default; diff --git a/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap b/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap index 88e717e1b..92074577e 100644 --- a/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap +++ b/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap @@ -37,17 +37,21 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre "--plyr-color-main", "--tblr-active-bg", "--tblr-azure", + "--tblr-azure-fg", "--tblr-backdrop-bg", "--tblr-bg-forms", "--tblr-bg-surface", "--tblr-bg-surface-inverted", "--tblr-bg-surface-secondary", "--tblr-black", + "--tblr-blue", + "--tblr-blue-fg", "--tblr-body-bg", "--tblr-body-color", "--tblr-border-color", "--tblr-border-color-translucent", "--tblr-border-radius", + "--tblr-border-radius-pill", "--tblr-border-style", "--tblr-border-width", "--tblr-card-bg", @@ -61,7 +65,12 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre "--tblr-control-padding-x", "--tblr-control-padding-y", "--tblr-control-placeholder-color", + "--tblr-cyan", + "--tblr-cyan-fg", "--tblr-danger", + "--tblr-danger-fg", + "--tblr-dark", + "--tblr-dark-fg", "--tblr-focus-ring", "--tblr-focus-ring-color", "--tblr-focus-ring-offset", @@ -79,26 +88,56 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre "--tblr-form-valid-border-color", "--tblr-gray-500", "--tblr-green", + "--tblr-green-fg", "--tblr-icon-size", + "--tblr-indigo", + "--tblr-indigo-fg", "--tblr-info", + "--tblr-info-fg", "--tblr-light", + "--tblr-light-fg", + "--tblr-lime", + "--tblr-lime-fg", + "--tblr-map-marker-bg", + "--tblr-map-marker-border-color", + "--tblr-map-marker-border-width", + "--tblr-map-marker-color", + "--tblr-map-marker-font-size", + "--tblr-map-marker-icon-size", + "--tblr-map-marker-size", + "--tblr-muted", + "--tblr-muted-fg", "--tblr-orange", + "--tblr-orange-fg", + "--tblr-pink", + "--tblr-pink-fg", "--tblr-primary", "--tblr-primary-200", + "--tblr-primary-fg", "--tblr-primary-lt", "--tblr-purple", + "--tblr-purple-fg", + "--tblr-red", + "--tblr-red-fg", "--tblr-secondary", "--tblr-secondary-bg", + "--tblr-secondary-fg", "--tblr-shadow-card", "--tblr-shadow-color", "--tblr-shadow-dropdown", "--tblr-shadow-input", "--tblr-signal-color", "--tblr-success", + "--tblr-success-fg", + "--tblr-teal", + "--tblr-teal-fg", "--tblr-tertiary", "--tblr-text-inverted", + "--tblr-warning", + "--tblr-warning-fg", "--tblr-white", "--tblr-yellow", + "--tblr-yellow-fg", "--ts-pr-caret", "--ts-pr-clear-button", ] diff --git a/core/scss/vendor/_maplibre.scss b/core/scss/vendor/_maplibre.scss index 562d9fc08..bd72aa78c 100644 --- a/core/scss/vendor/_maplibre.scss +++ b/core/scss/vendor/_maplibre.scss @@ -122,6 +122,91 @@ border-left-color: var(--bg-surface); } +// Popup that holds a card-like block: an image on top, padded content below +.map-popup-card { + .maplibregl-popup-content { + width: #{$map-popup-card-width}; + padding: 0; + overflow: hidden; + } + + .maplibregl-popup-close-button { + color: var(--body-color); + background-color: var(--bg-surface); + } +} + +// Marker drawn in HTML. MapLibre moves the marker with `transform`, so the +// shape is drawn by the pseudo-element and the root keeps no transform. +.map-marker { + // scss-docs-start map-marker-css-vars + --map-marker-size: #{$map-marker-size}; + --map-marker-bg: #{$map-marker-bg}; + --map-marker-color: #{$map-marker-color}; + --map-marker-border-width: #{$map-marker-border-width}; + --map-marker-border-color: #{$map-marker-border-color}; + --map-marker-font-size: #{$map-marker-font-size}; + --map-marker-icon-size: #{$map-marker-icon-size}; + // scss-docs-end map-marker-css-vars + position: relative; + z-index: 0; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: var(--map-marker-size); + min-height: var(--map-marker-size); + padding: var(--map-marker-border-width); + font-size: var(--map-marker-font-size); + font-weight: var(--font-weight-medium); + line-height: 1; + color: var(--map-marker-color); + cursor: pointer; + filter: #{$map-marker-shadow}; + + &::before { + position: absolute; + inset: 0; + z-index: -1; + content: ''; + background: var(--map-marker-bg); + border: var(--map-marker-border-width) solid var(--map-marker-border-color); + @include border-radius(var(--border-radius-pill)); + } + + .icon { + --icon-size: var(--map-marker-icon-size); + } +} + +.map-marker-dot { + --map-marker-size: #{$map-marker-dot-size}; +} + +// Teardrop: a square with one sharp corner, turned to point down. Its tip +// reaches 20.7% below the circle, so the root is that much taller. +.map-marker-pin { + box-sizing: content-box; + width: var(--map-marker-size); + min-width: 0; + height: var(--map-marker-size); + min-height: 0; + padding: 0 0 calc(var(--map-marker-size) * 0.207); + + &::before { + bottom: auto; + height: var(--map-marker-size); + @include border-radius(50% 50% 50% 0); + transform: rotate(-45deg); + } +} + +@each $name, $color in $theme-colors { + .map-marker-#{$name} { + --map-marker-bg: var(--#{$name}); + --map-marker-color: var(--#{$name}-fg); + } +} + // The control icons are black SVG images @if $enable-dark-mode { @include color-mode(dark) { diff --git a/docs/content/ui/components/map.mdx b/docs/content/ui/components/map.mdx index be3b2fa86..77ee87be3 100644 --- a/docs/content/ui/components/map.mdx +++ b/docs/content/ui/components/map.mdx @@ -4,17 +4,31 @@ summary: A map shows places and markers on an interactive world map. Tabler prov description: Show interactive maps with markers using MapLibre GL JS and free map tiles. No account or access token is needed. related: [/ui/plugins/vector-map] docs-libs: [maplibre-gl] -source: core/scss/_utilities.scss +source: core/scss/vendor/_maplibre.scss +css-vars: map-marker classnames: component: - class: ratio - desc: Wrapper that keeps the embed at a fixed aspect ratio + desc: Wrapper that keeps the map at a fixed aspect ratio + - class: map-marker + desc: Marker drawn in HTML, a circle that can hold an icon, a count or an avatar + modifier: + - class: map-marker-dot + desc: Small dot with no content + - class: map-marker-pin + desc: Teardrop pin that points down, used with the bottom anchor + - class: map-popup-card + desc: Popup with no padding and a fixed width, for an image above the text + color: + - class: map-marker-{color} + desc: Any theme color, for example map-marker-red or map-marker-green size: - class: ratio-{ratio} desc: The aspect to hold, for example ratio-16x9 or ratio-21x9 --- import Example from '@components/Example.astro'; import Map from '@shared/components/demo/Map.astro'; +import Icon from '@ui/Icon.astro'; import { Code } from 'astro:components'; import { site } from '@shared/lib/site.ts'; @@ -122,6 +136,69 @@ 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. +### Custom markers + +To draw a marker in HTML, create an element with the `.map-marker` class and pass it as `element`. The marker is a circle in the primary color. Put an icon, a short count or an avatar inside it. + +```js +const element = document.createElement('span') +element.className = 'map-marker map-marker-green' +element.textContent = '12' + +new maplibregl.Marker({ element: element }).setLngLat([13.35, 52.512]).addTo(map) +``` + +Add `.map-marker-dot` for a small dot, or `.map-marker-pin` for a pin that points down. A pin needs `anchor: 'bottom'`, so its tip sits on the place. Change the color with `.map-marker-{color}`. + +```js +const pin = document.createElement('span') +pin.className = 'map-marker map-marker-pin map-marker-red' + +new maplibregl.Marker({ element: pin, anchor: 'bottom' }).setLngLat([13.47, 52.518]).addTo(map) +``` + + + +
+
+
+
12
+
+
+
+
+
+ +You can also write the markers in the page and move them to the map. The preview pages do this: each marker sits in a hidden element with `data-lng` and `data-lat`, and a short loop passes it to MapLibre. + +```html + +``` + +```js +document.querySelectorAll('#map-markers > [data-lng]').forEach(function (item) { + new maplibregl.Marker({ element: item.firstElementChild, anchor: 'bottom' }) + .setLngLat([Number(item.dataset.lng), Number(item.dataset.lat)]) + .addTo(map) +}) +``` + +### Draggable markers + +Pass `draggable: true` to let people move a marker, for example to pick an address. Read the new position in the `dragend` event. + +```js +const marker = new maplibregl.Marker({ element: pin, anchor: 'bottom', draggable: true }).setLngLat([13.405, 52.52]).addTo(map) + +marker.on('dragend', function () { + console.log(marker.getLngLat()) +}) +``` + ### 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. @@ -135,6 +212,44 @@ new maplibregl.Marker({ color: 'var(--tblr-primary)' }) Use `setHTML` instead of `setText` for richer content. Only pass HTML you trust, as it is not escaped. +### Popup card + +For a popup with an image, add the `map-popup-card` class with the `className` option. It removes the padding and sets a fixed width, so the image reaches the edges. Keep the content in a `