4a4dede105
The real reason the gallery didn't look or function like the Members drawer
or Saved Messages: it was a position:fixed overlay floating over the timeline,
mounted from RoomViewHeader. Now it docks into the room layout row exactly like
MembersDrawer.
- new mediaGalleryAtom (mirrors bookmarksPanelAtom) holds the open state
- RoomViewHeader toggles the atom instead of local useState and no longer
renders the panel
- Room.tsx renders <MediaGallery> as a flex sibling of the timeline with a
vertical Line separator on desktop and key={room.roomId} to reset per room
- MediaGallery.css: static width on desktop, position:fixed inset:0 full-screen
only on mobile (identical strategy to MembersDrawer.css); root Box shrink="No"
The panel now shares the row with the timeline instead of overlapping it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
123 lines
3.1 KiB
TypeScript
123 lines
3.1 KiB
TypeScript
import { style } from '@vanilla-extract/css';
|
|
import { color, config, toRem } from 'folds';
|
|
|
|
// Right-side drawer DOCKED into the room layout row (a flex sibling of the
|
|
// timeline), exactly like MembersDrawer — not a floating overlay. 320px is a
|
|
// little wider than the 266px member/bookmark drawers because it hosts a media
|
|
// grid. On narrow viewports it becomes a full-screen fixed panel, matching the
|
|
// app's other drawers.
|
|
export const MediaGalleryDrawer = style({
|
|
width: toRem(320),
|
|
overflow: 'hidden',
|
|
'@media': {
|
|
'(max-width: 750px)': {
|
|
position: 'fixed',
|
|
inset: 0,
|
|
width: '100%',
|
|
zIndex: 500,
|
|
},
|
|
},
|
|
});
|
|
|
|
export const MediaGalleryHeader = style({
|
|
flexShrink: 0,
|
|
padding: `0 ${config.space.S200} 0 ${config.space.S300}`,
|
|
borderBottomWidth: config.borderWidth.B300,
|
|
});
|
|
|
|
export const MediaGalleryTabs = style({
|
|
flexShrink: 0,
|
|
padding: config.space.S200,
|
|
gap: config.space.S100,
|
|
borderBottomWidth: config.borderWidth.B300,
|
|
borderBottomStyle: 'solid',
|
|
borderBottomColor: color.Background.ContainerLine,
|
|
});
|
|
|
|
export const MediaGalleryContent = style({
|
|
padding: config.space.S200,
|
|
});
|
|
|
|
export const MediaGalleryGroup = style({
|
|
marginBottom: config.space.S300,
|
|
});
|
|
|
|
export const MediaGalleryGroupLabel = style({
|
|
padding: `${config.space.S200} ${config.space.S100}`,
|
|
});
|
|
|
|
export const MediaGalleryGrid = style({
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
gap: config.space.S100,
|
|
});
|
|
|
|
export const GalleryTile = style({
|
|
position: 'relative',
|
|
aspectRatio: '1',
|
|
overflow: 'hidden',
|
|
borderRadius: config.radii.R300,
|
|
background: color.SurfaceVariant.Container,
|
|
borderWidth: config.borderWidth.B300,
|
|
borderStyle: 'solid',
|
|
borderColor: color.SurfaceVariant.ContainerLine,
|
|
cursor: 'pointer',
|
|
padding: 0,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
transition: 'border-color 100ms ease-in-out',
|
|
selectors: {
|
|
'&:hover, &:focus-visible': {
|
|
borderColor: color.Primary.Main,
|
|
},
|
|
},
|
|
});
|
|
|
|
export const GalleryTileImg = style({
|
|
width: '100%',
|
|
height: '100%',
|
|
objectFit: 'cover',
|
|
objectPosition: 'center top',
|
|
display: 'block',
|
|
});
|
|
|
|
// Dark scrim is intentional: it overlays arbitrary media, so a theme surface
|
|
// token would not guarantee legibility of the sender/date caption.
|
|
export const GalleryTileOverlay = style({
|
|
position: 'absolute',
|
|
inset: 0,
|
|
background: 'linear-gradient(to top, rgba(0,0,0,0.72) 0%, transparent 55%)',
|
|
opacity: 0,
|
|
transition: 'opacity 100ms ease-in-out',
|
|
pointerEvents: 'none',
|
|
selectors: {
|
|
[`${GalleryTile}:hover &, ${GalleryTile}:focus-visible &`]: {
|
|
opacity: 1,
|
|
},
|
|
},
|
|
});
|
|
|
|
export const GalleryTileCaption = style({
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
padding: `${config.space.S100} ${config.space.S200}`,
|
|
});
|
|
|
|
export const GalleryVideoBadge = style({
|
|
position: 'absolute',
|
|
top: '50%',
|
|
left: '50%',
|
|
transform: 'translate(-50%, -50%)',
|
|
width: toRem(28),
|
|
height: toRem(28),
|
|
borderRadius: '50%',
|
|
background: 'rgba(0,0,0,0.6)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
pointerEvents: 'none',
|
|
});
|