Media & effects
Photo Box
Frosted paper box with fanning polaroids and tactile zoom inspection
detent-photo-box
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 4
- Wave
- phase-4-cinematic
- Group
- Media & effects
8.0 KB gzip for the class module
Variants
Hover, tap or press Enter on the frosted cover to slide the drawer out; the polaroids fan and join the tab order.
Activate a print to inspect it enlarged, then press Escape (or click anywhere) to put it back — focus returns to the
print you inspected. Each user change emits one composed event: detent-open-change with
{ open } and detent-select with { id }. Prints are plain data; a
src is accepted only for relative and http(s) URLs.
Recorded states
hoverrecorded state; no setteropenrecorded state; no setterclosedrecorded state; no setterexpandedrecorded state; no settercollapsedrecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/photo-box/define.js';
// or register it yourself
import { DetentPhotoBox } from '/components/photo-box/photo-box.js';
Attributes
opensubtitletitle
Properties
onOpenChangeonSelectprintprints
Slots
No slots are recorded for this component.
Events
detent-open-change— { open: boolean }detent-select— { id: string | null }
Example
Runnable example: site/examples/photo-box.ts. The snippet below is generated from the catalogued attributes.
<detent-photo-box
open
subtitle="Photo Box · 3 prints"
title="写真箱"
></detent-photo-box>
Property-only values
const element = document.querySelector('detent-photo-box');
if (element !== null) {
// prints: PhotoPrint[]
element.prints = [];
// onOpenChange: (open: boolean) => void
element.onOpenChange = () => {};
// onSelect: (id: string | null) => void
element.onSelect = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/PhotoBox.tsxsha256 553ff585c289…
Native files
The native module and its artifacts:
src/components/photo-box/photo-box.tssrc/components/photo-box/styles.csssrc/components/photo-box/define.tsdocs/components/photo-box.mdsite/examples/photo-box.tstests/browser/photo-box.spec.tsevidence/components/photo-box.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
open | open | boolean | undefined | Presence semantics: present means true, absent means false. |
subtitle | subtitle | string | 'Photo Box · 3 prints' | Kebab-case attribute; camelCase property. |
title | title | string | '写真箱' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
onOpenChange | (open: boolean) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
onSelect | (id: string | null) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
print | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
prints | PhotoPrint[] | DEFAULT_PRINTS | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-open-change | onOpenChange | (open: boolean) => void | Composed, bubbles; fires only for a user action. |
detent-select | onSelect | (id: string | null) => void | Composed, bubbles; fires only for a user action. |
Slots
No slots are recorded for this component.
CSS customisation
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- Events are user-driven only; upstream fired both callbacks from useEffect, including on mount and on controlled open writes.
- Upstream Framer Motion springs (duration 0.55, bounce 0.12..0.24) become CSS transitions over cubic-bezier(0.23,1,0.32,1): 550 ms transform, 280 ms opacity and filter. Owner approved this spring-for-easing substitution on 2026-10-29 (PC-10.4); motion fidelity remains an L6 manual gate.
- prints[].art (ReactNode) is dropped; the three default ids render local SVG art, an unknown id without src renders a neutral placeholder, and PhotoBoxDemo is not ported.
- prints[].src is scheme-checked (R-3.1/R-3.2): relative and http(s) accepted, javascript:, data:, vbscript: and file: refused with a development diagnostic and local-art fallback.
- aria-modal is honest: the rest of the composition is inert while the enlarged view is open, and collapsing returns focus to the inspected print.
- className is not an attribute; styling uses the documented parts and custom properties.
- WCAG corrections: cover #a8461a instead of #e8742f (3.0:1 to 5.91:1), subtext at 85% white instead of 75% (4.05:1 to 4.73:1), and an opaque lip caption.
What it does
A frosted paper box: hovering, tapping or pressing Enter on the cover slides the drawer out and fans three polaroids; activating a print inspects it enlarged.
Upstream behaviour
- Translucent frosted glass cover with realistic backdrop-blur and warm gradient sheen
- Sliding paper box drawer physics with spring-tuned easing and offset lip reveal
- Tri-slot polaroid fanning layout that expands on hover, tap, or focus
- Click-to-zoom polaroid inspection view with high-res framing and title captions
- Accessible keyboard navigation (Tab through prints, Enter to expand, Esc to dismiss)
- Responsive auto-scaling stage with ResizeObserver keeping pixel-perfect geometry down to mobile screens
Differences from upstream
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- Events are user-driven only; upstream fired both callbacks from useEffect, including on mount and on controlled open writes.
- Upstream Framer Motion springs (duration 0.55, bounce 0.12..0.24) become CSS transitions over cubic-bezier(0.23,1,0.32,1): 550 ms transform, 280 ms opacity and filter. Owner approved this spring-for-easing substitution on 2026-10-29 (PC-10.4); motion fidelity remains an L6 manual gate.
- prints[].art (ReactNode) is dropped; the three default ids render local SVG art, an unknown id without src renders a neutral placeholder, and PhotoBoxDemo is not ported.
- prints[].src is scheme-checked (R-3.1/R-3.2): relative and http(s) accepted, javascript:, data:, vbscript: and file: refused with a development diagnostic and local-art fallback.
- aria-modal is honest: the rest of the composition is inert while the enlarged view is open, and collapsing returns focus to the inspected print.
- className is not an attribute; styling uses the documented parts and custom properties.
- WCAG corrections: cover #a8461a instead of #e8742f (3.0:1 to 5.91:1), subtext at 85% white instead of 75% (4.05:1 to 4.73:1), and an opaque lip caption.