diff --git a/assets/css/docusaurus.css b/assets/css/docusaurus.css index 08bb031a..19c06d06 100644 --- a/assets/css/docusaurus.css +++ b/assets/css/docusaurus.css @@ -2,16 +2,13 @@ .app-section { padding: 40px 0; + background-color: #ffffff; } .govuk-template__body.app-homepage .govuk-template__body-inner { background-color: #f4f8fb; } -.app-section { - background-color: #ffffff; -} - .app-section--features { background-color: #f4f8fb; border-bottom: 1px solid #8eb8dc; @@ -45,16 +42,6 @@ } } -/* Black */ -/* .govuk-template--rebranded .govuk-service-navigation:not(.govuk-service-navigation--inverse) .govuk-service-navigation__list__link, -.govuk-template--rebranded .govuk-service-navigation:not(.govuk-service-navigation--inverse) .govuk-service-navigation__toggle { - color: #0b0c0c !important; -} - -.govuk-template--rebranded .govuk-service-navigation:not(.govuk-service-navigation--inverse) .govuk-service-navigation__list__item--active { - border-color: #0b0c0c !important; -} */ - /* Color 100% */ .govuk-template--rebranded .app-masthead .govuk-button--inverse, .govuk-template--rebranded .app-masthead .govuk-button--inverse:visited, @@ -76,29 +63,66 @@ box-shadow: 0 2px 0 #0b5c3e; } -/* Background colour 4% tint */ -/* .govuk-template--rebranded .app-homepage .app-section.app-section--features, -.govuk-template--rebranded .govuk-template__body.app-homepage .govuk-template__body-inner, -.govuk-template--rebranded .govuk-service-navigation, -.govuk-template--rebranded .govuk-footer { - background-color: #f5faf7 !important; -} */ - -/* Border colour 100% */ -/* .govuk-template--rebranded .govuk-footer { - border-color: #008531 !important; -} */ - -/* Border colour 30% tint */ -/* .govuk-template--rebranded .app-homepage .app-section.app-section--features, -.govuk-template--rebranded .govuk-service-navigation { - border-color: #b3dac1 !important; -} */ - /* Border colour 50% tint */ .govuk-template--rebranded .app-homepage .govuk-service-navigation--inverse .govuk-width-container { border-color: #66B683 !important; } +/* Example apps */ +.app-example { + margin-bottom: 20px; +} + +/* Example index cards */ +.app-example-card { + position: relative; + margin-bottom: 30px; + border: 1px solid #b1b4b6; +} + +.app-example-card > img { + display: block; + width: 100%; +} + +.app-example-card__body { + padding: 15px; +} + +.app-prose-scope *:not(.app-no-prose *) .app-example-card__body h2:last-child { + margin-bottom: 0; +} + +.app-example-card__body .govuk-heading-m { + margin-bottom: 0; +} + +.app-example-card .govuk-heading-m a::after { + content: ''; + position: absolute; + inset: 0; +} + +/* Fix sticky footer: the theme gives flex: 1 0 auto to any govuk-width-container + that is a direct child of govuk-template__body-inner, but the phase banner div + also matches that selector and grows incorrectly on short pages. Move the + flex-grow to
instead so only the content area stretches. */ +.govuk-template--rebranded .govuk-template__body-inner > .govuk-width-container { + flex: none; +} + +.govuk-template--rebranded .govuk-main-wrapper { + flex: 1 0 auto; +} + +/* Heading overides */ +.app-prose-scope *:not(.app-no-prose *) .govuk-heading-l { + font-size: 2.25rem; + line-height: 1.1111111111; +} -/* #8eb8dc */ \ No newline at end of file +.app-prose-scope *:not(.app-no-prose *) .govuk-heading-m { + font-size: 1.5rem; + line-height: 1.25; + margin-bottom: 20px; +} \ No newline at end of file diff --git a/demo/DemoMapBasic.js b/demo/DemoMapBasic.js new file mode 100644 index 00000000..ed482273 --- /dev/null +++ b/demo/DemoMapBasic.js @@ -0,0 +1,49 @@ +import { useEffect, useRef } from 'react' +import BrowserOnly from '@docusaurus/BrowserOnly' + +const MAP_STYLE = { + url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json', + attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`, + backgroundColor: '#f5f5f0' +} + +function MapInner () { + const initialised = useRef(false) + + useEffect(() => { + if (initialised.current) { + return + } + initialised.current = true + + Promise.all([ + import('../src/index.js'), + import('../providers/maplibre/src/index.js') + ]).then(([ + { default: InteractiveMap }, + { default: maplibreProvider } + ]) => { + // eslint-disable-next-line no-new + new InteractiveMap('demo-map-basic', { + behaviour: 'inline', + mapProvider: maplibreProvider(), + mapStyle: MAP_STYLE, + center: [-2.9631008,54.432306], + zoom: 15, + containerHeight: '500px', + enableZoomControls: true + }) + }) + }, []) + return
+} + +export default function DemoMapBasic () { + return ( + The map requires JavaScript to be enabled.} + > + {() => } + + ) +} diff --git a/demo/DemoMapButton.js b/demo/DemoMapButton.js index 283e79a3..64c7c6a2 100644 --- a/demo/DemoMapButton.js +++ b/demo/DemoMapButton.js @@ -1,120 +1,48 @@ import { useEffect, useRef } from 'react' import BrowserOnly from '@docusaurus/BrowserOnly' -import useBaseUrl from '@docusaurus/useBaseUrl' -const OS_ATTRIBUTION = `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}` - -function buildMapStyles (thumbnailUrl) { - return [ - { - id: 'outdoor', - label: 'Outdoor', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json', - thumbnail: thumbnailUrl, - attribution: OS_ATTRIBUTION, - backgroundColor: '#f5f5f0' - }, - { - id: 'night', - label: 'Night', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-night/style.json', - thumbnail: thumbnailUrl, - mapColorScheme: 'dark', - appColorScheme: 'dark', - attribution: OS_ATTRIBUTION - }, - { - id: 'deuteranopia', - label: 'Deuteranopia', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-deuteranopia/style.json', - thumbnail: thumbnailUrl, - attribution: OS_ATTRIBUTION - }, - { - id: 'tritanopia', - label: 'Tritanopia', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-tritanopia/style.json', - thumbnail: thumbnailUrl, - attribution: OS_ATTRIBUTION - } - ] +const MAP_STYLE = { + url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json', + attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`, + backgroundColor: '#f5f5f0' } -function MapInner ({ mapStyles }) { +function MapInner () { const initialised = useRef(false) useEffect(() => { - if (initialised.current) return + if (initialised.current) { + return + } initialised.current = true Promise.all([ import('../src/index.js'), - import('../providers/maplibre/src/index.js'), - import('../plugins/search/src/index.js'), - import('../plugins/beta/scale-bar/src/index.js'), - import('../plugins/beta/map-styles/src/index.js') + import('../providers/maplibre/src/index.js') ]).then(([ { default: InteractiveMap }, - { default: maplibreProvider }, - { default: searchPlugin }, - { default: scaleBarPlugin }, - { default: mapStylesPlugin } + { default: maplibreProvider } ]) => { - const nominatimDataset = { - name: 'nominatim', - urlTemplate: 'https://nominatim.openstreetmap.org/search?q={query}&format=json&limit=8&countrycodes=gb', - parseResults: (json, query) => { - if (!Array.isArray(json)) return [] - const esc = q => q.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') - const rx = new RegExp(`(${esc(query)})`, 'i') - return json.map(r => { - const [south, north, west, east] = r.boundingbox.map(Number) - const lon = +r.lon - const lat = +r.lat - const full = r.display_name || '' - const text = full.length > 80 ? `${full.slice(0, 79).trim()}…` : full - const marked = text.replace(rx, '$1') - return { - id: String(r.place_id), - bounds: [west, south, east, north], - point: [lon, lat], - text, - marked, - type: 'nominatim' - } - }) - } - } - // eslint-disable-next-line no-new new InteractiveMap('demo-map-button', { behaviour: 'buttonFirst', mapProvider: maplibreProvider(), - mapStyle: mapStyles[0], - center: [-1.6, 53.1], - zoom: 6, - containerHeight: '500px', - enableZoomControls: true, - plugins: [ - searchPlugin({ customDatasets: [nominatimDataset], showMarker: true }), - scaleBarPlugin({ units: 'metric' }), - mapStylesPlugin({ mapStyles }) - ] + mapStyle: MAP_STYLE, + center: [-2.9631008,54.432306], + zoom: 15, + enableZoomControls: true }) }) }, []) - return
+ return
} export default function DemoMapButton () { - const thumbnailUrl = useBaseUrl('/images/outdoor-map-thumb.jpg') - const mapStyles = buildMapStyles(thumbnailUrl) - return ( The map requires JavaScript to be enabled.} > - {() => } + {() => } ) } diff --git a/demo/DemoMapInline.js b/demo/DemoMapInline.js deleted file mode 100644 index 92ebf1b3..00000000 --- a/demo/DemoMapInline.js +++ /dev/null @@ -1,136 +0,0 @@ -import { useEffect, useRef } from 'react' -import BrowserOnly from '@docusaurus/BrowserOnly' -import useBaseUrl from '@docusaurus/useBaseUrl' - -const OS_ATTRIBUTION = `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}` - -function buildMapStyles (thumbnailUrl) { - return [ - { - id: 'outdoor', - label: 'Outdoor', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json', - thumbnail: thumbnailUrl, - attribution: OS_ATTRIBUTION, - backgroundColor: '#f5f5f0' - }, - { - id: 'night', - label: 'Night', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-night/style.json', - thumbnail: thumbnailUrl, - mapColorScheme: 'dark', - appColorScheme: 'dark', - attribution: OS_ATTRIBUTION - }, - { - id: 'deuteranopia', - label: 'Deuteranopia', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-deuteranopia/style.json', - thumbnail: thumbnailUrl, - attribution: OS_ATTRIBUTION - }, - { - id: 'tritanopia', - label: 'Tritanopia', - url: 'https://labs.os.uk/tiles/styles/open-zoomstack-tritanopia/style.json', - thumbnail: thumbnailUrl, - attribution: OS_ATTRIBUTION - } - ] -} - -function MapInner ({ mapStyles }) { - const initialised = useRef(false) - - useEffect(() => { - if (initialised.current) return - initialised.current = true - - Promise.all([ - import('../src/index.js'), - import('../providers/maplibre/src/index.js'), - import('../plugins/search/src/index.js'), - import('../plugins/beta/scale-bar/src/index.js'), - import('../plugins/beta/map-styles/src/index.js') - ]).then(([ - { default: InteractiveMap }, - { default: maplibreProvider }, - { default: searchPlugin }, - { default: scaleBarPlugin }, - { default: mapStylesPlugin } - ]) => { - const nominatimDataset = { - name: 'nominatim', - urlTemplate: 'https://nominatim.openstreetmap.org/search?q={query}&format=json&limit=8&countrycodes=gb', - parseResults: (json, query) => { - if (!Array.isArray(json)) return [] - const esc = q => q.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') - const rx = new RegExp(`(${esc(query)})`, 'i') - return json.map(r => { - const [south, north, west, east] = r.boundingbox.map(Number) - const lon = +r.lon - const lat = +r.lat - const full = r.display_name || '' - const text = full.length > 80 ? `${full.slice(0, 79).trim()}…` : full - const marked = text.replace(rx, '$1') - return { - id: String(r.place_id), - bounds: [west, south, east, north], - point: [lon, lat], - text, - marked, - type: 'nominatim' - } - }) - } - } - - // eslint-disable-next-line no-new - new InteractiveMap('demo-map-inline', { - behaviour: 'inline', - mapProvider: maplibreProvider(), - mapStyle: mapStyles[0], - center: [-1.6, 53.1], - zoom: 6, - containerHeight: '500px', - enableZoomControls: true, - plugins: [ - searchPlugin({ customDatasets: [nominatimDataset], showMarker: true }), - scaleBarPlugin({ units: 'metric' }), - mapStylesPlugin({ mapStyles }) - ] - }) - }) - }, []) - return ( - <> - - -
- -
- - ) -} - -export default function DemoMapInline () { - const thumbnailUrl = useBaseUrl('/images/outdoor-map-thumb.jpg') - const mapStyles = buildMapStyles(thumbnailUrl) - - return ( - The map requires JavaScript to be enabled.} - > - {() => } - - ) -} diff --git a/demo/DemoMapMarkerPanel.js b/demo/DemoMapMarkerPanel.js new file mode 100644 index 00000000..df690848 --- /dev/null +++ b/demo/DemoMapMarkerPanel.js @@ -0,0 +1,80 @@ +import { useEffect, useRef } from 'react' +import BrowserOnly from '@docusaurus/BrowserOnly' + +const MAP_STYLE = { + url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json', + attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`, + backgroundColor: '#f5f5f0' +} + +const MARKER_COORDS = [-2.9631008, 54.432306] +const PANEL_ID = PANEL_ID + +function MapInner () { + const initialised = useRef(false) + + useEffect(() => { + if (initialised.current) { + return + } + initialised.current = true + + Promise.all([ + import('../src/index.js'), + import('../providers/maplibre/src/index.js'), + import('../plugins/interact/src/index.js') + ]).then(([ + { default: InteractiveMap }, + { default: maplibreProvider }, + { default: createInteractPlugin } + ]) => { + const interactPlugin = createInteractPlugin({ + deselectOnClickOutside: true + }) + + const map = new InteractiveMap('demo-map-marker-panel', { + behaviour: 'inline', + mapProvider: maplibreProvider(), + mapStyle: MAP_STYLE, + center: MARKER_COORDS, + zoom: 15, + containerHeight: '500px', + enableZoomControls: true, + plugins: [interactPlugin] + }) + + map.on('map:ready', () => { + map.addMarker('demo-marker', MARKER_COORDS) + interactPlugin.enable() + + map.addPanel(PANEL_ID, { + label: 'Marker information', + html: '

You selected a marker.

', + mobile: { slot: 'drawer', dismissible: true }, + tablet: { slot: 'left-top', dismissible: true }, + desktop: { slot: 'left-top', dismissible: true } + }) + }) + + map.on('interact:selectionchange', ({ selectedMarkers }) => { + console.log(selectedMarkers) + if (selectedMarkers.length > 0) { + map.showPanel(PANEL_ID) + } + }) + + }) + }, []) + + return
+} + +export default function DemoMapMarkerPanel () { + return ( + The map requires JavaScript to be enabled.} + > + {() => } + + ) +} diff --git a/docs/assets/basic-map.jpg b/docs/assets/basic-map.jpg new file mode 100644 index 00000000..de2b0823 Binary files /dev/null and b/docs/assets/basic-map.jpg differ diff --git a/docs/assets/button-first.jpg b/docs/assets/button-first.jpg new file mode 100644 index 00000000..42b0b4b6 Binary files /dev/null and b/docs/assets/button-first.jpg differ diff --git a/docs/assets/maker-panel.jpg b/docs/assets/maker-panel.jpg new file mode 100644 index 00000000..c7430d35 Binary files /dev/null and b/docs/assets/maker-panel.jpg differ diff --git a/docs/examples.mdx b/docs/examples.mdx deleted file mode 100644 index c8816fae..00000000 --- a/docs/examples.mdx +++ /dev/null @@ -1,70 +0,0 @@ -import DemoMapInline from '../demo/DemoMapInline.js' -import DemoMapButton from '../demo/DemoMapButton.js' - -# Examples - -See [Getting started](getting-started) for installation and full configuration options. - -## Inline map - -Embed an interactive map directly on the page, allowing users to explore and interact with the map without leaving the current context. - - - -```js -import InteractiveMap from '@defra/interactive-map' -import maplibreProvider from '@defra/interactive-map/providers/maplibre' -import searchPlugin from '@defra/interactive-map/plugins/search' -import scaleBarPlugin from '@defra/interactive-map/plugins/scale-bar' -import mapStylesPlugin from '@defra/interactive-map/plugins/map-styles' - -new InteractiveMap('my-map', { - behaviour: 'inline', - mapProvider: maplibreProvider(), - mapStyle: { - url: '/assets/my-map-style.json', - attribution: '© OpenStreetMap contributors' - }, - center: [-1.6, 53.1], - zoom: 6, - containerHeight: '500px', - enableZoomControls: true, - plugins: [ - searchPlugin({ customDatasets: [nominatimDataset], showMarker: true }), - scaleBarPlugin({ units: 'metric' }), - mapStylesPlugin({ mapStyles: [...] }) - ] -}) -``` - -## Button-triggered map - -Trigger the map to show on button press, allowing users to access the map when needed without it taking up space on the page by default. - - - -```js -import InteractiveMap from '@defra/interactive-map' -import maplibreProvider from '@defra/interactive-map/providers/maplibre' -import searchPlugin from '@defra/interactive-map/plugins/search' -import scaleBarPlugin from '@defra/interactive-map/plugins/scale-bar' -import mapStylesPlugin from '@defra/interactive-map/plugins/map-styles' - -new InteractiveMap('my-map', { - behaviour: 'buttonFirst', - mapProvider: maplibreProvider(), - mapStyle: { - url: '/assets/my-map-style.json', - attribution: '© OpenStreetMap contributors' - }, - center: [-1.6, 53.1], - zoom: 6, - containerHeight: '500px', - enableZoomControls: true, - plugins: [ - searchPlugin({ customDatasets: [nominatimDataset], showMarker: true }), - scaleBarPlugin({ units: 'metric' }), - mapStylesPlugin({ mapStyles: [...] }) - ] -}) -``` \ No newline at end of file diff --git a/docs/examples/add-marker-with-panel.mdx b/docs/examples/add-marker-with-panel.mdx new file mode 100644 index 00000000..70c569bf --- /dev/null +++ b/docs/examples/add-marker-with-panel.mdx @@ -0,0 +1,52 @@ +import DemoMapMarkerPanel from '../../demo/DemoMapMarkerPanel.js' + +# Add a marker with a panel + +Add markers to the map and allow users to select them. Selecting a marker fires the `interact:selectionchange` event, which can be used to show a panel with relevant information. + + + +```js +import InteractiveMap from '@defra/interactive-map' +import maplibreProvider from '@defra/interactive-map/providers/maplibre' +import createInteractPlugin from '@defra/interactive-map/plugins/interact' + +const interactPlugin = createInteractPlugin({ + deselectOnClickOutside: true +}) + +const map = new InteractiveMap('my-map', { + behaviour: 'inline', + mapProvider: maplibreProvider(), + mapStyle: { + url: 'https://your-tile-url/style.json', + attribution: 'Your tile attribution' + }, + center: [-2.96, 54.43], + zoom: 15, + containerHeight: '500px', + enableZoomControls: true, + plugins: [interactPlugin] +}) + +map.on('map:ready', () => { + map.addMarker('my-marker', [-2.96, 54.43]) + interactPlugin.enable() + + map.addPanel('marker-info', { + label: 'Marker information', + html: '

You selected a marker.

', + mobile: { slot: 'drawer', dismissible: true }, + tablet: { slot: 'left-top', dismissible: true }, + desktop: { slot: 'left-top', dismissible: true } + }) +}) + +map.on('interact:selectionchange', ({ selectedMarkers }) => { + if (selectedMarkers.length > 0) { + map.showPanel('marker-info') + } else { + map.hidePanel('marker-info') + } +}) +``` diff --git a/docs/examples/basic-map.mdx b/docs/examples/basic-map.mdx new file mode 100644 index 00000000..53ecad39 --- /dev/null +++ b/docs/examples/basic-map.mdx @@ -0,0 +1,25 @@ +import DemoMapBasic from '../../demo/DemoMapBasic.js' + +# Basic map + +Embed an interactive map directly on the page, allowing users to explore and interact with the map without leaving the current context. + + + +```js +import InteractiveMap from '@defra/interactive-map' +import maplibreProvider from '@defra/interactive-map/providers/maplibre' + +new InteractiveMap('my-map', { + behaviour: 'inline', + mapProvider: maplibreProvider(), + mapStyle: { + url: 'https://your-tile-url/style.json', + attribution: 'Your tile attribution' + }, + center: [-1.6, 53.1], + zoom: 6, + containerHeight: '500px', + enableZoomControls: true +}) +``` diff --git a/docs/examples/button-map.mdx b/docs/examples/button-map.mdx new file mode 100644 index 00000000..c130bdaa --- /dev/null +++ b/docs/examples/button-map.mdx @@ -0,0 +1,25 @@ +import DemoMapButton from '../../demo/DemoMapButton.js' + +# Button-triggered map + +Trigger the map to show on button press, allowing users to access the map when needed without it taking up space on the page by default. + + + +```js +import InteractiveMap from '@defra/interactive-map' +import maplibreProvider from '@defra/interactive-map/providers/maplibre' + +new InteractiveMap('my-map', { + behaviour: 'buttonFirst', + mapProvider: maplibreProvider(), + mapStyle: { + url: 'https://your-tile-url/style.json', + attribution: 'Your tile attribution' + }, + center: [-1.6, 53.1], + zoom: 6, + containerHeight: '500px', + enableZoomControls: true +}) +``` diff --git a/docs/examples/index.mdx b/docs/examples/index.mdx new file mode 100644 index 00000000..0622d033 --- /dev/null +++ b/docs/examples/index.mdx @@ -0,0 +1,49 @@ +import useBaseUrl from '@docusaurus/useBaseUrl' +import basicMapImg from '../assets/basic-map.jpg' +import buttonFirstImg from '../assets/button-first.jpg' +import makerPanelImg from '../assets/maker-panel.jpg' + +export function ExampleCards() { + const basicHref = useBaseUrl('/examples/basic-map') + const buttonHref = useBaseUrl('/examples/button-map') + const interactHref = useBaseUrl('/examples/add-marker-with-panel') + + return ( +
+
+
+ +
+

+ Basic map +

+
+
+
+
+
+ + +
+
+
+ +
+
+ ) +} + +# Examples + + diff --git a/docs/index.mdx b/docs/index.mdx index 6d472ebe..5e708416 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -26,7 +26,7 @@ import Link from '@docusaurus/Link';

Getting Started

How to install and configure the component. Covers setup, initialisation options, and integration with common build tools.

Examples

-

Live interactive demos showing the component in action, including inline maps, button-triggered maps, and various configuration options.

+

Live interactive demos showing the component in action, including basic maps, button-triggered maps, and various configuration options.

API Reference

Full reference for all configuration options, events, methods, and plugin interfaces exposed by the component.

Plugins

diff --git a/docusaurus.config.cjs b/docusaurus.config.cjs index 1aebb713..cd10c010 100644 --- a/docusaurus.config.cjs +++ b/docusaurus.config.cjs @@ -84,7 +84,15 @@ const config = { { text: 'GOV.UK Prototype kit', href: '/getting-started#govuk-prototype-kit-plugin' } ], }, - { text: 'Examples', href: '/examples', sidebar: 'auto' }, + { + text: 'Examples', + href: '/examples', + sidebar: [ + { text: 'Basic map', href: '/examples/basic-map' }, + { text: 'Button-triggered map', href: '/examples/button-map' }, + { text: 'Add a marker with a panel', href: '/examples/add-marker-with-panel' }, + ], + }, { text: 'API', href: '/api', diff --git a/src/App/layout/layout.module.scss b/src/App/layout/layout.module.scss index 90fe11dc..50eafb88 100755 --- a/src/App/layout/layout.module.scss +++ b/src/App/layout/layout.module.scss @@ -436,6 +436,7 @@ // Inline border .im-o-app--inline { border: var(--app-border-width) solid var(--app-border-color); + box-sizing: border-box; } // Hide containers when keyboard hint is visible