@teamimpact/veda-ui-blocks 0.1.0-beta.7 → 0.1.0-beta.8

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 (35) hide show
  1. package/README.md +2 -3
  2. package/dist/default.css +1388 -407
  3. package/dist/default.css.map +1 -1
  4. package/dist/disasters.css +1137 -151
  5. package/dist/disasters.css.map +1 -1
  6. package/dist/index.d.ts +110 -40
  7. package/dist/index.js +484 -54
  8. package/package.json +4 -6
  9. package/src/styles/components/banner.scss +0 -18
  10. package/src/styles/components/card-cta.scss +0 -90
  11. package/src/styles/components/card-detailed.scss +0 -164
  12. package/src/styles/components/card-mini.scss +0 -81
  13. package/src/styles/components/card-simple.scss +0 -79
  14. package/src/styles/components/card.scss +0 -284
  15. package/src/styles/components/compare-map.scss +0 -15
  16. package/src/styles/components/footer.scss +0 -256
  17. package/src/styles/components/header.scss +0 -212
  18. package/src/styles/components/index.scss +0 -11
  19. package/src/styles/components/tag.scss +0 -89
  20. package/src/styles/default/index.scss +0 -10
  21. package/src/styles/default/theme-tokens.scss +0 -84
  22. package/src/styles/disasters/card-cta.scss +0 -31
  23. package/src/styles/disasters/card-mini.scss +0 -4
  24. package/src/styles/disasters/card-simple.scss +0 -13
  25. package/src/styles/disasters/card.scss +0 -13
  26. package/src/styles/disasters/compare-map.scss +0 -3
  27. package/src/styles/disasters/fonts.scss +0 -29
  28. package/src/styles/disasters/footer.scss +0 -9
  29. package/src/styles/disasters/header.scss +0 -10
  30. package/src/styles/disasters/index.scss +0 -22
  31. package/src/styles/disasters/tag.scss +0 -6
  32. package/src/styles/disasters/theme-tokens.scss +0 -150
  33. package/src/styles/earthgov/footer.scss +0 -15
  34. package/src/styles/earthgov/index.scss +0 -12
  35. package/src/styles/earthgov/theme-tokens.scss +0 -104
package/dist/index.js CHANGED
@@ -19,9 +19,13 @@ var isNull = (x) => x === null;
19
19
  var isUndefined = (x) => x === void 0;
20
20
  var isNil = (x) => isNull(x) || isUndefined(x);
21
21
  var isDefined = (x) => !isNil(x);
22
+ var isNumber = (x) => typeof x === "number";
22
23
  var isObject = (x) => !isNil(x) && typeof x === "object" && x instanceof Object;
24
+ var isEmptyObject = (x) => isObject(x) && isEmptyArray(Object.keys(x));
23
25
  var isArray = (as) => Array.isArray(as);
26
+ var isEmptyArray = (as) => isArray(as) && as.length === 0;
24
27
  var isNonEmptyArray = (as) => isArray(as) && as.length > 0;
28
+ var getTypedValues = Object.values;
25
29
 
26
30
  // src/utils/component-utils.tsx
27
31
  var get_external_anchor_props = (isExternal) => isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {};
@@ -228,31 +232,32 @@ function CardDetailed({
228
232
  title,
229
233
  description,
230
234
  tags = [],
231
- contentTypeTag,
235
+ callToAction,
236
+ callToActionSecondary,
237
+ tagPrimary,
232
238
  image,
233
- action,
234
239
  imagePosition = "top",
235
- borderAccent = false,
236
- titleAs = "h2",
237
240
  className = ""
238
241
  }) {
239
- const TitleTag = titleAs;
240
- const cardClassName = make_class_name("blocks-card-detailed", [
242
+ const rootClassName = make_class_name("blocks-card-detailed", [
241
243
  imagePosition !== "top" ? `blocks-card-detailed--image-${imagePosition}` : "",
242
244
  className
243
245
  ]);
244
- return /* @__PURE__ */ jsxs4("div", { className: cardClassName, children: [
245
- borderAccent && /* @__PURE__ */ jsx5("div", { "aria-hidden": "true", className: "blocks-card-detailed__accent-bar" }),
246
- /* @__PURE__ */ jsxs4("div", { className: "blocks-card-detailed__content", children: [
247
- intro && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__intro", children: intro }),
248
- /* @__PURE__ */ jsx5(TitleTag, { className: "blocks-card-detailed__title", children: title }),
249
- description && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__description", children: description }),
250
- isNonEmptyArray(tags) && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__tags", children: tags }),
251
- action && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__action", children: action })
252
- ] }),
253
- /* @__PURE__ */ jsxs4("div", { className: "blocks-card-detailed__image-wrapper", children: [
246
+ const hasContent = intro || title || description || tags.length || callToAction || callToActionSecondary;
247
+ return /* @__PURE__ */ jsxs4("div", { className: rootClassName, children: [
248
+ /* @__PURE__ */ jsxs4("div", { className: "blocks-card-detailed__media", children: [
254
249
  image,
255
- contentTypeTag && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__content-type-tag", children: contentTypeTag })
250
+ tagPrimary && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__tag-primary", children: tagPrimary })
251
+ ] }),
252
+ hasContent && /* @__PURE__ */ jsxs4("div", { className: "blocks-card-detailed__content-container", children: [
253
+ intro && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__intro", title: intro, children: intro }),
254
+ title && (typeof title === "string" ? /* @__PURE__ */ jsx5("h2", { className: "blocks-card-detailed__title", title, children: title }) : title),
255
+ isNonEmptyArray(tags) && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__tags", children: tags }),
256
+ description && /* @__PURE__ */ jsx5("p", { className: "blocks-card-detailed__description", children: description }),
257
+ (callToAction || callToActionSecondary) && /* @__PURE__ */ jsxs4("div", { className: "blocks-card-detailed__actions", children: [
258
+ callToAction && /* @__PURE__ */ jsx5("a", { href: callToAction.href, className: "usa-button", children: callToAction.label }),
259
+ callToActionSecondary && /* @__PURE__ */ jsx5("a", { href: callToActionSecondary.href, className: "usa-button usa-button--outline", children: callToActionSecondary.label })
260
+ ] })
256
261
  ] })
257
262
  ] });
258
263
  }
@@ -282,21 +287,19 @@ function CardSimple({
282
287
  image,
283
288
  url,
284
289
  tag,
285
- showOverlay = false,
286
290
  size = "default",
287
291
  titleAs = "h2",
288
292
  className
289
293
  }) {
290
294
  const TitleTag = titleAs;
291
295
  const rootClassName = make_class_name("blocks-card-simple", [
292
- showOverlay ? "blocks-card-simple--with-overlay" : void 0,
293
296
  size === "compact" ? "blocks-card-simple--compact" : void 0,
294
297
  className
295
298
  ]);
296
299
  return /* @__PURE__ */ jsxs6("a", { href: url, className: rootClassName, children: [
297
300
  /* @__PURE__ */ jsxs6("div", { className: "blocks-card-simple__media", children: [
298
301
  image,
299
- showOverlay && /* @__PURE__ */ jsx7("div", { className: "blocks-card-simple__overlay", "aria-hidden": "true" })
302
+ /* @__PURE__ */ jsx7("div", { className: "blocks-card-simple__overlay", "aria-hidden": "true" })
300
303
  ] }),
301
304
  /* @__PURE__ */ jsxs6("div", { className: "blocks-card-simple__body", children: [
302
305
  tag && /* @__PURE__ */ jsx7("div", { className: "blocks-card-simple__tag", children: tag }),
@@ -321,24 +324,24 @@ function Footer({
321
324
  {
322
325
  className: footerClassName,
323
326
  size: "slim",
324
- primary: /* @__PURE__ */ jsxs7(GridContainer, { children: [
325
- portalDetails.logo ? /* @__PURE__ */ jsx8("section", { className: "blocks-footer__primary-section-upper", children: portalDetails.logo }) : null,
326
- /* @__PURE__ */ jsxs7("section", { className: "blocks-footer__primary-section-lower", children: [
327
- /* @__PURE__ */ jsxs7("div", { className: "blocks-footer__title", children: [
327
+ primary: /* @__PURE__ */ jsx8(GridContainer, { children: /* @__PURE__ */ jsxs7("section", { className: "blocks-footer__primary-section", children: [
328
+ /* @__PURE__ */ jsxs7("div", { className: "blocks-footer__title", children: [
329
+ portalDetails.logo ? /* @__PURE__ */ jsx8("div", { className: "blocks-footer__logo", children: portalDetails.logo }) : null,
330
+ /* @__PURE__ */ jsxs7("div", { className: "blocks-footer__title-text", children: [
328
331
  /* @__PURE__ */ jsx8("span", { children: portalDetails.title }),
329
332
  portalDetails.tagline ? /* @__PURE__ */ jsx8("p", { children: portalDetails.tagline }) : null
330
- ] }),
331
- primaryNavItems.length > 0 ? /* @__PURE__ */ jsx8("nav", { className: "blocks-footer__primary-nav", "aria-label": "Footer primary navigation", children: /* @__PURE__ */ jsx8("ul", { children: primaryNavItems.map(({ label, href, isExternal }) => /* @__PURE__ */ jsx8("li", { className: "usa-footer__primary-content", children: /* @__PURE__ */ jsx8(
332
- Link,
333
- {
334
- className: "usa-footer__primary-link",
335
- href,
336
- ...get_external_anchor_props(isExternal),
337
- children: label
338
- }
339
- ) }, href)) }) }) : null
340
- ] })
341
- ] }),
333
+ ] })
334
+ ] }),
335
+ primaryNavItems.length > 0 ? /* @__PURE__ */ jsx8("nav", { className: "blocks-footer__primary-nav", "aria-label": "Footer primary navigation", children: /* @__PURE__ */ jsx8("ul", { children: primaryNavItems.map(({ label, href, isExternal }) => /* @__PURE__ */ jsx8("li", { className: "usa-footer__primary-content", children: /* @__PURE__ */ jsx8(
336
+ Link,
337
+ {
338
+ className: "usa-footer__primary-link",
339
+ href,
340
+ ...get_external_anchor_props(isExternal),
341
+ children: label
342
+ }
343
+ ) }, href)) }) }) : null
344
+ ] }) }),
342
345
  secondary: /* @__PURE__ */ jsxs7(Fragment, { children: [
343
346
  secondaryNavItems.length > 0 ? /* @__PURE__ */ jsx8("section", { className: "blocks-footer__secondary-section-upper", children: /* @__PURE__ */ jsx8(
344
347
  "nav",
@@ -351,6 +354,7 @@ function Footer({
351
354
  utilityNavItems?.length || portalDetails.updatedDate || portalDetails.contacts?.length ? /* @__PURE__ */ jsxs7("section", { className: "blocks-footer__secondary-section-lower", children: [
352
355
  utilityNavItems?.length ? /* @__PURE__ */ jsx8("nav", { className: "blocks-footer__utility-nav", "aria-label": "Site utilities", children: /* @__PURE__ */ jsx8("ul", { children: utilityNavItems.length ? utilityNavItems.map(({ text, label, href, isExternal }) => /* @__PURE__ */ jsxs7("li", { children: [
353
356
  text,
357
+ " ",
354
358
  /* @__PURE__ */ jsx8(Link, { href, ...get_external_anchor_props(isExternal), children: label })
355
359
  ] }, href)) : null }) }) : null,
356
360
  portalDetails.updatedDate || portalDetails.contacts?.length ? /* @__PURE__ */ jsxs7("div", { className: "blocks-footer__secondary-meta", children: [
@@ -622,8 +626,185 @@ function Tag({
622
626
  ] });
623
627
  }
624
628
 
625
- // src/geo/SingleLayerMap/SingleLayerMap.tsx
626
- import { Map as MapLibreMap } from "react-map-gl/maplibre";
629
+ // src/geo/Legend/Legend.tsx
630
+ import { Icon as Icon3 } from "@trussworks/react-uswds";
631
+ import { useState as useState3 } from "react";
632
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
633
+ function Legend({ initialExpanded = false, ...props }) {
634
+ const { title, description, unit, min, max, colorStops, provider, spatialExtent, timeDensity } = props;
635
+ const [isExpanded, setIsExpanded] = useState3(initialExpanded);
636
+ const subtitle = [provider, spatialExtent, timeDensity].some(isDefined) ? [provider, spatialExtent, timeDensity].filter(isDefined).join(" \xB7 ") : void 0;
637
+ return /* @__PURE__ */ jsxs11("div", { className: "blocks-legend", children: [
638
+ /* @__PURE__ */ jsxs11("div", { className: "blocks-legend__header", children: [
639
+ /* @__PURE__ */ jsxs11("div", { className: "blocks-legend__header-text", children: [
640
+ title && /* @__PURE__ */ jsx12("h3", { className: "blocks-legend__title", title, children: title }),
641
+ subtitle && /* @__PURE__ */ jsx12("p", { className: "blocks-legend__subtitle", title: subtitle, children: subtitle })
642
+ ] }),
643
+ description && /* @__PURE__ */ jsx12(
644
+ "button",
645
+ {
646
+ type: "button",
647
+ className: "blocks-legend__info-toggle",
648
+ "aria-expanded": isExpanded,
649
+ "aria-label": "Toggle layer description",
650
+ onClick: () => setIsExpanded((v) => !v),
651
+ children: /* @__PURE__ */ jsx12(Icon3.InfoOutline, { "aria-hidden": true, focusable: false, size: 3 })
652
+ }
653
+ )
654
+ ] }),
655
+ /* @__PURE__ */ jsx12("div", { className: "blocks-legend__gradient-bar", children: /* @__PURE__ */ jsx12(
656
+ "div",
657
+ {
658
+ className: "blocks-legend__gradient",
659
+ style: {
660
+ background: `linear-gradient(to right, ${colorStops.join(", ")})`
661
+ }
662
+ }
663
+ ) }),
664
+ /* @__PURE__ */ jsxs11("div", { className: "blocks-legend__labels", children: [
665
+ /* @__PURE__ */ jsx12("span", { className: "blocks-legend__min", children: min }),
666
+ /* @__PURE__ */ jsx12("span", { className: "blocks-legend__unit", children: unit }),
667
+ /* @__PURE__ */ jsx12("span", { className: "blocks-legend__max", children: max })
668
+ ] }),
669
+ isExpanded && description && /* @__PURE__ */ jsx12("div", { className: "blocks-legend__description", children: /* @__PURE__ */ jsx12("div", { className: "blocks-legend__description-inner", children: description }) })
670
+ ] });
671
+ }
672
+
673
+ // src/geo/StacCompareMap/StacCompareMap.tsx
674
+ import { useEffect as useEffect3, useId, useRef as useRef3, useState as useState4 } from "react";
675
+ import {
676
+ Layer,
677
+ Map as MapLibreMap,
678
+ NavigationControl,
679
+ Source
680
+ } from "react-map-gl/maplibre";
681
+
682
+ // src/geo/AttributionMapControl.tsx
683
+ import maplibregl from "maplibre-gl";
684
+ import { useControl } from "react-map-gl/maplibre";
685
+ function AttributionMapControl({ position = "top-right" }) {
686
+ useControl(() => new maplibregl.AttributionControl(), { position });
687
+ return null;
688
+ }
689
+
690
+ // src/geo/utils/formatDateRange.ts
691
+ function formatDateRange(dateRange) {
692
+ const from = /* @__PURE__ */ new Date(`${dateRange.from}T00:00:00Z`);
693
+ const to = /* @__PURE__ */ new Date(`${dateRange.to}T00:00:00Z`);
694
+ if (from.getUTCFullYear() === to.getUTCFullYear() && from.getUTCMonth() === to.getUTCMonth()) {
695
+ return from.toLocaleDateString("en-US", {
696
+ month: "short",
697
+ year: "numeric",
698
+ timeZone: "UTC"
699
+ });
700
+ }
701
+ const fromStr = from.toLocaleDateString("en-US", {
702
+ month: "short",
703
+ day: "numeric",
704
+ year: "numeric",
705
+ timeZone: "UTC"
706
+ });
707
+ const toStr = to.toLocaleDateString("en-US", {
708
+ month: "short",
709
+ day: "numeric",
710
+ year: "numeric",
711
+ timeZone: "UTC"
712
+ });
713
+ return `${fromStr} \u2013 ${toStr}`;
714
+ }
715
+
716
+ // src/geo/DateChip/DateChip.tsx
717
+ import { jsx as jsx13 } from "react/jsx-runtime";
718
+ function DateChip({ left, right }) {
719
+ const label = right === void 0 || left.from === right.from && left.to === right.to ? formatDateRange(left) : `${formatDateRange(left)} vs ${formatDateRange(right)}`;
720
+ return /* @__PURE__ */ jsx13("div", { className: "blocks-date-chip", children: label });
721
+ }
722
+
723
+ // src/geo/hooks/useStacRasterLayer.ts
724
+ import { useCollection } from "@developmentseed/stac-react";
725
+
726
+ // src/geo/hooks/useMosaicTiles.ts
727
+ import { useQuery } from "@tanstack/react-query";
728
+
729
+ // src/geo/api/titiler/fetchTilejson.ts
730
+ async function fetchTilejson(tilejsonUrl, queryString) {
731
+ const response = await fetch(`${tilejsonUrl}?${queryString}`);
732
+ if (!response.ok) {
733
+ const error = { detail: `Failed to fetch tilejson: ${response.statusText}` };
734
+ throw error;
735
+ }
736
+ const tilejson = await response.json();
737
+ if (!tilejson.tiles?.[0]) {
738
+ const error = { detail: "No tile URL found in response" };
739
+ throw error;
740
+ }
741
+ return tilejson.tiles[0];
742
+ }
743
+
744
+ // src/geo/api/titiler/mosaicLinks.ts
745
+ function findTileLink(links) {
746
+ return links.find((link) => link.rel === "tiles") ?? links.at(1) ?? links.at(0) ?? null;
747
+ }
748
+ function buildTilejsonUrl(tileLinkHref, tileMatrixSet) {
749
+ return tileLinkHref.replace("/{tileMatrixSetId}", `/${tileMatrixSet}`);
750
+ }
751
+ function extractTileUrl(registrationResponse, tileMatrixSet) {
752
+ const tileLink = findTileLink(registrationResponse.links);
753
+ if (!tileLink?.href) {
754
+ const error = { detail: "No tile URL found in registration response" };
755
+ throw error;
756
+ }
757
+ const tilejsonUrl = buildTilejsonUrl(tileLink.href, tileMatrixSet);
758
+ return tilejsonUrl;
759
+ }
760
+
761
+ // src/geo/api/titiler/registerMosaic.ts
762
+ async function registerMosaic(request) {
763
+ const { rasterApiUrl, collectionId, datetime } = request;
764
+ const response = await fetch(`${rasterApiUrl}/searches/register`, {
765
+ method: "POST",
766
+ headers: { "Content-Type": "application/json" },
767
+ body: JSON.stringify({
768
+ collections: [collectionId],
769
+ datetime
770
+ })
771
+ });
772
+ if (!response.ok) {
773
+ const error = {
774
+ detail: `Failed to register mosaic: ${response.statusText}`,
775
+ status: response.status
776
+ };
777
+ throw error;
778
+ }
779
+ return response.json();
780
+ }
781
+
782
+ // src/geo/api/titiler/fetchMosaicTileUrl.ts
783
+ var TILE_PARAMS = {
784
+ ASSETS: "assets",
785
+ RESCALE: "rescale",
786
+ RESAMPLING: "resampling",
787
+ COLORMAP_NAME: "colormap_name",
788
+ BIDX: "bidx"
789
+ };
790
+ function buildQueryParams(sourceParams) {
791
+ const params = new URLSearchParams();
792
+ if (sourceParams.assets) params.append(TILE_PARAMS.ASSETS, sourceParams.assets.join(","));
793
+ if (sourceParams.rescale) params.append(TILE_PARAMS.RESCALE, sourceParams.rescale);
794
+ if (sourceParams.resampling) params.append(TILE_PARAMS.RESAMPLING, sourceParams.resampling);
795
+ if (sourceParams.colormap_name)
796
+ params.append(TILE_PARAMS.COLORMAP_NAME, sourceParams.colormap_name);
797
+ if (sourceParams.bidx) params.append(TILE_PARAMS.BIDX, sourceParams.bidx.join(","));
798
+ return params;
799
+ }
800
+ async function fetchMosaicTileUrl(request) {
801
+ const { sourceParams, tileMatrixSet } = request;
802
+ const queryParams = buildQueryParams(sourceParams);
803
+ const queryString = queryParams.toString();
804
+ const registrationResponse = await registerMosaic(request);
805
+ const tilejsonUrl = extractTileUrl(registrationResponse, tileMatrixSet);
806
+ return fetchTilejson(tilejsonUrl, queryString);
807
+ }
627
808
 
628
809
  // src/geo/constants.ts
629
810
  var NASA_BLUE_MARBLE_BASEMAP_STYLE = {
@@ -647,25 +828,272 @@ var NASA_BLUE_MARBLE_BASEMAP_STYLE = {
647
828
  }
648
829
  ]
649
830
  };
831
+ var TITILER_BASE_URL = "https://openveda.cloud/api/raster";
832
+ var DEFAULT_RENDER_PARAMS = {
833
+ assets: ["cog_default"],
834
+ rescale: "0,0.0001",
835
+ resampling: "bilinear",
836
+ colormap_name: "viridis",
837
+ bidx: [1]
838
+ };
839
+ var DEFAULT_TILE_MATRIX_SET = "WebMercatorQuad";
840
+ var STAC_COLORSTOP_RGBA_ARG_COUNT = 4;
650
841
 
651
- // src/geo/SingleLayerMap/SingleLayerMap.tsx
652
- import "maplibre-gl/dist/maplibre-gl.css";
653
- import { jsx as jsx12 } from "react/jsx-runtime";
654
- var SingleLayerMap = function SingleLayerMap2({
842
+ // src/geo/hooks/useMosaicTiles.ts
843
+ function useMosaicTiles(rasterLayerConfig) {
844
+ const { collectionId, dateRange, tileMatrixSet = DEFAULT_TILE_MATRIX_SET } = rasterLayerConfig;
845
+ const datetime = dateRange.from === dateRange.to ? dateRange.from : `${dateRange.from}/${dateRange.to}`;
846
+ const request = {
847
+ rasterApiUrl: TITILER_BASE_URL,
848
+ collectionId,
849
+ datetime,
850
+ sourceParams: {
851
+ ...DEFAULT_RENDER_PARAMS,
852
+ assets: [...DEFAULT_RENDER_PARAMS.assets],
853
+ bidx: [...DEFAULT_RENDER_PARAMS.bidx]
854
+ },
855
+ tileMatrixSet
856
+ };
857
+ return useQuery({
858
+ queryKey: [collectionId, dateRange],
859
+ queryFn: () => fetchMosaicTileUrl(request),
860
+ staleTime: 30 * 60 * 1e3,
861
+ gcTime: 60 * 60 * 1e3,
862
+ enabled: !!collectionId && !!dateRange?.from && !!dateRange?.to
863
+ });
864
+ }
865
+
866
+ // src/geo/hooks/useStacRasterLegend.ts
867
+ import { useQuery as useQuery2 } from "@tanstack/react-query";
868
+
869
+ // src/geo/utils/stac.formatters.ts
870
+ var formatSpatialExtent = (spatialExtent) => {
871
+ const [minX, minY, maxX, maxY] = spatialExtent?.bbox?.[0] ?? [];
872
+ if (minX === void 0 || minY === void 0 || maxX === void 0 || maxY === void 0)
873
+ return void 0;
874
+ return maxX - minX >= 359 && maxY - minY >= 179 ? "global" : "regional";
875
+ };
876
+
877
+ // src/geo/utils/stac.typeguards.ts
878
+ var isRgbaColorStopMap = (value) => {
879
+ return !isNil(value) && isObject(value) && !isEmptyObject(value) && getTypedValues(value).every(
880
+ (rgba) => isArray(rgba) && rgba.length === STAC_COLORSTOP_RGBA_ARG_COUNT && rgba.every(isNumber)
881
+ );
882
+ };
883
+
884
+ // src/geo/utils/extractLegendProps.ts
885
+ function extractLegendProps(collection, colorStopMap) {
886
+ const rescale = collection.renders?.dashboard?.rescale?.[0];
887
+ if (!rescale) return null;
888
+ if (!isRgbaColorStopMap(colorStopMap)) return null;
889
+ const colorStops = getTypedValues(colorStopMap).map((rgba) => `rgba(${rgba.join(",")})`);
890
+ const title = collection.title;
891
+ const description = collection.description;
892
+ const provider = collection.providers?.[0]?.name;
893
+ const timeDensity = collection.properties?.["dashboard:time_density"];
894
+ const spatialExtent = formatSpatialExtent(collection.extent?.spatial);
895
+ const unit = collection.item_assets?.cog_default?.["raster:bands"]?.[0]?.unit;
896
+ return {
897
+ type: "gradient",
898
+ ...title && { title },
899
+ ...description && { description },
900
+ min: rescale[0],
901
+ max: rescale[1],
902
+ colorStops,
903
+ ...provider && { provider },
904
+ ...timeDensity && { timeDensity },
905
+ ...spatialExtent && { spatialExtent },
906
+ ...unit && { unit }
907
+ };
908
+ }
909
+
910
+ // src/geo/hooks/useStacRasterLegend.ts
911
+ function useStacRasterLegend(collection) {
912
+ const query = useQuery2({
913
+ queryKey: ["legend", collection],
914
+ queryFn: async () => {
915
+ if (!collection) return null;
916
+ const colormapName = collection?.renders?.dashboard?.colormap_name;
917
+ if (!colormapName) return null;
918
+ const response = await fetch(`${TITILER_BASE_URL}/colorMaps/${colormapName}`);
919
+ if (!response.ok) {
920
+ const error = {
921
+ detail: `Colormap "${colormapName}" not found (HTTP ${response.status})`,
922
+ status: response.status
923
+ };
924
+ throw error;
925
+ }
926
+ const colorMap = await response.json();
927
+ return extractLegendProps(collection, colorMap);
928
+ },
929
+ enabled: !!collection
930
+ });
931
+ return {
932
+ legend: query.data ?? null,
933
+ isLoading: query.isLoading,
934
+ error: query.error || null
935
+ };
936
+ }
937
+
938
+ // src/geo/hooks/useStacRasterLayer.ts
939
+ function useStacRasterLayer(rasterLayerConfig) {
940
+ const { collectionId } = rasterLayerConfig;
941
+ const {
942
+ collection,
943
+ isLoading: isLoadingCollection,
944
+ error: collectionError
945
+ } = useCollection(collectionId);
946
+ const {
947
+ data: tiles,
948
+ isLoading: tilesetLoading,
949
+ error: tilesetError
950
+ } = useMosaicTiles(rasterLayerConfig);
951
+ const {
952
+ legend,
953
+ isLoading: isLoadingLegend,
954
+ error: legendError
955
+ } = useStacRasterLegend(collection);
956
+ return {
957
+ mapSource: {
958
+ tiles: tiles ?? null
959
+ },
960
+ legend,
961
+ isLoading: isLoadingCollection || tilesetLoading || isLoadingLegend,
962
+ error: collectionError || tilesetError || legendError || null
963
+ };
964
+ }
965
+
966
+ // src/geo/StacCompareMap/StacCompareMap.tsx
967
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
968
+ var MAP_PANEL_STYLE = {
969
+ position: "absolute",
970
+ top: 0,
971
+ bottom: 0,
972
+ width: "100%"
973
+ };
974
+ function StacCompareMap({
975
+ baseMapStyle,
976
+ initialViewState,
977
+ leftLayerConfig,
978
+ rightLayerConfig,
979
+ className
980
+ }) {
981
+ const instanceId = useId();
982
+ const leftMapRef = useRef3(null);
983
+ const rightMapRef = useRef3(null);
984
+ const compareRef = useRef3(null);
985
+ const containerRef = useRef3(null);
986
+ const [leftMapLoaded, setLeftMapLoaded] = useState4(false);
987
+ const [rightMapLoaded, setRightMapLoaded] = useState4(false);
988
+ const containerClassName = make_class_name("blocks-stacmap-compare", [className]);
989
+ const left = useStacRasterLayer(leftLayerConfig);
990
+ const right = useStacRasterLayer(rightLayerConfig);
991
+ const isSameCollection = leftLayerConfig.collectionId === rightLayerConfig.collectionId;
992
+ const leftSourceId = `${leftLayerConfig.collectionId}-raster-source-left-${instanceId}`;
993
+ const leftLayerId = `${leftLayerConfig.collectionId}-raster-layer-left-${instanceId}`;
994
+ const rightSourceId = `${rightLayerConfig.collectionId}-raster-source-right-${instanceId}`;
995
+ const rightLayerId = `${rightLayerConfig.collectionId}-raster-layer-right-${instanceId}`;
996
+ useEffect3(() => {
997
+ if (!leftMapLoaded || !rightMapLoaded) return;
998
+ if (compareRef.current) return;
999
+ const leftMap = leftMapRef.current?.getMap();
1000
+ const rightMap = rightMapRef.current?.getMap();
1001
+ const container = containerRef.current;
1002
+ if (!leftMap || !rightMap || !container) return;
1003
+ import("mapbox-gl-compare").then(({ default: MapboxCompare }) => {
1004
+ if (compareRef.current) return;
1005
+ try {
1006
+ compareRef.current = new MapboxCompare(leftMap, rightMap, container, {
1007
+ mousemove: false,
1008
+ orientation: "vertical"
1009
+ });
1010
+ } catch (error) {
1011
+ console.error("Failed to initialize mapbox-gl-compare:", error);
1012
+ }
1013
+ });
1014
+ }, [leftMapLoaded, rightMapLoaded]);
1015
+ useEffect3(() => {
1016
+ return () => {
1017
+ compareRef.current?.remove();
1018
+ compareRef.current = null;
1019
+ };
1020
+ }, []);
1021
+ const mapProps = {
1022
+ ...baseMapStyle && { mapStyle: baseMapStyle },
1023
+ ...initialViewState && { initialViewState },
1024
+ style: MAP_PANEL_STYLE
1025
+ };
1026
+ return /* @__PURE__ */ jsxs12("div", { ref: containerRef, className: containerClassName, children: [
1027
+ /* @__PURE__ */ jsxs12(
1028
+ MapLibreMap,
1029
+ {
1030
+ ref: leftMapRef,
1031
+ ...mapProps,
1032
+ attributionControl: false,
1033
+ onLoad: () => setLeftMapLoaded(true),
1034
+ children: [
1035
+ /* @__PURE__ */ jsx14(AttributionMapControl, {}),
1036
+ /* @__PURE__ */ jsx14(NavigationControl, { position: "top-left", showCompass: false }),
1037
+ left.mapSource.tiles && /* @__PURE__ */ jsx14(Source, { id: leftSourceId, type: "raster", tiles: [left.mapSource.tiles], children: /* @__PURE__ */ jsx14(Layer, { id: leftLayerId, type: "raster" }) })
1038
+ ]
1039
+ }
1040
+ ),
1041
+ /* @__PURE__ */ jsxs12(
1042
+ MapLibreMap,
1043
+ {
1044
+ ref: rightMapRef,
1045
+ ...mapProps,
1046
+ attributionControl: false,
1047
+ onLoad: () => setRightMapLoaded(true),
1048
+ children: [
1049
+ /* @__PURE__ */ jsx14(AttributionMapControl, {}),
1050
+ right.mapSource.tiles && /* @__PURE__ */ jsx14(Source, { id: rightSourceId, type: "raster", tiles: [right.mapSource.tiles], children: /* @__PURE__ */ jsx14(Layer, { id: rightLayerId, type: "raster" }) })
1051
+ ]
1052
+ }
1053
+ ),
1054
+ /* @__PURE__ */ jsx14("div", { className: "blocks-stacmap-compare__date-chip", children: /* @__PURE__ */ jsx14(DateChip, { left: leftLayerConfig.dateRange, right: rightLayerConfig.dateRange }) }),
1055
+ !isSameCollection && left.legend && /* @__PURE__ */ jsx14("div", { className: "blocks-stacmap-compare__legend-left", children: /* @__PURE__ */ jsx14(Legend, { ...left.legend }) }),
1056
+ right.legend && /* @__PURE__ */ jsx14("div", { className: "blocks-stacmap-compare__legend-right", children: /* @__PURE__ */ jsx14(Legend, { ...right.legend }) })
1057
+ ] });
1058
+ }
1059
+
1060
+ // src/geo/StacSingleLayerMap/StacSingleLayerMap.tsx
1061
+ import { useId as useId2 } from "react";
1062
+ import {
1063
+ Layer as Layer2,
1064
+ Map as MapLibreMap2,
1065
+ NavigationControl as NavigationControl2,
1066
+ Source as Source2
1067
+ } from "react-map-gl/maplibre";
1068
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1069
+ function StacSingleLayerMap({
655
1070
  baseMapStyle = NASA_BLUE_MARBLE_BASEMAP_STYLE,
656
1071
  initialViewState,
657
- children
1072
+ layerConfig
658
1073
  }) {
659
- return /* @__PURE__ */ jsx12(
660
- MapLibreMap,
661
- {
662
- mapStyle: baseMapStyle,
663
- ...initialViewState && { initialViewState },
664
- style: { width: "100%", flex: 1, minHeight: 0 },
665
- children
666
- }
667
- );
668
- };
1074
+ const instanceId = useId2();
1075
+ const sourceId = `${layerConfig.collectionId}-raster-source-${instanceId}`;
1076
+ const layerId = `${layerConfig.collectionId}-raster-layer-${instanceId}`;
1077
+ const { mapSource, legend } = useStacRasterLayer(layerConfig);
1078
+ return /* @__PURE__ */ jsxs13("div", { className: "blocks-stacmap-singlelayer", children: [
1079
+ /* @__PURE__ */ jsxs13(
1080
+ MapLibreMap2,
1081
+ {
1082
+ mapStyle: baseMapStyle,
1083
+ ...initialViewState && { initialViewState },
1084
+ style: { position: "absolute", inset: 0 },
1085
+ attributionControl: false,
1086
+ children: [
1087
+ /* @__PURE__ */ jsx15(AttributionMapControl, {}),
1088
+ /* @__PURE__ */ jsx15(NavigationControl2, { position: "top-left", showCompass: false }),
1089
+ mapSource.tiles && /* @__PURE__ */ jsx15(Source2, { id: sourceId, type: "raster", tiles: [mapSource.tiles], children: /* @__PURE__ */ jsx15(Layer2, { id: layerId, type: "raster" }) })
1090
+ ]
1091
+ }
1092
+ ),
1093
+ /* @__PURE__ */ jsx15("div", { className: "blocks-stacmap-singlelayer__legend", children: legend && /* @__PURE__ */ jsx15(Legend, { ...legend }) }),
1094
+ /* @__PURE__ */ jsx15("div", { className: "blocks-stacmap-singlelayer__date-chip", children: /* @__PURE__ */ jsx15(DateChip, { left: layerConfig.dateRange }) })
1095
+ ] });
1096
+ }
669
1097
  export {
670
1098
  Banner,
671
1099
  Button,
@@ -676,6 +1104,8 @@ export {
676
1104
  CardSimple,
677
1105
  Footer,
678
1106
  Header,
679
- SingleLayerMap,
1107
+ Legend,
1108
+ StacCompareMap,
1109
+ StacSingleLayerMap,
680
1110
  Tag
681
1111
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamimpact/veda-ui-blocks",
3
- "version": "0.1.0-beta.7",
3
+ "version": "0.1.0-beta.8",
4
4
  "description": "UI component library for VEDA-based portals, built on USWDS and React",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,12 +24,10 @@
24
24
  "types": "./dist/index.d.ts"
25
25
  },
26
26
  "./default.css": "./dist/default.css",
27
- "./disasters.css": "./dist/disasters.css",
28
- "./earthgov.css": "./dist/earthgov.css"
27
+ "./disasters.css": "./dist/disasters.css"
29
28
  },
30
29
  "files": [
31
- "dist",
32
- "src/styles"
30
+ "dist"
33
31
  ],
34
32
  "peerDependencies": {
35
33
  "@developmentseed/stac-react": "^1.0.0-alpha.2",
@@ -72,7 +70,7 @@
72
70
  "vitest": "^4.0.18"
73
71
  },
74
72
  "scripts": {
75
- "build": "tsup src/index.ts --format esm --dts && pnpm run build:assets && pnpm run build:css",
73
+ "build": "tsup src/index.ts --format esm --dts && pnpm run build:css",
76
74
  "build:assets": "./scripts/build-assets.sh",
77
75
  "build:css": "./scripts/build-css.sh --quiet",
78
76
  "build:css:watch": "./scripts/build-css.sh --quiet --watch --load-path=src/styles",
@@ -1,18 +0,0 @@
1
- @use "uswds-core" as *;
2
-
3
- .blocks-banner {
4
- &__row {
5
- display: flex;
6
- flex-wrap: wrap;
7
- gap: units(4);
8
- }
9
-
10
- &__guidance {
11
- flex: 1 1 100%;
12
- min-width: 0;
13
- @include at-media("tablet") {
14
- flex: 1 1 0;
15
- max-width: 50%;
16
- }
17
- }
18
- }