From 4110f3e0ef81b7e3bb618724464ae0b605d19411 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 21 May 2024 13:36:37 +0200 Subject: [PATCH 01/20] Basic zoom control story --- package-lock.json | 1 + package.json | 1 + src/pages/BaseLayer/index.tsx | 15 ++++++--- src/pages/ZoomControl/index.tsx | 37 +++++++++++++++++++++ src/pages/ZoomControl/styles.module.css | 5 +++ src/stories/pages/Controls/index.stories.ts | 23 +++++++++++++ 6 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/pages/ZoomControl/index.tsx create mode 100644 src/pages/ZoomControl/styles.module.css create mode 100644 src/stories/pages/Controls/index.stories.ts diff --git a/package-lock.json b/package-lock.json index 1355bdb..fd2d261 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@amsterdam/design-system-assets": "^0.2.0", "@amsterdam/design-system-css": "^0.8.0", "@amsterdam/design-system-react": "^0.8.0", + "@amsterdam/design-system-react-icons": "^0.1.12", "@amsterdam/design-system-tokens": "^0.8.0", "leaflet": "^1.9.4", "proj4": "^2.11.0", diff --git a/package.json b/package.json index f01b92a..31d2de9 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@amsterdam/design-system-assets": "^0.2.0", "@amsterdam/design-system-css": "^0.8.0", "@amsterdam/design-system-react": "^0.8.0", + "@amsterdam/design-system-react-icons": "^0.1.12", "@amsterdam/design-system-tokens": "^0.8.0", "leaflet": "^1.9.4", "proj4": "^2.11.0", diff --git a/src/pages/BaseLayer/index.tsx b/src/pages/BaseLayer/index.tsx index 063a7c2..85bf7f6 100644 --- a/src/pages/BaseLayer/index.tsx +++ b/src/pages/BaseLayer/index.tsx @@ -5,7 +5,12 @@ import 'leaflet/dist/leaflet.css'; import getCrsRd from '@/utils/getCrsRd'; import styles from './styles.module.css'; -const BaseLayer: FunctionComponent = () => { +const BaseLayer: FunctionComponent = ({ + center, + zoom, + minZoom, + maxZoom, +}: L.MapOptions) => { const containerRef = useRef(null); // Use state instead of a ref for storing the Leaflet map object otherwise you may run into DOM issues when React StrictMode is enabled @@ -21,8 +26,8 @@ const BaseLayer: FunctionComponent = () => { } const map = new L.Map(containerRef.current, { - center: L.latLng([52.370216, 4.895168]), - zoom: 12, + center: center ?? L.latLng([52.370216, 4.895168]), + zoom: zoom ?? 12, layers: [ L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { attribution: '', @@ -31,8 +36,8 @@ const BaseLayer: FunctionComponent = () => { }), ], zoomControl: false, - maxZoom: 16, - minZoom: 3, + maxZoom: maxZoom ?? 16, + minZoom: minZoom ?? 3, // Ensure proper handling for Rijksdriehoekcoördinaten crs: getCrsRd(), // Prevent the user browsing too far outside Amsterdam otherwise the map will render blank greyspace. Amsterdam tile layer only supports Amsterdam and the immediate surrounding areas diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx new file mode 100644 index 0000000..4c9fad5 --- /dev/null +++ b/src/pages/ZoomControl/index.tsx @@ -0,0 +1,37 @@ +import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; +import { + EnlargeIcon, + MinimiseIcon, +} from '@amsterdam/design-system-react-icons'; + +import styles from './styles.module.css'; + +const ZoomControl = () => { + // const handleZoomInClick = () => { + // if (mapRef.current) { + // mapRef.current?.setZoom(mapRef.current.getZoom() + 1); + // } + // }; + // const handleZoomOutClick = () => { + // if (mapRef.current) { + // mapRef.current?.setZoom(mapRef.current.getZoom() - 1); + // } + // }; + + return ( + <> +
+ + +
+ + ); +}; + +export default ZoomControl; diff --git a/src/pages/ZoomControl/styles.module.css b/src/pages/ZoomControl/styles.module.css new file mode 100644 index 0000000..cd4cac9 --- /dev/null +++ b/src/pages/ZoomControl/styles.module.css @@ -0,0 +1,5 @@ +.container { + display: inline-flex; + flex-direction: column; + gap: var(--ams-space-inside-xs, 10px); +} diff --git a/src/stories/pages/Controls/index.stories.ts b/src/stories/pages/Controls/index.stories.ts new file mode 100644 index 0000000..cf08a08 --- /dev/null +++ b/src/stories/pages/Controls/index.stories.ts @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import ZoomControl from '@/pages/ZoomControl'; + +import '@amsterdam/design-system-tokens/dist/index.css'; +import '@amsterdam/design-system-assets/font/index.css'; +import '@amsterdam/design-system-css/dist/index.css'; + +const meta = { + title: 'React/ZoomControl', + component: ZoomControl, + parameters: { + // layout: 'fullscreen', + // options: { + // panelPosition: 'bottom', + // bottomPanelHeight: 0, + // }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Buttons: Story = {}; From bf5e502abbd573f5f4d44931349efd55ea768d03 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 21 May 2024 13:36:37 +0200 Subject: [PATCH 02/20] Basic zoom control story --- package-lock.json | 1 + package.json | 1 + src/pages/BaseLayer/index.tsx | 15 ++++++--- src/pages/ZoomControl/index.tsx | 37 +++++++++++++++++++++ src/pages/ZoomControl/styles.module.css | 5 +++ src/stories/pages/Controls/index.stories.ts | 23 +++++++++++++ 6 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/pages/ZoomControl/index.tsx create mode 100644 src/pages/ZoomControl/styles.module.css create mode 100644 src/stories/pages/Controls/index.stories.ts diff --git a/package-lock.json b/package-lock.json index e1e6b68..4175912 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@amsterdam/design-system-assets": "^0.2.0", "@amsterdam/design-system-css": "^0.8.0", "@amsterdam/design-system-react": "^0.8.0", + "@amsterdam/design-system-react-icons": "^0.1.12", "@amsterdam/design-system-tokens": "^0.8.0", "leaflet": "^1.9.4", "proj4": "^2.11.0", diff --git a/package.json b/package.json index 99b9905..6f9f992 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@amsterdam/design-system-assets": "^0.2.0", "@amsterdam/design-system-css": "^0.8.0", "@amsterdam/design-system-react": "^0.8.0", + "@amsterdam/design-system-react-icons": "^0.1.12", "@amsterdam/design-system-tokens": "^0.8.0", "leaflet": "^1.9.4", "proj4": "^2.11.0", diff --git a/src/pages/BaseLayer/index.tsx b/src/pages/BaseLayer/index.tsx index 063a7c2..85bf7f6 100644 --- a/src/pages/BaseLayer/index.tsx +++ b/src/pages/BaseLayer/index.tsx @@ -5,7 +5,12 @@ import 'leaflet/dist/leaflet.css'; import getCrsRd from '@/utils/getCrsRd'; import styles from './styles.module.css'; -const BaseLayer: FunctionComponent = () => { +const BaseLayer: FunctionComponent = ({ + center, + zoom, + minZoom, + maxZoom, +}: L.MapOptions) => { const containerRef = useRef(null); // Use state instead of a ref for storing the Leaflet map object otherwise you may run into DOM issues when React StrictMode is enabled @@ -21,8 +26,8 @@ const BaseLayer: FunctionComponent = () => { } const map = new L.Map(containerRef.current, { - center: L.latLng([52.370216, 4.895168]), - zoom: 12, + center: center ?? L.latLng([52.370216, 4.895168]), + zoom: zoom ?? 12, layers: [ L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { attribution: '', @@ -31,8 +36,8 @@ const BaseLayer: FunctionComponent = () => { }), ], zoomControl: false, - maxZoom: 16, - minZoom: 3, + maxZoom: maxZoom ?? 16, + minZoom: minZoom ?? 3, // Ensure proper handling for Rijksdriehoekcoördinaten crs: getCrsRd(), // Prevent the user browsing too far outside Amsterdam otherwise the map will render blank greyspace. Amsterdam tile layer only supports Amsterdam and the immediate surrounding areas diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx new file mode 100644 index 0000000..4c9fad5 --- /dev/null +++ b/src/pages/ZoomControl/index.tsx @@ -0,0 +1,37 @@ +import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; +import { + EnlargeIcon, + MinimiseIcon, +} from '@amsterdam/design-system-react-icons'; + +import styles from './styles.module.css'; + +const ZoomControl = () => { + // const handleZoomInClick = () => { + // if (mapRef.current) { + // mapRef.current?.setZoom(mapRef.current.getZoom() + 1); + // } + // }; + // const handleZoomOutClick = () => { + // if (mapRef.current) { + // mapRef.current?.setZoom(mapRef.current.getZoom() - 1); + // } + // }; + + return ( + <> +
+ + +
+ + ); +}; + +export default ZoomControl; diff --git a/src/pages/ZoomControl/styles.module.css b/src/pages/ZoomControl/styles.module.css new file mode 100644 index 0000000..cd4cac9 --- /dev/null +++ b/src/pages/ZoomControl/styles.module.css @@ -0,0 +1,5 @@ +.container { + display: inline-flex; + flex-direction: column; + gap: var(--ams-space-inside-xs, 10px); +} diff --git a/src/stories/pages/Controls/index.stories.ts b/src/stories/pages/Controls/index.stories.ts new file mode 100644 index 0000000..cf08a08 --- /dev/null +++ b/src/stories/pages/Controls/index.stories.ts @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import ZoomControl from '@/pages/ZoomControl'; + +import '@amsterdam/design-system-tokens/dist/index.css'; +import '@amsterdam/design-system-assets/font/index.css'; +import '@amsterdam/design-system-css/dist/index.css'; + +const meta = { + title: 'React/ZoomControl', + component: ZoomControl, + parameters: { + // layout: 'fullscreen', + // options: { + // panelPosition: 'bottom', + // bottomPanelHeight: 0, + // }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Buttons: Story = {}; From ca4d4c52a2b32fb8c47a5b126d89b35f17c7e5fe Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 21 May 2024 15:01:14 +0200 Subject: [PATCH 03/20] chore: Update ZoomControl icons size to "level-5" --- src/pages/ZoomControl/index.tsx | 4 +-- src/stories/pages/ZoomControl/index.mdx | 26 +++++++++++++++++++ .../index.stories.ts | 7 ----- 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 src/stories/pages/ZoomControl/index.mdx rename src/stories/pages/{Controls => ZoomControl}/index.stories.ts (76%) diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx index 4c9fad5..efcc52f 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControl/index.tsx @@ -23,11 +23,11 @@ const ZoomControl = () => {
diff --git a/src/stories/pages/ZoomControl/index.mdx b/src/stories/pages/ZoomControl/index.mdx new file mode 100644 index 0000000..ca484f0 --- /dev/null +++ b/src/stories/pages/ZoomControl/index.mdx @@ -0,0 +1,26 @@ +import { Canvas, Meta, Source, Story } from '@storybook/blocks'; +import * as ZoomControlStories from './index.stories'; +import ZoomControl from '@/pages/ZoomControl/index?raw'; +import styles from '@/pages/ZoomControl/styles.module.css?raw'; + + + +# Amsterdam ZoomControl + +# Description + +The container for the zoom control. + +## Usage + +### React Components + +#### 1. ZoomControl.tsx + + + +### CSS Styling + +#### 1. styles.module.css + + diff --git a/src/stories/pages/Controls/index.stories.ts b/src/stories/pages/ZoomControl/index.stories.ts similarity index 76% rename from src/stories/pages/Controls/index.stories.ts rename to src/stories/pages/ZoomControl/index.stories.ts index cf08a08..5fc1c72 100644 --- a/src/stories/pages/Controls/index.stories.ts +++ b/src/stories/pages/ZoomControl/index.stories.ts @@ -8,13 +8,6 @@ import '@amsterdam/design-system-css/dist/index.css'; const meta = { title: 'React/ZoomControl', component: ZoomControl, - parameters: { - // layout: 'fullscreen', - // options: { - // panelPosition: 'bottom', - // bottomPanelHeight: 0, - // }, - }, } satisfies Meta; export default meta; From a41012355779f91587cd9400238697a26d2b18fc Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 21 May 2024 15:02:01 +0200 Subject: [PATCH 04/20] TODO Pass ref to ZoomControl component --- src/pages/ZoomControl/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx index efcc52f..c8f025d 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControl/index.tsx @@ -7,6 +7,7 @@ import { import styles from './styles.module.css'; const ZoomControl = () => { + // TODO pass ref to this component // const handleZoomInClick = () => { // if (mapRef.current) { // mapRef.current?.setZoom(mapRef.current.getZoom() + 1); From d0266fdcb95b6a49db7930dfd8a7edb3b0b37c30 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 21 May 2024 16:32:11 +0200 Subject: [PATCH 05/20] Buttons on a map and a test --- src/pages/ZoomControl/index.test.tsx | 13 +++ src/pages/ZoomControl/index.tsx | 86 ++++++++++++++----- src/pages/ZoomControl/styles.module.css | 11 +++ src/stories/pages/ZoomControl/index.mdx | 19 ++-- .../{index.stories.ts => index.stories.tsx} | 1 + 5 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 src/pages/ZoomControl/index.test.tsx rename src/stories/pages/ZoomControl/{index.stories.ts => index.stories.tsx} (91%) diff --git a/src/pages/ZoomControl/index.test.tsx b/src/pages/ZoomControl/index.test.tsx new file mode 100644 index 0000000..2dc78e0 --- /dev/null +++ b/src/pages/ZoomControl/index.test.tsx @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import ZoomControl from '.'; + +describe('ZoomControl', () => { + it('renders the component', () => { + render(); + const ZoomInbutton = screen.getByRole('button', { name: /Zoom in/i }); + const ZoomOutbutton = screen.getByRole('button', { name: /Zoom out/i }); + expect(ZoomInbutton).toBeInTheDocument(); + expect(ZoomOutbutton).toBeInTheDocument(); + }); +}); diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx index c8f025d..701163d 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControl/index.tsx @@ -1,4 +1,7 @@ import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import L from 'leaflet'; +import getCrsRd from '@/utils/getCrsRd'; import { EnlargeIcon, MinimiseIcon, @@ -6,30 +9,71 @@ import { import styles from './styles.module.css'; -const ZoomControl = () => { - // TODO pass ref to this component - // const handleZoomInClick = () => { - // if (mapRef.current) { - // mapRef.current?.setZoom(mapRef.current.getZoom() + 1); - // } - // }; - // const handleZoomOutClick = () => { - // if (mapRef.current) { - // mapRef.current?.setZoom(mapRef.current.getZoom() - 1); - // } - // }; +const ZoomControl: FunctionComponent = () => { + const containerRef = useRef(null); + const [mapInstance, setMapInstance] = useState(null); + const createdMapInstance = useRef(false); + + const handleZoomInClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() + 1); + } + }; + const handleZoomOutClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() - 1); + } + }; + + // Set the Leaflet map and Amsterdam base layer + useEffect(() => { + if (containerRef.current === null || createdMapInstance.current !== false) { + return; + } + + const map = new L.Map(containerRef.current, { + center: L.latLng([52.370216, 4.895168]), + zoom: 12, + layers: [ + L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { + attribution: '', + subdomains: ['t1', 't2', 't3', 't4'], + tms: true, + }), + ], + zoomControl: false, + maxZoom: 16, + minZoom: 3, + crs: getCrsRd(), + maxBounds: [ + [52.25168, 4.64034], + [52.50536, 5.10737], + ], + }); + + map.attributionControl.setPrefix(false); + + createdMapInstance.current = true; + setMapInstance(map); + + return () => { + if (mapInstance) mapInstance.remove(); + }; + }, []); return ( <> -
- - +
+
+ + +
); diff --git a/src/pages/ZoomControl/styles.module.css b/src/pages/ZoomControl/styles.module.css index cd4cac9..74422e1 100644 --- a/src/pages/ZoomControl/styles.module.css +++ b/src/pages/ZoomControl/styles.module.css @@ -1,5 +1,16 @@ .container { + height: 100%; + position: relative; + width: 100%; +} + +.buttons { + bottom: var(--ams-space-inside-xs, 10px); display: inline-flex; flex-direction: column; gap: var(--ams-space-inside-xs, 10px); + position: absolute; + right: var(--ams-space-inside-xs, 10px); + /* TODO Temporary fix, need to hook into control layer */ + z-index: 999; } diff --git a/src/stories/pages/ZoomControl/index.mdx b/src/stories/pages/ZoomControl/index.mdx index ca484f0..e9b0760 100644 --- a/src/stories/pages/ZoomControl/index.mdx +++ b/src/stories/pages/ZoomControl/index.mdx @@ -13,11 +13,20 @@ The container for the zoom control. ## Usage -### React Components - -#### 1. ZoomControl.tsx - - +#### ZoomControl width Design System buttons + +```jsx +
+ + +
+``` ### CSS Styling diff --git a/src/stories/pages/ZoomControl/index.stories.ts b/src/stories/pages/ZoomControl/index.stories.tsx similarity index 91% rename from src/stories/pages/ZoomControl/index.stories.ts rename to src/stories/pages/ZoomControl/index.stories.tsx index 5fc1c72..d8a7384 100644 --- a/src/stories/pages/ZoomControl/index.stories.ts +++ b/src/stories/pages/ZoomControl/index.stories.tsx @@ -4,6 +4,7 @@ import ZoomControl from '@/pages/ZoomControl'; import '@amsterdam/design-system-tokens/dist/index.css'; import '@amsterdam/design-system-assets/font/index.css'; import '@amsterdam/design-system-css/dist/index.css'; +import { Story } from '@storybook/blocks'; const meta = { title: 'React/ZoomControl', From b77bc9462d0ea05122caa076f7556e7585ff82f8 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 21 May 2024 21:05:22 +0200 Subject: [PATCH 06/20] Fullscreen page pattern static --- src/stories/pages/BaseLayer/index.stories.ts | 6 +- .../pages/Fullscreen/FullscreenPage.tsx | 210 ++++++++++++++++++ .../pages/Fullscreen/index.stories.tsx | 21 ++ src/stories/pages/ZoomControl/index.mdx | 18 +- .../pages/ZoomControl/index.stories.tsx | 11 +- 5 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/stories/pages/Fullscreen/FullscreenPage.tsx create mode 100644 src/stories/pages/Fullscreen/index.stories.tsx diff --git a/src/stories/pages/BaseLayer/index.stories.ts b/src/stories/pages/BaseLayer/index.stories.ts index 6638ec1..d4b008d 100644 --- a/src/stories/pages/BaseLayer/index.stories.ts +++ b/src/stories/pages/BaseLayer/index.stories.ts @@ -16,4 +16,8 @@ const meta = { export default meta; type Story = StoryObj; -export const Base: Story = {}; +export const Base: Story = { + args: { + zoom: 15, + }, +}; diff --git a/src/stories/pages/Fullscreen/FullscreenPage.tsx b/src/stories/pages/Fullscreen/FullscreenPage.tsx new file mode 100644 index 0000000..7660518 --- /dev/null +++ b/src/stories/pages/Fullscreen/FullscreenPage.tsx @@ -0,0 +1,210 @@ +import { + AspectRatio, + Column, + Footer, + Grid, + Header, + Heading, + Link, + LinkList, + Overlap, + PageMenu, + Paragraph, + Screen, + SkipLink, + VisuallyHidden, +} from '@amsterdam/design-system-react'; +import { ChattingIcon, PhoneIcon } from '@amsterdam/design-system-react-icons'; +import BaseLayer from '../../../pages/BaseLayer'; + +export const FullscreenPage = () => { + return ( + <> + Direct naar inhoud + + + +
Menu} + /> + + + + + + + + + +
+ + + Colofon + + + + + + Contact + + + Heeft u een vraag en kunt u het antwoord niet vinden op deze + site? Neem dan contact met ons op. + + + + Contactformulier + + + Adressen en openingstijden + + + Bel 14 020 + + + + + + + + Volg de gemeente + + + + Nieuwsbrief Amsterdam + + + Twitter + + + Facebook + + + Instagram + + + LinkedIn + + + Mastodon + + + YouTube + + + Werkenbij + + + + + + + + + Kalender + + + Van buurtactiviteiten tot inspraakavonden. Wat organiseert + de gemeente voor u? Kijk op{' '} + + Kalender Amsterdam + + . + + + + + Uit in Amsterdam + + + Benieuwd wat er allemaal te doen is in de stad? Op{' '} + + Iamsterdam.com + {' '} + vindt u de beste tips op het gebied van cultuur, uitgaan + en evenementen. + + + + + + +
+ + + + Home + Zoeken + Nieuws + Burgerzaken + Kunst en cultuur + Projecten + Project + Parkeren + + + + + + ); +}; diff --git a/src/stories/pages/Fullscreen/index.stories.tsx b/src/stories/pages/Fullscreen/index.stories.tsx new file mode 100644 index 0000000..102f85b --- /dev/null +++ b/src/stories/pages/Fullscreen/index.stories.tsx @@ -0,0 +1,21 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +import { Meta, StoryObj } from '@storybook/react'; +import { FullscreenPage } from './FullscreenPage'; + +const meta = { + title: 'Docs/Patterns/Fullscreen', + component: FullscreenPage, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; + +export const Default: StoryObj = { + render: () => , +}; diff --git a/src/stories/pages/ZoomControl/index.mdx b/src/stories/pages/ZoomControl/index.mdx index e9b0760..a83756b 100644 --- a/src/stories/pages/ZoomControl/index.mdx +++ b/src/stories/pages/ZoomControl/index.mdx @@ -13,7 +13,7 @@ The container for the zoom control. ## Usage -#### ZoomControl width Design System buttons +#### ZoomControl with Design System buttons ```jsx
@@ -33,3 +33,19 @@ The container for the zoom control. #### 1. styles.module.css + +### JavaScript + +```js +const handleZoomInClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() + 1); + } +}; + +const handleZoomOutClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() - 1); + } +}; +``` diff --git a/src/stories/pages/ZoomControl/index.stories.tsx b/src/stories/pages/ZoomControl/index.stories.tsx index d8a7384..7873b33 100644 --- a/src/stories/pages/ZoomControl/index.stories.tsx +++ b/src/stories/pages/ZoomControl/index.stories.tsx @@ -9,9 +9,18 @@ import { Story } from '@storybook/blocks'; const meta = { title: 'React/ZoomControl', component: ZoomControl, + args: { + zoom: 12, + minZoom: 3, + maxZoom: 16, + }, } satisfies Meta; export default meta; type Story = StoryObj; -export const Buttons: Story = {}; +export const Buttons: Story = { + args: { + zoom: 14, + }, +}; From b9d978f53c0af61094ffb99ddfdeed1957189ce1 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 09:29:13 +0200 Subject: [PATCH 07/20] Testing a full page --- src/pages/Fullscreen/FullscreenPage.tsx | 51 +++++ src/pages/Fullscreen/FullscreenPageFooter.tsx | 187 ++++++++++++++++ src/pages/Fullscreen/FullscreenPageHeader.tsx | 13 ++ src/pages/Fullscreen/index.ts | 3 + src/pages/ZoomControl/index.tsx | 2 + src/pages/ZoomControl/styles.module.css | 6 +- .../pages/Fullscreen/FullscreenPage.tsx | 210 ------------------ .../pages/Fullscreen/index.stories.tsx | 24 +- 8 files changed, 280 insertions(+), 216 deletions(-) create mode 100644 src/pages/Fullscreen/FullscreenPage.tsx create mode 100644 src/pages/Fullscreen/FullscreenPageFooter.tsx create mode 100644 src/pages/Fullscreen/FullscreenPageHeader.tsx create mode 100644 src/pages/Fullscreen/index.ts delete mode 100644 src/stories/pages/Fullscreen/FullscreenPage.tsx diff --git a/src/pages/Fullscreen/FullscreenPage.tsx b/src/pages/Fullscreen/FullscreenPage.tsx new file mode 100644 index 0000000..eaa48c3 --- /dev/null +++ b/src/pages/Fullscreen/FullscreenPage.tsx @@ -0,0 +1,51 @@ +import { + AspectRatio, + Overlap, + Screen, + SkipLink, +} from '@amsterdam/design-system-react'; +import { + Dispatch, + ReactElement, + SetStateAction, + createContext, + useState, +} from 'react'; +import ZoomControl from '../ZoomControl'; + +export type FullscreenPageProps = { + header?: ReactElement; + footer?: ReactElement; +}; + +interface MapContextType { + map: string; + setMap: Dispatch>; +} + +// Create the context with the correct type and a default value +export const MapContext = createContext(null); + +export const FullscreenPage = ({ header, footer }: FullscreenPageProps) => { + const [map, setMap] = useState('test'); + + return ( + <> + Direct naar inhoud + + {header} + + + + + {/* */} + + + + + + {footer} + + + ); +}; diff --git a/src/pages/Fullscreen/FullscreenPageFooter.tsx b/src/pages/Fullscreen/FullscreenPageFooter.tsx new file mode 100644 index 0000000..511e4f3 --- /dev/null +++ b/src/pages/Fullscreen/FullscreenPageFooter.tsx @@ -0,0 +1,187 @@ +import { + Column, + Footer, + Grid, + Heading, + Link, + LinkList, + PageMenu, + Paragraph, + VisuallyHidden, +} from '@amsterdam/design-system-react'; +import { ChattingIcon, PhoneIcon } from '@amsterdam/design-system-react-icons'; + +export const FullscreenPageFooter = () => { + return ( + <> +
+ + + Colofon + + + + + + Contact + + + Heeft u een vraag en kunt u het antwoord niet vinden op deze + site? Neem dan contact met ons op. + + + + Contactformulier + + + Adressen en openingstijden + + + Bel 14 020 + + + + + + + + Volg de gemeente + + + + Nieuwsbrief Amsterdam + + + Twitter + + + Facebook + + + Instagram + + + LinkedIn + + + Mastodon + + + YouTube + + + Werkenbij + + + + + + + + + Kalender + + + Van buurtactiviteiten tot inspraakavonden. Wat organiseert + de gemeente voor u? Kijk op{' '} + + Kalender Amsterdam + + . + + + + + Uit in Amsterdam + + + Benieuwd wat er allemaal te doen is in de stad? Op{' '} + + Iamsterdam.com + {' '} + vindt u de beste tips op het gebied van cultuur, uitgaan en + evenementen. + + + + + + +
+ + + + Home + Zoeken + Nieuws + Burgerzaken + Kunst en cultuur + Projecten + Project + Parkeren + + + + + ); +}; diff --git a/src/pages/Fullscreen/FullscreenPageHeader.tsx b/src/pages/Fullscreen/FullscreenPageHeader.tsx new file mode 100644 index 0000000..bb40b89 --- /dev/null +++ b/src/pages/Fullscreen/FullscreenPageHeader.tsx @@ -0,0 +1,13 @@ +import { Grid, Header } from '@amsterdam/design-system-react'; + +export const FullscreenPageHeader = () => { + return ( + + +
Menu} + /> + + + ); +}; diff --git a/src/pages/Fullscreen/index.ts b/src/pages/Fullscreen/index.ts new file mode 100644 index 0000000..57cf3cb --- /dev/null +++ b/src/pages/Fullscreen/index.ts @@ -0,0 +1,3 @@ +export { FullscreenPage } from './FullscreenPage'; +export { FullscreenPageHeader } from './FullscreenPageHeader'; +export { FullscreenPageFooter } from './FullscreenPageFooter'; diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx index 701163d..70f09eb 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControl/index.tsx @@ -42,6 +42,8 @@ const ZoomControl: FunctionComponent = () => { }), ], zoomControl: false, + /** Disable scroll wheel zoom when the (custom) zoom control is used */ + scrollWheelZoom: false, maxZoom: 16, minZoom: 3, crs: getCrsRd(), diff --git a/src/pages/ZoomControl/styles.module.css b/src/pages/ZoomControl/styles.module.css index 74422e1..f696033 100644 --- a/src/pages/ZoomControl/styles.module.css +++ b/src/pages/ZoomControl/styles.module.css @@ -5,12 +5,12 @@ } .buttons { - bottom: var(--ams-space-inside-xs, 10px); + bottom: var(--ams-space-inside-lg, 24px); display: inline-flex; flex-direction: column; - gap: var(--ams-space-inside-xs, 10px); + gap: var(--ams-space-inside-xs, 8px); position: absolute; - right: var(--ams-space-inside-xs, 10px); + right: var(--ams-space-inside-lg, 24px); /* TODO Temporary fix, need to hook into control layer */ z-index: 999; } diff --git a/src/stories/pages/Fullscreen/FullscreenPage.tsx b/src/stories/pages/Fullscreen/FullscreenPage.tsx deleted file mode 100644 index 7660518..0000000 --- a/src/stories/pages/Fullscreen/FullscreenPage.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import { - AspectRatio, - Column, - Footer, - Grid, - Header, - Heading, - Link, - LinkList, - Overlap, - PageMenu, - Paragraph, - Screen, - SkipLink, - VisuallyHidden, -} from '@amsterdam/design-system-react'; -import { ChattingIcon, PhoneIcon } from '@amsterdam/design-system-react-icons'; -import BaseLayer from '../../../pages/BaseLayer'; - -export const FullscreenPage = () => { - return ( - <> - Direct naar inhoud - - - -
Menu} - /> - - - - - - - - - -
- - - Colofon - - - - - - Contact - - - Heeft u een vraag en kunt u het antwoord niet vinden op deze - site? Neem dan contact met ons op. - - - - Contactformulier - - - Adressen en openingstijden - - - Bel 14 020 - - - - - - - - Volg de gemeente - - - - Nieuwsbrief Amsterdam - - - Twitter - - - Facebook - - - Instagram - - - LinkedIn - - - Mastodon - - - YouTube - - - Werkenbij - - - - - - - - - Kalender - - - Van buurtactiviteiten tot inspraakavonden. Wat organiseert - de gemeente voor u? Kijk op{' '} - - Kalender Amsterdam - - . - - - - - Uit in Amsterdam - - - Benieuwd wat er allemaal te doen is in de stad? Op{' '} - - Iamsterdam.com - {' '} - vindt u de beste tips op het gebied van cultuur, uitgaan - en evenementen. - - - - - - -
- - - - Home - Zoeken - Nieuws - Burgerzaken - Kunst en cultuur - Projecten - Project - Parkeren - - - - - - ); -}; diff --git a/src/stories/pages/Fullscreen/index.stories.tsx b/src/stories/pages/Fullscreen/index.stories.tsx index 102f85b..0669abb 100644 --- a/src/stories/pages/Fullscreen/index.stories.tsx +++ b/src/stories/pages/Fullscreen/index.stories.tsx @@ -4,18 +4,36 @@ */ import { Meta, StoryObj } from '@storybook/react'; -import { FullscreenPage } from './FullscreenPage'; +import { + FullscreenPage, + FullscreenPageFooter, + FullscreenPageHeader, +} from '../../../pages/Fullscreen'; const meta = { - title: 'Docs/Patterns/Fullscreen', + title: 'Patterns/Fullscreen', component: FullscreenPage, parameters: { layout: 'fullscreen', }, + args: { + header: , + footer: , + }, + argTypes: { + header: { control: { disable: true } }, + footer: { control: { disable: true } }, + }, } satisfies Meta; export default meta; export const Default: StoryObj = { - render: () => , + parameters: { + docs: { + source: { + disable: true, + }, + }, + }, }; From 0501c1b4666be8171fb84e5c995439dfd6e897d9 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 10:37:56 +0200 Subject: [PATCH 08/20] Moved ZoomControl to isolated component and fullscreen page example --- .../ZoomControl/index.test.tsx | 0 src/components/ZoomControl/index.tsx | 40 +++++++++++++++++++ src/components/ZoomControl/styles.module.css | 5 +++ src/pages/Fullscreen/FullscreenPage.tsx | 31 ++++++-------- src/pages/ZoomControlLayer/index.test.tsx | 13 ++++++ .../index.tsx | 30 ++------------ .../styles.module.css | 3 -- src/stories/pages/ZoomControl/index.mdx | 4 +- .../pages/ZoomControl/index.stories.tsx | 16 ++------ 9 files changed, 81 insertions(+), 61 deletions(-) rename src/{pages => components}/ZoomControl/index.test.tsx (100%) create mode 100644 src/components/ZoomControl/index.tsx create mode 100644 src/components/ZoomControl/styles.module.css create mode 100644 src/pages/ZoomControlLayer/index.test.tsx rename src/pages/{ZoomControl => ZoomControlLayer}/index.tsx (62%) rename src/pages/{ZoomControl => ZoomControlLayer}/styles.module.css (74%) diff --git a/src/pages/ZoomControl/index.test.tsx b/src/components/ZoomControl/index.test.tsx similarity index 100% rename from src/pages/ZoomControl/index.test.tsx rename to src/components/ZoomControl/index.test.tsx diff --git a/src/components/ZoomControl/index.tsx b/src/components/ZoomControl/index.tsx new file mode 100644 index 0000000..e865895 --- /dev/null +++ b/src/components/ZoomControl/index.tsx @@ -0,0 +1,40 @@ +import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; +import L from 'leaflet'; +import { + EnlargeIcon, + MinimiseIcon, +} from '@amsterdam/design-system-react-icons'; + +import styles from './styles.module.css'; + +export type ZoomControlProps = { + mapInstance?: L.Map | null; +}; + +const ZoomControl = ({ mapInstance }: ZoomControlProps) => { + const handleZoomInClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() + 1); + } + }; + const handleZoomOutClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() - 1); + } + }; + + return ( +
+ + +
+ ); +}; + +export default ZoomControl; diff --git a/src/components/ZoomControl/styles.module.css b/src/components/ZoomControl/styles.module.css new file mode 100644 index 0000000..ea55444 --- /dev/null +++ b/src/components/ZoomControl/styles.module.css @@ -0,0 +1,5 @@ +.container { + display: inline-flex; + flex-direction: column; + gap: var(--ams-space-inside-xs, 8px); +} diff --git a/src/pages/Fullscreen/FullscreenPage.tsx b/src/pages/Fullscreen/FullscreenPage.tsx index eaa48c3..e94b7a0 100644 --- a/src/pages/Fullscreen/FullscreenPage.tsx +++ b/src/pages/Fullscreen/FullscreenPage.tsx @@ -4,30 +4,25 @@ import { Screen, SkipLink, } from '@amsterdam/design-system-react'; -import { - Dispatch, - ReactElement, - SetStateAction, - createContext, - useState, -} from 'react'; -import ZoomControl from '../ZoomControl'; +import { ReactElement } from 'react'; +// import BaseLayer from '../BaseLayer'; +import ZoomControlLayer from '../ZoomControlLayer'; export type FullscreenPageProps = { header?: ReactElement; footer?: ReactElement; }; -interface MapContextType { - map: string; - setMap: Dispatch>; -} +// interface MapContextType { +// map: string; +// setMap: Dispatch>; +// } // Create the context with the correct type and a default value -export const MapContext = createContext(null); +// export const MapContext = createContext(null); export const FullscreenPage = ({ header, footer }: FullscreenPageProps) => { - const [map, setMap] = useState('test'); + // const [map, setMap] = useState('test'); return ( <> @@ -37,10 +32,10 @@ export const FullscreenPage = ({ header, footer }: FullscreenPageProps) => { - - {/* */} - - + {/* */} + {/* */} + + {/* */} diff --git a/src/pages/ZoomControlLayer/index.test.tsx b/src/pages/ZoomControlLayer/index.test.tsx new file mode 100644 index 0000000..2dc78e0 --- /dev/null +++ b/src/pages/ZoomControlLayer/index.test.tsx @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import ZoomControl from '.'; + +describe('ZoomControl', () => { + it('renders the component', () => { + render(); + const ZoomInbutton = screen.getByRole('button', { name: /Zoom in/i }); + const ZoomOutbutton = screen.getByRole('button', { name: /Zoom out/i }); + expect(ZoomInbutton).toBeInTheDocument(); + expect(ZoomOutbutton).toBeInTheDocument(); + }); +}); diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControlLayer/index.tsx similarity index 62% rename from src/pages/ZoomControl/index.tsx rename to src/pages/ZoomControlLayer/index.tsx index 70f09eb..96c119e 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControlLayer/index.tsx @@ -1,30 +1,15 @@ -import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; import { FunctionComponent, useEffect, useRef, useState } from 'react'; import L from 'leaflet'; import getCrsRd from '@/utils/getCrsRd'; -import { - EnlargeIcon, - MinimiseIcon, -} from '@amsterdam/design-system-react-icons'; import styles from './styles.module.css'; +import ZoomControl from '../../components/ZoomControl'; -const ZoomControl: FunctionComponent = () => { +const ZoomControlLayer: FunctionComponent = () => { const containerRef = useRef(null); const [mapInstance, setMapInstance] = useState(null); const createdMapInstance = useRef(false); - const handleZoomInClick = () => { - if (mapInstance) { - mapInstance?.setZoom(mapInstance.getZoom() + 1); - } - }; - const handleZoomOutClick = () => { - if (mapInstance) { - mapInstance?.setZoom(mapInstance.getZoom() - 1); - } - }; - // Set the Leaflet map and Amsterdam base layer useEffect(() => { if (containerRef.current === null || createdMapInstance.current !== false) { @@ -67,18 +52,11 @@ const ZoomControl: FunctionComponent = () => { <>
- - +
); }; -export default ZoomControl; +export default ZoomControlLayer; diff --git a/src/pages/ZoomControl/styles.module.css b/src/pages/ZoomControlLayer/styles.module.css similarity index 74% rename from src/pages/ZoomControl/styles.module.css rename to src/pages/ZoomControlLayer/styles.module.css index f696033..3154cb8 100644 --- a/src/pages/ZoomControl/styles.module.css +++ b/src/pages/ZoomControlLayer/styles.module.css @@ -6,9 +6,6 @@ .buttons { bottom: var(--ams-space-inside-lg, 24px); - display: inline-flex; - flex-direction: column; - gap: var(--ams-space-inside-xs, 8px); position: absolute; right: var(--ams-space-inside-lg, 24px); /* TODO Temporary fix, need to hook into control layer */ diff --git a/src/stories/pages/ZoomControl/index.mdx b/src/stories/pages/ZoomControl/index.mdx index a83756b..0ad1638 100644 --- a/src/stories/pages/ZoomControl/index.mdx +++ b/src/stories/pages/ZoomControl/index.mdx @@ -1,7 +1,7 @@ import { Canvas, Meta, Source, Story } from '@storybook/blocks'; import * as ZoomControlStories from './index.stories'; -import ZoomControl from '@/pages/ZoomControl/index?raw'; -import styles from '@/pages/ZoomControl/styles.module.css?raw'; +import ZoomControl from '@/components/ZoomControl/index?raw'; +import styles from '@/components/ZoomControl/styles.module.css?raw'; diff --git a/src/stories/pages/ZoomControl/index.stories.tsx b/src/stories/pages/ZoomControl/index.stories.tsx index 7873b33..aa40b93 100644 --- a/src/stories/pages/ZoomControl/index.stories.tsx +++ b/src/stories/pages/ZoomControl/index.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; -import ZoomControl from '@/pages/ZoomControl'; +import ZoomControl from '@/components/ZoomControl'; import '@amsterdam/design-system-tokens/dist/index.css'; import '@amsterdam/design-system-assets/font/index.css'; @@ -7,20 +7,12 @@ import '@amsterdam/design-system-css/dist/index.css'; import { Story } from '@storybook/blocks'; const meta = { - title: 'React/ZoomControl', + title: 'Components/ZoomControl', component: ZoomControl, - args: { - zoom: 12, - minZoom: 3, - maxZoom: 16, - }, } satisfies Meta; export default meta; + type Story = StoryObj; -export const Buttons: Story = { - args: { - zoom: 14, - }, -}; +export const Default: Story = {}; From 5872c916048a0d405439fe9f9034551fb5506bd0 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 10:40:16 +0200 Subject: [PATCH 09/20] ZoomControl docs --- src/stories/pages/ZoomControl/index.mdx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/stories/pages/ZoomControl/index.mdx b/src/stories/pages/ZoomControl/index.mdx index 0ad1638..2db3719 100644 --- a/src/stories/pages/ZoomControl/index.mdx +++ b/src/stories/pages/ZoomControl/index.mdx @@ -15,18 +15,7 @@ The container for the zoom control. #### ZoomControl with Design System buttons -```jsx -
- - -
-``` + ### CSS Styling From de2b55e6ff640c0434bd26c21e83b4ff7faa63c8 Mon Sep 17 00:00:00 2001 From: janthijs <92784122+janthijs@users.noreply.github.com> Date: Wed, 22 May 2024 11:28:54 +0200 Subject: [PATCH 10/20] fix: first stab --- src/pages/Fullscreen/FullscreenPage.tsx | 16 ++------ src/pages/Fullscreen/Map.tsx | 41 ++++++++++++++++++++ src/pages/Fullscreen/MapContext.ts | 16 ++++++++ src/pages/Fullscreen/MapStyles.module.css | 4 ++ src/pages/Fullscreen/mapOptions.ts | 24 ++++++++++++ src/pages/ZoomControl/index.tsx | 46 ++--------------------- 6 files changed, 92 insertions(+), 55 deletions(-) create mode 100644 src/pages/Fullscreen/Map.tsx create mode 100644 src/pages/Fullscreen/MapContext.ts create mode 100644 src/pages/Fullscreen/MapStyles.module.css create mode 100644 src/pages/Fullscreen/mapOptions.ts diff --git a/src/pages/Fullscreen/FullscreenPage.tsx b/src/pages/Fullscreen/FullscreenPage.tsx index eaa48c3..d13d49b 100644 --- a/src/pages/Fullscreen/FullscreenPage.tsx +++ b/src/pages/Fullscreen/FullscreenPage.tsx @@ -4,14 +4,9 @@ import { Screen, SkipLink, } from '@amsterdam/design-system-react'; -import { - Dispatch, - ReactElement, - SetStateAction, - createContext, - useState, -} from 'react'; +import { Dispatch, ReactElement, SetStateAction, createContext } from 'react'; import ZoomControl from '../ZoomControl'; +import Map from './Map'; export type FullscreenPageProps = { header?: ReactElement; @@ -27,8 +22,6 @@ interface MapContextType { export const MapContext = createContext(null); export const FullscreenPage = ({ header, footer }: FullscreenPageProps) => { - const [map, setMap] = useState('test'); - return ( <> Direct naar inhoud @@ -37,10 +30,9 @@ export const FullscreenPage = ({ header, footer }: FullscreenPageProps) => { - - {/* */} + - + diff --git a/src/pages/Fullscreen/Map.tsx b/src/pages/Fullscreen/Map.tsx new file mode 100644 index 0000000..ad5fd5b --- /dev/null +++ b/src/pages/Fullscreen/Map.tsx @@ -0,0 +1,41 @@ +import { useEffect, useRef, useState } from 'react'; +import L from 'leaflet'; +import { MapContext } from './MapContext'; +import { MAP_OPTIONS } from './mapOptions'; +import 'leaflet/dist/leaflet.css'; +import styles from './MapStyles.module.css'; + +export type MapProps = { + mapOptions?: L.MapOptions; + children: React.ReactNode; +}; + +export default function Map({ mapOptions = MAP_OPTIONS, children }: MapProps) { + const [mapInstance, setMapInstance] = useState(null); + const createdMapInstance = useRef(false); + const mapRef = useRef(null); + + useEffect(() => { + if (mapRef.current === null || createdMapInstance.current !== false) { + return; + } + + createdMapInstance.current = true; + const map = new L.Map(mapRef.current, { ...MAP_OPTIONS, ...mapOptions }); + setMapInstance(map); + + return () => { + mapInstance?.remove(); + }; + }, [mapInstance, mapOptions, mapRef]); + + return ( +
+ {!!mapInstance && ( + + {children} + + )} +
+ ); +} diff --git a/src/pages/Fullscreen/MapContext.ts b/src/pages/Fullscreen/MapContext.ts new file mode 100644 index 0000000..35d8c02 --- /dev/null +++ b/src/pages/Fullscreen/MapContext.ts @@ -0,0 +1,16 @@ +import type { Map } from 'leaflet'; +import { createContext, useContext } from 'react'; + +export const MapContext = createContext<{ mapInstance: Map | null }>({ + mapInstance: null, +}); + +export function useMapInstance() { + const { mapInstance } = useContext(MapContext); + + if (mapInstance === null) { + throw Error('Fout, geen mapinstance gevonden in context.'); + } + + return mapInstance; +} diff --git a/src/pages/Fullscreen/MapStyles.module.css b/src/pages/Fullscreen/MapStyles.module.css new file mode 100644 index 0000000..a533399 --- /dev/null +++ b/src/pages/Fullscreen/MapStyles.module.css @@ -0,0 +1,4 @@ +.map-container { + height: 100%; + width: 100%; +} diff --git a/src/pages/Fullscreen/mapOptions.ts b/src/pages/Fullscreen/mapOptions.ts new file mode 100644 index 0000000..faec4b6 --- /dev/null +++ b/src/pages/Fullscreen/mapOptions.ts @@ -0,0 +1,24 @@ +import L from 'leaflet'; +import getCrsRd from '@/utils/getCrsRd'; + +export const MAP_OPTIONS = { + center: [52.370216, 4.895168] as [number, number], + zoom: 12, + layers: [ + L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { + attribution: '', + subdomains: ['t1', 't2', 't3', 't4'], + tms: true, + }), + ], + zoomControl: false, + maxZoom: 16, + minZoom: 3, + // Ensure proper handling for Rijksdriehoekcoördinaten + crs: getCrsRd(), + // Prevent the user browsing too far outside Amsterdam otherwise the map will render blank greyspace. Amsterdam tile layer only supports Amsterdam and the immediate surrounding areas + maxBounds: [ + [52.25168, 4.64034], + [52.50536, 5.10737], + ] as [number, number][], +}; diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx index 70f09eb..e963142 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControl/index.tsx @@ -1,18 +1,16 @@ import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; -import { FunctionComponent, useEffect, useRef, useState } from 'react'; -import L from 'leaflet'; -import getCrsRd from '@/utils/getCrsRd'; +import { FunctionComponent, useRef } from 'react'; import { EnlargeIcon, MinimiseIcon, } from '@amsterdam/design-system-react-icons'; import styles from './styles.module.css'; +import { useMapInstance } from '../Fullscreen/MapContext'; const ZoomControl: FunctionComponent = () => { const containerRef = useRef(null); - const [mapInstance, setMapInstance] = useState(null); - const createdMapInstance = useRef(false); + const mapInstance = useMapInstance(); const handleZoomInClick = () => { if (mapInstance) { @@ -25,44 +23,6 @@ const ZoomControl: FunctionComponent = () => { } }; - // Set the Leaflet map and Amsterdam base layer - useEffect(() => { - if (containerRef.current === null || createdMapInstance.current !== false) { - return; - } - - const map = new L.Map(containerRef.current, { - center: L.latLng([52.370216, 4.895168]), - zoom: 12, - layers: [ - L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { - attribution: '', - subdomains: ['t1', 't2', 't3', 't4'], - tms: true, - }), - ], - zoomControl: false, - /** Disable scroll wheel zoom when the (custom) zoom control is used */ - scrollWheelZoom: false, - maxZoom: 16, - minZoom: 3, - crs: getCrsRd(), - maxBounds: [ - [52.25168, 4.64034], - [52.50536, 5.10737], - ], - }); - - map.attributionControl.setPrefix(false); - - createdMapInstance.current = true; - setMapInstance(map); - - return () => { - if (mapInstance) mapInstance.remove(); - }; - }, []); - return ( <>
From 05b59311c82a0c4cd717c24a246dd6319caa71eb Mon Sep 17 00:00:00 2001 From: janthijs <92784122+janthijs@users.noreply.github.com> Date: Wed, 22 May 2024 11:34:26 +0200 Subject: [PATCH 11/20] fix: update --- src/pages/ZoomControl/ZoomControl.tsx | 44 +++++++++++++++++++++++++ src/pages/ZoomControl/index.tsx | 46 +++++---------------------- 2 files changed, 52 insertions(+), 38 deletions(-) create mode 100644 src/pages/ZoomControl/ZoomControl.tsx diff --git a/src/pages/ZoomControl/ZoomControl.tsx b/src/pages/ZoomControl/ZoomControl.tsx new file mode 100644 index 0000000..e963142 --- /dev/null +++ b/src/pages/ZoomControl/ZoomControl.tsx @@ -0,0 +1,44 @@ +import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; +import { FunctionComponent, useRef } from 'react'; +import { + EnlargeIcon, + MinimiseIcon, +} from '@amsterdam/design-system-react-icons'; + +import styles from './styles.module.css'; +import { useMapInstance } from '../Fullscreen/MapContext'; + +const ZoomControl: FunctionComponent = () => { + const containerRef = useRef(null); + const mapInstance = useMapInstance(); + + const handleZoomInClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() + 1); + } + }; + const handleZoomOutClick = () => { + if (mapInstance) { + mapInstance?.setZoom(mapInstance.getZoom() - 1); + } + }; + + return ( + <> +
+
+ + +
+
+ + ); +}; + +export default ZoomControl; diff --git a/src/pages/ZoomControl/index.tsx b/src/pages/ZoomControl/index.tsx index e963142..07d20f9 100644 --- a/src/pages/ZoomControl/index.tsx +++ b/src/pages/ZoomControl/index.tsx @@ -1,44 +1,14 @@ -import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; -import { FunctionComponent, useRef } from 'react'; -import { - EnlargeIcon, - MinimiseIcon, -} from '@amsterdam/design-system-react-icons'; +import { FunctionComponent } from 'react'; -import styles from './styles.module.css'; -import { useMapInstance } from '../Fullscreen/MapContext'; - -const ZoomControl: FunctionComponent = () => { - const containerRef = useRef(null); - const mapInstance = useMapInstance(); - - const handleZoomInClick = () => { - if (mapInstance) { - mapInstance?.setZoom(mapInstance.getZoom() + 1); - } - }; - const handleZoomOutClick = () => { - if (mapInstance) { - mapInstance?.setZoom(mapInstance.getZoom() - 1); - } - }; +import Map from '../Fullscreen/Map'; +import ZoomControl from './ZoomControl'; +const ZoomControlStory: FunctionComponent = () => { return ( - <> -
-
- - -
-
- + + + ); }; -export default ZoomControl; +export default ZoomControlStory; From cf34ac3a9b337b5a03493e3e41abecd92c2709a6 Mon Sep 17 00:00:00 2001 From: janthijs <92784122+janthijs@users.noreply.github.com> Date: Wed, 22 May 2024 11:53:59 +0200 Subject: [PATCH 12/20] fix: remove ref --- src/pages/ZoomControl/ZoomControl.tsx | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/pages/ZoomControl/ZoomControl.tsx b/src/pages/ZoomControl/ZoomControl.tsx index e963142..ed37b24 100644 --- a/src/pages/ZoomControl/ZoomControl.tsx +++ b/src/pages/ZoomControl/ZoomControl.tsx @@ -1,5 +1,5 @@ import { Button, Icon, VisuallyHidden } from '@amsterdam/design-system-react'; -import { FunctionComponent, useRef } from 'react'; +import { FunctionComponent } from 'react'; import { EnlargeIcon, MinimiseIcon, @@ -9,7 +9,6 @@ import styles from './styles.module.css'; import { useMapInstance } from '../Fullscreen/MapContext'; const ZoomControl: FunctionComponent = () => { - const containerRef = useRef(null); const mapInstance = useMapInstance(); const handleZoomInClick = () => { @@ -24,20 +23,16 @@ const ZoomControl: FunctionComponent = () => { }; return ( - <> -
-
- - -
-
- +
+ + +
); }; From 6578382ec9e19676deaeff1c5932287af6e3ed37 Mon Sep 17 00:00:00 2001 From: janthijs <92784122+janthijs@users.noreply.github.com> Date: Wed, 22 May 2024 13:43:53 +0200 Subject: [PATCH 13/20] fix: move tilelayer to effect --- src/pages/Fullscreen/FullscreenPage.tsx | 2 +- src/pages/Fullscreen/Map.tsx | 9 +++++++-- src/pages/Fullscreen/mapOptions.ts | 8 -------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/pages/Fullscreen/FullscreenPage.tsx b/src/pages/Fullscreen/FullscreenPage.tsx index d13d49b..edb0039 100644 --- a/src/pages/Fullscreen/FullscreenPage.tsx +++ b/src/pages/Fullscreen/FullscreenPage.tsx @@ -5,7 +5,7 @@ import { SkipLink, } from '@amsterdam/design-system-react'; import { Dispatch, ReactElement, SetStateAction, createContext } from 'react'; -import ZoomControl from '../ZoomControl'; +import ZoomControl from '../ZoomControl/ZoomControl'; import Map from './Map'; export type FullscreenPageProps = { diff --git a/src/pages/Fullscreen/Map.tsx b/src/pages/Fullscreen/Map.tsx index ad5fd5b..579c5e9 100644 --- a/src/pages/Fullscreen/Map.tsx +++ b/src/pages/Fullscreen/Map.tsx @@ -7,21 +7,26 @@ import styles from './MapStyles.module.css'; export type MapProps = { mapOptions?: L.MapOptions; - children: React.ReactNode; + children?: React.ReactNode; }; export default function Map({ mapOptions = MAP_OPTIONS, children }: MapProps) { + const mapRef = useRef(null); const [mapInstance, setMapInstance] = useState(null); const createdMapInstance = useRef(false); - const mapRef = useRef(null); useEffect(() => { + console.log(mapRef.current, mapInstance); if (mapRef.current === null || createdMapInstance.current !== false) { return; } createdMapInstance.current = true; const map = new L.Map(mapRef.current, { ...MAP_OPTIONS, ...mapOptions }); + L.tileLayer(`https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png`, { + subdomains: ['t1', 't2', 't3', 't4'], + tms: true, + }).addTo(map); setMapInstance(map); return () => { diff --git a/src/pages/Fullscreen/mapOptions.ts b/src/pages/Fullscreen/mapOptions.ts index faec4b6..0b39f0f 100644 --- a/src/pages/Fullscreen/mapOptions.ts +++ b/src/pages/Fullscreen/mapOptions.ts @@ -1,16 +1,8 @@ -import L from 'leaflet'; import getCrsRd from '@/utils/getCrsRd'; export const MAP_OPTIONS = { center: [52.370216, 4.895168] as [number, number], zoom: 12, - layers: [ - L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { - attribution: '', - subdomains: ['t1', 't2', 't3', 't4'], - tms: true, - }), - ], zoomControl: false, maxZoom: 16, minZoom: 3, From 49b93a4fb77e20f1257af5fa340d15d5e386a7fd Mon Sep 17 00:00:00 2001 From: janthijs <92784122+janthijs@users.noreply.github.com> Date: Wed, 22 May 2024 13:45:20 +0200 Subject: [PATCH 14/20] fix; remove log --- src/pages/Fullscreen/Map.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Fullscreen/Map.tsx b/src/pages/Fullscreen/Map.tsx index 579c5e9..2972524 100644 --- a/src/pages/Fullscreen/Map.tsx +++ b/src/pages/Fullscreen/Map.tsx @@ -16,7 +16,6 @@ export default function Map({ mapOptions = MAP_OPTIONS, children }: MapProps) { const createdMapInstance = useRef(false); useEffect(() => { - console.log(mapRef.current, mapInstance); if (mapRef.current === null || createdMapInstance.current !== false) { return; } From 5a7d7c2a6f1363ef08aeaba9636710c2564ca4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Noordegraaf?= Date: Wed, 22 May 2024 14:37:55 +0200 Subject: [PATCH 15/20] [19] first attempt --- src/pages/WFSLayer/index.test.tsx | 35 +++++++++++ src/pages/WFSLayer/index.tsx | 21 +++++++ src/pages/WFSLayer/styles.module.css | 4 ++ src/pages/WFSLayer/useGeoJSONLayer.test.tsx | 54 +++++++++++++++++ src/pages/WFSLayer/useGeoJSONLayer.ts | 26 +++++++++ src/pages/WFSLayer/useLeafletMap.test.tsx | 26 +++++++++ src/pages/WFSLayer/useLeafletMap.ts | 48 +++++++++++++++ src/stories/pages/WFSLayer/index.mdx | 65 +++++++++++++++++++++ src/stories/pages/WFSLayer/index.stories.ts | 19 ++++++ 9 files changed, 298 insertions(+) create mode 100644 src/pages/WFSLayer/index.test.tsx create mode 100644 src/pages/WFSLayer/index.tsx create mode 100644 src/pages/WFSLayer/styles.module.css create mode 100644 src/pages/WFSLayer/useGeoJSONLayer.test.tsx create mode 100644 src/pages/WFSLayer/useGeoJSONLayer.ts create mode 100644 src/pages/WFSLayer/useLeafletMap.test.tsx create mode 100644 src/pages/WFSLayer/useLeafletMap.ts create mode 100644 src/stories/pages/WFSLayer/index.mdx create mode 100644 src/stories/pages/WFSLayer/index.stories.ts diff --git a/src/pages/WFSLayer/index.test.tsx b/src/pages/WFSLayer/index.test.tsx new file mode 100644 index 0000000..8cd5fe1 --- /dev/null +++ b/src/pages/WFSLayer/index.test.tsx @@ -0,0 +1,35 @@ +import { render } from '@testing-library/react'; +import WFSLayer from '.'; +import useGeoJSONLayer from './useGeoJSONLayer'; +import useLeafletMap from './useLeafletMap'; + +vi.mock('./useGeoJSONLayer', () => ({ + __esModule: true, + default: vi.fn(), +})); + +vi.mock('./useLeafletMap', () => ({ + __esModule: true, + default: vi.fn(), +})); + +describe('WFSLayer', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('renders the component', () => { + const { container } = render(); + expect(container.firstChild).toBeDefined(); + }); + + it('calls useLeafletMap', () => { + render(); + expect(useGeoJSONLayer).toHaveBeenCalled(); + }); + + it('calls useGeoJSONLayer', () => { + render(); + expect(useLeafletMap).toHaveBeenCalled(); + }); +}); diff --git a/src/pages/WFSLayer/index.tsx b/src/pages/WFSLayer/index.tsx new file mode 100644 index 0000000..dcf14a6 --- /dev/null +++ b/src/pages/WFSLayer/index.tsx @@ -0,0 +1,21 @@ +import { useRef } from 'react'; +import type { FunctionComponent } from 'react'; +import 'leaflet/dist/leaflet.css'; + +import styles from './styles.module.css'; +import useGeoJSONLayer from './useGeoJSONLayer'; +import useLeafletMap from './useLeafletMap'; + +const WFSLayer: FunctionComponent = () => { + const containerRef = useRef(null); + const mapInstance = useLeafletMap(containerRef); + + useGeoJSONLayer( + mapInstance, + 'https://map.data.amsterdam.nl/maps/bag?REQUEST=Getfeature&VERSION=1.1.0&SERVICE=wfs&TYPENAME=ms:pand&srsName=EPSG:4326&outputformat=geojson&bbox=4.886568897250246%2C52.36966606270195%2C4.892099548064893%2C52.37253554766886' + ); + + return
; +}; + +export default WFSLayer; diff --git a/src/pages/WFSLayer/styles.module.css b/src/pages/WFSLayer/styles.module.css new file mode 100644 index 0000000..d171d7c --- /dev/null +++ b/src/pages/WFSLayer/styles.module.css @@ -0,0 +1,4 @@ +.container { + height: 100%; + min-height: 100%; +} diff --git a/src/pages/WFSLayer/useGeoJSONLayer.test.tsx b/src/pages/WFSLayer/useGeoJSONLayer.test.tsx new file mode 100644 index 0000000..b37cd88 --- /dev/null +++ b/src/pages/WFSLayer/useGeoJSONLayer.test.tsx @@ -0,0 +1,54 @@ +import { render } from '@testing-library/react'; +import L from 'leaflet'; +import useGeoJSONLayer from './useGeoJSONLayer'; + +vi.mock('leaflet', () => ({ + __esModule: true, + default: { + map: () => ({ setView: () => ({}) }), + tileLayer: () => ({ addTo: () => ({}) }), + geoJSON: () => ({ addTo: () => ({}) }), + }, +})); + +const mockedGeoJsonResponse = () => + Promise.resolve({ + json() { + return { data: 'mocked data' }; + }, + ok: true, + }) as unknown as Response; + +describe('useGeoJSONLayer', () => { + let originalFetch: typeof global.fetch; + + beforeAll(() => { + originalFetch = global.fetch; + }); + + beforeEach(() => { + // @ts-expect-error mock fetch + global.fetch = vi.fn(mockedGeoJsonResponse); + }); + + afterAll(() => { + global.fetch = originalFetch; + }); + + it('fetches GeoJSON data and adds it to the map', async () => { + // @ts-expect-error mock fetch + global.fetch = vi.fn(mockedGeoJsonResponse); + + // Test component that uses the hook + const TestComponent = () => { + const map = L.map(document.createElement('div')); + useGeoJSONLayer(map, 'http://example.com/geojson'); + + return
Test component
; + }; + + render(); + + expect(global.fetch).toHaveBeenCalledWith('http://example.com/geojson'); + }); +}); diff --git a/src/pages/WFSLayer/useGeoJSONLayer.ts b/src/pages/WFSLayer/useGeoJSONLayer.ts new file mode 100644 index 0000000..669e5b0 --- /dev/null +++ b/src/pages/WFSLayer/useGeoJSONLayer.ts @@ -0,0 +1,26 @@ +import { useEffect } from 'react'; +import L from 'leaflet'; + +const useGeoJSONLayer = (map: L.Map | null, url: string) => { + useEffect(() => { + if (map === null) { + return; + } + + fetch(url) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + L.geoJSON(data).addTo(map); + }) + .catch(error => { + console.error('Error fetching GeoJSON data:', error); + }); + }, [map, url]); +}; + +export default useGeoJSONLayer; diff --git a/src/pages/WFSLayer/useLeafletMap.test.tsx b/src/pages/WFSLayer/useLeafletMap.test.tsx new file mode 100644 index 0000000..b527242 --- /dev/null +++ b/src/pages/WFSLayer/useLeafletMap.test.tsx @@ -0,0 +1,26 @@ +import { useRef } from 'react'; +import { render, screen } from '@testing-library/react'; +import useLeafletMap from './useLeafletMap'; + +describe('useLeafletMap', () => { + it('initializes a Leaflet map', async () => { + // Test component that uses the hook + const TestComponent = () => { + const containerRef = useRef(null); + const mapInstance = useLeafletMap(containerRef); + + return ( +
+ {mapInstance ? 'Map initialized' : 'Map not initialized'} +
+ ); + }; + + render(); + + // Wait for the map to be initialized + const mapElement = await screen.findByText('Map initialized'); + + expect(mapElement).toBeInTheDocument(); + }); +}); diff --git a/src/pages/WFSLayer/useLeafletMap.ts b/src/pages/WFSLayer/useLeafletMap.ts new file mode 100644 index 0000000..3e8dccf --- /dev/null +++ b/src/pages/WFSLayer/useLeafletMap.ts @@ -0,0 +1,48 @@ +import { useEffect, useRef, useState } from 'react'; +import type { RefObject } from 'react'; +import L from 'leaflet'; +import getCrsRd from '@/utils/getCrsRd'; + +const useLeafletMap = (container: RefObject) => { + const [mapInstance, setMapInstance] = useState(null); + const createdMapInstance = useRef(false); + + useEffect(() => { + if (container.current === null || createdMapInstance.current !== false) { + return; + } + + const map = new L.Map(container.current, { + center: L.latLng([52.370216, 4.895168]), + zoom: 12, + layers: [ + L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { + attribution: '', + subdomains: ['t1', 't2', 't3', 't4'], + tms: true, + }), + ], + zoomControl: false, + maxZoom: 16, + minZoom: 3, + crs: getCrsRd(), + maxBounds: [ + [52.25168, 4.64034], + [52.50536, 5.10737], + ], + }); + + map.attributionControl.setPrefix(false); + + createdMapInstance.current = true; + setMapInstance(map); + + return () => { + if (mapInstance) mapInstance.remove(); + }; + }, []); + + return mapInstance; +}; + +export default useLeafletMap; diff --git a/src/stories/pages/WFSLayer/index.mdx b/src/stories/pages/WFSLayer/index.mdx new file mode 100644 index 0000000..cd702e4 --- /dev/null +++ b/src/stories/pages/WFSLayer/index.mdx @@ -0,0 +1,65 @@ +import { Canvas, Meta, Source, Story } from '@storybook/blocks'; +import * as WFSLayerStories from './index.stories'; + +import WFSLayer from '@/pages/WFSLayer/index?raw'; +import styles from '@/pages/WFSLayer/styles.module.css?raw'; +import getCrsRd from '@/utils/getCrsRd?raw'; +import useGeoJSONLayer from '@/pages/WFSLayer/useGeoJSONLayer?raw'; +import useLeafletMap from '@/pages/WFSLayer/useLeafletMap?raw'; + + + +# Amsterdam WFSLayer +## Requirements + +- [BaseLayer](../?path=/docs/react-baselayer--docs) +- GeoJson data endpoint + +## Description + +This is the Amsterdam WFS layer on a plain Leaflet map. + +## Background + +### WFS layer + +A WFS (Web Feature Service) layer is a layer that is served as a GeoJson file. This file can be requested by a URL. The URL can be used to fetch the GeoJson data and display it on a map. + +## How to implement + +To accomplish the Amsterdam WFS layer there are four files: + +1. The React components + * [WFSLayer.tsx](#1-wfslayertsx) +2. The custom hooks (2 files) + * [useGeoJsonLayer.ts](#1-usegeojsonlayerts) + * [useLeafletMap.ts](#1-useleafletmapts) +3. The CSS styles (1 file) + * [styles.module.css](#1-stylesmodulecss) + +## Usage + +The following files are required: + +### React Components + +#### 1. WFSLayer.tsx + + + +### Custom Hooks + +#### 1. useGeoJsonLayer.ts + + + +#### 2. useLeafletMap.ts + + + +### CSS Styling + +#### 1. styles.module.css + + + diff --git a/src/stories/pages/WFSLayer/index.stories.ts b/src/stories/pages/WFSLayer/index.stories.ts new file mode 100644 index 0000000..71f07f3 --- /dev/null +++ b/src/stories/pages/WFSLayer/index.stories.ts @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import WFSLayer from '@/pages/WFSLayer'; + +const meta = { + title: 'React/WFSLayer', + component: WFSLayer, + parameters: { + layout: 'fullscreen', + options: { + panelPosition: 'bottom', + bottomPanelHeight: 0, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Base: Story = {}; From c1da0933f56674bb7fcbd6e07678def81c257884 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 14:48:40 +0200 Subject: [PATCH 16/20] ZoomControl decorator and FullscreenMap as compontent --- src/components/ZoomControl/ZoomControl.tsx | 2 +- src/pages/Fullscreen/FullscreenPage.tsx | 26 ++++++------------- src/pages/Fullscreen/FullscreenPageMap.tsx | 19 ++++++++++++++ src/pages/ZoomControlLayer/index.test.tsx | 13 ---------- src/pages/ZoomControlLayer/index.tsx | 14 ---------- src/pages/ZoomControlLayer/styles.module.css | 5 ---- src/stories/pages/Fullscreen/index.mdx | 20 ++++++++++++++ .../pages/Fullscreen/index.stories.tsx | 13 +++------- .../pages/ZoomControl/index.stories.tsx | 14 ++++++---- 9 files changed, 61 insertions(+), 65 deletions(-) create mode 100644 src/pages/Fullscreen/FullscreenPageMap.tsx delete mode 100644 src/pages/ZoomControlLayer/index.test.tsx delete mode 100644 src/pages/ZoomControlLayer/index.tsx delete mode 100644 src/pages/ZoomControlLayer/styles.module.css create mode 100644 src/stories/pages/Fullscreen/index.mdx diff --git a/src/components/ZoomControl/ZoomControl.tsx b/src/components/ZoomControl/ZoomControl.tsx index cf5d21d..509c6c1 100644 --- a/src/components/ZoomControl/ZoomControl.tsx +++ b/src/components/ZoomControl/ZoomControl.tsx @@ -6,7 +6,7 @@ import { } from '@amsterdam/design-system-react-icons'; import styles from './styles.module.css'; -import { useMapInstance } from '../Map/MapContext'; +import { useMapInstance } from '@/components/Map/MapContext'; const ZoomControl: FunctionComponent = () => { const mapInstance = useMapInstance(); diff --git a/src/pages/Fullscreen/FullscreenPage.tsx b/src/pages/Fullscreen/FullscreenPage.tsx index 79c97e3..4c0cd0d 100644 --- a/src/pages/Fullscreen/FullscreenPage.tsx +++ b/src/pages/Fullscreen/FullscreenPage.tsx @@ -1,33 +1,23 @@ -import { - AspectRatio, - Overlap, - Screen, - SkipLink, -} from '@amsterdam/design-system-react'; +import { Screen, SkipLink } from '@amsterdam/design-system-react'; import { ReactElement } from 'react'; -import Map from '../../components/Map/Map'; -import ZoomControl from '../../components/ZoomControl/ZoomControl'; export type FullscreenPageProps = { header?: ReactElement; + map?: ReactElement; footer?: ReactElement; }; -export const FullscreenPage = ({ header, footer }: FullscreenPageProps) => { +export const FullscreenPage = ({ + header, + map, + footer, +}: FullscreenPageProps) => { return ( <> Direct naar inhoud {header} - - - - - - - - - + {map} {footer} diff --git a/src/pages/Fullscreen/FullscreenPageMap.tsx b/src/pages/Fullscreen/FullscreenPageMap.tsx new file mode 100644 index 0000000..3a7b1a0 --- /dev/null +++ b/src/pages/Fullscreen/FullscreenPageMap.tsx @@ -0,0 +1,19 @@ +import { AspectRatio } from '@amsterdam/design-system-react'; +import Map from '../../components/Map/Map'; +import ZoomControl from '../../components/ZoomControl/ZoomControl'; + +export type FullScreenPageMapProps = { + scrollWheelZoom?: boolean; +}; + +const FullscreenPageMap = ({ scrollWheelZoom }: FullScreenPageMapProps) => { + return ( + + + + + + ); +}; + +export default FullscreenPageMap; diff --git a/src/pages/ZoomControlLayer/index.test.tsx b/src/pages/ZoomControlLayer/index.test.tsx deleted file mode 100644 index 2dc78e0..0000000 --- a/src/pages/ZoomControlLayer/index.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import ZoomControl from '.'; - -describe('ZoomControl', () => { - it('renders the component', () => { - render(); - const ZoomInbutton = screen.getByRole('button', { name: /Zoom in/i }); - const ZoomOutbutton = screen.getByRole('button', { name: /Zoom out/i }); - expect(ZoomInbutton).toBeInTheDocument(); - expect(ZoomOutbutton).toBeInTheDocument(); - }); -}); diff --git a/src/pages/ZoomControlLayer/index.tsx b/src/pages/ZoomControlLayer/index.tsx deleted file mode 100644 index dcae8d4..0000000 --- a/src/pages/ZoomControlLayer/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { FunctionComponent } from 'react'; - -import Map from '../../components/Map/Map'; -import ZoomControl from '../../components/ZoomControl/ZoomControl'; - -const ZoomControlStory: FunctionComponent = () => { - return ( - - - - ); -}; - -export default ZoomControlStory; diff --git a/src/pages/ZoomControlLayer/styles.module.css b/src/pages/ZoomControlLayer/styles.module.css deleted file mode 100644 index 6f6e01a..0000000 --- a/src/pages/ZoomControlLayer/styles.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.container { - height: 100%; - position: relative; - width: 100%; -} diff --git a/src/stories/pages/Fullscreen/index.mdx b/src/stories/pages/Fullscreen/index.mdx new file mode 100644 index 0000000..931699b --- /dev/null +++ b/src/stories/pages/Fullscreen/index.mdx @@ -0,0 +1,20 @@ +import { Canvas, Meta, Source, Story } from '@storybook/blocks'; +import * as FullscreenPageStories from './index.stories'; + + + +# Amsterdam Fullscreen Page Demo + +## Description + +The container for the fullscreen page is set up like this: + +```jsx + + + + + +``` + +The map component is a wrapper around the Leaflet map library and sets a context for child components. The `mapOptions` prop is passed to the Leaflet map instance. diff --git a/src/stories/pages/Fullscreen/index.stories.tsx b/src/stories/pages/Fullscreen/index.stories.tsx index 0669abb..89d8b7e 100644 --- a/src/stories/pages/Fullscreen/index.stories.tsx +++ b/src/stories/pages/Fullscreen/index.stories.tsx @@ -9,6 +9,7 @@ import { FullscreenPageFooter, FullscreenPageHeader, } from '../../../pages/Fullscreen'; +import FullscreenPageMap from '../../../pages/Fullscreen/FullscreenPageMap'; const meta = { title: 'Patterns/Fullscreen', @@ -18,22 +19,16 @@ const meta = { }, args: { header: , + map: , footer: , }, argTypes: { header: { control: { disable: true } }, + map: { control: { disable: true } }, footer: { control: { disable: true } }, }, } satisfies Meta; export default meta; -export const Default: StoryObj = { - parameters: { - docs: { - source: { - disable: true, - }, - }, - }, -}; +export const Default: StoryObj = {}; diff --git a/src/stories/pages/ZoomControl/index.stories.tsx b/src/stories/pages/ZoomControl/index.stories.tsx index 2a91982..05071c3 100644 --- a/src/stories/pages/ZoomControl/index.stories.tsx +++ b/src/stories/pages/ZoomControl/index.stories.tsx @@ -1,14 +1,18 @@ import type { Meta, StoryObj } from '@storybook/react'; -import ZoomControl from '../../../components/ZoomControl/ZoomControl'; - -import '@amsterdam/design-system-tokens/dist/index.css'; -import '@amsterdam/design-system-assets/font/index.css'; -import '@amsterdam/design-system-css/dist/index.css'; +import ZoomControl from '@/components/ZoomControl/ZoomControl'; import { Story } from '@storybook/blocks'; +import Map from '../../../components/Map/Map'; const meta = { title: 'Components/ZoomControl', component: ZoomControl, + decorators: [ + Story => ( + + + + ), + ], } satisfies Meta; export default meta; From 8ffc367793787ac6c1352afccc0abdade886168d Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 15:01:24 +0200 Subject: [PATCH 17/20] Ordering stories --- .storybook/preview.tsx | 14 ++++++++++++++ src/stories/Alternatives.mdx | 13 +++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 0afbbb3..c050be3 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -20,6 +20,20 @@ export const parameters = { backgrounds: { disable: true, }, + options: { + storySort: { + order: [ + 'Introduction', + 'Alternative*', + 'Coordinate*', + 'Global', + 'React', + 'React-Leaflet', + 'Components', + 'Patterns', + ], + }, + }, viewport: { viewports, }, diff --git a/src/stories/Alternatives.mdx b/src/stories/Alternatives.mdx index 4b291a3..973e454 100644 --- a/src/stories/Alternatives.mdx +++ b/src/stories/Alternatives.mdx @@ -1,4 +1,4 @@ -import { Meta } from "@storybook/blocks"; +import { Meta } from '@storybook/blocks'; @@ -9,6 +9,7 @@ import { Meta } from "@storybook/blocks"; ## Map engines #### [ESRI ArcGIS](https://www.esri.com/en-us/arcgis/about-arcgis/overview) + - **Pro:** Provides a seamless end-to-end solution from data-entry to configuring map applications/pages. - **Pro:** Professional and shareable maps. For example, [NYC OpenData](https://data.cityofnewyork.us/Housing-Development/arcGIS/pq44-rrf3) and [Gemeente Eindhoven](https://eindhoven.maps.arcgis.com/home/index.html). - **Pro:** More tools for non-developers @@ -17,12 +18,14 @@ import { Meta } from "@storybook/blocks"; - **Con:** Overwhelming with the number of features #### [Google Maps](https://google.com/maps) + - **Pro:** Familiar UI, delivers fast and smooth UX - **Pro:** Lots of data and frequently refreshed - **Con:** Pricing is a bit complicated - **Con:** Google/USA = legally complicated #### [Leaflet](https://leafletjs.com/) + - **Pro:** Open-source and free to use - **Pro:** Flexible and compatible with multiple providers - **Pro:** Lightweight and minimal code for simple maps @@ -31,6 +34,7 @@ import { Meta } from "@storybook/blocks"; - **Con:** Customizing map styles requires expertise #### [Mapbox](https://mapbox.com/) + - **Pro:** WebGL, fast and nice - **Pro:** Easily customizable (albeit a bit overwhelming) - **Pro:** (Excluding google maps) In the last five years has become the most popular maps packge [(npmtrends)](https://npmtrends.com/@tomtom-international/web-sdk-maps-vs-leaflet-vs-mapbox-gl-vs-maplibre-gl-vs-openlayers) @@ -39,6 +43,7 @@ import { Meta } from "@storybook/blocks"; - **Con:** Can get quickly overcomplicated #### [MapLibre](https://maplibre.org/) + - **Pro:** Derived from OpenLayers; uses WebGL, fast and nice - **Pro:** Open-source and free to use - **Pro:** Flexible and compatible with multiple providers @@ -46,6 +51,7 @@ import { Meta } from "@storybook/blocks"; - **Con:** (Excluding google maps) Is the third most popular map library [(npmtrends)](https://npmtrends.com/@tomtom-international/web-sdk-maps-vs-leaflet-vs-mapbox-gl-vs-maplibre-gl-vs-openlayers) but with less than half the number of users to Mapbox/Leaflet #### [OpenLayers](https://openlayers.org/) + - **Pro:** Open-source and free to use - **Pro:** Flexible and compatible with multiple providers - **Pro:** Large number of [examples](https://openlayers.org/en/latest/examples/) @@ -53,6 +59,7 @@ import { Meta } from "@storybook/blocks"; - **Con:** Not very popular in relation to Leaflet, Mapbox and MapLibre ([(npmtrends)](https://npmtrends.com/@tomtom-international/web-sdk-maps-vs-leaflet-vs-mapbox-gl-vs-maplibre-gl-vs-openlayers)) #### [TomTom Maps](https://www.tomtom.com/products/maps/) + - **Pro:** High quality map data - **Pro:** Fast and smooth UX - **Pro:** More tools for non-developers @@ -65,6 +72,7 @@ import { Meta } from "@storybook/blocks"; #### [Amsterdam Base Layer](../?path=/docs/alternatives--docs) #### [NL Maps](https://nlmaps.nl/) + - **Pro:** Powered and supported by [Kadaster](https://kadaster.nl/) - **Pro:** Nice UI with several themes - **Con:** Setup with Node.js/Typescript is a bit legacy @@ -72,9 +80,10 @@ import { Meta } from "@storybook/blocks"; - **Con:** Dependency on external company if changes are ever needed #### [OpenStreetMap](https://www.openstreetmap.org/) + - **Pro:** Open-source and built on a collaborative community - **Pro:** Large community of contributors - **Con:** Default styling is ugly - **Con:** Dependency on open source contributions if changes are ever needed -Leaflet provide more examples of other tile layer providers [here](https://leaflet-extras.github.io/leaflet-providers/preview/). \ No newline at end of file +Leaflet provide more examples of other tile layer providers [here](https://leaflet-extras.github.io/leaflet-providers/preview/). From 2571fcc2a6866fe68135be8f7ea5f862c4128bec Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 15:07:42 +0200 Subject: [PATCH 18/20] Build the test later --- src/components/ZoomControl/ZoomControl.test.tsx | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/components/ZoomControl/ZoomControl.test.tsx diff --git a/src/components/ZoomControl/ZoomControl.test.tsx b/src/components/ZoomControl/ZoomControl.test.tsx deleted file mode 100644 index 9457a11..0000000 --- a/src/components/ZoomControl/ZoomControl.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import ZoomControl from './ZoomControl'; - -describe('ZoomControl', () => { - it('renders the component', () => { - render(); - const ZoomInbutton = screen.getByRole('button', { name: /Zoom in/i }); - const ZoomOutbutton = screen.getByRole('button', { name: /Zoom out/i }); - expect(ZoomInbutton).toBeInTheDocument(); - expect(ZoomOutbutton).toBeInTheDocument(); - }); -}); From 2bbd8a09d9110047b5855dd175d1296c922049e7 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Wed, 22 May 2024 15:44:35 +0200 Subject: [PATCH 19/20] Update Map component to use default map options --- src/components/Map/Map.tsx | 12 +++++++++--- .../{mapOptions.ts => defaultMapOptions.ts} | 2 +- src/pages/BaseLayer/index.tsx | 19 +++++++------------ src/stories/pages/BaseLayer/index.stories.ts | 6 +----- 4 files changed, 18 insertions(+), 21 deletions(-) rename src/components/Map/{mapOptions.ts => defaultMapOptions.ts} (93%) diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx index 062de65..ffe5ce0 100644 --- a/src/components/Map/Map.tsx +++ b/src/components/Map/Map.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import L from 'leaflet'; import { MapContext } from './MapContext'; -import { MAP_OPTIONS } from './mapOptions'; +import { DEFAULT_MAP_OPTIONS } from './defaultMapOptions'; import 'leaflet/dist/leaflet.css'; import styles from './MapStyles.module.css'; @@ -10,7 +10,10 @@ export type MapProps = { children?: React.ReactNode; }; -export default function Map({ mapOptions = MAP_OPTIONS, children }: MapProps) { +export default function Map({ + mapOptions = DEFAULT_MAP_OPTIONS, + children, +}: MapProps) { const mapRef = useRef(null); const [mapInstance, setMapInstance] = useState(null); const createdMapInstance = useRef(false); @@ -21,7 +24,10 @@ export default function Map({ mapOptions = MAP_OPTIONS, children }: MapProps) { } createdMapInstance.current = true; - const map = new L.Map(mapRef.current, { ...MAP_OPTIONS, ...mapOptions }); + const map = new L.Map(mapRef.current, { + ...DEFAULT_MAP_OPTIONS, + ...mapOptions, + }); L.tileLayer(`https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png`, { subdomains: ['t1', 't2', 't3', 't4'], tms: true, diff --git a/src/components/Map/mapOptions.ts b/src/components/Map/defaultMapOptions.ts similarity index 93% rename from src/components/Map/mapOptions.ts rename to src/components/Map/defaultMapOptions.ts index 0b39f0f..c85c043 100644 --- a/src/components/Map/mapOptions.ts +++ b/src/components/Map/defaultMapOptions.ts @@ -1,6 +1,6 @@ import getCrsRd from '@/utils/getCrsRd'; -export const MAP_OPTIONS = { +export const DEFAULT_MAP_OPTIONS = { center: [52.370216, 4.895168] as [number, number], zoom: 12, zoomControl: false, diff --git a/src/pages/BaseLayer/index.tsx b/src/pages/BaseLayer/index.tsx index 0f4554d..063a7c2 100644 --- a/src/pages/BaseLayer/index.tsx +++ b/src/pages/BaseLayer/index.tsx @@ -5,12 +5,7 @@ import 'leaflet/dist/leaflet.css'; import getCrsRd from '@/utils/getCrsRd'; import styles from './styles.module.css'; -const BaseLayer: FunctionComponent = ({ - center, - zoom, - minZoom, - maxZoom, -}: L.MapOptions) => { +const BaseLayer: FunctionComponent = () => { const containerRef = useRef(null); // Use state instead of a ref for storing the Leaflet map object otherwise you may run into DOM issues when React StrictMode is enabled @@ -26,8 +21,8 @@ const BaseLayer: FunctionComponent = ({ } const map = new L.Map(containerRef.current, { - center: center ?? L.latLng([52.370216, 4.895168]), - zoom: zoom ?? 12, + center: L.latLng([52.370216, 4.895168]), + zoom: 12, layers: [ L.tileLayer('https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png', { attribution: '', @@ -36,8 +31,8 @@ const BaseLayer: FunctionComponent = ({ }), ], zoomControl: false, - maxZoom: maxZoom ?? 16, - minZoom: minZoom ?? 3, + maxZoom: 16, + minZoom: 3, // Ensure proper handling for Rijksdriehoekcoördinaten crs: getCrsRd(), // Prevent the user browsing too far outside Amsterdam otherwise the map will render blank greyspace. Amsterdam tile layer only supports Amsterdam and the immediate surrounding areas @@ -50,8 +45,8 @@ const BaseLayer: FunctionComponent = ({ // Remove Leaflet link from the map map.attributionControl.setPrefix(false); - // // Set the map as created and store the object to state - // createdMapInstance.current = true; + // Set the map as created and store the object to state + createdMapInstance.current = true; setMapInstance(map); // On component unmount, destroy the map and all related events diff --git a/src/stories/pages/BaseLayer/index.stories.ts b/src/stories/pages/BaseLayer/index.stories.ts index d4b008d..6638ec1 100644 --- a/src/stories/pages/BaseLayer/index.stories.ts +++ b/src/stories/pages/BaseLayer/index.stories.ts @@ -16,8 +16,4 @@ const meta = { export default meta; type Story = StoryObj; -export const Base: Story = { - args: { - zoom: 15, - }, -}; +export const Base: Story = {}; From caf099f9aa208f65d1249366ff8125b94122df33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Noordegraaf?= Date: Wed, 22 May 2024 16:05:38 +0200 Subject: [PATCH 20/20] [19] center content --- .../WFSLayer/{index.test.tsx => WFSLayer.test.tsx} | 2 +- src/pages/WFSLayer/{index.tsx => WFSLayer.tsx} | 0 src/pages/WFSLayer/useLeafletMap.ts | 4 ++-- src/stories/pages/WFSLayer/index.mdx | 12 ++++++------ src/stories/pages/WFSLayer/index.stories.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename src/pages/WFSLayer/{index.test.tsx => WFSLayer.test.tsx} (95%) rename src/pages/WFSLayer/{index.tsx => WFSLayer.tsx} (100%) diff --git a/src/pages/WFSLayer/index.test.tsx b/src/pages/WFSLayer/WFSLayer.test.tsx similarity index 95% rename from src/pages/WFSLayer/index.test.tsx rename to src/pages/WFSLayer/WFSLayer.test.tsx index 8cd5fe1..93de7d6 100644 --- a/src/pages/WFSLayer/index.test.tsx +++ b/src/pages/WFSLayer/WFSLayer.test.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react'; -import WFSLayer from '.'; +import WFSLayer from './WFSLayer'; import useGeoJSONLayer from './useGeoJSONLayer'; import useLeafletMap from './useLeafletMap'; diff --git a/src/pages/WFSLayer/index.tsx b/src/pages/WFSLayer/WFSLayer.tsx similarity index 100% rename from src/pages/WFSLayer/index.tsx rename to src/pages/WFSLayer/WFSLayer.tsx diff --git a/src/pages/WFSLayer/useLeafletMap.ts b/src/pages/WFSLayer/useLeafletMap.ts index 3e8dccf..8bf43b3 100644 --- a/src/pages/WFSLayer/useLeafletMap.ts +++ b/src/pages/WFSLayer/useLeafletMap.ts @@ -27,8 +27,8 @@ const useLeafletMap = (container: RefObject) => { minZoom: 3, crs: getCrsRd(), maxBounds: [ - [52.25168, 4.64034], - [52.50536, 5.10737], + [52.36966606270195, 4.886568897250246], + [52.37253554766886, 4.892099548064893], ], }); diff --git a/src/stories/pages/WFSLayer/index.mdx b/src/stories/pages/WFSLayer/index.mdx index cd702e4..d9e47aa 100644 --- a/src/stories/pages/WFSLayer/index.mdx +++ b/src/stories/pages/WFSLayer/index.mdx @@ -1,7 +1,7 @@ import { Canvas, Meta, Source, Story } from '@storybook/blocks'; import * as WFSLayerStories from './index.stories'; -import WFSLayer from '@/pages/WFSLayer/index?raw'; +import WFSLayer from '@/pages/WFSLayer/WFSLayer?raw'; import styles from '@/pages/WFSLayer/styles.module.css?raw'; import getCrsRd from '@/utils/getCrsRd?raw'; import useGeoJSONLayer from '@/pages/WFSLayer/useGeoJSONLayer?raw'; @@ -10,6 +10,7 @@ import useLeafletMap from '@/pages/WFSLayer/useLeafletMap?raw'; # Amsterdam WFSLayer + ## Requirements - [BaseLayer](../?path=/docs/react-baselayer--docs) @@ -30,12 +31,12 @@ A WFS (Web Feature Service) layer is a layer that is served as a GeoJson file. T To accomplish the Amsterdam WFS layer there are four files: 1. The React components - * [WFSLayer.tsx](#1-wfslayertsx) + - [WFSLayer.tsx](#1-wfslayertsx) 2. The custom hooks (2 files) - * [useGeoJsonLayer.ts](#1-usegeojsonlayerts) - * [useLeafletMap.ts](#1-useleafletmapts) + - [useGeoJsonLayer.ts](#1-usegeojsonlayerts) + - [useLeafletMap.ts](#1-useleafletmapts) 3. The CSS styles (1 file) - * [styles.module.css](#1-stylesmodulecss) + - [styles.module.css](#1-stylesmodulecss) ## Usage @@ -62,4 +63,3 @@ The following files are required: #### 1. styles.module.css - diff --git a/src/stories/pages/WFSLayer/index.stories.ts b/src/stories/pages/WFSLayer/index.stories.ts index 71f07f3..a7a5bd5 100644 --- a/src/stories/pages/WFSLayer/index.stories.ts +++ b/src/stories/pages/WFSLayer/index.stories.ts @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; -import WFSLayer from '@/pages/WFSLayer'; +import WFSLayer from '@/pages/WFSLayer/WFSLayer'; const meta = { title: 'React/WFSLayer',