fix(room-preview): make the preview card actually populate + richer
Two review agents found the preview card degrades to 'id + logo + Join' for real
reasons. Fixes:
- **via was dropped at the summary fetch** (root cause). RoomSummaryLoader now
accepts + forwards `via` to getRoomSummary (and keys the query on it);
JoinBeforeNavigate passes viaServers; the lobby Preview chip carries data-via and
Lobby.handleOpenRoom appends it to the navigated URL. Without this, previews of
rooms the HS isn't already in came back sparse/404.
- **No loading/error state** -> RoomSummaryLoader now surfaces {loading,error};
JoinBeforeNavigate shows a Spinner while loading instead of the degraded card.
- **Room id leaked as name AND topic AND header** -> stop using the raw `!id` as
the topic fallback (show 'No description'); name falls back to canonical_alias
then alias-localpart; the page header shows the summary name.
- **Richer, membership-aware card**: forward canonical_alias (shown under the name),
world_readable ('Readable' badge), and membership -> the button now shows Accept
invite / Requested / Banned correctly instead of a Join that lies; a 'hero' layout
for the single-preview page (primary Join button, unclamped topic). Removed dead
`|| undefined ||`.
IRoomSummary re-adds canonical_alias (the SDK type Omit<>s it though MSC3266
returns it). 738 tests pass, build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,24 +6,42 @@ import { useMatrixClient } from '../hooks/useMatrixClient';
|
|||||||
import { LocalRoomSummary, useLocalRoomSummary } from '../hooks/useLocalRoomSummary';
|
import { LocalRoomSummary, useLocalRoomSummary } from '../hooks/useLocalRoomSummary';
|
||||||
import { AsyncState, AsyncStatus } from '../hooks/useAsyncCallback';
|
import { AsyncState, AsyncStatus } from '../hooks/useAsyncCallback';
|
||||||
|
|
||||||
export type IRoomSummary = Awaited<ReturnType<MatrixClient['getRoomSummary']>>;
|
// MSC3266 returns canonical_alias at runtime, but the SDK's RoomSummary type
|
||||||
|
// Omit<>s it — add it back so callers can use it.
|
||||||
|
export type IRoomSummary = Awaited<ReturnType<MatrixClient['getRoomSummary']>> & {
|
||||||
|
canonical_alias?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoomSummaryState = { loading: boolean; error: unknown };
|
||||||
|
|
||||||
type RoomSummaryLoaderProps = {
|
type RoomSummaryLoaderProps = {
|
||||||
roomIdOrAlias: string;
|
roomIdOrAlias: string;
|
||||||
children: (roomSummary?: IRoomSummary) => ReactNode;
|
/**
|
||||||
|
* Resident servers to route the summary request through. REQUIRED for a room
|
||||||
|
* the local homeserver isn't already in (the exact case a preview exists for) —
|
||||||
|
* without it the summary comes back sparse or 404s.
|
||||||
|
*/
|
||||||
|
via?: string[];
|
||||||
|
children: (roomSummary: IRoomSummary | undefined, state: RoomSummaryState) => ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RoomSummaryLoader({ roomIdOrAlias, children }: RoomSummaryLoaderProps) {
|
export function RoomSummaryLoader({ roomIdOrAlias, via, children }: RoomSummaryLoaderProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
|
||||||
const fetchSummary = useCallback(() => mx.getRoomSummary(roomIdOrAlias), [mx, roomIdOrAlias]);
|
const fetchSummary = useCallback(
|
||||||
|
() => mx.getRoomSummary(roomIdOrAlias, via),
|
||||||
|
[mx, roomIdOrAlias, via],
|
||||||
|
);
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
queryKey: [roomIdOrAlias, `summary`],
|
// `via` is part of the key so a no-via failure isn't reused once we have servers.
|
||||||
|
queryKey: [roomIdOrAlias, 'summary', via ?? []],
|
||||||
queryFn: fetchSummary,
|
queryFn: fetchSummary,
|
||||||
|
retry: 1,
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
return children(data);
|
return children(data, { loading: isLoading, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LocalRoomSummaryLoader({
|
export function LocalRoomSummaryLoader({
|
||||||
|
|||||||
@@ -141,6 +141,12 @@ type RoomCardProps = {
|
|||||||
roomType?: string;
|
roomType?: string;
|
||||||
joinRule?: string;
|
joinRule?: string;
|
||||||
encrypted?: boolean;
|
encrypted?: boolean;
|
||||||
|
canonicalAlias?: string;
|
||||||
|
worldReadable?: boolean;
|
||||||
|
/** The viewer's own membership over federation (leave/invite/knock/ban). */
|
||||||
|
membership?: string;
|
||||||
|
/** Larger, centered treatment for the single-room preview page. */
|
||||||
|
hero?: boolean;
|
||||||
viaServers?: string[];
|
viaServers?: string[];
|
||||||
onView?: (roomId: string) => void;
|
onView?: (roomId: string) => void;
|
||||||
renderTopicViewer: (name: string, topic: string, requestClose: () => void) => ReactNode;
|
renderTopicViewer: (name: string, topic: string, requestClose: () => void) => ReactNode;
|
||||||
@@ -168,6 +174,10 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
roomType,
|
roomType,
|
||||||
joinRule,
|
joinRule,
|
||||||
encrypted,
|
encrypted,
|
||||||
|
canonicalAlias,
|
||||||
|
worldReadable,
|
||||||
|
membership,
|
||||||
|
hero,
|
||||||
viaServers,
|
viaServers,
|
||||||
onView,
|
onView,
|
||||||
renderTopicViewer,
|
renderTopicViewer,
|
||||||
@@ -183,16 +193,19 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
joinedRoom ? getStateEvent(joinedRoom, StateEvent.RoomTopic) : undefined,
|
joinedRoom ? getStateEvent(joinedRoom, StateEvent.RoomTopic) : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const fallbackName = getMxIdLocalPart(roomIdOrAlias) ?? roomIdOrAlias;
|
// Name falls back to the alias (readable), then the alias localpart, then the
|
||||||
const fallbackTopic = roomIdOrAlias;
|
// raw id — never the raw `!id` when a nicer form exists.
|
||||||
|
const fallbackName = canonicalAlias || getMxIdLocalPart(roomIdOrAlias) || roomIdOrAlias;
|
||||||
|
|
||||||
const avatar = joinedRoom
|
const avatar = joinedRoom
|
||||||
? getRoomAvatarUrl(mx, joinedRoom, 96, useAuthentication)
|
? getRoomAvatarUrl(mx, joinedRoom, 96, useAuthentication)
|
||||||
: avatarUrl && mxcUrlToHttp(mx, avatarUrl, useAuthentication, 96, 96, 'crop');
|
: avatarUrl && mxcUrlToHttp(mx, avatarUrl, useAuthentication, 96, 96, 'crop');
|
||||||
|
|
||||||
const roomName = joinedRoom?.name || name || fallbackName;
|
const roomName = joinedRoom?.name || name || fallbackName;
|
||||||
const roomTopic =
|
// No topic → render a muted placeholder, NOT the raw room id.
|
||||||
(topicEvent?.getContent().topic as string) || undefined || topic || fallbackTopic;
|
const roomTopic = (topicEvent?.getContent().topic as string) || topic || undefined;
|
||||||
|
// Only show an alias line when it adds info beyond the displayed name.
|
||||||
|
const aliasLine = canonicalAlias && canonicalAlias !== roomName ? canonicalAlias : undefined;
|
||||||
const joinedMemberCount = joinedRoom?.getJoinedMemberCount() ?? memberCount;
|
const joinedMemberCount = joinedRoom?.getJoinedMemberCount() ?? memberCount;
|
||||||
|
|
||||||
useStateEventCallback(
|
useStateEventCallback(
|
||||||
@@ -225,19 +238,22 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
);
|
);
|
||||||
// A knock-rule room can't be joined directly — request to join instead.
|
// A knock-rule room can't be joined directly — request to join instead.
|
||||||
const isKnock = joinRule === JoinRule.Knock;
|
const isKnock = joinRule === JoinRule.Knock;
|
||||||
|
// Membership known from the summary survives reloads (unlike local knockState).
|
||||||
|
const invited = membership === 'invite';
|
||||||
|
const alreadyKnocked = membership === 'knock';
|
||||||
|
const banned = membership === 'ban';
|
||||||
const action = isKnock ? knock : join;
|
const action = isKnock ? knock : join;
|
||||||
const actionState = isKnock ? knockState : joinState;
|
const actionState = isKnock ? knockState : joinState;
|
||||||
const acting =
|
const acting =
|
||||||
actionState.status === AsyncStatus.Loading || actionState.status === AsyncStatus.Success;
|
actionState.status === AsyncStatus.Loading || actionState.status === AsyncStatus.Success;
|
||||||
let actionLabel = acting ? 'Joining' : 'Join';
|
const requested = alreadyKnocked || knockState.status === AsyncStatus.Success;
|
||||||
if (isKnock) {
|
let actionLabel: string;
|
||||||
actionLabel =
|
if (banned) actionLabel = 'Banned';
|
||||||
knockState.status === AsyncStatus.Success
|
else if (invited) actionLabel = acting ? 'Joining' : 'Accept invite';
|
||||||
? 'Requested'
|
else if (isKnock)
|
||||||
: acting
|
actionLabel = requested ? 'Requested' : acting ? 'Requesting' : 'Request to join';
|
||||||
? 'Requesting'
|
else actionLabel = acting ? 'Joining' : 'Join';
|
||||||
: 'Request to join';
|
const actionDisabled = banned || requested || acting;
|
||||||
}
|
|
||||||
const chip = joinRuleLabel(joinRule);
|
const chip = joinRuleLabel(joinRule);
|
||||||
|
|
||||||
const [viewTopic, setViewTopic] = useState(false);
|
const [viewTopic, setViewTopic] = useState(false);
|
||||||
@@ -267,9 +283,25 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
</Box>
|
</Box>
|
||||||
<Box grow="Yes" direction="Column" gap="100">
|
<Box grow="Yes" direction="Column" gap="100">
|
||||||
<RoomCardName>{roomName}</RoomCardName>
|
<RoomCardName>{roomName}</RoomCardName>
|
||||||
<RoomCardTopic onClick={openTopic} onKeyDown={onEnterOrSpace(openTopic)} tabIndex={0}>
|
{aliasLine && (
|
||||||
{roomTopic}
|
<Text size="T200" priority="300" truncate>
|
||||||
</RoomCardTopic>
|
{aliasLine}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{roomTopic ? (
|
||||||
|
<RoomCardTopic
|
||||||
|
onClick={openTopic}
|
||||||
|
onKeyDown={onEnterOrSpace(openTopic)}
|
||||||
|
tabIndex={0}
|
||||||
|
style={hero ? { WebkitLineClamp: 10 } : undefined}
|
||||||
|
>
|
||||||
|
{roomTopic}
|
||||||
|
</RoomCardTopic>
|
||||||
|
) : (
|
||||||
|
<Text size="T200" priority="300" style={{ opacity: 0.6 }}>
|
||||||
|
No description
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
<Overlay open={viewTopic} backdrop={<OverlayBackdrop />}>
|
<Overlay open={viewTopic} backdrop={<OverlayBackdrop />}>
|
||||||
<OverlayCenter>
|
<OverlayCenter>
|
||||||
@@ -281,7 +313,7 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
escapeDeactivates: stopPropagation,
|
escapeDeactivates: stopPropagation,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderTopicViewer(roomName, roomTopic, closeTopic)}
|
{renderTopicViewer(roomName, roomTopic ?? '', closeTopic)}
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
</OverlayCenter>
|
</OverlayCenter>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
@@ -292,7 +324,7 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
<Text size="T200">{`${millify(joinedMemberCount)} Members`}</Text>
|
<Text size="T200">{`${millify(joinedMemberCount)} Members`}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{!joinedRoom && (chip || encrypted) && (
|
{!joinedRoom && (chip || encrypted || worldReadable) && (
|
||||||
<Box gap="100" wrap="Wrap">
|
<Box gap="100" wrap="Wrap">
|
||||||
{chip && (
|
{chip && (
|
||||||
<Badge variant="Secondary" fill="Soft" outlined>
|
<Badge variant="Secondary" fill="Soft" outlined>
|
||||||
@@ -304,6 +336,11 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
<Text size="L400">Encrypted</Text>
|
<Text size="L400">Encrypted</Text>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{worldReadable && (
|
||||||
|
<Badge variant="Secondary" fill="Soft" outlined>
|
||||||
|
<Text size="L400">Readable</Text>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{typeof joinedRoomId === 'string' && (
|
{typeof joinedRoomId === 'string' && (
|
||||||
@@ -321,9 +358,10 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||||||
{typeof joinedRoomId !== 'string' && actionState.status !== AsyncStatus.Error && (
|
{typeof joinedRoomId !== 'string' && actionState.status !== AsyncStatus.Error && (
|
||||||
<Button
|
<Button
|
||||||
onClick={action}
|
onClick={action}
|
||||||
variant="Secondary"
|
variant={hero ? 'Primary' : 'Secondary'}
|
||||||
|
fill={hero ? 'Solid' : undefined}
|
||||||
size="300"
|
size="300"
|
||||||
disabled={acting}
|
disabled={actionDisabled}
|
||||||
before={acting && <Spinner size="50" variant="Secondary" fill="Soft" />}
|
before={acting && <Spinner size="50" variant="Secondary" fill="Soft" />}
|
||||||
>
|
>
|
||||||
<Text size="B300" truncate>
|
<Text size="B300" truncate>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Icon, IconButton, Icons, Scroll, Text, toRem } from 'folds';
|
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, toRem } from 'folds';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { RoomCard } from '../../components/room-card';
|
import { RoomCard } from '../../components/room-card';
|
||||||
import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
||||||
@@ -32,53 +32,68 @@ export function JoinBeforeNavigate({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<PageHeader balance>
|
<RoomSummaryLoader roomIdOrAlias={roomIdOrAlias} via={viaServers}>
|
||||||
<Box grow="Yes" gap="200">
|
{(summary, state) => (
|
||||||
<Box shrink="No">
|
<>
|
||||||
{screenSize === ScreenSize.Mobile && (
|
<PageHeader balance>
|
||||||
<BackRouteHandler>
|
<Box grow="Yes" gap="200">
|
||||||
{(onBack) => (
|
<Box shrink="No">
|
||||||
<IconButton onClick={onBack} aria-label="Back">
|
{screenSize === ScreenSize.Mobile && (
|
||||||
<Icon src={Icons.ArrowLeft} />
|
<BackRouteHandler>
|
||||||
</IconButton>
|
{(onBack) => (
|
||||||
)}
|
<IconButton onClick={onBack} aria-label="Back">
|
||||||
</BackRouteHandler>
|
<Icon src={Icons.ArrowLeft} />
|
||||||
)}
|
</IconButton>
|
||||||
</Box>
|
)}
|
||||||
<Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
|
</BackRouteHandler>
|
||||||
<Text size="H3" truncate>
|
|
||||||
{roomIdOrAlias}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</PageHeader>
|
|
||||||
<Box grow="Yes">
|
|
||||||
<Scroll hideTrack visibility="Hover" size="0">
|
|
||||||
<Box style={{ height: '100%' }} grow="Yes" alignItems="Center" justifyContent="Center">
|
|
||||||
<RoomSummaryLoader roomIdOrAlias={roomIdOrAlias}>
|
|
||||||
{(summary) => (
|
|
||||||
<RoomCard
|
|
||||||
style={{ maxWidth: toRem(364), width: '100%' }}
|
|
||||||
roomIdOrAlias={roomIdOrAlias}
|
|
||||||
allRooms={allRooms}
|
|
||||||
avatarUrl={summary?.avatar_url}
|
|
||||||
name={summary?.name}
|
|
||||||
topic={summary?.topic}
|
|
||||||
memberCount={summary?.num_joined_members}
|
|
||||||
roomType={summary?.room_type}
|
|
||||||
joinRule={summary?.join_rule}
|
|
||||||
encrypted={!!summary?.['im.nheko.summary.encryption']}
|
|
||||||
viaServers={viaServers}
|
|
||||||
renderTopicViewer={(name, topic, requestClose) => (
|
|
||||||
<RoomTopicViewer name={name} topic={topic} requestClose={requestClose} />
|
|
||||||
)}
|
)}
|
||||||
onView={handleView}
|
</Box>
|
||||||
/>
|
<Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
|
||||||
)}
|
<Text size="H3" truncate>
|
||||||
</RoomSummaryLoader>
|
{summary?.name || summary?.canonical_alias || roomIdOrAlias}
|
||||||
</Box>
|
</Text>
|
||||||
</Scroll>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</PageHeader>
|
||||||
|
<Box grow="Yes">
|
||||||
|
<Scroll hideTrack visibility="Hover" size="0">
|
||||||
|
<Box
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
grow="Yes"
|
||||||
|
alignItems="Center"
|
||||||
|
justifyContent="Center"
|
||||||
|
>
|
||||||
|
{state.loading && !summary ? (
|
||||||
|
<Spinner size="600" variant="Secondary" />
|
||||||
|
) : (
|
||||||
|
<RoomCard
|
||||||
|
hero
|
||||||
|
style={{ maxWidth: toRem(420), width: '100%' }}
|
||||||
|
roomIdOrAlias={roomIdOrAlias}
|
||||||
|
allRooms={allRooms}
|
||||||
|
avatarUrl={summary?.avatar_url}
|
||||||
|
name={summary?.name}
|
||||||
|
canonicalAlias={summary?.canonical_alias}
|
||||||
|
topic={summary?.topic}
|
||||||
|
memberCount={summary?.num_joined_members}
|
||||||
|
roomType={summary?.room_type}
|
||||||
|
joinRule={summary?.join_rule}
|
||||||
|
encrypted={!!summary?.['im.nheko.summary.encryption']}
|
||||||
|
worldReadable={summary?.world_readable}
|
||||||
|
membership={summary?.membership}
|
||||||
|
viaServers={viaServers}
|
||||||
|
renderTopicViewer={(name, topic, requestClose) => (
|
||||||
|
<RoomTopicViewer name={name} topic={topic} requestClose={requestClose} />
|
||||||
|
)}
|
||||||
|
onView={handleView}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Scroll>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</RoomSummaryLoader>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { useCategoryHandler } from '../../hooks/useCategoryHandler';
|
|||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { allRoomsAtom } from '../../state/room-list/roomList';
|
import { allRoomsAtom } from '../../state/room-list/roomList';
|
||||||
import { getCanonicalAliasOrRoomId, rateLimitedActions } from '../../utils/matrix';
|
import { getCanonicalAliasOrRoomId, rateLimitedActions } from '../../utils/matrix';
|
||||||
import { getSpaceRoomPath } from '../../pages/pathUtils';
|
import { getSpaceRoomPath, withSearchParam } from '../../pages/pathUtils';
|
||||||
import { StateEvent } from '../../../types/matrix/room';
|
import { StateEvent } from '../../../types/matrix/room';
|
||||||
import { CanDropCallback, useDnDMonitor } from './DnD';
|
import { CanDropCallback, useDnDMonitor } from './DnD';
|
||||||
import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable';
|
import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable';
|
||||||
@@ -411,8 +411,13 @@ export function Lobby() {
|
|||||||
const handleOpenRoom: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
const handleOpenRoom: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||||
const rId = evt.currentTarget.getAttribute('data-room-id');
|
const rId = evt.currentTarget.getAttribute('data-room-id');
|
||||||
if (!rId) return;
|
if (!rId) return;
|
||||||
|
// Preview chips carry the room's resident servers so getRoomSummary can
|
||||||
|
// resolve a room the homeserver isn't already in (else the preview is sparse).
|
||||||
|
const via = evt.currentTarget.getAttribute('data-via');
|
||||||
const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId);
|
const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId);
|
||||||
navigate(getSpaceRoomPath(pSpaceIdOrAlias, getCanonicalAliasOrRoomId(mx, rId)));
|
let path = getSpaceRoomPath(pSpaceIdOrAlias, getCanonicalAliasOrRoomId(mx, rId));
|
||||||
|
if (via) path = withSearchParam(path, { viaServers: via });
|
||||||
|
navigate(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePinToSidebar = useCallback(
|
const togglePinToSidebar = useCallback(
|
||||||
|
|||||||
@@ -102,14 +102,17 @@ function RoomJoinButton({ roomId, via }: RoomJoinButtonProps) {
|
|||||||
// room isn't joined — renders the preview card instead of the timeline.
|
// room isn't joined — renders the preview card instead of the timeline.
|
||||||
function RoomPreviewChip({
|
function RoomPreviewChip({
|
||||||
roomId,
|
roomId,
|
||||||
|
via,
|
||||||
onOpen,
|
onOpen,
|
||||||
}: {
|
}: {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
via?: string[];
|
||||||
onOpen: MouseEventHandler<HTMLButtonElement>;
|
onOpen: MouseEventHandler<HTMLButtonElement>;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
data-room-id={roomId}
|
data-room-id={roomId}
|
||||||
|
data-via={via && via.length > 0 ? via.join(',') : undefined}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
fill="None"
|
fill="None"
|
||||||
@@ -391,7 +394,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
|||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box shrink="No" gap="100" alignItems="Center">
|
<Box shrink="No" gap="100" alignItems="Center">
|
||||||
<RoomPreviewChip roomId={roomId} onOpen={onOpen} />
|
<RoomPreviewChip roomId={roomId} via={content.via} onOpen={onOpen} />
|
||||||
<RoomJoinButton roomId={roomId} via={content.via} />
|
<RoomJoinButton roomId={roomId} via={content.via} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
@@ -439,7 +442,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
|||||||
joinRule={summary.join_rule}
|
joinRule={summary.join_rule}
|
||||||
options={
|
options={
|
||||||
<Box shrink="No" gap="100" alignItems="Center">
|
<Box shrink="No" gap="100" alignItems="Center">
|
||||||
<RoomPreviewChip roomId={roomId} onOpen={onOpen} />
|
<RoomPreviewChip roomId={roomId} via={content.via} onOpen={onOpen} />
|
||||||
<RoomJoinButton roomId={roomId} via={content.via} />
|
<RoomJoinButton roomId={roomId} via={content.via} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user