Skip to content

feat(core): add XDSLightbox component - #2298

Merged
czarandy merged 1 commit into
mainfrom
worktree-XDSLightbox
May 26, 2026
Merged

feat(core): add XDSLightbox component#2298
czarandy merged 1 commit into
mainfrom
worktree-XDSLightbox

Conversation

@czarandy

@czarandy czarandy commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

New XDSLightbox component — a fullscreen overlay for viewing images and videos at full resolution.

  • Single media mode: src, alt, caption, type props
  • Gallery mode: images[], index, onIndexChange with prev/next buttons and arrow key navigation
  • Video support: type='video' renders <video> with native controls; zoom/pan disabled for video
  • Zoom: opt-in via hasZoom (images only) — double-click toggles 1x/2x, drag to pan when zoomed
  • Built on native <dialog> with showModal() for focus trapping and top-layer
  • Controls use XDSIconButton with <XDSIcon>, white via --color-on-dark token
  • Caption hugs the media, gallery counter at top-left
  • Accessible: aria-label, keyboard navigation, focus restore on close
  • Exports: XDSLightbox, XDSLightboxProps, XDSLightboxImage, XDSLightboxMediaType

Test plan

  • 17 unit tests pass (dialog, single image, caption, gallery nav, arrow keys, ref forwarding)
  • No type errors from tsc --noEmit
  • Pre-commit hooks pass (lint, sync check, package boundaries)
  • Verify single image in Storybook (Default, WithCaption stories)
  • Verify gallery navigation (Gallery story)
  • Verify zoom behavior (WithZoom story — double-click to zoom, drag to pan)
  • Verify video playback with native controls

@vercel

vercel Bot commented May 21, 2026

Copy link
Copy Markdown

@czarandy must be a member of the Meta Open Source team on Vercel to deploy.
- Click here to add @czarandy to the team.
- If you initiated this build, request access.

Learn more about collaboration on Vercel and other options here.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label May 21, 2026
@czarandy
czarandy force-pushed the worktree-XDSLightbox branch from b289152 to f417df3 Compare May 21, 2026 20:21
@vercel

vercel Bot commented May 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xds-sandbox Error Error May 24, 2026 6:27am

Request Review

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@xds/core 20.8KB 32.1KB 4.7KB

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API is clean and the implementation is solid, but this needs to go through the full specification protocol before merging. Issue #1198 says "Specification protocol in progress" but it was never completed.

Specifically:

  1. External research + use case analysis — need a proper survey of Fancybox, PhotoSwipe, Radix Dialog patterns, etc. and a decision matrix. The current API is reasonable but decisions need to be grounded.

  2. Vibe tests — especially around the media single-vs-array overload. Does an AI naturally reach for media={{src, alt}} vs media={[{src, alt}]}? Does it correctly wire index/onIndexChange for galleries?

  3. Missing slots — real-world lightboxes need toolbar actions (download, share, open in tab). The spec should surface this and decide on a slot/render prop pattern. Also caption should be ReactNode not just string.

  4. Trigger pattern — XDSDialog uses useXDSDialog hook with a trigger ref. Lightbox should match: useXDSLightbox returning trigger props + state, consistent with the dialog family.

  5. Video autoPlay — currently hardcoded to true. Should be hasAutoPlay defaulting to false per boolean conventions.

The code quality and conventions compliance are good — this is purely about running the spec protocol to validate the API surface before shipping.

@czarandy

Copy link
Copy Markdown
Collaborator Author

I did look at Fancybox, PhotoSwipe and Lightbox2 to base this version on. I'm not going to do the "decision matrix" or whatever.

real-world lightboxes need toolbar actions (download, share, open in tab).
seems not typical, I don't see that in other examples

XDSDialog uses useXDSDialog hook with a trigger ref. Lightbox should match: useXDSLightbox returning trigger props + state, consistent with the dialog family.
I see no reason to have a hook, that seems like not something that would come up. Lightbox is simple: it's controlled by isOpen/onOpenChange and doesn't need trigger positioning since it's a full screen overlay.

@czarandy

Copy link
Copy Markdown
Collaborator Author

⏺ ## Lightbox Vibe Test Results

All 3 Lightbox vibe tests completed.

Test Result Media prop Index wiring Escape hatches
fwc-12 (single image) Pass Array (not single object) Yes None
fwc-13 (gallery) Pass Array Yes None
fwc-14 (mixed media) Pass Array w/ type: 'video' Yes Inline styles on thumbnail grid

Key findings

  1. The API is intuitive — all 3 tests correctly used XDSLightbox with zero escape hatches for the core viewer functionality. Nobody tried to build a custom
    overlay/dialog.

  2. Array form is the natural default — even fwc-12 (single image) used the array form with index/onIndexChange instead of media={{src, alt}}. The single-object
    shorthand isn't being discovered. If you want people to use it, the doc should lead with the single-object example and explicitly call out "for a single image, pass an
    object directly."

  3. hasZoom and type: 'video' discovered correctly — fwc-14 found both without issues.

  4. Minor escape hatch — fwc-14 used inline styles for the thumbnail grid cards instead of StyleX, but that's a general pattern issue, not Lightbox-specific.

@czarandy

Copy link
Copy Markdown
Collaborator Author

The single-object shorthand isn't being discovered.

This is probably fine, we don't really care that much which version gets used, it's just in case.

@cixzhang

Copy link
Copy Markdown
Contributor

Imperative hook form is pretty typical for our layers. It's not about trigger positioning but rather so we have a way to open layers from things like menus where the items might unmount because the menu closes after showing the layer

Comment thread packages/core/src/Lightbox/XDSLightbox.tsx
Comment thread packages/core/src/Lightbox/XDSLightbox.tsx
}

export const Default: Story = {
render: () => <SingleImageDemo />,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these storybook examples it would be ideal to render them directly in the render methods instead of wrapping in a separate component because this obfuscates the code examples

Fullscreen overlay for viewing images and videos at full resolution.

- Unified media prop: single object or array for gallery mode
- Video support: type='video' renders <video> with native controls
- Gallery mode: prev/next buttons and arrow key navigation
- Zoom: opt-in via hasZoom (images only), double-click toggles 1x/2x,
  drag to pan
- Built on native <dialog> with showModal() for focus trapping
- Controls use XDSIconButton with XDSIcon (close, chevronLeft/Right)
- Dark backdrop, white controls via --color-on-dark token
- Caption hugs media, gallery counter at top-left
- Accessible: aria-label, keyboard nav, focus restore on close
- Exports: XDSLightbox, XDSLightboxProps, XDSLightboxMedia,
  XDSLightboxMediaType
@czarandy

Copy link
Copy Markdown
Collaborator Author

Added useXDSLightbox hook + fix the other comments

@czarandy
czarandy requested a review from cixzhang May 24, 2026 06:27
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Vercel Preview Deployment

Status ✅ Deployed
Preview Open Preview
Commit f934473
Inspect Vercel Dashboard
Workflow View Logs

No authentication required — anyone with the link can view the preview.

@czarandy
czarandy merged commit f0ab019 into main May 26, 2026
21 of 22 checks passed
@czarandy
czarandy deleted the worktree-XDSLightbox branch May 26, 2026 01:55
cixzhang pushed a commit that referenced this pull request Jun 21, 2026
Fullscreen overlay for viewing images and videos at full resolution.

- Unified media prop: single object or array for gallery mode
- Video support: type='video' renders <video> with native controls
- Gallery mode: prev/next buttons and arrow key navigation
- Zoom: opt-in via hasZoom (images only), double-click toggles 1x/2x,
  drag to pan
- Built on native <dialog> with showModal() for focus trapping
- Controls use XDSIconButton with XDSIcon (close, chevronLeft/Right)
- Dark backdrop, white controls via --color-on-dark token
- Caption hugs media, gallery counter at top-left
- Accessible: aria-label, keyboard nav, focus restore on close
- Exports: XDSLightbox, XDSLightboxProps, XDSLightboxMedia,
  XDSLightboxMediaType
cixzhang pushed a commit that referenced this pull request Jun 21, 2026
Fullscreen overlay for viewing images and videos at full resolution.

- Unified media prop: single object or array for gallery mode
- Video support: type='video' renders <video> with native controls
- Gallery mode: prev/next buttons and arrow key navigation
- Zoom: opt-in via hasZoom (images only), double-click toggles 1x/2x,
  drag to pan
- Built on native <dialog> with showModal() for focus trapping
- Controls use XDSIconButton with XDSIcon (close, chevronLeft/Right)
- Dark backdrop, white controls via --color-on-dark token
- Caption hugs media, gallery counter at top-left
- Accessible: aria-label, keyboard nav, focus restore on close
- Exports: XDSLightbox, XDSLightboxProps, XDSLightboxMedia,
  XDSLightboxMediaType
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants