@zekidev/ui 2.0.0 → 2.1.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.
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ // src/sections/map/leaflet-map-inner.tsx
4
+ import "leaflet/dist/leaflet.css";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import L from "leaflet";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var OSM_ATTR = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
9
+ var CARTO_ATTR = `${OSM_ATTR} &copy; <a href="https://carto.com/attributions">CARTO</a>`;
10
+ var ESRI_ATTR = "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community";
11
+ var TILES = {
12
+ // OpenStreetMap
13
+ standard: {
14
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
15
+ attribution: OSM_ATTR
16
+ },
17
+ // CartoDB Positron — clean, minimal, ideal for data overlays
18
+ light: {
19
+ url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
20
+ attribution: CARTO_ATTR
21
+ },
22
+ // CartoDB Dark Matter
23
+ dark: {
24
+ url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
25
+ attribution: CARTO_ATTR
26
+ },
27
+ // CartoDB Voyager — colorful, modern
28
+ voyager: {
29
+ url: "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
30
+ attribution: CARTO_ATTR
31
+ },
32
+ // OpenTopoMap — topographic with elevation contours
33
+ topo: {
34
+ url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
35
+ attribution: `${OSM_ATTR}, <a href="https://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a>`,
36
+ maxZoom: 17
37
+ },
38
+ // ESRI World Imagery — satellite/aerial photography
39
+ satellite: {
40
+ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
41
+ attribution: ESRI_ATTR,
42
+ maxZoom: 18
43
+ },
44
+ // ESRI World Street Map — detailed street basemap
45
+ streets: {
46
+ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
47
+ attribution: ESRI_ATTR,
48
+ maxZoom: 18
49
+ }
50
+ };
51
+ var markerIcon = new L.Icon({
52
+ iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png",
53
+ iconRetinaUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png",
54
+ shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png",
55
+ iconSize: [25, 41],
56
+ iconAnchor: [12, 41],
57
+ popupAnchor: [1, -34],
58
+ shadowSize: [41, 41]
59
+ });
60
+ async function geocodeAddress(address) {
61
+ try {
62
+ const res = await fetch(
63
+ `https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(address)}`,
64
+ { headers: { "Accept-Language": "en", "User-Agent": "LandingBuilder/1.0" } }
65
+ );
66
+ if (!res.ok) return null;
67
+ const data = await res.json();
68
+ if (!data.length) return null;
69
+ return [parseFloat(data[0].lat), parseFloat(data[0].lon)];
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ async function geocodeAll(locations) {
75
+ const result = [];
76
+ for (const loc of locations) {
77
+ if (loc.lat != null && loc.lng != null) {
78
+ result.push({ ...loc, coords: [loc.lat, loc.lng] });
79
+ continue;
80
+ }
81
+ if (loc.address) {
82
+ const coords = await geocodeAddress(loc.address);
83
+ if (coords) result.push({ ...loc, coords });
84
+ await new Promise((r) => setTimeout(r, 1e3));
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+ function buildPopup(loc) {
90
+ const parts = [];
91
+ parts.push(`<strong style="display:block;font-size:13px">${loc.name}</strong>`);
92
+ if (loc.address)
93
+ parts.push(`<span style="display:block;font-size:11px;color:#6b7280;margin-top:2px">${loc.address}</span>`);
94
+ if (loc.phone)
95
+ parts.push(`<span style="display:block;font-size:11px;margin-top:2px">${loc.phone}</span>`);
96
+ if (loc.hours)
97
+ parts.push(`<span style="display:block;font-size:11px;color:#6b7280;margin-top:2px">${loc.hours}</span>`);
98
+ if (loc.address)
99
+ parts.push(
100
+ `<a href="https://maps.google.com/?q=${encodeURIComponent(loc.address)}" target="_blank" rel="noopener noreferrer" style="display:block;font-size:11px;color:#2563eb;margin-top:4px">Ver en Google Maps \u2192</a>`
101
+ );
102
+ return parts.join("");
103
+ }
104
+ function LeafletMapInner({ locations, mapStyle = "standard", className }) {
105
+ const containerRef = useRef(null);
106
+ const mapRef = useRef(null);
107
+ const tileRef = useRef(null);
108
+ const [loading, setLoading] = useState(!!locations?.length);
109
+ useEffect(() => {
110
+ if (!containerRef.current || mapRef.current) return;
111
+ const tile = TILES[mapStyle] ?? TILES.standard;
112
+ const map = L.map(containerRef.current, { scrollWheelZoom: false });
113
+ const tileLayer = L.tileLayer(tile.url, { attribution: tile.attribution, maxZoom: tile.maxZoom ?? 19 }).addTo(map);
114
+ map.setView([20, 0], 2);
115
+ mapRef.current = map;
116
+ tileRef.current = tileLayer;
117
+ return () => {
118
+ map.remove();
119
+ mapRef.current = null;
120
+ tileRef.current = null;
121
+ };
122
+ }, []);
123
+ useEffect(() => {
124
+ if (!mapRef.current) return;
125
+ const tile = TILES[mapStyle] ?? TILES.standard;
126
+ tileRef.current?.remove();
127
+ tileRef.current = L.tileLayer(tile.url, { attribution: tile.attribution, maxZoom: tile.maxZoom ?? 19 }).addTo(mapRef.current);
128
+ }, [mapStyle]);
129
+ useEffect(() => {
130
+ if (!locations?.length) {
131
+ setLoading(false);
132
+ return;
133
+ }
134
+ let cancelled = false;
135
+ setLoading(true);
136
+ geocodeAll(locations).then((geocoded) => {
137
+ if (cancelled || !mapRef.current) return;
138
+ mapRef.current.eachLayer((layer) => {
139
+ if (layer instanceof L.Marker) layer.remove();
140
+ });
141
+ geocoded.forEach((loc) => {
142
+ L.marker(loc.coords, { icon: markerIcon }).addTo(mapRef.current).bindPopup(buildPopup(loc));
143
+ });
144
+ if (geocoded.length === 1) {
145
+ mapRef.current.setView(geocoded[0].coords, 14);
146
+ } else if (geocoded.length > 1) {
147
+ mapRef.current.fitBounds(
148
+ geocoded.map((l) => l.coords),
149
+ { padding: [48, 48] }
150
+ );
151
+ }
152
+ setLoading(false);
153
+ });
154
+ return () => {
155
+ cancelled = true;
156
+ };
157
+ }, [locations]);
158
+ return /* @__PURE__ */ jsxs("div", { className: `relative ${className ?? ""}`, children: [
159
+ loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-[1000] flex items-center justify-center bg-muted/80 rounded-xl", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 text-muted-foreground text-sm", children: [
160
+ /* @__PURE__ */ jsxs("svg", { className: "animate-spin h-6 w-6", viewBox: "0 0 24 24", fill: "none", children: [
161
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
162
+ /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
163
+ ] }),
164
+ "Geocoding addresses\u2026"
165
+ ] }) }),
166
+ /* @__PURE__ */ jsx("div", { ref: containerRef, style: { height: "100%", width: "100%" } })
167
+ ] });
168
+ }
169
+ export {
170
+ LeafletMapInner
171
+ };
package/package.json CHANGED
@@ -1,26 +1,43 @@
1
1
  {
2
2
  "name": "@zekidev/ui",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
7
- "types": "./dist/index.d.ts",
8
- "import": "./dist/index.js",
9
- "require": "./dist/index.cjs",
10
- "default": "./dist/index.js"
7
+ "types": "./src/index.ts",
8
+ "import": "./src/index.ts",
9
+ "default": "./src/index.ts"
11
10
  },
12
11
  "./styles": "./src/styles/globals.css",
13
12
  "./tailwind-config": "./tailwind.config.ts"
14
13
  },
15
- "main": "./dist/index.cjs",
16
- "types": "./dist/index.d.ts",
14
+ "main": "./src/index.ts",
15
+ "types": "./src/index.ts",
17
16
  "files": [
18
17
  "dist",
19
18
  "src/styles",
20
19
  "tailwind.config.ts"
21
20
  ],
22
21
  "publishConfig": {
23
- "access": "public"
22
+ "access": "public",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./styles": "./src/styles/globals.css",
30
+ "./tailwind-config": "./tailwind.config.ts"
31
+ },
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup && tsc -p tsconfig.build.json",
38
+ "dev": "tsup --watch",
39
+ "type-check": "tsc --noEmit",
40
+ "lint": "eslint src --ext ts,tsx"
24
41
  },
25
42
  "dependencies": {
26
43
  "@portabletext/react": "^3.0.0",
@@ -56,12 +73,5 @@
56
73
  "tailwindcss": "^3.4.0",
57
74
  "tsup": "^8.0.0",
58
75
  "typescript": "^5.4.0"
59
- },
60
- "scripts": {
61
- "build": "tsup && tsc -p tsconfig.build.json",
62
- "dev": "tsup --watch",
63
- "type-check": "tsc --noEmit",
64
- "lint": "eslint src --ext ts,tsx"
65
- },
66
- "module": "./dist/index.js"
67
- }
76
+ }
77
+ }