mirror of
https://github.com/zvx-echo6/refactored-recon.git
synced 2026-05-20 06:34:34 +02:00
design: resolve open questions in directions redesign
Six open questions answered: - Mobile uses bottom sheet (768px breakpoint), desktop uses side panel - Long-press timing: 450ms + 5-10px movement threshold - Save place wedge visible to guests with login prompt - Single-ring radial; future expansion via contextual wedge sets - Three-tier pin persistence: transient → localStorage → backend - Map click fills active input; long-press still opens radial
This commit is contained in:
parent
18ee0fc738
commit
c121559e01
1 changed files with 135 additions and 47 deletions
|
|
@ -132,14 +132,14 @@ routeError: null
|
|||
|----------|--------|------|-------|
|
||||
| Top | Drop pin | Pin | Red |
|
||||
| Top-right | Directions to here | Arrow-in | Blue |
|
||||
| Bottom-right | Save place | Star | Purple |
|
||||
| Bottom-right | Save place | Star + 🔒 | Purple |
|
||||
| Bottom | What's here | Info | Orange |
|
||||
| Bottom-left | Add as stop | Plus | Yellow |
|
||||
| Top-left | Directions from here | Arrow-out | Green |
|
||||
|
||||
### Behavior
|
||||
|
||||
- **Trigger:** Right-click (desktop) or long-press 400-500ms (mobile)
|
||||
- **Trigger:** Right-click (desktop) or long-press 450ms (mobile)
|
||||
- **Center disc:** ~40px diameter, shows coordinates immediately, reverse-geocoded label async
|
||||
- **Wedge highlight:** On hover (desktop) or drag-over (mobile)
|
||||
- **Commit:** Release on wedge (mobile) or click wedge (desktop)
|
||||
|
|
@ -260,6 +260,11 @@ radialMenuState: {
|
|||
lon: number,
|
||||
label: string | null // Reverse-geocoded, async populated
|
||||
}
|
||||
|
||||
// Pin persistence (three-tier)
|
||||
transientPins: [] // In-memory, lost on refresh
|
||||
// localStorage: 'navi_saved_pins' key for guest saves
|
||||
// Backend sync: future work for authed users
|
||||
```
|
||||
|
||||
### Removed
|
||||
|
|
@ -308,12 +313,12 @@ pendingDestination: null // No longer needed — explicit inputs replace hidden
|
|||
| **Directions to here** | Opens directions if closed, fills To with coords, focuses From if empty |
|
||||
| **Add as stop** | Inserts new stop before destination |
|
||||
| **What's here** | Reverse geocode → opens place panel |
|
||||
| **Drop pin** | Creates transient marker (session-only) |
|
||||
| **Save place** | Opens save dialog (auth required) |
|
||||
| **Drop pin** | Creates transient marker (session-only by default) |
|
||||
| **Save place** | Guest: opens login flow; Authed: opens save dialog |
|
||||
|
||||
5. Release outside or Escape → dismisses without action
|
||||
|
||||
### Click map with active input
|
||||
### Map click with active input
|
||||
|
||||
When directions panel is open and an input is focused (`activeInputSlot !== null`):
|
||||
|
||||
|
|
@ -323,6 +328,19 @@ When directions panel is open and an input is focused (`activeInputSlot !== null
|
|||
4. Input loses focus, `activeInputSlot = null`
|
||||
5. Route recalculates
|
||||
|
||||
### Map long-press / right-click with active input
|
||||
|
||||
Even when an input is active:
|
||||
|
||||
1. Long-press / right-click opens radial menu (overrides click-to-fill)
|
||||
2. User can select "Directions from here" or "Directions to here"
|
||||
3. Explicitly overrides the active input — intentional action takes priority
|
||||
|
||||
### Map click with no active input
|
||||
|
||||
1. No action (map interaction only)
|
||||
2. Future v2 consideration: reverse-geocode and show place panel
|
||||
|
||||
### Swap button
|
||||
|
||||
1. Click swap button
|
||||
|
|
@ -374,22 +392,24 @@ handleDirections = () => {
|
|||
| Platform | Gesture | Duration |
|
||||
|----------|---------|----------|
|
||||
| Desktop | Right-click | Instant |
|
||||
| Mobile | Long-press | 400-500ms |
|
||||
| Mobile | Long-press | 450ms |
|
||||
|
||||
### Conflict Avoidance
|
||||
|
||||
Long-press must NOT fire during active pan:
|
||||
- Track touch start position
|
||||
- If touch moves >5px before timer fires, cancel long-press
|
||||
- If touch moves >5-10px before timer fires, cancel long-press
|
||||
- Pan gesture takes priority
|
||||
- Matches iOS Safari default contextmenu synthesis behavior
|
||||
|
||||
### Geometry
|
||||
|
||||
```
|
||||
Outer radius: ~80px from center
|
||||
Inner radius: ~40px (center disc)
|
||||
Outer radius: ~80px from center (desktop), ~100px (mobile)
|
||||
Inner radius: ~40px (center disc, desktop), ~50px (mobile)
|
||||
Wedge angle: 60° each (6 wedges)
|
||||
Gap between wedges: 2px
|
||||
Minimum touch target: 48px per wedge
|
||||
```
|
||||
|
||||
### Visual States
|
||||
|
|
@ -400,6 +420,7 @@ Gap between wedges: 2px
|
|||
| Wedge icon | White, 50% opacity | White, 100% opacity | White |
|
||||
| Wedge label | Hidden | Shown (tooltip) | Shown |
|
||||
| Center disc | Dark, coords visible | — | — |
|
||||
| Save place (guest) | Lock icon overlay | — | — |
|
||||
|
||||
### Animation
|
||||
|
||||
|
|
@ -414,30 +435,36 @@ Gap between wedges: 2px
|
|||
|
||||
### Panel Layout
|
||||
|
||||
**Decision needed:** Bottom sheet vs side panel
|
||||
**Decision: Bottom sheet on mobile (<768px), side panel on desktop.**
|
||||
|
||||
| Option | Pros | Cons |
|
||||
|--------|------|------|
|
||||
| Bottom sheet | Familiar (Google Maps), thumb-friendly | Complex sheet state management |
|
||||
| Side panel | Consistent with desktop, more vertical space | Covers more map, less thumb-friendly |
|
||||
Bottom sheet states:
|
||||
- **Peek:** Route summary only (collapsed)
|
||||
- **Half:** Inputs + summary visible
|
||||
- **Full:** Inputs + turn-by-turn maneuvers
|
||||
|
||||
**Recommendation:** Bottom sheet with three states: collapsed (summary only), half (inputs + summary), full (inputs + maneuvers).
|
||||
Drag handle at top for resize between states.
|
||||
|
||||
**Implementation notes:**
|
||||
- Use React state for sheet position: `sheetState: 'peek' | 'half' | 'full'`
|
||||
- CSS transforms for smooth transitions: `transform: translateY()`
|
||||
- Touch gesture detection for drag-to-resize
|
||||
|
||||
### Long-press Timing
|
||||
|
||||
**Decision needed:** Exact timing
|
||||
**Decision: 450ms with 5-10px movement threshold.**
|
||||
|
||||
| Duration | Feel |
|
||||
|----------|------|
|
||||
| 400ms | Snappy, risk of accidental trigger |
|
||||
| 450ms | Balanced |
|
||||
| 500ms | Deliberate, slightly sluggish |
|
||||
If finger moves more than threshold during press window, abort long-press and treat as pan. This matches iOS Safari's default contextmenu synthesis behavior.
|
||||
|
||||
**Recommendation:** Start with 450ms, tune based on testing.
|
||||
**Implementation notes:**
|
||||
- Create reusable `useLongPress` hook
|
||||
- Track `touchstart` position
|
||||
- `setTimeout` for 450ms
|
||||
- Clear timeout on `touchmove` if delta > threshold
|
||||
- Fire callback on timeout completion
|
||||
|
||||
### Radial Sizing
|
||||
|
||||
Mobile radial should be larger for finger touch:
|
||||
Mobile radial larger for finger touch:
|
||||
- Outer radius: ~100px (vs 80px desktop)
|
||||
- Center disc: ~50px (vs 40px desktop)
|
||||
- Minimum wedge touch target: 48px
|
||||
|
|
@ -503,36 +530,97 @@ Separate session will address:
|
|||
|
||||
---
|
||||
|
||||
## 14. Open Questions
|
||||
## 14. Resolved Decisions
|
||||
|
||||
### For Matt to decide:
|
||||
### 1. Mobile Layout
|
||||
|
||||
1. **Bottom sheet vs side panel on mobile?**
|
||||
- Bottom sheet recommended but adds complexity
|
||||
**Decision:** Bottom sheet on mobile (<768px breakpoint), side panel on desktop.
|
||||
|
||||
2. **Long-press timing exactly?**
|
||||
- 400ms / 450ms / 500ms
|
||||
- Recommend 450ms
|
||||
Three sheet states:
|
||||
- **Peek:** Summary only (route time/distance)
|
||||
- **Half:** Inputs + summary visible
|
||||
- **Full:** Inputs + turn-by-turn maneuvers
|
||||
|
||||
3. **Should "Save place" wedge be visible to guests or hidden?**
|
||||
- Visible with login prompt = more discoverable
|
||||
- Hidden = cleaner for guests
|
||||
- Recommend: visible, shows "Sign in to save" toast
|
||||
Drag-to-resize between states via handle at top of sheet.
|
||||
|
||||
4. **Inner ring of secondary actions in radial v2?**
|
||||
- Could add less-common actions in inner ring
|
||||
- Recommend: stay single-ring for v1, evaluate need later
|
||||
**Implementation notes:**
|
||||
- `sheetState` in store: `'peek' | 'half' | 'full'`
|
||||
- CSS transforms (`translateY`) for smooth animation
|
||||
- Touch gesture handler for drag detection
|
||||
- Snap to nearest state on release
|
||||
|
||||
5. **What does "Drop pin" persistence look like?**
|
||||
- Session only (lost on refresh)
|
||||
- localStorage (persists locally)
|
||||
- Auth-only saved (sync across devices)
|
||||
- Recommend: session-only for v1, localStorage for v2
|
||||
### 2. Long-press Timing
|
||||
|
||||
6. **Radial on map click during active input?**
|
||||
- Option A: No radial, click fills input directly
|
||||
- Option B: Radial appears, "Use this location" wedge fills input
|
||||
- Recommend: Option A (direct fill) for simplicity
|
||||
**Decision:** 450ms with 5-10px movement threshold.
|
||||
|
||||
If finger moves more than threshold during the press window, abort long-press and treat as pan gesture. This matches iOS Safari's default contextmenu synthesis behavior.
|
||||
|
||||
**Implementation notes:**
|
||||
- Create `useLongPress(callback, delay = 450)` hook
|
||||
- Track `touchstart` coordinates
|
||||
- On `touchmove`, check if `Math.hypot(dx, dy) > threshold`
|
||||
- If exceeded, clear timeout (abort)
|
||||
- If timeout fires without abort, invoke callback with coords
|
||||
|
||||
### 3. Save Place for Guests
|
||||
|
||||
**Decision:** Visible wedge with subtle lock icon overlay to indicate auth required.
|
||||
|
||||
Tapping as guest opens login flow with return-to-action after auth completes.
|
||||
|
||||
**Implementation notes:**
|
||||
- "Save place" wedge always visible
|
||||
- Lock icon (🔒) overlaid at 50% opacity on wedge icon
|
||||
- On tap: check auth state
|
||||
- Authed: open save dialog
|
||||
- Guest: trigger login modal with `returnAction: 'save-place'`
|
||||
- After successful auth, resume save flow
|
||||
|
||||
### 4. Radial Ring Structure
|
||||
|
||||
**Decision:** Single ring only. Six wedges maximum.
|
||||
|
||||
Future expansion via contextual wedge sets (different actions based on what was clicked — e.g., clicking on a route segment could show "Avoid this road" instead of "Drop pin") rather than deeper nested rings.
|
||||
|
||||
**Implementation notes:**
|
||||
- `wedges` prop on RadialMenu is array of 6 max
|
||||
- Context-aware wedge selection handled by parent component
|
||||
- No inner ring in v1; revisit if six actions prove insufficient
|
||||
|
||||
### 5. Drop Pin Persistence
|
||||
|
||||
**Decision:** Three-tier model.
|
||||
|
||||
| Tier | Scope | Storage | Behavior |
|
||||
|------|-------|---------|----------|
|
||||
| Transient | Session | In-memory (`transientPins[]`) | Default. Lost on refresh. |
|
||||
| Guest save | Device | `localStorage` (`navi_saved_pins`) | Survives refresh, lost on cache clear or other device. |
|
||||
| Authed sync | Account | Backend API | Syncs across devices. Future work. |
|
||||
|
||||
**Implementation notes:**
|
||||
- Transient pins: `transientPins: []` in store
|
||||
- Guest save: on "Save" tap, copy pin to localStorage
|
||||
- localStorage schema: `[{ id, lat, lon, name, createdAt }]`
|
||||
- Backend sync: TODO placeholder; initial implementation is localStorage only
|
||||
- Authed users see "Sync to account" option (greyed out with "Coming soon" for v1)
|
||||
|
||||
### 6. Radial vs Click During Active Input
|
||||
|
||||
**Decision:** Different gestures, different behaviors.
|
||||
|
||||
| State | Gesture | Behavior |
|
||||
|-------|---------|----------|
|
||||
| Active input | Map click | Fills active input with click coords (reverse-geocoded) |
|
||||
| Active input | Map long-press / right-click | Opens radial (explicit override allowed) |
|
||||
| No active input | Map click | No action (v2: reverse-geocode + place panel) |
|
||||
| No active input | Map long-press / right-click | Opens radial (primary use case) |
|
||||
|
||||
**Implementation notes:**
|
||||
- Click handler checks `activeInputSlot`
|
||||
- If set: fill input, reverse-geocode for label
|
||||
- If null: no-op (or v2 place panel)
|
||||
- Long-press / contextmenu handler always opens radial
|
||||
- Radial "Directions from/to here" explicitly sets input, overriding prior value
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -576,4 +664,4 @@ Separate session will address:
|
|||
|
||||
---
|
||||
|
||||
*Document created 2026-04-26. Implementation to follow in dedicated session.*
|
||||
*Document created 2026-04-26. Updated 2026-04-26 with resolved decisions. Implementation to follow in dedicated session.*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue