feat(room-preview): preview affordance in space lobby + explore grids
CI / Build & Quality Checks (push) Successful in 10m48s
CI / Trigger Desktop Build (push) Successful in 7s

Follow-up to the room-preview work so you can actually reach a preview from the
room lists, not just via a matrix.to link:

- Space lobby (RoomItem): un-joined child rooms now show an Eye 'Preview' chip
  next to Join. It routes through the existing onOpen (data-room-id -> space/room
  path), which renders the full JoinBeforeNavigate preview card because the room
  isn't joined.
- Explore grids (Server + Featured): pass join_rule (and encryption, where the
  summary provides it) to RoomCard, so the directory/featured cards — which already
  are full preview cards — now show the join-rule chip and a Request-to-join button
  for knock rooms instead of a Join that fails.

738 tests pass, build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 14:07:51 -04:00
parent 44420220d4
commit 7fd3164b1f
3 changed files with 40 additions and 2 deletions
+34 -1
View File
@@ -97,6 +97,31 @@ function RoomJoinButton({ roomId, via }: RoomJoinButtonProps) {
);
}
// Open the full preview page (JoinBeforeNavigate) for an un-joined room. onOpen
// reads data-room-id and navigates to the space→room path, which — because the
// room isn't joined — renders the preview card instead of the timeline.
function RoomPreviewChip({
roomId,
onOpen,
}: {
roomId: string;
onOpen: MouseEventHandler<HTMLButtonElement>;
}) {
return (
<Chip
data-room-id={roomId}
onClick={onOpen}
variant="Secondary"
fill="None"
size="400"
radii="Pill"
aria-label="Preview room"
>
<Icon size="50" src={Icons.Eye} />
</Chip>
);
}
function RoomProfileLoading() {
return (
<Box grow="Yes" gap="300">
@@ -365,7 +390,10 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
</Chip>
</Box>
) : (
<Box shrink="No" gap="100" alignItems="Center">
<RoomPreviewChip roomId={roomId} onOpen={onOpen} />
<RoomJoinButton roomId={roomId} via={content.via} />
</Box>
)
}
/>
@@ -409,7 +437,12 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
memberCount={summary.num_joined_members}
suggested={content.suggested}
joinRule={summary.join_rule}
options={<RoomJoinButton roomId={roomId} via={content.via} />}
options={
<Box shrink="No" gap="100" alignItems="Center">
<RoomPreviewChip roomId={roomId} onOpen={onOpen} />
<RoomJoinButton roomId={roomId} via={content.via} />
</Box>
}
/>
)}
</>
@@ -70,6 +70,8 @@ export function FeaturedRooms() {
name={roomSummary?.name}
topic={roomSummary?.topic}
memberCount={roomSummary?.num_joined_members}
joinRule={roomSummary?.join_rule}
encrypted={!!roomSummary?.['im.nheko.summary.encryption']}
onView={navigateSpace}
renderTopicViewer={(name, topic, requestClose) => (
<RoomTopicViewer
@@ -101,6 +103,8 @@ export function FeaturedRooms() {
name={roomSummary?.name}
topic={roomSummary?.topic}
memberCount={roomSummary?.num_joined_members}
joinRule={roomSummary?.join_rule}
encrypted={!!roomSummary?.['im.nheko.summary.encryption']}
onView={navigateRoom}
renderTopicViewer={(name, topic, requestClose) => (
<RoomTopicViewer
+1
View File
@@ -604,6 +604,7 @@ export function PublicRooms() {
topic={chunkRoom.topic}
memberCount={chunkRoom.num_joined_members}
roomType={chunkRoom.room_type}
joinRule={chunkRoom.join_rule}
onView={
chunkRoom.room_type === RoomType.Space
? navigateSpace