Last active
February 23, 2026 16:45
-
-
Save kgonzago/035d502c0e4330a120c417a5d8c9eae0 to your computer and use it in GitHub Desktop.
filter + apply + map + details
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| CONCEPT SNIPPET: | |
| Demonstrates accessible UI patterns (status announcements, focus management) | |
| using Calcite, Jimu UI, and ArcGIS Map components. Filtering logic and feature details | |
| are intentionally mocked for illustration only. | |
| **/ | |
| import React, { useEffect, useRef, useState } from "react"; | |
| // Jimu UI (Experience Builder) | |
| import { Button } from "jimu-ui"; | |
| // Calcite Components React wrappers | |
| import { | |
| CalcitePanel, | |
| CalciteLabel, | |
| CalciteSelect, | |
| CalciteOption | |
| } from "@esri/calcite-components-react"; | |
| // ArcGIS Maps SDK for JavaScript components | |
| import "@arcgis/map-components/components/arcgis-map"; | |
| import "@arcgis/map-components/components/arcgis-search"; | |
| import "@arcgis/map-components/components/arcgis-zoom"; | |
| import "@arcgis/map-components/components/arcgis-legend"; | |
| import "@arcgis/map-components/components/arcgis-feature"; | |
| type LayerKey = "parcels" | "buildings" | "roads"; | |
| export function AccessibleFilterMapWidget() { | |
| const mapElRef = useRef<HTMLArcgisMapElement | null>(null); | |
| // Ref for restoring focus after UI updates (pattern demo). | |
| // Jimu UI Button may not always forward a raw HTMLButtonElement ref, | |
| // so we keep this loose + add an id fallback. | |
| const applyBtnRef = useRef<HTMLElement | null>(null); | |
| const [layer, setLayer] = useState<LayerKey>("parcels"); | |
| const [status, setStatus] = useState("Loading map…"); | |
| const [selectedObjectId, setSelectedObjectId] = useState<number | null>(null); | |
| // Example filter criteria (conceptual). In real code you’d apply this to a layer query/effect. | |
| const whereByLayer: Record<LayerKey, string> = { | |
| parcels: "1=1", | |
| buildings: "1=1", | |
| roads: "1=1" | |
| }; | |
| useEffect(() => { | |
| const mapEl = mapElRef.current; | |
| if (!mapEl) return; | |
| (async () => { | |
| try { | |
| await mapEl.viewOnReady(); | |
| const { portalItem } = mapEl.map; | |
| mapEl.aria = { | |
| label: portalItem.title, | |
| }; | |
| setStatus("Map loaded."); | |
| } catch { | |
| setStatus("Map failed to load."); | |
| } | |
| })(); | |
| }, []); | |
| const restoreApplyFocus = () => { | |
| // Prefer ref if it works; fallback to id-based focus (robust for concept demo). | |
| if (applyBtnRef.current && "focus" in applyBtnRef.current) { | |
| (applyBtnRef.current as HTMLElement).focus(); | |
| return; | |
| } | |
| document.getElementById("apply-filters-btn")?.focus(); | |
| }; | |
| const applyFilter = async () => { | |
| const whereQuery = whereByLayer[layer]; | |
| // Status text should be concise; it will be announced via live region. | |
| setStatus(`Applying “${layer}” filter…`); | |
| // Concept snippet: simulate async work. | |
| await new Promise((r) => setTimeout(r, 250)); | |
| // Concept snippet: simulate a selected feature after filtering. | |
| setSelectedObjectId(101); | |
| setStatus(`Filter applied to “${layer}” (${whereQuery}). Showing selected feature details.`); | |
| // Keep focus stable after updates (a11y pattern). | |
| restoreApplyFocus(); | |
| }; | |
| return ( | |
| <div> | |
| {/* Live region for status updates so screen readers hear changes. */} | |
| <p | |
| id="widget-status" | |
| role="status" | |
| aria-live="polite" | |
| aria-atomic="true" | |
| style={{ marginTop: 0 }} | |
| > | |
| {status} | |
| {/* Optional helper for SR users; keep it short to avoid over-announcing. */} | |
| <span style={{ position: "absolute", left: -10000, top: "auto", width: 1, height: 1, overflow: "hidden" }}> | |
| Status updates will be announced. | |
| </span> | |
| </p> | |
| <CalcitePanel heading="Layer filters" description="Select a layer and apply filters"> | |
| <CalciteLabel> | |
| Select layer | |
| <CalciteSelect | |
| value={layer} | |
| onCalciteSelectChange={(event) => { | |
| const target = event.target as HTMLSelectElement; | |
| setLayer(target.value as LayerKey); | |
| setStatus(`Layer set to “${target.value}”. Ready to apply filter.`); | |
| }} | |
| > | |
| <CalciteOption value="parcels">Parcels</CalciteOption> | |
| <CalciteOption value="buildings">Buildings</CalciteOption> | |
| <CalciteOption value="roads">Roads</CalciteOption> | |
| </CalciteSelect> | |
| </CalciteLabel> | |
| {/* Jimu UI button: semantic button + keyboard support from the library */} | |
| <Button | |
| id="apply-filters-btn" | |
| type="primary" | |
| onClick={applyFilter} | |
| // Use callback ref to attempt to capture focusable element | |
| ref={(el: any) => { | |
| // store any focusable element; the fallback by id covers the rest | |
| applyBtnRef.current = el as unknown as HTMLElement; | |
| }} | |
| aria-describedby="widget-status" | |
| > | |
| Apply filters | |
| </Button> | |
| </CalcitePanel> | |
| {/* Map components. Controls use slots inside arcgis-map. */} | |
| <arcgis-map | |
| ref={(el: HTMLArcgisMapElement) => (mapElRef.current = el)} | |
| item-id="05e015c5f0314db9a487a9b46cb37eca" | |
| > | |
| <arcgis-search position="top-right"></arcgis-search> | |
| <arcgis-zoom position="top-right"></arcgis-zoom> | |
| <arcgis-legend position="bottom-left"></arcgis-legend> | |
| </arcgis-map> | |
| {/* Feature details outside the map for screen reader and keyboard users. */} | |
| <section aria-label="Selected feature details"> | |
| {selectedObjectId == null ? ( | |
| <p>No feature selected.</p> | |
| ) : ( | |
| <arcgis-feature object-id={String(selectedObjectId)}></arcgis-feature> | |
| )} | |
| </section> | |
| </div> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment