@local-logic/maps 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/.storybook/defaults.ts +40 -0
- package/.storybook/globals.css +32 -0
- package/.storybook/main.ts +19 -0
- package/.storybook/preview.tsx +28 -0
- package/CHANGELOG.md +7 -0
- package/README.md +3 -0
- package/eslint.config.mjs +46 -0
- package/lint-staged.config.cjs +4 -0
- package/package.json +78 -0
- package/postcss.config.cjs +6 -0
- package/prettier.config.cjs +1 -0
- package/src/components/Map/Root/BaseMap/Empty/index.tsx +7 -0
- package/src/components/Map/Root/BaseMap/Google/index.tsx +96 -0
- package/src/components/Map/Root/BaseMap/Mapbox/index.tsx +66 -0
- package/src/components/Map/Root/BaseMap/Maptiler/index.tsx +59 -0
- package/src/components/Map/Root/BaseMap/index.tsx +36 -0
- package/src/components/Map/Root/BaseMap/styles.ts +2 -0
- package/src/components/Map/Root/BaseMap/types.ts +3 -0
- package/src/components/Map/Root/Markers/Cluster/index.tsx +26 -0
- package/src/components/Map/Root/Markers/Cluster/styles.ts +14 -0
- package/src/components/Map/Root/Markers/Google/index.tsx +49 -0
- package/src/components/Map/Root/Markers/Google/styles.ts +1 -0
- package/src/components/Map/Root/Markers/Mapbox/index.tsx +41 -0
- package/src/components/Map/Root/Markers/Maptiler/index.tsx +44 -0
- package/src/components/Map/Root/Markers/index.tsx +61 -0
- package/src/components/Map/Root/Markers/styles.ts +29 -0
- package/src/components/Map/Root/Markers/types.ts +15 -0
- package/src/components/Map/Root/constants.ts +6 -0
- package/src/components/Map/Root/context.tsx +15 -0
- package/src/components/Map/Root/index.tsx +17 -0
- package/src/components/Map/Root/types.ts +42 -0
- package/src/components/Map/index.stories.tsx +117 -0
- package/src/components/Map/index.test.tsx +16 -0
- package/src/components/Map/index.tsx +1 -0
- package/src/index.ts +1 -0
- package/tailwind.config.cjs +1 -0
- package/tsconfig.json +14 -0
- package/tsconfig.node.json +8 -0
- package/vite-env.d.ts +13 -0
- package/vite.config.ts +46 -0
package/.env.example
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const mapDefaults = {
|
|
2
|
+
mapProvider: {
|
|
3
|
+
table: { disable: true },
|
|
4
|
+
},
|
|
5
|
+
center: {
|
|
6
|
+
table: { disable: true },
|
|
7
|
+
},
|
|
8
|
+
markers: {
|
|
9
|
+
table: { disable: true },
|
|
10
|
+
},
|
|
11
|
+
zoom: {
|
|
12
|
+
name: "Zoom",
|
|
13
|
+
control: {
|
|
14
|
+
type: "number",
|
|
15
|
+
min: 0,
|
|
16
|
+
max: 50,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
zoomPosition: {
|
|
20
|
+
name: "Zoom position",
|
|
21
|
+
control: { type: "radio" },
|
|
22
|
+
options: ["top-left", "top-right", "bottom-left", "bottom-right"],
|
|
23
|
+
},
|
|
24
|
+
pitch: {
|
|
25
|
+
name: "Pitch",
|
|
26
|
+
control: {
|
|
27
|
+
type: "number",
|
|
28
|
+
min: 0,
|
|
29
|
+
max: 50,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
bearing: {
|
|
33
|
+
name: "Bearing",
|
|
34
|
+
control: {
|
|
35
|
+
type: "number",
|
|
36
|
+
min: 0,
|
|
37
|
+
max: 50,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
html {
|
|
6
|
+
height: 100%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-family: 'Inter', sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
h1,
|
|
14
|
+
h2,
|
|
15
|
+
h3,
|
|
16
|
+
h4,
|
|
17
|
+
h5,
|
|
18
|
+
h6 {
|
|
19
|
+
font-family: 'Inter', sans-serif;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@layer utilities {
|
|
23
|
+
/* Chrome, Safari and Opera */
|
|
24
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
25
|
+
display: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.no-scrollbar {
|
|
29
|
+
-ms-overflow-style: none; /* IE and Edge */
|
|
30
|
+
scrollbar-width: none; /* Firefox */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StorybookConfig } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
const storybookCfg: StorybookConfig = {
|
|
4
|
+
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
|
5
|
+
addons: [
|
|
6
|
+
"@storybook/addon-links",
|
|
7
|
+
"@storybook/addon-essentials",
|
|
8
|
+
"@storybook/addon-interactions",
|
|
9
|
+
],
|
|
10
|
+
core: {
|
|
11
|
+
builder: "@storybook/builder-vite",
|
|
12
|
+
},
|
|
13
|
+
framework: "@storybook/react-vite",
|
|
14
|
+
typescript: {
|
|
15
|
+
reactDocgen: false,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default storybookCfg;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import "@local-logic/core/style.css";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
import { Preview } from '@storybook/react';
|
|
4
|
+
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
|
5
|
+
|
|
6
|
+
const preview: Preview = {
|
|
7
|
+
decorators: [
|
|
8
|
+
withThemeByDataAttribute({
|
|
9
|
+
themes: {
|
|
10
|
+
light: 'day',
|
|
11
|
+
dark: 'night',
|
|
12
|
+
},
|
|
13
|
+
defaultTheme: 'light',
|
|
14
|
+
attributeName: "data-theme",
|
|
15
|
+
}),
|
|
16
|
+
],
|
|
17
|
+
parameters: {
|
|
18
|
+
actions: { argTypesRegex: "^on[A-Z].*" },
|
|
19
|
+
controls: {
|
|
20
|
+
matchers: {
|
|
21
|
+
color: /(background|color)$/i,
|
|
22
|
+
date: /Date$/,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default preview;
|
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import js from "@eslint/js";
|
|
6
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const compat = new FlatCompat({
|
|
11
|
+
baseDirectory: __dirname,
|
|
12
|
+
recommendedConfig: js.configs.recommended,
|
|
13
|
+
allConfig: js.configs.all,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export default defineConfig([
|
|
17
|
+
{
|
|
18
|
+
extends: compat.extends(
|
|
19
|
+
"@local-logic/eslint-config/react",
|
|
20
|
+
"plugin:storybook/recommended",
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
settings: {
|
|
24
|
+
"import/resolver": {
|
|
25
|
+
typescript: {},
|
|
26
|
+
node: {
|
|
27
|
+
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
files: ["**/*.tsx"],
|
|
34
|
+
rules: {
|
|
35
|
+
"react/prop-types": "off",
|
|
36
|
+
"node/file-extension-in-import": "off",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
files: ["**/*.cjs"],
|
|
41
|
+
rules: {
|
|
42
|
+
"@typescript-eslint/no-require-imports": "off",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
globalIgnores(["dist/"]),
|
|
46
|
+
]);
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@local-logic/maps",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "This is a maps implementation allowing for the display of Local Logic data on a map.",
|
|
5
|
+
"author": "Local Logic",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"packageManager": "yarn@4.5.1",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "vite",
|
|
16
|
+
"build": "vite build",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"stats": "STATS=1 vite build",
|
|
19
|
+
"size": "yarn run build && size-limit",
|
|
20
|
+
"lint": "TIMING=1 eslint --ext .js,.jsx,.ts,.tsx .",
|
|
21
|
+
"check-types": "tsc --project ./tsconfig.json --noEmit",
|
|
22
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
23
|
+
"storybook:run": "storybook dev -p 6006",
|
|
24
|
+
"storybook:build": "storybook build"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": ">=18",
|
|
28
|
+
"react-dom": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@eslint/eslintrc": "^3.3.0",
|
|
32
|
+
"@eslint/js": "^9.22.0",
|
|
33
|
+
"@local-logic/eslint-config": "*",
|
|
34
|
+
"@storybook/addon-actions": "^8.6.7",
|
|
35
|
+
"@storybook/addon-essentials": "^8.6.7",
|
|
36
|
+
"@storybook/addon-interactions": "^8.6.7",
|
|
37
|
+
"@storybook/addon-links": "^8.6.7",
|
|
38
|
+
"@storybook/addon-themes": "^8.6.7",
|
|
39
|
+
"@storybook/blocks": "^8.6.7",
|
|
40
|
+
"@storybook/builder-vite": "^8.6.7",
|
|
41
|
+
"@storybook/react": "^8.6.7",
|
|
42
|
+
"@storybook/react-vite": "^8.6.7",
|
|
43
|
+
"@storybook/testing-library": "^0.2.2",
|
|
44
|
+
"@testing-library/react": "^16.2.0",
|
|
45
|
+
"@types/geojson": "^7946.0.16",
|
|
46
|
+
"@types/react": "^19.0.11",
|
|
47
|
+
"@types/react-dom": "^19.0.4",
|
|
48
|
+
"@types/supercluster": "^7.1.3",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
|
50
|
+
"@typescript-eslint/parser": "^8.27.0",
|
|
51
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
52
|
+
"eslint": "^9.22.0",
|
|
53
|
+
"eslint-plugin-import": "^2.31.0",
|
|
54
|
+
"eslint-plugin-storybook": "^0.11.6",
|
|
55
|
+
"react": "^19.0.0",
|
|
56
|
+
"react-dom": "^19.0.0",
|
|
57
|
+
"rollup-plugin-visualizer": "^5.14.0",
|
|
58
|
+
"storybook": "^8.6.7",
|
|
59
|
+
"tsconfig": "*",
|
|
60
|
+
"typescript": "^5.8.2",
|
|
61
|
+
"vite": "^6.2.2",
|
|
62
|
+
"vite-plugin-dts": "^4.5.3",
|
|
63
|
+
"vite-plugin-svgr": "^4.3.0",
|
|
64
|
+
"vitest": "^3.0.9"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@local-logic/core": "*",
|
|
68
|
+
"@local-logic/design-system": "*",
|
|
69
|
+
"@phosphor-icons/react": "^2.1.7",
|
|
70
|
+
"@turf/turf": "^7.2.0",
|
|
71
|
+
"@vis.gl/react-google-maps": "^1.5.2",
|
|
72
|
+
"mapbox-gl": "^3.10.0",
|
|
73
|
+
"maplibre-gl": "^5.2.0",
|
|
74
|
+
"react-map-gl": "^8.0.1",
|
|
75
|
+
"supercluster": "^8.0.1",
|
|
76
|
+
"use-supercluster": "^1.2.0"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("@local-logic/eslint-config/prettier.config");
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
APIProvider,
|
|
5
|
+
Map as GoogleMap,
|
|
6
|
+
ControlPosition,
|
|
7
|
+
useMap,
|
|
8
|
+
} from "@vis.gl/react-google-maps";
|
|
9
|
+
|
|
10
|
+
import { useRootElement } from "../../context";
|
|
11
|
+
import { defaultMapValues } from "../../constants";
|
|
12
|
+
|
|
13
|
+
import type { BaseMapProps } from "../types";
|
|
14
|
+
|
|
15
|
+
const zoomPositionMap = {
|
|
16
|
+
"top-left": ControlPosition.LEFT_TOP,
|
|
17
|
+
"top-right": ControlPosition.RIGHT_TOP,
|
|
18
|
+
"bottom-left": ControlPosition.LEFT_BOTTOM,
|
|
19
|
+
"bottom-right": ControlPosition.RIGHT_BOTTOM,
|
|
20
|
+
};
|
|
21
|
+
const mapId = "a7ff20eb973126bb";
|
|
22
|
+
|
|
23
|
+
const MapControl = ({ isMapLoaded }: { isMapLoaded: boolean }) => {
|
|
24
|
+
const map = useMap();
|
|
25
|
+
|
|
26
|
+
const { setBounds } = useRootElement();
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!isMapLoaded || !map) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const bounds = map.getBounds()?.toJSON();
|
|
34
|
+
|
|
35
|
+
if (bounds) {
|
|
36
|
+
setBounds([bounds.west, bounds.south, bounds.east, bounds.north]);
|
|
37
|
+
}
|
|
38
|
+
}, [isMapLoaded, map]);
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Map IDS:
|
|
45
|
+
* https://console.cloud.google.com/google/maps-apis/studio/maps?project=engineering-internal-396021
|
|
46
|
+
*
|
|
47
|
+
* Map styles:
|
|
48
|
+
* https://console.cloud.google.com/google/maps-apis/studio/styles?project=engineering-internal-396021
|
|
49
|
+
*/
|
|
50
|
+
export default function GoogleBaseMap({ children }: BaseMapProps) {
|
|
51
|
+
const [isMapLoaded, setIsMapLoaded] = useState(false);
|
|
52
|
+
const {
|
|
53
|
+
mapProvider,
|
|
54
|
+
center,
|
|
55
|
+
zoom = defaultMapValues.zoom,
|
|
56
|
+
pitch = defaultMapValues.pitch,
|
|
57
|
+
bearing = defaultMapValues.bearing,
|
|
58
|
+
cooperativeGestures = defaultMapValues.cooperativeGestures,
|
|
59
|
+
zoomPosition,
|
|
60
|
+
} = useRootElement();
|
|
61
|
+
|
|
62
|
+
if (mapProvider.name !== "google") {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleMapLoaded = () => {
|
|
67
|
+
setIsMapLoaded(true);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<APIProvider apiKey={mapProvider.apiKey}>
|
|
72
|
+
<MapControl isMapLoaded={isMapLoaded} />
|
|
73
|
+
<GoogleMap
|
|
74
|
+
mapId={mapId}
|
|
75
|
+
defaultCenter={{
|
|
76
|
+
lat: center.latitude,
|
|
77
|
+
lng: center.longitude,
|
|
78
|
+
}}
|
|
79
|
+
defaultZoom={zoom}
|
|
80
|
+
defaultTilt={pitch}
|
|
81
|
+
defaultHeading={bearing}
|
|
82
|
+
gestureHandling={!cooperativeGestures ? "greedy" : "cooperative"}
|
|
83
|
+
streetViewControl={false}
|
|
84
|
+
fullscreenControl={false}
|
|
85
|
+
clickableIcons={false}
|
|
86
|
+
mapTypeControl={false}
|
|
87
|
+
zoomControlOptions={{
|
|
88
|
+
position: zoomPosition && zoomPositionMap[zoomPosition],
|
|
89
|
+
}}
|
|
90
|
+
onTilesLoaded={handleMapLoaded}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</GoogleMap>
|
|
94
|
+
</APIProvider>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import Mapbox, { NavigationControl, useMap } from "react-map-gl/mapbox";
|
|
4
|
+
import "mapbox-gl/dist/mapbox-gl.css";
|
|
5
|
+
|
|
6
|
+
import type { BBox } from "geojson";
|
|
7
|
+
|
|
8
|
+
import { useRootElement } from "../../context";
|
|
9
|
+
import { defaultMapValues } from "../../constants";
|
|
10
|
+
|
|
11
|
+
import type { BaseMapProps } from "../types";
|
|
12
|
+
|
|
13
|
+
const MapControl = () => {
|
|
14
|
+
const { current: map } = useMap();
|
|
15
|
+
const { setBounds } = useRootElement();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!map) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const bounds = map.getBounds();
|
|
23
|
+
|
|
24
|
+
if (!bounds) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setBounds(bounds.toArray().flat() as BBox);
|
|
29
|
+
}, [map]);
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default function MapboxBaseMap({ children }: BaseMapProps) {
|
|
35
|
+
const {
|
|
36
|
+
mapProvider,
|
|
37
|
+
center,
|
|
38
|
+
zoom = defaultMapValues.zoom,
|
|
39
|
+
pitch = defaultMapValues.pitch,
|
|
40
|
+
bearing = defaultMapValues.bearing,
|
|
41
|
+
cooperativeGestures = defaultMapValues.cooperativeGestures,
|
|
42
|
+
zoomPosition,
|
|
43
|
+
} = useRootElement();
|
|
44
|
+
|
|
45
|
+
if (mapProvider.name !== "mapbox") {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Mapbox
|
|
51
|
+
mapboxAccessToken={mapProvider.apiKey}
|
|
52
|
+
initialViewState={{
|
|
53
|
+
...center,
|
|
54
|
+
zoom,
|
|
55
|
+
pitch,
|
|
56
|
+
bearing,
|
|
57
|
+
}}
|
|
58
|
+
cooperativeGestures={cooperativeGestures}
|
|
59
|
+
mapStyle={`mapbox://styles/mapbox/streets-v9`}
|
|
60
|
+
>
|
|
61
|
+
<MapControl />
|
|
62
|
+
{zoomPosition && <NavigationControl position={zoomPosition} />}
|
|
63
|
+
{children}
|
|
64
|
+
</Mapbox>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import Maplibre, { NavigationControl, useMap } from "react-map-gl/maplibre";
|
|
4
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
5
|
+
|
|
6
|
+
import type { BBox } from "geojson";
|
|
7
|
+
|
|
8
|
+
import { useRootElement } from "../../context";
|
|
9
|
+
import { defaultMapValues } from "../../constants";
|
|
10
|
+
|
|
11
|
+
import type { BaseMapProps } from "../types";
|
|
12
|
+
|
|
13
|
+
const MapControl = () => {
|
|
14
|
+
const { current: map } = useMap();
|
|
15
|
+
const { setBounds } = useRootElement();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!map) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setBounds(map.getBounds().toArray().flat() as BBox);
|
|
23
|
+
}, [map]);
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default function MaptilerBaseMap({ children }: BaseMapProps) {
|
|
29
|
+
const {
|
|
30
|
+
mapProvider,
|
|
31
|
+
center,
|
|
32
|
+
zoom = defaultMapValues.zoom,
|
|
33
|
+
pitch = defaultMapValues.pitch,
|
|
34
|
+
bearing = defaultMapValues.bearing,
|
|
35
|
+
cooperativeGestures = defaultMapValues.cooperativeGestures,
|
|
36
|
+
zoomPosition,
|
|
37
|
+
} = useRootElement();
|
|
38
|
+
|
|
39
|
+
if (mapProvider.name !== "maptiler") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Maplibre
|
|
45
|
+
initialViewState={{
|
|
46
|
+
...center,
|
|
47
|
+
zoom,
|
|
48
|
+
pitch,
|
|
49
|
+
bearing,
|
|
50
|
+
}}
|
|
51
|
+
cooperativeGestures={cooperativeGestures}
|
|
52
|
+
mapStyle={`https://api.maptiler.com/maps/${mapProvider.maptilerTheme}/style.json?key=${mapProvider.apiKey}`}
|
|
53
|
+
>
|
|
54
|
+
<MapControl />
|
|
55
|
+
{zoomPosition && <NavigationControl position={zoomPosition} />}
|
|
56
|
+
{children}
|
|
57
|
+
</Maplibre>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { lazy, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { useRootElement } from "../context";
|
|
4
|
+
|
|
5
|
+
import type { BaseMapProps } from "./types";
|
|
6
|
+
import * as styles from "./styles";
|
|
7
|
+
|
|
8
|
+
const MaptilerBaseMap = lazy(() => import("./Maptiler"));
|
|
9
|
+
const GoogleBaseMap = lazy(() => import("./Google"));
|
|
10
|
+
const MapboxBaseMap = lazy(() => import("./Mapbox"));
|
|
11
|
+
const EmptyBaseMap = lazy(() => import("./Empty"));
|
|
12
|
+
|
|
13
|
+
export const BaseMap = ({ children }: BaseMapProps) => {
|
|
14
|
+
const { mapProvider } = useRootElement();
|
|
15
|
+
|
|
16
|
+
const BaseMapProvider = useMemo(() => {
|
|
17
|
+
switch (mapProvider?.name) {
|
|
18
|
+
case "maptiler":
|
|
19
|
+
return MaptilerBaseMap;
|
|
20
|
+
case "google":
|
|
21
|
+
return GoogleBaseMap;
|
|
22
|
+
case "mapbox":
|
|
23
|
+
return MapboxBaseMap;
|
|
24
|
+
default:
|
|
25
|
+
return EmptyBaseMap;
|
|
26
|
+
}
|
|
27
|
+
}, [mapProvider]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={styles.wrapper}>
|
|
31
|
+
<BaseMapProvider>
|
|
32
|
+
{children}
|
|
33
|
+
</BaseMapProvider>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import type { ClusterPoint } from "../types";
|
|
4
|
+
|
|
5
|
+
import * as styles from "./styles";
|
|
6
|
+
|
|
7
|
+
export function Cluster({ cluster }: { cluster: ClusterPoint }) {
|
|
8
|
+
// Taking a somewhat similar calculation to what LocalLogic SDK used to
|
|
9
|
+
// do for cluster diameter (bigger depending on count)
|
|
10
|
+
const diameter = useMemo(() => (
|
|
11
|
+
20 + cluster.properties.point_count * 4
|
|
12
|
+
), [cluster.properties.point_count]);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={styles.container}
|
|
17
|
+
style={{
|
|
18
|
+
width: `${diameter}px`,
|
|
19
|
+
height: `${diameter}px`,
|
|
20
|
+
fontSize: `${diameter / 2.5}px`
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
{cluster.properties.point_count}
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { MapPin } from "@phosphor-icons/react";
|
|
4
|
+
import { AdvancedMarker as GoogleMarker } from "@vis.gl/react-google-maps";
|
|
5
|
+
|
|
6
|
+
import type { MapMarkerProps } from "../types";
|
|
7
|
+
|
|
8
|
+
import { Cluster } from "../Cluster";
|
|
9
|
+
|
|
10
|
+
import * as globalStyles from "../styles";
|
|
11
|
+
import * as styles from "./styles";
|
|
12
|
+
|
|
13
|
+
export default function GoogleMarkers({ clusters, children }: MapMarkerProps) {
|
|
14
|
+
return clusters.map((cluster, index) => {
|
|
15
|
+
const longitude = cluster.geometry.coordinates[0];
|
|
16
|
+
const latitude = cluster.geometry.coordinates[1];
|
|
17
|
+
|
|
18
|
+
const handleOnClick = () => {
|
|
19
|
+
// TODO Without the on click the hover doesn't work
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<GoogleMarker
|
|
24
|
+
key={`map-marker-${longitude}-${latitude}-${index}`}
|
|
25
|
+
position={{
|
|
26
|
+
lat: latitude,
|
|
27
|
+
lng: longitude,
|
|
28
|
+
}}
|
|
29
|
+
onClick={handleOnClick}
|
|
30
|
+
className={styles.marker}
|
|
31
|
+
>
|
|
32
|
+
<div className={globalStyles.container}>
|
|
33
|
+
{cluster.properties.cluster ? (
|
|
34
|
+
<Cluster cluster={cluster} />
|
|
35
|
+
) : (
|
|
36
|
+
<>
|
|
37
|
+
{cluster.properties.icon ? (
|
|
38
|
+
<cluster.properties.icon className={globalStyles.icon} />
|
|
39
|
+
) : (
|
|
40
|
+
<MapPin className={globalStyles.icon} />
|
|
41
|
+
)}
|
|
42
|
+
{children}
|
|
43
|
+
</>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
</GoogleMarker>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const marker = `-translate-y-1/2`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { MapPin } from "@phosphor-icons/react";
|
|
4
|
+
import { Marker as MapboxMarker } from "react-map-gl/mapbox";
|
|
5
|
+
|
|
6
|
+
import type { MapMarkerProps } from "../types";
|
|
7
|
+
|
|
8
|
+
import { Cluster } from "../Cluster";
|
|
9
|
+
|
|
10
|
+
import * as styles from "../styles";
|
|
11
|
+
|
|
12
|
+
export default function MapboxMarkers({ clusters, children }: MapMarkerProps) {
|
|
13
|
+
return clusters.map((cluster, index) => {
|
|
14
|
+
const longitude = cluster.geometry.coordinates[0];
|
|
15
|
+
const latitude = cluster.geometry.coordinates[1];
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<MapboxMarker
|
|
19
|
+
key={`map-marker-${longitude}-${latitude}-${index}`}
|
|
20
|
+
anchor="bottom"
|
|
21
|
+
longitude={longitude}
|
|
22
|
+
latitude={latitude}
|
|
23
|
+
>
|
|
24
|
+
<div className={styles.container}>
|
|
25
|
+
{cluster.properties.cluster ? (
|
|
26
|
+
<Cluster cluster={cluster} />
|
|
27
|
+
) : (
|
|
28
|
+
<>
|
|
29
|
+
{cluster.properties.icon ? (
|
|
30
|
+
<cluster.properties.icon className={styles.icon} />
|
|
31
|
+
) : (
|
|
32
|
+
<MapPin className={styles.icon} />
|
|
33
|
+
)}
|
|
34
|
+
{children}
|
|
35
|
+
</>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
</MapboxMarker>
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { MapPin } from "@phosphor-icons/react";
|
|
4
|
+
import { Marker as MaplibreMarker } from "react-map-gl/maplibre";
|
|
5
|
+
|
|
6
|
+
import type { MapMarkerProps } from "../types";
|
|
7
|
+
|
|
8
|
+
import { Cluster } from "../Cluster";
|
|
9
|
+
|
|
10
|
+
import * as styles from "../styles";
|
|
11
|
+
|
|
12
|
+
export default function MaptilerMarkers({
|
|
13
|
+
clusters,
|
|
14
|
+
children,
|
|
15
|
+
}: MapMarkerProps) {
|
|
16
|
+
return clusters?.map((cluster, index) => {
|
|
17
|
+
const longitude = cluster.geometry.coordinates[0];
|
|
18
|
+
const latitude = cluster.geometry.coordinates[1];
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<MaplibreMarker
|
|
22
|
+
key={`map-marker-${longitude}-${latitude}-${index}`}
|
|
23
|
+
anchor="bottom"
|
|
24
|
+
longitude={longitude}
|
|
25
|
+
latitude={latitude}
|
|
26
|
+
>
|
|
27
|
+
<div className={styles.container}>
|
|
28
|
+
{cluster.properties.cluster ? (
|
|
29
|
+
<Cluster cluster={cluster} />
|
|
30
|
+
) : (
|
|
31
|
+
<>
|
|
32
|
+
{cluster.properties.icon ? (
|
|
33
|
+
<cluster.properties.icon className={styles.icon} />
|
|
34
|
+
) : (
|
|
35
|
+
<MapPin className={styles.icon} />
|
|
36
|
+
)}
|
|
37
|
+
{children}
|
|
38
|
+
</>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
</MaplibreMarker>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React, { lazy, useMemo, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import type { PointFeature, AnyProps } from "supercluster";
|
|
4
|
+
import { point } from "@turf/turf";
|
|
5
|
+
|
|
6
|
+
import useSupercluser from "use-supercluster";
|
|
7
|
+
|
|
8
|
+
import { useRootElement } from "../context";
|
|
9
|
+
|
|
10
|
+
import type { MarkerProps, ClusterPoint } from "./types";
|
|
11
|
+
|
|
12
|
+
import * as styles from "./styles";
|
|
13
|
+
|
|
14
|
+
const MaptilerMarkers = lazy(() => import("./Maptiler"));
|
|
15
|
+
const GoogleMarkers = lazy(() => import("./Google"));
|
|
16
|
+
const MapboxMarkers = lazy(() => import("./Mapbox"));
|
|
17
|
+
|
|
18
|
+
export const Markers = ({ markers, ...rest }: MarkerProps) => {
|
|
19
|
+
const { mapProvider, bounds, setBounds } = useRootElement();
|
|
20
|
+
const { clusters, supercluster } = useSupercluser({
|
|
21
|
+
points: markers.map(marker => (point([marker.longitude, marker.latitude], {
|
|
22
|
+
icon: marker.icon,
|
|
23
|
+
}))),
|
|
24
|
+
bounds,
|
|
25
|
+
zoom: 16,
|
|
26
|
+
options: { radius: 50, maxZoom: 30 },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const BaseMarkers = useMemo(() => {
|
|
30
|
+
switch (mapProvider?.name) {
|
|
31
|
+
case "maptiler":
|
|
32
|
+
return MaptilerMarkers;
|
|
33
|
+
case "google":
|
|
34
|
+
return GoogleMarkers;
|
|
35
|
+
case "mapbox":
|
|
36
|
+
return MapboxMarkers;
|
|
37
|
+
default:
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}, [mapProvider]);
|
|
41
|
+
|
|
42
|
+
if (!BaseMarkers) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<BaseMarkers clusters={clusters as ClusterPoint[]} {...rest}>
|
|
48
|
+
{/* POI Pin Background */}
|
|
49
|
+
<svg
|
|
50
|
+
viewBox="0 0 20 28"
|
|
51
|
+
fill="none"
|
|
52
|
+
className={styles.background}
|
|
53
|
+
>
|
|
54
|
+
<path
|
|
55
|
+
d="M20 10C20 15.5228 12 27.5 10 27.5C8 27.5 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10Z"
|
|
56
|
+
fill="inherit"
|
|
57
|
+
/>
|
|
58
|
+
</svg>
|
|
59
|
+
</BaseMarkers>
|
|
60
|
+
)
|
|
61
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const container = `
|
|
2
|
+
cursor-pointer
|
|
3
|
+
relative
|
|
4
|
+
h-8
|
|
5
|
+
w-6
|
|
6
|
+
text-base-white
|
|
7
|
+
group
|
|
8
|
+
transition-transform
|
|
9
|
+
hover:scale-125
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const background = `
|
|
13
|
+
cursor-pointer
|
|
14
|
+
fill-primary-100
|
|
15
|
+
stroke-1
|
|
16
|
+
box-border
|
|
17
|
+
transition-colors
|
|
18
|
+
transition-transform
|
|
19
|
+
group-hover:stroke-base-white
|
|
20
|
+
group-focus:stroke-base-white
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const icon = `
|
|
24
|
+
absolute
|
|
25
|
+
top-0 left-0 right-0 bottom-1
|
|
26
|
+
transition-transform
|
|
27
|
+
m-auto
|
|
28
|
+
h-4 w-4
|
|
29
|
+
`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { PointFeature, ClusterProperties } from "supercluster";
|
|
3
|
+
import type { Marker } from "../types";
|
|
4
|
+
|
|
5
|
+
export type ClusterPoint = PointFeature<ClusterProperties & Marker>;
|
|
6
|
+
|
|
7
|
+
export type MarkerProps = {
|
|
8
|
+
markers: Marker[];
|
|
9
|
+
onClick?: (marker: Marker) => void;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type MapMarkerProps = Omit<MarkerProps, "markers"> & {
|
|
14
|
+
clusters: ClusterPoint[];
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React, { useContext } from "react";
|
|
2
|
+
import type { BBox } from "geojson";
|
|
3
|
+
import type { RootProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export type DefaultContext = RootProps & {
|
|
6
|
+
bounds?: BBox;
|
|
7
|
+
setBounds: (bounds: BBox) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const RootElementContext = React.createContext<DefaultContext>(
|
|
11
|
+
{} as DefaultContext,
|
|
12
|
+
);
|
|
13
|
+
const useRootElement = () => useContext(RootElementContext);
|
|
14
|
+
|
|
15
|
+
export { useRootElement, RootElementContext };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import type { RootProps } from "./types";
|
|
3
|
+
import { RootElementContext, type DefaultContext } from "./context";
|
|
4
|
+
|
|
5
|
+
export function Root(props: RootProps) {
|
|
6
|
+
const [bounds, setBounds] = useState<DefaultContext["bounds"]>();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<RootElementContext.Provider value={{ ...props, bounds, setBounds }}>
|
|
10
|
+
{props.children}
|
|
11
|
+
</RootElementContext.Provider>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { BaseMap } from "./BaseMap";
|
|
16
|
+
export { Markers } from "./Markers";
|
|
17
|
+
export type { MarkerProps } from "./Markers/types";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Icon } from "@phosphor-icons/react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* We should try to keep the types "agnostic" but if we have to pick a side,
|
|
6
|
+
* we should side with the maptiler namings.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type MapProvider = {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
name: "maptiler" | "google" | "mapbox";
|
|
12
|
+
maptilerTheme?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type Coordinates = {
|
|
16
|
+
latitude: number;
|
|
17
|
+
longitude: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type Marker = {
|
|
21
|
+
latitude: Coordinates["latitude"];
|
|
22
|
+
longitude: Coordinates["longitude"];
|
|
23
|
+
icon?: Icon;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ZoomPosition =
|
|
27
|
+
| "top-left"
|
|
28
|
+
| "top-right"
|
|
29
|
+
| "bottom-left"
|
|
30
|
+
| "bottom-right";
|
|
31
|
+
|
|
32
|
+
export type RootProps = {
|
|
33
|
+
mapProvider: MapProvider;
|
|
34
|
+
center: Coordinates;
|
|
35
|
+
zoom?: number;
|
|
36
|
+
zoomPosition?: ZoomPosition;
|
|
37
|
+
pitch?: number;
|
|
38
|
+
bearing?: number;
|
|
39
|
+
cooperativeGestures?: boolean;
|
|
40
|
+
markers?: Marker[];
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StoryFn, Meta } from "@storybook/react";
|
|
3
|
+
import { mapDefaults as storybookMapDefaults } from "~/../.storybook/defaults";
|
|
4
|
+
import type { RootProps, Marker } from "./Root/types";
|
|
5
|
+
import { defaultMapValues } from "./Root/constants";
|
|
6
|
+
import * as Map from "./Root";
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: "Map",
|
|
10
|
+
component: Map.Root,
|
|
11
|
+
argTypes: storybookMapDefaults,
|
|
12
|
+
} as Meta<RootProps>;
|
|
13
|
+
|
|
14
|
+
const defaultValues = {
|
|
15
|
+
...defaultMapValues,
|
|
16
|
+
center: {
|
|
17
|
+
latitude: 45.5282164,
|
|
18
|
+
longitude: -73.5978527,
|
|
19
|
+
},
|
|
20
|
+
zoomPosition: "bottom-right" as RootProps["zoomPosition"],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const markers = [
|
|
24
|
+
{
|
|
25
|
+
latitude: 45.527399,
|
|
26
|
+
longitude: -73.598126,
|
|
27
|
+
// icon: "Storefront",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
latitude: 45.527302,
|
|
31
|
+
longitude: -73.597405,
|
|
32
|
+
// icon: "CartPlusInside",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
latitude: 45.527302,
|
|
36
|
+
longitude: -73.597405,
|
|
37
|
+
// icon: "Brandy",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
latitude: 45.527302,
|
|
41
|
+
longitude: -73.597405,
|
|
42
|
+
// icon: "SpoonAndFork",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
latitude: 45.527302,
|
|
46
|
+
longitude: -73.597405,
|
|
47
|
+
// icon: "Coffee",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
latitude: 45.527302,
|
|
51
|
+
longitude: -73.597405,
|
|
52
|
+
// icon: "Baby",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
latitude: 45.527256,
|
|
56
|
+
longitude: -73.600229,
|
|
57
|
+
// icon: "Train",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
latitude: 45.527256,
|
|
61
|
+
longitude: -73.600229,
|
|
62
|
+
// icon: "Barbell",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
latitude: 45.526643,
|
|
66
|
+
longitude: -73.600293,
|
|
67
|
+
// icon: "GasPump",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
latitude: 45.527025,
|
|
71
|
+
longitude: -73.600897,
|
|
72
|
+
// icon: "Heartbeat",
|
|
73
|
+
},
|
|
74
|
+
] as Marker[];
|
|
75
|
+
|
|
76
|
+
const maptilerThemes = {
|
|
77
|
+
day: "600d69cb-288d-445e-9839-3dfe4d76b31a",
|
|
78
|
+
night: "dd191599-2a92-49fc-9a33-e12391753ad5",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const Template: StoryFn<RootProps> = (args) => (
|
|
82
|
+
<div className="w-full h-[calc(100vh-30px)]">
|
|
83
|
+
<Map.Root {...args}>
|
|
84
|
+
<Map.BaseMap>
|
|
85
|
+
<Map.Markers markers={markers} />
|
|
86
|
+
</Map.BaseMap>
|
|
87
|
+
</Map.Root>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export const Maptiler = Template.bind({});
|
|
92
|
+
Maptiler.args = {
|
|
93
|
+
mapProvider: {
|
|
94
|
+
name: "maptiler",
|
|
95
|
+
apiKey: import.meta.env.VITE_MAPTILER_KEY,
|
|
96
|
+
maptilerTheme: maptilerThemes.day,
|
|
97
|
+
},
|
|
98
|
+
...defaultValues,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const Mapbox = Template.bind({});
|
|
102
|
+
Mapbox.args = {
|
|
103
|
+
mapProvider: {
|
|
104
|
+
name: "mapbox",
|
|
105
|
+
apiKey: import.meta.env.VITE_MAPBOX_KEY,
|
|
106
|
+
},
|
|
107
|
+
...defaultValues,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const Google = Template.bind({});
|
|
111
|
+
Google.args = {
|
|
112
|
+
mapProvider: {
|
|
113
|
+
name: "google",
|
|
114
|
+
apiKey: import.meta.env.VITE_GOOGLE_KEY,
|
|
115
|
+
},
|
|
116
|
+
...defaultValues,
|
|
117
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// filepath: /Users/corado/dev/client-packages/packages/maps/src/components/Map/index.test.tsx
|
|
4
|
+
|
|
5
|
+
// Mock the "./Root" module
|
|
6
|
+
vi.mock("./Root", () => ({
|
|
7
|
+
__esModule: true,
|
|
8
|
+
default: "MockedRoot",
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe("Map Component Exports", () => {
|
|
12
|
+
it("should export Root from './Root'", async () => {
|
|
13
|
+
const { Root } = await import("./index");
|
|
14
|
+
expect(Root).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as Root from "./Root";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as Map from "~/components/Map";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("@local-logic/design-system/tailwind.config.cjs");
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "tsconfig/react-library.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"paths": {
|
|
9
|
+
"~/*": ["./src/*"]
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"include": ["./src/**/*", "./vite-env.d.ts"],
|
|
13
|
+
"exclude": ["dist", "build", "node_modules"]
|
|
14
|
+
}
|
package/vite-env.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vite-plugin-svgr/client" />
|
|
2
|
+
/// <reference types="vite/client" />
|
|
3
|
+
|
|
4
|
+
interface ImportMetaEnv {
|
|
5
|
+
readonly VITE_MAPTILER_KEY: string;
|
|
6
|
+
readonly VITE_MAPBOX_KEY: string;
|
|
7
|
+
readonly VITE_GOOGLE_KEY: string;
|
|
8
|
+
// more env variables...
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ImportMeta {
|
|
12
|
+
readonly env: ImportMetaEnv;
|
|
13
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import react from "@vitejs/plugin-react";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { visualizer } from "rollup-plugin-visualizer";
|
|
4
|
+
import { defineConfig, PluginOption } from "vite";
|
|
5
|
+
import svgr from "vite-plugin-svgr";
|
|
6
|
+
import dts from "vite-plugin-dts";
|
|
7
|
+
|
|
8
|
+
// https://vitejs.dev/config/
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
build: {
|
|
11
|
+
lib: {
|
|
12
|
+
entry: resolve(__dirname, "src/index.ts"),
|
|
13
|
+
fileName: (format) => `index.${format}.js`,
|
|
14
|
+
name: "LocalLogicMaps",
|
|
15
|
+
},
|
|
16
|
+
/**
|
|
17
|
+
* This option will only minify the UMD output. Package users can still see
|
|
18
|
+
* the unminified "index.es.js" output.
|
|
19
|
+
*/
|
|
20
|
+
minify: true,
|
|
21
|
+
},
|
|
22
|
+
resolve: {
|
|
23
|
+
alias: {
|
|
24
|
+
"~": resolve(__dirname, "./src"),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
plugins: [
|
|
28
|
+
svgr({
|
|
29
|
+
include: "**/*.svg",
|
|
30
|
+
}),
|
|
31
|
+
/**
|
|
32
|
+
* Generate typings
|
|
33
|
+
*/
|
|
34
|
+
dts({
|
|
35
|
+
insertTypesEntry: true,
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
// Has to be after generating the types
|
|
39
|
+
react(),
|
|
40
|
+
|
|
41
|
+
// This option MUST come last. Note that build size WILL NOT represent build
|
|
42
|
+
// size in client app because it is not minified (client app should handle
|
|
43
|
+
// minification).
|
|
44
|
+
visualizer({ brotliSize: true, open: !!process.env.STATS }) as PluginOption,
|
|
45
|
+
],
|
|
46
|
+
});
|