docs: add APP-2046 lightbox refresh design spec
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# Lightbox Refresh — Design Spec
|
||||
|
||||
**Ticket:** APP-2046
|
||||
**Figma:** https://www.figma.com/design/TqFguaWhkvAPnCth5u7cvE/Everything?node-id=7221-2117
|
||||
**Branch:** `app-2046`
|
||||
**PR strategy:** one PR — migration + reskin together
|
||||
|
||||
## Summary
|
||||
|
||||
Refresh lightbox chrome and co-locate all lightbox code under `/features/lightbox/`.
|
||||
Native gets a new visual treatment (translucent circular buttons, dots pagination,
|
||||
menu-based Share/Save). Web gets a minimal reskin of buttons and menu, no layout
|
||||
changes. All gestures, animations, pager mechanics, and the underlying image
|
||||
viewer are preserved.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
- Move every lightbox file into `/features/lightbox/` with a light reorg.
|
||||
- Rebuild native chrome (header, footer, pager dots, image menu).
|
||||
- Reskin web chrome buttons to match native's translucent treatment.
|
||||
- Add `Share image` menu item on web (currently only `Download image`).
|
||||
- Restyle alt-text strip on both platforms to match.
|
||||
|
||||
### Out of scope
|
||||
- Reaction bar (labeled "Inactive" in Figma).
|
||||
- Web layout changes — keep chevron navigation, keep alt-text expand behavior,
|
||||
keep keyboard shortcuts.
|
||||
- Gesture code (pinch/pan/zoom/swipe-to-dismiss) — file moves only.
|
||||
- Open/close spring animation and backdrop fade — file moves only.
|
||||
- Alt-text interaction model — kept, just restyled.
|
||||
|
||||
## Target file structure
|
||||
|
||||
```
|
||||
/src/features/lightbox/
|
||||
├── index.tsx # re-exports Lightbox (platform split)
|
||||
├── Lightbox.tsx # native shell (from view/com/lightbox/Lightbox.tsx)
|
||||
├── Lightbox.web.tsx # web impl (from view/com/lightbox/Lightbox.web.tsx)
|
||||
├── state.tsx # from state/lightbox.tsx
|
||||
├── types.ts # from ImageViewing/@types/index.ts
|
||||
├── chrome/
|
||||
│ ├── CircleChromeButton.tsx # NEW — shared primitive for ••• and ✕
|
||||
│ ├── Header.tsx # NEW — replaces ImageDefaultHeader
|
||||
│ ├── Footer.tsx # NEW — replaces LightboxFooter
|
||||
│ ├── PagerDots.tsx # NEW — dots indicator (native only)
|
||||
│ └── ImageMenu.tsx # NEW — ContextMenu wrapper
|
||||
└── pager/
|
||||
├── ImagePager.tsx # from ImageViewing/index.tsx
|
||||
├── transforms.ts # from ImageViewing/transforms.ts
|
||||
└── ImageItem/
|
||||
├── ImageItem.tsx
|
||||
├── ImageItem.ios.tsx
|
||||
└── ImageItem.android.tsx
|
||||
```
|
||||
|
||||
### Source-to-destination map
|
||||
|
||||
| From | To |
|
||||
|---|---|
|
||||
| `src/state/lightbox.tsx` | `src/features/lightbox/state.tsx` |
|
||||
| `src/view/com/lightbox/Lightbox.tsx` | `src/features/lightbox/Lightbox.tsx` |
|
||||
| `src/view/com/lightbox/Lightbox.web.tsx` | `src/features/lightbox/Lightbox.web.tsx` |
|
||||
| `src/view/com/lightbox/ImageViewing/index.tsx` | `src/features/lightbox/pager/ImagePager.tsx` |
|
||||
| `src/view/com/lightbox/ImageViewing/transforms.ts` | `src/features/lightbox/pager/transforms.ts` |
|
||||
| `src/view/com/lightbox/ImageViewing/@types/index.ts` | `src/features/lightbox/types.ts` |
|
||||
| `src/view/com/lightbox/ImageViewing/components/ImageItem/*` | `src/features/lightbox/pager/ImageItem/*` |
|
||||
| `src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx` | *deleted — replaced by `chrome/Header.tsx`* |
|
||||
|
||||
### Consumer import updates
|
||||
|
||||
All the imports that must move from `#/state/lightbox` → `#/features/lightbox/state` or
|
||||
`#/view/com/lightbox/Lightbox` → `#/features/lightbox`:
|
||||
|
||||
**State (`LightboxStateProvider`, `useLightbox`, `useLightboxControls`, type `Lightbox`):**
|
||||
|
||||
- `src/App.native.tsx`
|
||||
- `src/App.web.tsx`
|
||||
- `src/state/util.ts`
|
||||
- `src/screens/Profile/Header/Shell.tsx`
|
||||
- `src/components/Post/Embed/ImageEmbed.tsx`
|
||||
- `src/view/com/profile/ProfileSubpageHeader.tsx`
|
||||
- `src/view/com/util/List.tsx`
|
||||
|
||||
**Lightbox component itself:**
|
||||
|
||||
- `src/view/shell/index.tsx`
|
||||
- `src/view/shell/index.web.tsx`
|
||||
|
||||
Internal imports within the lightbox files themselves will also update (e.g.,
|
||||
`ImagePager.tsx` importing the type from sibling `state.tsx`).
|
||||
|
||||
## Native chrome design
|
||||
|
||||
### `chrome/CircleChromeButton.tsx`
|
||||
- Shared primitive used by `•••` and `✕` triggers, and reused on web.
|
||||
- 36 × 36 pressable circle.
|
||||
- Background: `rgba(0, 0, 0, 0.45)` — fixed, not theme-driven, because it sits
|
||||
over arbitrary images.
|
||||
- Icon: white, 20px.
|
||||
- `hitSlop: 10`.
|
||||
|
||||
### `chrome/Header.tsx`
|
||||
- Absolutely-positioned row, `top: insets.top`, `px_md`, `py_sm`.
|
||||
- Left slot: `ImageMenu` trigger (`•••`).
|
||||
- Right slot: `CircleChromeButton` with `XIcon`, closes lightbox.
|
||||
- Show/hide driven by the existing `showControls` shared value in `ImagePager.tsx`
|
||||
(opacity + translateY). No new gesture logic.
|
||||
|
||||
### `chrome/ImageMenu.tsx`
|
||||
- Wraps `ContextMenu.Outer` + `ContextMenu.Trigger`.
|
||||
- Trigger child renders `CircleChromeButton` with `DotsHorizontal` icon.
|
||||
- `onTap` handler calls `control.open('full')` so single tap opens — no press-and-hold.
|
||||
- Two items:
|
||||
- `Share image` → `ShareIcon`, calls existing share util.
|
||||
- `Save image` → `Download`, calls existing save util.
|
||||
- Menu card surface is theme-aware (white in light mode, dark in dark mode) —
|
||||
inherited from `ContextMenu`'s defaults.
|
||||
|
||||
### `chrome/Footer.tsx`
|
||||
- Absolutely-positioned, `bottom: insets.bottom`, `px_md`, `pb_sm`.
|
||||
- Renders `AltTextStrip` (if current image has alt text) above `PagerDots`.
|
||||
- `AltTextStrip`:
|
||||
- Translucent dark surface: `rgba(0, 0, 0, 0.45)`.
|
||||
- White text, `text_sm`.
|
||||
- Tap expands/collapses (port the expand/collapse state logic from
|
||||
`Lightbox.web.tsx` lines 227-245).
|
||||
|
||||
### `chrome/PagerDots.tsx`
|
||||
- Renders only when `images.length > 1`.
|
||||
- Bottom-center row.
|
||||
- Active dot: `6 × 6`, `bg: white`.
|
||||
- Inactive dot: `6 × 6`, `bg: rgba(255,255,255,0.4)`.
|
||||
- Gap: `4px`.
|
||||
- Active index driven by the existing `imageIndex` shared value the pager exposes.
|
||||
|
||||
## Web chrome design
|
||||
|
||||
Changes in `Lightbox.web.tsx` are visual only:
|
||||
|
||||
- Replace the existing menu trigger button (lines 251-296) and close button
|
||||
(lines 297-312) with `CircleChromeButton` from the native chrome folder.
|
||||
(It's a plain styled Pressable — platform-agnostic.)
|
||||
- Menu items:
|
||||
- Existing `Download image` stays.
|
||||
- NEW `Share image` — uses Web Share API when available, falls back to
|
||||
copy-link-to-clipboard.
|
||||
- Restyle alt-text strip to match native's translucent treatment. Keep
|
||||
expand/collapse behavior.
|
||||
|
||||
### Web out of scope (confirmed)
|
||||
- Left/right chevron navigation (lines 184-225).
|
||||
- Screen-reader "Image N of M" announcement (line 246-250).
|
||||
- Keyboard shortcut registration / hotkey scope management.
|
||||
- No dots indicator on web (chevrons + "Image N of M" already serve this role).
|
||||
|
||||
## Preserved behaviors
|
||||
|
||||
Both platforms keep:
|
||||
|
||||
- Tap-to-toggle chrome visibility.
|
||||
- Swipe-down-to-dismiss (native).
|
||||
- Pinch/pan/double-tap zoom.
|
||||
- Horizontal pager between images.
|
||||
- Open/close spring from source thumbnail.
|
||||
- `expo-screen-orientation` lock on open, unlock on close.
|
||||
- Hotkey scope disable while lightbox is open.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- `chrome/` depends only on `#/components` and the state module. It knows
|
||||
nothing about gestures or pager internals.
|
||||
- `pager/` owns gestures, layout, animation. Chrome is rendered via props/slots.
|
||||
- `state.tsx` exports are unchanged — pure move with updated import paths.
|
||||
- `CircleChromeButton` is reused across native header and web chrome — one
|
||||
visual source of truth.
|
||||
|
||||
## Testing & verification
|
||||
|
||||
- Visual inspection on iOS simulator, Android emulator, web.
|
||||
- Verify open/close animation unchanged from main.
|
||||
- Verify tap-to-toggle chrome still works.
|
||||
- Verify share and save still work end-to-end from the new menu.
|
||||
- Verify alt text still displays and expands.
|
||||
- Confirm no new TypeScript errors: `yarn typecheck`.
|
||||
- Confirm no new lint errors: `yarn lint`.
|
||||
- Confirm Jest still passes: `yarn test`.
|
||||
Reference in New Issue
Block a user