Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d2e76e4
fix(Lightbox): a single photo's caption sits on the caption's backing
vjeux Nov 8, 2026
d398515
fix(Lightbox): a single photo's caption shows as a gallery's
vjeux Nov 8, 2026
b64c3d4
feat(Lightbox): a gallery's caption sits in a two-line band
vjeux Nov 8, 2026
50875b6
docs(Lightbox): caption notes and records cover a single photo
vjeux Nov 8, 2026
232f499
test(Layer): the one-gutter check lists Lightbox's full-screen viewer
vjeux Nov 8, 2026
b565bdc
docs(Lightbox): FR1b covers a single captioned video too
vjeux Nov 8, 2026
18d4bb5
Merge main into the caption band
vjeux Nov 8, 2026
9a9b0f7
Merge the caption band's main merge
vjeux Nov 8, 2026
801fa11
docs(Lightbox): FR1b quotes only the owner's exact words
vjeux Nov 8, 2026
f2fdc74
Merge main into the caption band
vjeux Nov 8, 2026
785873b
Merge the caption band's second main merge
vjeux Nov 8, 2026
5877a0e
Merge main's drag to close into the caption band
vjeux Nov 8, 2026
cd2e283
fix(Lightbox): a drag to close fades a gallery's caption band
vjeux Nov 8, 2026
5c56c40
feat(Lightbox): a caption's "more" and "less" are an inline link
vjeux Nov 8, 2026
c6efc6e
Merge the caption band's main merge and inline link
vjeux Nov 8, 2026
926524a
fix(Lightbox): the caption's toggle takes its touch target from Core
vjeux Nov 8, 2026
d6950dc
Merge the caption band's touch target fix
vjeux Nov 8, 2026
d038a4b
Merge main's tempo for the drag to close into the caption band
vjeux Nov 8, 2026
2b0b009
Merge the caption band's main merge for the drag to close's tempo
vjeux Nov 8, 2026
29779c1
Merge main's first truncation measurement into the caption band
vjeux Nov 8, 2026
cff0218
Merge the caption band's merge of main's truncation measurement
vjeux Nov 8, 2026
74a1798
Merge main's nearest-item index into the caption band
vjeux Nov 9, 2026
f4874b7
Merge the caption band's merge of main's nearest-item index
vjeux Nov 9, 2026
ef505e3
Merge main's busy proof test fixes into the caption band
vjeux Nov 9, 2026
f782a98
Merge the caption band's merge of main's busy proof test fixes
vjeux Nov 9, 2026
f53cbe5
fix(Lightbox): "more" and "less" take the caption's colour
vjeux Nov 9, 2026
79b5a27
Merge the caption band's toggle colour fix
vjeux Nov 9, 2026
36836d2
Merge main's Lightbox screen reader and zoom reset changes
vjeux Nov 9, 2026
24cb6d1
Merge the caption band's merge of main
vjeux Nov 9, 2026
48232ac
fix(Lightbox): the caption measures through one shared observer path
vjeux Nov 9, 2026
1c6211d
Merge the caption band's shared observer path
vjeux Nov 9, 2026
c450f37
Merge main
vjeux Nov 9, 2026
7de97bf
Merge the caption band's merge of main
vjeux Nov 9, 2026
0412ac8
fix(Lightbox): the caption's toggle sits on a whole line box
vjeux Nov 9, 2026
84aeecf
Merge the caption band's whole line box for the toggle
vjeux Nov 9, 2026
f27d557
fix(Lightbox): the toggle's place is as tall as the caption's lines
vjeux Nov 9, 2026
12f50c4
Merge the caption toggle place made of line boxes
vjeux Nov 9, 2026
558fa34
fix(Lightbox): an open caption's toggle place shrinks when it widens
vjeux Nov 10, 2026
cd23648
Merge the open caption toggle place that shrinks when it widens
vjeux Nov 10, 2026
547f67e
fix(Lightbox): an opened caption counts its lines without the toggle
vjeux Nov 10, 2026
fceae0c
Merge the opened caption that counts its lines without the toggle
vjeux Nov 10, 2026
59a4256
Merge main
vjeux Nov 10, 2026
2d69149
Merge main
vjeux Nov 10, 2026
2a2aa33
fix(Lightbox): keep internal markers off public data attributes
vjeux Nov 10, 2026
e7e9aef
fix(Lightbox): new text in an open caption counts its lines again
vjeux Nov 10, 2026
766883d
docs(Lightbox): name where focus goes after paging from the toggle
vjeux Nov 10, 2026
5eddce8
Merge the caption band's attribute and recount fixes
vjeux Nov 10, 2026
8e8db8e
fix(Lightbox): a click on the caption band leaves the viewer open
vjeux Nov 10, 2026
36ec042
Merge the caption band's click rule
vjeux Nov 10, 2026
5b1475c
docs(Lightbox): a double-click on a caption zooms the photo
vjeux Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/lightbox-gallery-caption-row.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@astryxdesign/core': patch
---

[feat] A Lightbox gallery's caption sits in a band two lines tall under every photo, so the photo keeps one size and place from item to item. A photo with no caption keeps the band's space without its backing. A longer caption shows two lines, the second ending in an inline "more" link, which opens the whole caption over the photo. The whole caption is always the viewer's accessible description.

The viewer is a media surface: its controls, caption and counter take the theme's dark surface tokens through `MediaTheme`. The caption is body `Text` whose lines start at one edge, "more" and "less" are `Link`'s button form in the caption's own type, and the band clears the screen's bottom edge and its safe area. A gallery's caption sits on a backing that keeps it at 4.5:1 over any photo. A tap that opens the viewer paints no focus ring on its first control, and paging from a focused "more" or "less" keeps focus in the viewer.

@vjeux
2 changes: 1 addition & 1 deletion .changeset/lightbox-gallery-paging.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[feat] A Lightbox gallery now pages the way the iOS Photos viewer does. On a phone or tablet, drag or flick between items: the row follows your finger, and the release lands on the next or previous item, one at most. A gap of 40 px separates the items while they move. A zoomed photo pans with your finger until it reaches its edge; a new drag from that edge moves to the next item. A photo you leave zoomed returns to its fitted size once the gallery rests. Under a finger, the previous and next buttons and the arrow keys slide to the item instead of swapping it in place.

Each item's caption moves with its item, right under its photo. `onIndexChange` also reports a swipe.
Each item's caption moves with its item, in a caption band the gallery's photos share. `onIndexChange` also reports a swipe.

Lightbox now loads its touch code with it, as Toast and Slider do. Its import grows from 51.4 KB to 99.6 KB gzip with the zoom and paging beneath, mostly shared touch modules that a page loads once; Lightbox's own code adds 14.1 KB minified.

Expand Down
7 changes: 7 additions & 0 deletions .changeset/lightbox-single-caption-backing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] A single photo's caption in a `Lightbox` now shows as a gallery's does. It sits in the band at the bottom of the viewer, on the caption's backing, which keeps 4.5:1 over any page behind it. The photo centres above it. A caption longer than two lines ends in "more", which opens it in full over the photo. A single photo with no caption shows no band.

@vjeux
42 changes: 37 additions & 5 deletions apps/storybook/stories/Lightbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const WithZoom: Story = {
},
};

/** Zoom on a photo with a caption, which sits under the fitted photo. */
/** Zoom on a photo with a caption, in the caption band a gallery's pages use. */
export const ZoomWithCaption: Story = {
render: () => {
const [isOpen, setIsOpen] = useState(false);
Expand Down Expand Up @@ -176,8 +176,9 @@ export const GalleryWithZoom: Story = {
};

/**
* A gallery whose captions differ in length: the photo keeps one box
* whichever pages, and so whichever captions, are rendered.
* A gallery whose captions differ in length. The first runs long, so two
* lines cut it short and "more" shows it all. The next runs about two lines,
* one has none, and the last is short. The photo keeps one box on every page.
*/
export const GalleryWithCaptions: Story = {
render: () => {
Expand All @@ -194,9 +195,13 @@ export const GalleryWithCaptions: Story = {
{
...GALLERY_MEDIA[0],
caption:
'A winding path through the forest, past the old mill and down to the river, where the trail turns back toward the village',
'A winding path through the forest, past the old mill and down to the river, where the trail turns back toward the village. In autumn the beeches along it turn copper, and on a clear morning you can see the church spire from the bridge before the path climbs the last hill home.',
},
{
...GALLERY_MEDIA[1],
caption:
'Morning fog lifting off the lake, seen from the old stone bridge',
},
GALLERY_MEDIA[1],
GALLERY_MEDIA[3],
{...GALLERY_MEDIA[2], caption: 'Golden hour'},
]}
Expand Down Expand Up @@ -233,6 +238,33 @@ export const GalleryKeepsIndex: Story = {
},
};

/**
* A single photo whose caption runs long. As on a gallery's page, two lines
* cut it short and "more" shows it all.
*/
export const SingleWithLongCaption: Story = {
render: () => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>
Open single photo with a long caption
</button>
<Lightbox
isOpen={isOpen}
onOpenChange={setIsOpen}
media={{
...GALLERY_MEDIA[0],
caption:
'A winding path through the forest, past the old mill and down to the river, where the trail turns back toward the village. In autumn the beeches along it turn copper, and on a clear morning you can see the church spire from the bridge before the path climbs the last hill home.',
}}
hasZoom
/>
</>
);
},
};

export const WithCaption: Story = {
render: () => {
const [isOpen, setIsOpen] = useState(false);
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/AST-038-layer-text-boundary/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ and ownership guards MUST remain component-owned when that equivalence is absent
| [useLayer](../../../packages/core/src/Layer/useLayer.tsx) base typography | Existing body family/size/leading are the one equivalent baseline moved into the private shared reset by the text-only implementation. No token/value or precedence change for those three declarations is intended. |
| [Tooltip](../../../packages/core/src/Tooltip/useTooltip.tsx) painted surface | Explicit body type, inverted colors, and content `wordBreak: break-word` are deliberate. Keep them; moving inner declarations outward can change supported overrides even where values match. |
| [Toast](../../../packages/core/src/Toast/Toast.tsx) | Uses body **defaults** for size/leading, surface inversion, and content `overflowWrap: anywhere`. This is not equivalent to replacing them with inherited live body variables. Keep them. |
| [Lightbox](../../../packages/core/src/Lightbox/Lightbox.tsx), menu rows, keyboard hints, chart/radial tooltips | Centered large captions, menu label/supporting type and start alignment, smaller nowrap keyboard hints, and compact chart wrapping remain intentional local presentation. Do not delete them as redundant resets. |
| [Lightbox](../../../packages/core/src/Lightbox/Lightbox.tsx), menu rows, keyboard hints, chart/radial tooltips | Lightbox's caption renders through Text under MediaTheme (`component:Lightbox`). Menu label/supporting type and start alignment, smaller nowrap keyboard hints, and compact chart wrapping remain intentional local presentation. Do not delete them as redundant resets. |
| [useLayer portal writing context](../../../packages/core/src/Layer/useLayer.tsx), [padding reset](../../../packages/core/src/Layout/padding.stylex.ts) | Corrective portals preserve lost direction/writing mode without snapshotting tokens. The existing padding boundary resets its own structural variables. Preserve both; neither is a generic group or text barrier. |
| Button/InputGroup end-cap rules and Collapsible/BottomSheetSwitcher context guards | Already solve distinct membership/ownership problems. Preserve them; the text boundary does not replace sibling classification, nested presentation isolation, or sheet registration scope. |
| `all: unset` on Item/TreeListItem/Collapsible/Stepper/Token/Thumbnail/ChatComposer controls | Local UA/control resets, not layer-root inheritance fixes. No new `all`/`unset`/`revert` layer reset is proposed. ToastViewport's `inset: unset` is geometry; menuWidth's `unset`/`revert` strings are caller-supplied width keywords routed to inline-size rather than `min()`, not a general inheritance reset. |
Expand Down
Loading