components/DailyWorkAreaModal.tsx
Purpose
This module implements a full-screen React Native modal for defining and editing a user’s “daily work area” using an interactive map interface.
The component combines:
- map-based geographic selection
- address autocomplete and geocoding
- adjustable radius selection
- work-area persistence actions
- accessibility support
- safe-area aware mobile layout behavior
The modal allows a user to:
- Search for a work location by address
- Select a center point on a map
- Define a circular work radius
- Save the resulting work area for the current day
- Reuse a previously saved work area
The file also encapsulates several reusable interaction primitives:
- a custom slider implementation
- region synchronization logic
- draft-building helpers
- debounced address suggestion handling
- map marker/radius rendering behavior
Invariants
Draft Consistency
A valid DailyWorkAreaDraft always contains:
type DailyWorkAreaDraft = {
centerLat: number;
centerLon: number;
radiusKm: number;
};
The modal never emits partial drafts.
Radius Bounds
Work area radius is always constrained to:
1 km ≤ radius ≤ 25 km
using:
clampRadius()
Radius Quantization
Radius values are normalized to fixed 0.5 km increments.
Examples:
| Input | Stored |
|---|---|
3.26 | 3.5 |
4.74 | 4.5 |
This guarantees stable UI rendering and predictable persistence values.
Slider Ratio Mapping
Slider state is always represented internally as a normalized ratio:
0.0 → minimum radius
1.0 → maximum radius
Conversions are reversible through:
radiusToRatio()ratioToRadius()
Complete Map Selection
Map taps always produce a fully valid draft object.
buildDraftFromPress() guarantees:
- valid center coordinates
- retained radius when possible
- fallback default radius otherwise
Address Selection Synchronization
Selecting an address suggestion always synchronizes:
- text input value
- selected draft coordinates
- map camera position
- autocomplete suggestion state
This prevents UI desynchronization between the map and search field.
Suggestion Request Ordering
Autocomplete requests are guarded by monotonically increasing request IDs.
Older async responses are ignored if newer searches have started.
This prevents stale suggestion lists from overwriting newer results.
Debounced Address Search
Address suggestion requests are delayed by:
ADDRESS_SEARCH_DEBOUNCE_MS = 350
This reduces:
- unnecessary network traffic
- rapid API churn
- autocomplete flickering
Modal Reinitialization Behavior
The map only resets when:
- the modal becomes newly visible
- the opening region changes
This avoids unnecessary MapView remounts during normal interaction.
Accessibility Guarantees
The component maintains accessibility semantics for:
- adjustable slider controls
- buttons
- suggestion list items
- close actions
Accessibility metadata includes:
- labels
- hints
- adjustable values
- disabled states
Busy-State Locking
When busy === true, the UI prevents:
- map edits
- radius edits
- address selection
- close actions
- save actions
This prevents conflicting async mutations.
Variants
Map Selection Variant
Users may define a work area by:
Direct Map Interaction
- tapping the map
- dragging the radius slider
Address Search
- typing an address
- selecting autocomplete suggestions
- resolving a geocoded location
Both paths produce equivalent draft structures.
Radius Slider Variant
The custom slider supports:
- touch taps
- drag gestures
- accessibility adjustable behavior
Internally it uses PanResponder rather than platform-native slider components.
Platform Rendering Variant
Marker rendering differs slightly on iOS:
IOS_MARKER_CENTER_OFFSET
This compensates for platform-specific marker anchoring behavior.
Region Initialization Variant
The modal accepts a caller-provided openingRegion.
If absent, it falls back to:
DEFAULT_REGION
This allows:
- context-aware openings
- persisted map state
- location-aware defaults
Address Suggestion Lifecycle Variants
Autocomplete UI transitions through several states:
| State | Behavior |
|---|---|
| idle | no suggestions shown |
| searching | loading indicator visible |
| success | suggestions rendered |
| error | error text displayed |
| selected | suggestions cleared |
Draft Presence Variant
Several UI behaviors depend on whether a draft exists.
No Draft
- slider disabled
- helper text shown
- no map overlays rendered
Active Draft
- marker visible
- radius circle visible
- slider enabled
- save enabled
Modal Control Variant
The modal supports optional close-button behavior.
Consumers may:
- omit close controls entirely
- customize accessibility labels
- externally manage modal dismissal
Exports
DailyWorkAreaDraft
type DailyWorkAreaDraft = {
centerLat: number;
centerLon: number;
radiusKm: number;
};
Represents a circular geographic work area centered at a coordinate.
DailyWorkAreaModalProps
Defines the full controlled API for the modal component.
Includes:
- visibility state
- draft state
- address-search state
- busy state
- action callbacks
- region initialization
- persistence actions
The modal is fully controlled by the parent component.
DailyWorkAreaModal
export default function DailyWorkAreaModal(...)
Primary exported component.
Responsibilities include:
- modal lifecycle management
- map reset synchronization
- safe-area integration
- delegation into modal content rendering
Internal Components
DailyWorkAreaModalContent
Encapsulates the main modal UI and interaction logic.
Responsibilities include:
- address search lifecycle
- map interaction
- suggestion rendering
- draft mutation
- action buttons
- status rendering
RadiusSlider
Custom gesture-based slider component for selecting work area radius.
Features:
- drag gestures
- touch selection
- accessibility adjustable semantics
- quantized radius output
- disabled-state handling
Internal Helper Functions
clampRadius(radiusKm)
Constrains radius values to supported limits.
quantizeRadius(radiusKm)
Rounds radius values to nearest 0.5 km.
regionsEqual(left, right)
Performs exact equality checks on map regions.
Used to avoid unnecessary map resets.
buildDraftFromPress(event, previous)
Builds a draft from a map tap event.
buildDraftFromAddress(...)
Builds a draft from a resolved address lookup.
formatRadiusLabel(radiusKm)
Formats human-readable slider labels.
Examples:
2 km radius
2.5 km radius
radiusToRatio(radiusKm)
Converts radius values into normalized slider ratios.
ratioToRadius(ratio)
Converts normalized slider ratios back into quantized radius values.