diff --git a/src/essence/Tools/AOI/AOIComponent/AOIComponent.scss b/src/essence/Tools/AOI/AOIComponent/AOIComponent.scss deleted file mode 100644 index 230173089..000000000 --- a/src/essence/Tools/AOI/AOIComponent/AOIComponent.scss +++ /dev/null @@ -1,525 +0,0 @@ -// AOI plugin — scoped under .aoi-tool-host / .aoi-tool / .aoi-tooltip roots only. -// All literal values live in _aoi-tokens.scss; everywhere else uses var(--aoi-*). -// A host theme overrides --mmgis-* on :root and propagates through the var() fallbacks. - -@use 'aoi-tokens' as tokens; - -.aoi-tool-host { - position: fixed; - top: 70px; - right: 16px; - width: 372px; - max-height: calc(100vh - 90px); - overflow: hidden; - z-index: 1003; - border-radius: var(--mmgis-radius-md, 6px); - box-shadow: var(--mmgis-shadow-pop, 0 2px 6px rgba(0, 0, 0, 0.18)); - pointer-events: auto; -} - -.aoi-tool, -.aoi-tooltip { - @include tokens.aoi-tokens; - - box-sizing: border-box; - color: var(--aoi-fg); - font-family: var(--aoi-font-body); - font-size: var(--aoi-font-size-md); - background: var(--aoi-bg); -} - -.aoi-tool *, -.aoi-tooltip * { - box-sizing: border-box; -} - -.aoi-tool { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - padding: var(--aoi-space-4); - gap: var(--aoi-space-3); -} - -.aoi-tool__header { - display: flex; - align-items: center; - justify-content: space-between; -} - -.aoi-tool__title { - display: inline-flex; - align-items: center; - gap: var(--aoi-space-2); - font-size: var(--aoi-font-size-lg); - font-weight: 600; -} - -.aoi-tool__title-icon { - font-size: 16px; - line-height: 1; - color: var(--aoi-accent); -} - -.aoi-tool__close { - background: none; - border: 0; - color: var(--aoi-fg-muted); - font-size: 18px; - line-height: 1; - cursor: pointer; - padding: var(--aoi-space-1); - display: inline-flex; - align-items: center; - justify-content: center; -} - -.aoi-tool__close:hover { - color: var(--aoi-fg); -} - -.aoi-tool__alert { - display: flex; - align-items: flex-start; - gap: var(--aoi-space-2); - margin: var(--aoi-space-3) var(--aoi-space-4) 0; - padding: var(--aoi-space-3); - border-radius: var(--aoi-radius-sm); - border-left: 4px solid var(--aoi-danger); - background: rgba(181, 9, 9, 0.08); - color: var(--aoi-fg); - font-size: var(--aoi-font-size-sm); - line-height: 1.35; -} - -.aoi-tool__alert--error { - border-left-color: var(--aoi-danger); - background: rgba(181, 9, 9, 0.08); -} - -.aoi-tool__alert-icon { - flex: 0 0 auto; - color: var(--aoi-danger); - font-size: 18px; - line-height: 1; - margin-top: 1px; -} - -.aoi-tool__alert-text { - flex: 1 1 auto; - margin: 0; -} - -.aoi-tool__alert-dismiss { - flex: 0 0 auto; - width: 24px; - height: 24px; - padding: 0; - border: 0; - border-radius: var(--aoi-radius-sm); - background: transparent; - color: var(--aoi-fg-muted); - cursor: pointer; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.aoi-tool__alert-dismiss:hover { - color: var(--aoi-fg); - background: var(--aoi-bg-muted); -} - -.aoi-tool__alert-dismiss .mdi { - font-size: 16px; -} - -.aoi-tool__tabs { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: var(--aoi-space-1); - border: 1px solid var(--aoi-border); - border-radius: var(--aoi-radius-sm); - padding: var(--aoi-space-1); -} - -.aoi-tool__tab { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--aoi-space-1); - padding: var(--aoi-space-2) var(--aoi-space-1); - border: 0; - background: transparent; - color: var(--aoi-fg-muted); - border-radius: var(--aoi-radius-sm); - cursor: pointer; - font-family: inherit; - font-size: var(--aoi-font-size-sm); -} - -.aoi-tool__tab:hover { - color: var(--aoi-fg); -} - -.aoi-tool__tab--active { - background: var(--aoi-bg-muted); - color: var(--aoi-fg); -} - -.aoi-tool__tab-icon { - font-size: 18px; - line-height: 1; -} - -.aoi-tool__tab-label { - font-size: var(--aoi-font-size-sm); -} - -.aoi-tool__body { - flex: 1 1 auto; - min-height: 0; -} - -.aoi-panel { - display: flex; - flex-direction: column; - gap: var(--aoi-space-3); -} - -.aoi-panel__hint { - margin: 0; - color: var(--aoi-fg); - font-size: var(--aoi-font-size-sm); -} - -.aoi-panel__hint--secondary { - color: var(--aoi-fg-muted); -} - -.aoi-panel__empty { - margin: 0; - color: var(--aoi-fg-muted); - font-size: var(--aoi-font-size-sm); -} - -.aoi-panel__error { - margin: 0; - color: var(--aoi-danger); - font-size: var(--aoi-font-size-sm); -} - -.aoi-panel--analyzing { - flex: 1 1 auto; - align-items: center; - justify-content: center; - padding: var(--aoi-space-5) var(--aoi-space-4); - text-align: center; - gap: var(--aoi-space-2); -} - -.aoi-analyzing__spinner { - width: 40px; - height: 40px; - border: 3px solid var(--aoi-bg-muted); - border-top-color: var(--aoi-accent); - border-radius: 50%; - margin-bottom: var(--aoi-space-3); - animation: aoi-spin 800ms linear infinite; -} - -@keyframes aoi-spin { - to { transform: rotate(360deg); } -} - -@media (prefers-reduced-motion: reduce) { - .aoi-analyzing__spinner { animation-duration: 2400ms; } -} - -.aoi-analyzing__caption { - margin: 0; - color: var(--aoi-fg-muted); - font-size: var(--aoi-font-size-md); -} - -.aoi-analyzing__label { - margin: 0; - color: var(--aoi-fg); - font-size: var(--aoi-font-size-lg); - font-weight: 700; - line-height: 1.2; -} - -.aoi-analyzing__percent { - margin: var(--aoi-space-3) 0 0; - color: var(--aoi-accent); - font-size: 28px; - font-weight: 700; - line-height: 1; -} - -.aoi-analyzing__bar { - width: 80%; - height: 6px; - background: var(--aoi-bg-muted); - border-radius: 999px; - overflow: hidden; - margin-top: var(--aoi-space-2); -} - -.aoi-analyzing__bar-fill { - height: 100%; - background: var(--aoi-accent); - border-radius: 999px; - transition: width 200ms ease-out; -} - -.aoi-analyzing__status { - margin: var(--aoi-space-3) 0 0; - color: var(--aoi-fg-muted); - font-size: var(--aoi-font-size-sm); -} - -.aoi-search { - position: relative; - display: block; -} - -.aoi-search__input { - width: 100%; - height: 36px; - padding: 0 var(--aoi-space-5) 0 var(--aoi-space-3); - border: 1px solid var(--aoi-border); - border-radius: var(--aoi-radius-sm); - background: var(--aoi-bg); - color: var(--aoi-fg); - font: inherit; -} - -.aoi-search__input:focus { - outline: 2px solid var(--aoi-accent); - outline-offset: -1px; -} - -.aoi-search__input:disabled { - background: var(--aoi-bg-muted); - color: var(--aoi-fg-muted); - cursor: not-allowed; -} - -.aoi-search__icon { - position: absolute; - right: var(--aoi-space-3); - top: 50%; - transform: translateY(-50%); - font-size: 16px; - line-height: 1; - color: var(--aoi-fg-muted); - pointer-events: none; -} - -.aoi-search__results { - list-style: none; - margin: 0; - padding: 0; - border: 1px solid var(--aoi-border); - border-radius: var(--aoi-radius-sm); - max-height: 220px; - overflow-y: auto; -} - -.aoi-search__result { - display: flex; - width: 100%; - align-items: center; - justify-content: space-between; - padding: var(--aoi-space-2) var(--aoi-space-3); - background: transparent; - border: 0; - border-bottom: 1px solid var(--aoi-border); - color: var(--aoi-fg); - cursor: pointer; - text-align: left; - font: inherit; -} - -.aoi-search__result:last-child { - border-bottom: 0; -} - -.aoi-search__result:hover { - background: var(--aoi-bg-muted); -} - -.aoi-search__result-kind { - color: var(--aoi-fg-muted); - font-size: var(--aoi-font-size-sm); - text-transform: capitalize; -} - -.aoi-draw__shapes { - display: flex; - align-items: center; - gap: var(--aoi-space-2); - flex-wrap: wrap; -} - -.aoi-draw__shape, -.aoi-draw__history { - width: 36px; - height: 36px; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid var(--aoi-border); - border-radius: var(--aoi-radius-sm); - background: var(--aoi-bg); - color: var(--aoi-fg); - cursor: pointer; -} - -.aoi-draw__shape:hover:not(:disabled), -.aoi-draw__history:hover:not(:disabled) { - border-color: var(--aoi-border-hover); -} - -.aoi-draw__shape--active { - background: var(--aoi-bg-muted); - border-color: var(--aoi-fg); -} - -.aoi-draw__shape:disabled, -.aoi-draw__history:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.aoi-draw__shape-icon, -.aoi-draw__history-icon { - font-size: 16px; - line-height: 1; -} - -.aoi-draw__divider { - width: 1px; - height: 24px; - background: var(--aoi-border); - margin: 0 var(--aoi-space-1); -} - -.aoi-draw__actions { - display: flex; - gap: var(--aoi-space-2); -} - -.aoi-draw__confirm, -.aoi-draw__cancel { - flex: 1 1 auto; - height: 36px; - border-radius: var(--aoi-radius-sm); - cursor: pointer; - font: inherit; - font-weight: 600; -} - -.aoi-draw__confirm { - border: 1px solid var(--aoi-accent); - background: var(--aoi-accent); - color: var(--aoi-accent-fg); -} - -.aoi-draw__confirm:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.aoi-draw__cancel { - border: 1px solid var(--aoi-border); - background: var(--aoi-bg); - color: var(--aoi-fg); -} - -.aoi-upload__button { - width: 100%; - height: 40px; - border: 1px dashed var(--aoi-accent); - border-radius: var(--aoi-radius-sm); - background: transparent; - color: var(--aoi-accent); - cursor: pointer; - font: inherit; - font-weight: 600; - text-decoration: underline; -} - -.aoi-upload__button:hover:not(:disabled) { - background: var(--aoi-bg-muted); -} - -.aoi-upload__button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.aoi-upload__input { - display: none; -} - -.aoi-upload__formats { - margin: 0; - padding-left: var(--aoi-space-4); - color: var(--aoi-fg); - font-size: var(--aoi-font-size-sm); -} - -.aoi-tooltip { - position: absolute; - transform: translate(-50%, calc(-100% - var(--aoi-space-3))); - min-width: 220px; - padding: var(--aoi-space-3); - border: 1px solid var(--aoi-border); - border-radius: var(--aoi-radius-md); - box-shadow: var(--aoi-shadow-pop); - pointer-events: auto; - z-index: 1000; -} - -.aoi-tooltip__label { - margin: 0 0 var(--aoi-space-2); - font-size: var(--aoi-font-size-md); - font-weight: 600; -} - -.aoi-tooltip__actions { - display: flex; - gap: var(--aoi-space-2); -} - -.aoi-tooltip__primary, -.aoi-tooltip__secondary { - flex: 1 1 auto; - height: 32px; - border-radius: var(--aoi-radius-sm); - cursor: pointer; - font: inherit; - font-weight: 600; -} - -.aoi-tooltip__primary { - border: 1px solid var(--aoi-accent); - background: var(--aoi-accent); - color: var(--aoi-accent-fg); -} - -.aoi-tooltip__primary:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.aoi-tooltip__secondary { - border: 1px solid var(--aoi-border); - background: var(--aoi-bg); - color: var(--aoi-fg); -} diff --git a/src/essence/Tools/AOI/AOIComponent/AOIComponent.test.tsx b/src/essence/Tools/AOI/AOIComponent/AOIComponent.test.tsx deleted file mode 100644 index 761d95de5..000000000 --- a/src/essence/Tools/AOI/AOIComponent/AOIComponent.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Stub test file for AOIComponent. -// -// Mirrors the tinacms-portal-monorepo per-component test convention. -// Real assertions can be added once MMGIS's Jest config picks up TSX files -// under Tools/. For now this file documents the public surface contract. - -import React from 'react' -import AOIComponent, { - AOIComponentProps, - AOIMode, - AOIShape, -} from './AOIComponent' - -describe('AOIComponent', () => { - it('exports its public types', () => { - const _mode: AOIMode = 'search' - const _shape: AOIShape = 'polygon' - // Component is exported as default - expect(AOIComponent).toBeDefined() - expect(_mode).toBe('search') - expect(_shape).toBe('polygon') - }) - - it('accepts the full AOIComponentProps shape', () => { - const props: AOIComponentProps = { - mode: 'search', - onModeChange: () => undefined, - searchQuery: '', - searchResults: [], - onSearchQueryChange: () => undefined, - onSearchSelect: () => undefined, - drawShape: null, - isDrawing: false, - drawVerticesCount: 0, - onDrawShapeChange: () => undefined, - onDrawConfirm: () => undefined, - onDrawCancel: () => undefined, - uploadStatus: 'idle', - onUploadFile: () => undefined, - onClose: () => undefined, - } - expect(props.mode).toBe('search') - }) -}) diff --git a/src/essence/Tools/AOI/AOIComponent/AOIComponent.tsx b/src/essence/Tools/AOI/AOIComponent/AOIComponent.tsx deleted file mode 100644 index a2b529a66..000000000 --- a/src/essence/Tools/AOI/AOIComponent/AOIComponent.tsx +++ /dev/null @@ -1,389 +0,0 @@ -import React, { useEffect, useRef } from 'react' -import { Alert, Button, TextInput } from '@trussworks/react-uswds' -import './AOIComponent.scss' - -export type AOIMode = 'search' | 'inspect' | 'draw' | 'upload' -export type AOIShape = 'point' | 'linestring' | 'polygon' | 'rectangle' | 'circle' -export type UploadStatus = 'idle' | 'parsing' | 'error' -export type AnalysisStatus = 'idle' | 'running' - -export interface AOISearchResult { - id: string - label: string - kind: 'city' | 'county' | 'state' -} - -export interface AOIComponentProps { - mode: AOIMode - onModeChange: (mode: AOIMode) => void - - searchQuery: string - searchResults: AOISearchResult[] - searchDisabled?: boolean - searchLoading?: boolean - onSearchQueryChange: (q: string) => void - onSearchSelect: (id: string) => void - - drawShape: AOIShape | null - drawShapes?: AOIShape[] - drawDisabled?: boolean - isDrawing: boolean - drawVerticesCount: number - onDrawShapeChange: (shape: AOIShape) => void - onDrawConfirm: () => void - onDrawCancel: () => void - - uploadStatus: UploadStatus - uploadError?: string - onUploadFile: (file: File) => void - - analysisStatus?: AnalysisStatus - analysisLabel?: string - analysisDone?: number - analysisTotal?: number - analysisError?: string | null - onDismissAnalysisError?: () => void - - onClose: () => void -} - -const MODES: Array<{ id: AOIMode; label: string; icon: string }> = [ - { id: 'search', label: 'Search', icon: 'magnify' }, - { id: 'inspect', label: 'Inspect', icon: 'hand-pointing-up' }, - { id: 'draw', label: 'Draw', icon: 'vector-polyline' }, - { id: 'upload', label: 'Upload', icon: 'tray-arrow-up' }, -] - -export function AOIComponent(props: AOIComponentProps) { - const isAnalyzing = props.analysisStatus === 'running' - - return ( -
-
-
-