@tpzdsp/next-toolkit 1.12.0 → 1.13.0

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.
Files changed (40) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -6
  3. package/src/assets/styles/globals.css +21 -0
  4. package/src/assets/styles/ol.css +147 -176
  5. package/src/components/InfoBox/InfoBox.stories.tsx +457 -0
  6. package/src/components/InfoBox/InfoBox.test.tsx +382 -0
  7. package/src/components/InfoBox/InfoBox.tsx +177 -0
  8. package/src/components/InfoBox/hooks/index.ts +3 -0
  9. package/src/components/InfoBox/hooks/useInfoBoxPosition.test.ts +187 -0
  10. package/src/components/InfoBox/hooks/useInfoBoxPosition.ts +69 -0
  11. package/src/components/InfoBox/hooks/useInfoBoxState.test.ts +168 -0
  12. package/src/components/InfoBox/hooks/useInfoBoxState.ts +71 -0
  13. package/src/components/InfoBox/hooks/usePortalMount.test.ts +62 -0
  14. package/src/components/InfoBox/hooks/usePortalMount.ts +15 -0
  15. package/src/components/InfoBox/types.ts +6 -0
  16. package/src/components/InfoBox/utils/focusTrapConfig.test.ts +310 -0
  17. package/src/components/InfoBox/utils/focusTrapConfig.ts +59 -0
  18. package/src/components/InfoBox/utils/index.ts +2 -0
  19. package/src/components/InfoBox/utils/positionUtils.test.ts +170 -0
  20. package/src/components/InfoBox/utils/positionUtils.ts +89 -0
  21. package/src/components/index.ts +8 -0
  22. package/src/http/logger.ts +1 -1
  23. package/src/map/FullScreenControl.ts +126 -0
  24. package/src/map/LayerSwitcherControl.ts +87 -181
  25. package/src/map/LayerSwitcherPanel.tsx +173 -0
  26. package/src/map/MapComponent.tsx +6 -35
  27. package/src/map/createControlButton.ts +72 -0
  28. package/src/map/geocoder/Geocoder.test.tsx +115 -0
  29. package/src/map/geocoder/Geocoder.tsx +393 -0
  30. package/src/map/geocoder/groupResults.ts +12 -0
  31. package/src/map/geocoder/index.ts +4 -0
  32. package/src/map/geocoder/types.ts +11 -0
  33. package/src/map/geometries.ts +7 -1
  34. package/src/map/index.ts +4 -1
  35. package/src/map/osOpenNamesSearch.ts +112 -57
  36. package/src/map/useKeyboardDrawing.ts +2 -2
  37. package/src/map/utils.ts +2 -1
  38. package/src/test/renderers.tsx +9 -20
  39. package/src/map/geocoder.ts +0 -61
  40. package/src/ol-geocoder.d.ts +0 -1
package/README.md CHANGED
@@ -139,7 +139,7 @@ interface MyComponentProps extends BaseProps {
139
139
 
140
140
  ```bash
141
141
  # Install OpenLayers dependencies
142
- npm install ol ol-geocoder ol-mapbox-style proj4
142
+ npm install ol ol-mapbox-style proj4
143
143
  ```
144
144
 
145
145
  ```typescript
@@ -152,14 +152,14 @@ import '@your-org/nextjs-library/styles/ol';
152
152
  Some components require additional peer dependencies that are marked as optional:
153
153
 
154
154
  - **Select components**: Require `react-select`
155
- - **Map components**: Require OpenLayers packages (`ol`, `ol-geocoder`, etc.)
155
+ - **Map components**: Require OpenLayers packages (`ol`, `ol-mapbox-style`, etc.)
156
156
 
157
157
  ```bash
158
158
  # For Select components
159
159
  npm install react-select
160
160
 
161
161
  # For Map components
162
- npm install ol ol-geocoder ol-mapbox-style proj4 @turf/turf geojson
162
+ npm install ol ol-mapbox-style proj4 @turf/turf geojson
163
163
  ```
164
164
 
165
165
  ```typescript
@@ -451,7 +451,7 @@ npm install react-select
451
451
 
452
452
  # Error: Cannot resolve module 'ol/ol.css'
453
453
  # Solution: Install OpenLayers dependencies
454
- npm install ol ol-geocoder ol-mapbox-style proj4
454
+ npm install ol ol-mapbox-style proj4
455
455
 
456
456
  # Or import components selectively to avoid these dependencies:
457
457
  import { Button, Card } from '@your-org/nextjs-library'; // ✅ No extra deps needed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpzdsp/next-toolkit",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "A reusable React component library for Next.js applications",
5
5
  "engines": {
6
6
  "node": ">= 24.12.0",
@@ -171,7 +171,6 @@
171
171
  "jsonwebtoken": "^9.0.3",
172
172
  "next": "^16.1.4",
173
173
  "ol": "^10.6.1",
174
- "ol-geocoder": "^4.3.3",
175
174
  "ol-mapbox-style": "^13.0.1",
176
175
  "postcss": "^8.5.6",
177
176
  "prettier": "^3.8.1",
@@ -213,7 +212,6 @@
213
212
  "geojson": "^0.5.0",
214
213
  "next": "^16.1.4",
215
214
  "ol": "^10.6.1",
216
- "ol-geocoder": "^4.3.3",
217
215
  "ol-mapbox-style": "^13.0.1",
218
216
  "proj4": "^2.19.10",
219
217
  "react": "^19.2.1",
@@ -256,9 +254,6 @@
256
254
  "ol": {
257
255
  "optional": true
258
256
  },
259
- "ol-geocoder": {
260
- "optional": true
261
- },
262
257
  "ol-mapbox-style": {
263
258
  "optional": true
264
259
  },
@@ -3,6 +3,27 @@
3
3
  @tailwind components;
4
4
  @tailwind utilities;
5
5
 
6
+ /**
7
+ * Accessibility: Respect user's motion preferences
8
+ *
9
+ * Many users experience discomfort or motion sickness from animations.
10
+ * This disables transitions and animations when the user has enabled
11
+ * "Reduce motion" in their operating system settings.
12
+ *
13
+ * Using 0.01ms instead of 0s ensures animation events still fire
14
+ * (which some JavaScript might depend on), but the effect is instant.
15
+ */
16
+ @media (prefers-reduced-motion: reduce) {
17
+ *,
18
+ *::before,
19
+ *::after {
20
+ animation-duration: 0.01ms !important;
21
+ animation-iteration-count: 1 !important;
22
+ transition-duration: 0.01ms !important;
23
+ scroll-behavior: auto !important;
24
+ }
25
+ }
26
+
6
27
  /* Custom font faces */
7
28
  @layer base {
8
29
  /* Add your custom font faces here */
@@ -1,42 +1,93 @@
1
1
  @import 'ol/ol.css';
2
- @import 'ol-geocoder/dist/ol-geocoder.min.css';
3
2
 
4
- /* Universal OpenLayers control focus style */
3
+ /**
4
+ * OpenLayers Control Styles
5
+ *
6
+ * This file contains styles for OpenLayers map controls (zoom, layer switcher, fullscreen).
7
+ *
8
+ * Design System:
9
+ * - All control buttons extend the `.ol-btn` base class
10
+ * - Control panels should be implemented as React components with Tailwind CSS
11
+ * - Use CSS custom properties (--focus-color, etc.) for consistent theming
12
+ *
13
+ * To add a new control:
14
+ * 1. Add the `ol-btn` class to your button element
15
+ * 2. Override only the properties you need (e.g., font-size, icon)
16
+ * 3. Position the control container with absolute positioning
17
+ */
18
+
19
+ :root {
20
+ --focus-color: #ffdd00;
21
+ --focus-outline-color: #ffbf47;
22
+ --control-bg: #fff;
23
+ --control-border: #505a5f;
24
+ --control-text: #0b0c0c;
25
+ --control-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
26
+ }
27
+
28
+ /* Base button class for all OpenLayers controls */
29
+ .ol-btn,
30
+ .ol-btn:hover,
31
+ .ol-btn:active,
32
+ .ol-btn:focus,
33
+ .ol-btn:focus-visible {
34
+ width: 40px !important;
35
+ height: 40px !important;
36
+ border: none !important;
37
+ outline: none !important;
38
+ }
39
+
40
+ .ol-btn {
41
+ background: var(--control-bg);
42
+ color: var(--control-text);
43
+ font-size: 1.5rem;
44
+ font-weight: bold;
45
+ line-height: 1;
46
+ box-shadow: var(--control-shadow);
47
+ cursor: pointer;
48
+ transition:
49
+ box-shadow 0.2s,
50
+ background 0.2s;
51
+ margin: 0;
52
+ padding: 0;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ }
57
+
58
+ /* Ensure SVG icons in buttons are consistently sized and centered */
59
+ .ol-btn svg {
60
+ width: 20px !important;
61
+ height: 20px !important;
62
+ flex-shrink: 0;
63
+ display: block;
64
+ margin: 0 auto;
65
+ }
66
+
67
+ .ol-btn:hover,
68
+ .ol-btn:active {
69
+ background: var(--focus-color) !important;
70
+ }
71
+
72
+ .ol-btn:focus,
73
+ .ol-btn:focus-visible {
74
+ outline: 3px solid var(--focus-outline-color) !important;
75
+ background: var(--focus-color) !important;
76
+ z-index: 2;
77
+ }
78
+
79
+ /* Universal OpenLayers control focus style for non-button elements */
5
80
  #map:focus,
6
81
  .ol-viewport:focus,
7
82
  .ol-control:focus,
8
- .ol-control button:focus,
9
- .ol-control button:focus-visible,
10
83
  .ol-attribution:focus,
11
- .ol-attribution button:focus,
12
- .ol-zoom:focus,
13
- .ol-zoom button:focus,
14
- .ol-layer-switcher-toggle:focus,
15
- .ol-layer-switcher-close:focus,
16
- .ol-layer-switcher-panel button:focus,
17
84
  .ol-attribution ul li a:focus,
18
- .ol-attribution ul li a:focus-visible,
19
- .ol-geocoder ul.gcd-txt-result > li > a:focus,
20
- .ol-geocoder ul.gcd-txt-result > li > a:focus-visible {
21
- outline: 3px solid #ffbf47 !important;
22
- border-color: #ffbf47 !important;
85
+ .ol-attribution ul li a:focus-visible {
86
+ outline: 3px solid var(--focus-outline-color) !important;
87
+ border-color: var(--focus-outline-color) !important;
23
88
  z-index: 2;
24
89
  }
25
90
 
26
- .ol-geocoder #gcd-input-search:focus,
27
- .ol-geocoder .gcd-txt-search:focus,
28
- .ol-geocoder .gcd-txt-search:focus-visible {
29
- background-color: #ffbf47 !important;
30
- z-index: 2;
31
- }
32
-
33
- .ol-geocoder .gcd-txt-input:focus,
34
- .ol-geocoder .gcd-txt-input:focus-visible {
35
- box-shadow:
36
- inset 0 0 0 1px #ffbf47,
37
- inset 0 0 6px #ffbf47;
38
- }
39
-
40
91
  /* Zoom control container */
41
92
  .ol-zoom {
42
93
  top: 1.5rem;
@@ -47,61 +98,26 @@
47
98
  z-index: 10;
48
99
  }
49
100
 
50
- /* Zoom buttons */
101
+ /* Apply base button styling to zoom buttons */
51
102
  .ol-zoom button,
52
- .ol-control button {
53
- width: 40px;
54
- height: 40px;
55
- background: #fff;
56
- color: #0b0c0c;
57
- font-size: 2rem;
58
- font-weight: bold;
59
- line-height: 1;
60
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
61
- cursor: pointer;
62
- transition:
63
- border-color 0.2s,
64
- box-shadow 0.2s,
65
- background 0.2s;
66
- margin: 0;
67
- padding: 0;
68
- display: flex;
69
- align-items: center;
70
- justify-content: center;
71
- }
72
-
73
103
  .ol-zoom button:hover,
74
- .ol-control button:hover {
75
- background: #f3f2f1;
76
- border-color: #1d70b8;
77
- outline: unset;
78
- }
79
-
80
- /* Layer switcher styles */
81
- .ol-layer-switcher {
82
- top: 0px;
83
- right: 1rem;
84
- }
85
-
86
- .ol-layer-switcher-toggle {
87
- position: absolute;
88
- top: 110px;
89
- right: 0px;
104
+ .ol-zoom button:active,
105
+ .ol-zoom button:focus,
106
+ .ol-zoom button:focus-visible {
107
+ width: 40px !important;
108
+ height: 40px !important;
109
+ border: none !important;
110
+ outline: none !important;
90
111
  }
91
112
 
92
- .ol-layer-switcher-toggle button {
93
- width: 40px;
94
- height: 40px;
95
- background: #fff;
96
- border: 2px solid #505a5f;
97
- color: #0b0c0c;
98
- font-size: 1.5rem;
113
+ .ol-zoom button {
114
+ background: var(--control-bg) !important;
115
+ color: var(--control-text) !important;
99
116
  font-weight: bold;
100
117
  line-height: 1;
101
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
118
+ box-shadow: var(--control-shadow);
102
119
  cursor: pointer;
103
120
  transition:
104
- border-color 0.2s,
105
121
  box-shadow 0.2s,
106
122
  background 0.2s;
107
123
  margin: 0;
@@ -109,124 +125,79 @@
109
125
  display: flex;
110
126
  align-items: center;
111
127
  justify-content: center;
128
+ font-size: 0 !important; /* Hide the default +/- text */
129
+ position: relative;
112
130
  }
113
131
 
114
- .ol-layer-switcher-panel {
115
- position: absolute;
116
- top: 110px;
117
- right: -100%;
118
- display: flex;
119
- flex-direction: column;
120
- align-items: stretch;
121
- padding: 1rem 1rem;
122
- min-width: 220px;
123
- background: #fff;
124
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
125
- gap: 0.5rem;
126
- transition: right 0.3s ease-in-out;
127
- }
128
-
129
- .ol-layer-switcher-panel.open {
130
- right: 3rem;
131
- }
132
-
133
- /* Header for the close button, aligns it to the top-right */
134
- .ol-layer-switcher-header {
135
- display: flex;
136
- justify-content: flex-end;
137
- align-items: center;
138
- width: 100%;
139
- }
140
-
141
- /* Small, circular close button */
142
- .ol-layer-switcher-close {
143
- font-size: 1.25rem;
144
- width: 2rem;
145
- height: 2rem;
146
- padding: 0;
147
- background: none;
148
- border: none;
149
- color: #505a5f;
150
- cursor: pointer;
151
- border-radius: 50%;
152
- transition: background 0.2s;
153
- }
154
-
155
- .ol-layer-switcher-close:hover,
156
- .ol-layer-switcher-close:focus {
157
- background: #e1e1e1;
158
- }
159
-
160
- /* Content area for basemap buttons */
161
- .ol-layer-switcher-content {
162
- display: flex;
163
- flex-direction: row;
164
- gap: 10px;
165
- }
166
-
167
- .ol-layer-switcher-content button {
168
- display: flex;
169
- flex-direction: column;
170
- align-items: center;
171
- justify-content: center;
172
- border: 2px solid #ccc;
173
- background-color: #fff;
174
- min-width: 135px;
175
- min-height: 135px;
176
- cursor: pointer;
177
- color: #000;
178
- }
179
-
180
- .ol-layer-switcher-content button:hover {
181
- background-color: #ddd;
132
+ .ol-zoom button:hover,
133
+ .ol-zoom button:active {
134
+ background: var(--focus-color) !important;
182
135
  }
183
136
 
184
- .ol-layer-switcher-content button.active {
185
- background-color: #007bff;
186
- color: white;
187
- border-color: #0056b3;
188
- box-shadow: 0 4px 10px rgba(0, 123, 255, 0.3);
189
- transform: scale(1.05);
190
- transition: all 0.2s ease;
137
+ .ol-zoom button:focus,
138
+ .ol-zoom button:focus-visible {
139
+ outline: 3px solid var(--focus-outline-color) !important;
140
+ background: var(--focus-color) !important;
141
+ z-index: 2;
191
142
  }
192
143
 
193
- .ol-layer-switcher-content button.active img {
194
- filter: brightness(1.2);
144
+ /* Override default zoom button text with SVG icons */
145
+ .ol-zoom-in::before,
146
+ .ol-zoom-out::before {
147
+ content: '';
148
+ display: block;
149
+ width: 20px;
150
+ height: 20px;
151
+ background-size: contain;
152
+ background-repeat: no-repeat;
153
+ background-position: center;
154
+ font-size: 0; /* Ensure no text shows */
195
155
  }
196
156
 
197
- .ol-layer-switcher-content button:not(.active) img {
198
- filter: grayscale(1);
157
+ .ol-zoom-in::before {
158
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 5v14m-7-7h14" stroke="%230b0c0c" stroke-width="2" stroke-linecap="round"/></svg>');
199
159
  }
200
160
 
201
- .ol-layer-switcher-content button img {
202
- max-width: 80px;
203
- height: auto;
204
- border: 2px solid #000;
161
+ .ol-zoom-out::before {
162
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M5 12h14" stroke="%230b0c0c" stroke-width="2" stroke-linecap="round"/></svg>');
205
163
  }
206
164
 
207
- .ol-layer-switcher-content button div {
208
- font-size: 14px;
209
- padding-top: 0.5rem;
165
+ /* Layer switcher control */
166
+ .ol-layer-switcher {
167
+ position: absolute;
168
+ top: 110px;
169
+ right: 1rem;
170
+ z-index: 9999;
210
171
  }
211
172
 
212
- /* Geocoder tweaks */
213
-
214
- #gcd-container,
215
- .ol-geocoder .gcd-txt-control {
216
- height: unset !important;
173
+ /* Full screen control */
174
+ .ol-fullscreen {
175
+ position: absolute;
176
+ top: 155px;
177
+ right: 1rem;
217
178
  }
218
179
 
219
- .ol-geocoder .gcd-txt-glass,
220
- .ol-geocoder ul.gcd-txt-result {
221
- top: unset !important;
180
+ /* Full screen mode for map container */
181
+ .map-fullscreen {
182
+ position: fixed !important;
183
+ top: 0 !important;
184
+ left: 0 !important;
185
+ right: 0 !important;
186
+ bottom: 0 !important;
187
+ width: 100vw !important;
188
+ height: 100vh !important;
189
+ z-index: 9999 !important;
190
+ margin: 0 !important;
191
+ padding: 0 !important;
222
192
  }
223
193
 
224
- .ol-geocoder ul.gcd-txt-result > li:nth-child(odd),
225
- .ol-geocoder ul.gcd-txt-result > li:nth-child(even) {
226
- background-color: #fff;
227
- background-color: #fff;
194
+ /* Ensure map fills container in full screen mode */
195
+ .map-fullscreen .ol-viewport {
196
+ width: 100% !important;
197
+ height: 100% !important;
228
198
  }
229
199
 
230
- .ol-geocoder ul.gcd-txt-result > li > a:hover {
231
- background-color: #f3f2f1;
200
+ /* Hide other UI elements when in full screen (optional) */
201
+ .map-fullscreen ~ * {
202
+ display: none;
232
203
  }