@tpzdsp/next-toolkit 1.15.0 → 1.15.2
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/package.json +1 -1
- package/src/components/accordion/Accordion.tsx +29 -14
- package/src/map/MapComponent.tsx +38 -12
- package/src/map/MapContext.tsx +7 -0
- package/src/map/Popup.tsx +6 -32
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ type Props = {
|
|
|
11
11
|
title: string;
|
|
12
12
|
children: ReactNode;
|
|
13
13
|
defaultOpen?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export type AccordionProps = ExtendProps<'div', Props>;
|
|
@@ -19,6 +20,7 @@ export const Accordion = ({
|
|
|
19
20
|
title,
|
|
20
21
|
children,
|
|
21
22
|
defaultOpen = false,
|
|
23
|
+
disabled = false,
|
|
22
24
|
className,
|
|
23
25
|
...props
|
|
24
26
|
}: AccordionProps) => {
|
|
@@ -28,14 +30,25 @@ export const Accordion = ({
|
|
|
28
30
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
|
-
<div
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
'flex flex-col border-l-2 border-neutral-100',
|
|
36
|
+
disabled ? 'opacity-50' : '',
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
32
41
|
<button
|
|
33
|
-
aria-expanded={isOpen}
|
|
34
|
-
aria-controls={contentId}
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
aria-expanded={disabled ? undefined : isOpen}
|
|
43
|
+
aria-controls={disabled ? undefined : contentId}
|
|
44
|
+
disabled={disabled}
|
|
45
|
+
className={cn(
|
|
46
|
+
`flex justify-between items-center px-2 py-1 bg-[#fefefefe] text-[color:#000000]
|
|
47
|
+
border-y-2 border-neutral-100`,
|
|
48
|
+
disabled ? 'cursor-not-allowed' : 'focus-yellow',
|
|
49
|
+
)}
|
|
37
50
|
id={buttonId}
|
|
38
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
51
|
+
onClick={disabled ? undefined : () => setIsOpen(!isOpen)}
|
|
39
52
|
type="button"
|
|
40
53
|
>
|
|
41
54
|
<span>{title}</span>
|
|
@@ -45,14 +58,16 @@ export const Accordion = ({
|
|
|
45
58
|
</span>
|
|
46
59
|
</button>
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
{!disabled ? (
|
|
62
|
+
<section
|
|
63
|
+
id={contentId}
|
|
64
|
+
aria-labelledby={buttonId}
|
|
65
|
+
aria-hidden={!isOpen}
|
|
66
|
+
className={cn('p-2 bg-[#efefef]', isOpen ? 'block' : 'hidden')}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</section>
|
|
70
|
+
) : null}
|
|
56
71
|
</div>
|
|
57
72
|
);
|
|
58
73
|
};
|
package/src/map/MapComponent.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { memo, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { memo, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
4
4
|
|
|
5
|
-
import { Map, Overlay, View } from 'ol';
|
|
5
|
+
import { Feature, Map, Overlay, View } from 'ol';
|
|
6
6
|
import { Attribution, ScaleLine, Zoom } from 'ol/control';
|
|
7
7
|
import { fromLonLat } from 'ol/proj';
|
|
8
8
|
|
|
@@ -11,12 +11,13 @@ import { FullScreenControl } from './FullScreenControl';
|
|
|
11
11
|
import { LayerSwitcherControl } from './LayerSwitcherControl';
|
|
12
12
|
import { useMap } from './MapContext';
|
|
13
13
|
import { Popup } from './Popup';
|
|
14
|
-
import { getPopupPositionClass } from './utils';
|
|
14
|
+
import { getPopupPositionClass, LAYER_NAMES } from './utils';
|
|
15
15
|
import type { PopupDirection } from '../types/map';
|
|
16
16
|
|
|
17
17
|
export type MapComponentProps = {
|
|
18
18
|
osMapsApiKey?: string;
|
|
19
19
|
basePath: string;
|
|
20
|
+
children?: ReactNode;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
const positionTransforms: Record<PopupDirection, string> = {
|
|
@@ -43,8 +44,7 @@ const arrowStyles: Record<PopupDirection, string> = {
|
|
|
43
44
|
*
|
|
44
45
|
* @return {*}
|
|
45
46
|
*/
|
|
46
|
-
const MapComponentBase = ({ osMapsApiKey, basePath }: MapComponentProps) => {
|
|
47
|
-
const [popupFeatures, setPopupFeatures] = useState([]);
|
|
47
|
+
const MapComponentBase = ({ osMapsApiKey, basePath, children }: MapComponentProps) => {
|
|
48
48
|
const [popupCoordinate, setPopupCoordinate] = useState<number[] | null>(null);
|
|
49
49
|
const [popupPositionClass, setPopupPositionClass] = useState<PopupDirection>('bottom-right');
|
|
50
50
|
|
|
@@ -57,6 +57,8 @@ const MapComponentBase = ({ osMapsApiKey, basePath }: MapComponentProps) => {
|
|
|
57
57
|
mapConfig: { center, zoom },
|
|
58
58
|
setMap,
|
|
59
59
|
isDrawing,
|
|
60
|
+
popupFeatures,
|
|
61
|
+
setPopupFeatures,
|
|
60
62
|
} = useMap();
|
|
61
63
|
|
|
62
64
|
useEffect(() => {
|
|
@@ -117,17 +119,41 @@ const MapComponentBase = ({ osMapsApiKey, basePath }: MapComponentProps) => {
|
|
|
117
119
|
return;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
newMap.forEachFeatureAtPixel(event.pixel, (feature) => {
|
|
121
|
-
const
|
|
122
|
+
newMap.forEachFeatureAtPixel(event.pixel, (feature, layer) => {
|
|
123
|
+
const clusterFeatures = feature.get('features');
|
|
124
|
+
const layerName = (layer as { get(k: string): unknown } | null)?.get('name');
|
|
122
125
|
|
|
123
|
-
if (
|
|
126
|
+
if (clusterFeatures?.length > 0) {
|
|
124
127
|
const coordinate = event.coordinate;
|
|
125
128
|
const direction = getPopupPositionClass(coordinate, newMap);
|
|
126
129
|
|
|
127
|
-
setPopupFeatures(
|
|
130
|
+
setPopupFeatures(clusterFeatures);
|
|
128
131
|
setPopupCoordinate(coordinate);
|
|
129
132
|
setPopupPositionClass(direction);
|
|
130
133
|
overlay.setPosition(event.coordinate);
|
|
134
|
+
|
|
135
|
+
return true; // stop iteration
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Direct feature (e.g. polygon) — only show popup for the sampling points
|
|
139
|
+
// layer, not boundary or AOI layers which may also have name properties.
|
|
140
|
+
if (layerName !== LAYER_NAMES.SamplingPoints) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const name = feature.get('name');
|
|
145
|
+
const notation = feature.get('notation');
|
|
146
|
+
|
|
147
|
+
if (name || notation) {
|
|
148
|
+
const coordinate = event.coordinate;
|
|
149
|
+
const direction = getPopupPositionClass(coordinate, newMap);
|
|
150
|
+
|
|
151
|
+
setPopupFeatures([feature as Feature]);
|
|
152
|
+
setPopupCoordinate(coordinate);
|
|
153
|
+
setPopupPositionClass(direction);
|
|
154
|
+
overlay.setPosition(event.coordinate);
|
|
155
|
+
|
|
156
|
+
return true; // stop iteration
|
|
131
157
|
}
|
|
132
158
|
});
|
|
133
159
|
});
|
|
@@ -163,12 +189,12 @@ const MapComponentBase = ({ osMapsApiKey, basePath }: MapComponentProps) => {
|
|
|
163
189
|
>
|
|
164
190
|
{popupFeatures.length > 0 ? (
|
|
165
191
|
<Popup
|
|
166
|
-
features={popupFeatures}
|
|
167
192
|
onClose={closePopup}
|
|
168
193
|
clickedCoord={popupCoordinate}
|
|
169
194
|
arrowClasses={arrowStyles[popupPositionClass]}
|
|
170
|
-
|
|
171
|
-
|
|
195
|
+
>
|
|
196
|
+
{children}
|
|
197
|
+
</Popup>
|
|
172
198
|
) : null}
|
|
173
199
|
</div>
|
|
174
200
|
</div>
|
package/src/map/MapContext.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { Dispatch, RefObject, ReactNode, SetStateAction } from 'react';
|
|
4
4
|
import { createContext, useContext, useRef, useState, useMemo, useCallback } from 'react';
|
|
5
5
|
|
|
6
|
+
import { Feature } from 'ol';
|
|
6
7
|
import type { Coordinate } from 'ol/coordinate';
|
|
7
8
|
import BaseLayer from 'ol/layer/Base';
|
|
8
9
|
import Layer from 'ol/layer/Layer';
|
|
@@ -33,6 +34,8 @@ export type MapContextType = {
|
|
|
33
34
|
setSelectedLayer: Dispatch<SetStateAction<Layer | null>>;
|
|
34
35
|
isDrawing: boolean;
|
|
35
36
|
setIsDrawing: Dispatch<SetStateAction<boolean>>;
|
|
37
|
+
popupFeatures: Feature[];
|
|
38
|
+
setPopupFeatures: Dispatch<SetStateAction<Feature[]>>;
|
|
36
39
|
};
|
|
37
40
|
|
|
38
41
|
type MapProviderProps = {
|
|
@@ -60,6 +63,7 @@ export const MapProvider = ({ initialState = {}, children }: MapProviderProps) =
|
|
|
60
63
|
const [aoi, setAoi] = useState<Coordinate[][] | null>(null);
|
|
61
64
|
const [selectedLayer, setSelectedLayer] = useState<Layer | null>(null);
|
|
62
65
|
const [isDrawing, setIsDrawing] = useState(false);
|
|
66
|
+
const [popupFeatures, setPopupFeatures] = useState<Feature[]>([]);
|
|
63
67
|
|
|
64
68
|
const getLayers = useCallback(() => map?.getLayers().getArray(), [map]);
|
|
65
69
|
|
|
@@ -163,6 +167,8 @@ export const MapProvider = ({ initialState = {}, children }: MapProviderProps) =
|
|
|
163
167
|
setIsDrawing,
|
|
164
168
|
clearLayer,
|
|
165
169
|
resetMap,
|
|
170
|
+
popupFeatures,
|
|
171
|
+
setPopupFeatures,
|
|
166
172
|
...initialState,
|
|
167
173
|
}),
|
|
168
174
|
[
|
|
@@ -177,6 +183,7 @@ export const MapProvider = ({ initialState = {}, children }: MapProviderProps) =
|
|
|
177
183
|
isDrawing,
|
|
178
184
|
clearLayer,
|
|
179
185
|
resetMap,
|
|
186
|
+
popupFeatures,
|
|
180
187
|
initialState,
|
|
181
188
|
],
|
|
182
189
|
);
|
package/src/map/Popup.tsx
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { GoLinkExternal } from 'react-icons/go';
|
|
5
|
-
import { IoMdCloseCircle } from 'react-icons/io';
|
|
3
|
+
import { type ReactNode } from 'react';
|
|
6
4
|
|
|
7
|
-
import {
|
|
5
|
+
import { IoMdCloseCircle } from 'react-icons/io';
|
|
8
6
|
|
|
9
7
|
type PopupProps = {
|
|
10
|
-
|
|
8
|
+
children: ReactNode;
|
|
11
9
|
onClose: () => void;
|
|
12
10
|
clickedCoord: number[] | null;
|
|
13
11
|
arrowClasses: string;
|
|
14
|
-
baseUrl: string;
|
|
15
12
|
};
|
|
16
13
|
|
|
17
14
|
export const Popup = ({
|
|
18
|
-
|
|
15
|
+
children,
|
|
19
16
|
onClose,
|
|
20
17
|
clickedCoord,
|
|
21
18
|
arrowClasses = 'bottom-left',
|
|
22
|
-
baseUrl,
|
|
23
19
|
}: PopupProps) => {
|
|
24
|
-
if (!
|
|
20
|
+
if (!clickedCoord) {
|
|
25
21
|
return null;
|
|
26
22
|
}
|
|
27
23
|
|
|
@@ -40,29 +36,7 @@ export const Popup = ({
|
|
|
40
36
|
className="space-y-2 pt-4 pb-1 overflow-y-auto max-h-[300px] bg-white border border-border
|
|
41
37
|
rounded-lg divide-y divide-gray-300"
|
|
42
38
|
>
|
|
43
|
-
{
|
|
44
|
-
const id = feature.get('id');
|
|
45
|
-
const name = feature.get('name');
|
|
46
|
-
const notation = feature.get('notation');
|
|
47
|
-
const url = `${baseUrl}${notation}`;
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<div key={id}>
|
|
51
|
-
<strong>
|
|
52
|
-
<ExternalLink
|
|
53
|
-
href={url}
|
|
54
|
-
className="px-4 my-2 text-blue-500 underline flex flex-row gap-1 items-center"
|
|
55
|
-
>
|
|
56
|
-
<span>{name}</span>
|
|
57
|
-
|
|
58
|
-
<span className="flex-none w-5 h-5 flex items-center justify-center">
|
|
59
|
-
<GoLinkExternal size={20} className="cursor-pointer" />
|
|
60
|
-
</span>
|
|
61
|
-
</ExternalLink>
|
|
62
|
-
</strong>
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
})}
|
|
39
|
+
{children}
|
|
66
40
|
</div>
|
|
67
41
|
|
|
68
42
|
<div
|