mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 14:30:29 +03:00
redesign thread selector
This commit is contained in:
parent
67c6785bf3
commit
a6a3ac3b24
8 changed files with 154 additions and 115 deletions
|
|
@ -1,90 +0,0 @@
|
|||
import { Avatar, Box, Icon, Icons, Text } from 'folds';
|
||||
import React, { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { IThreadBundledRelationship, Room } from 'matrix-js-sdk';
|
||||
import { ContainerColor } from '../../styles/ContainerColor.css';
|
||||
import * as css from './styles.css';
|
||||
import { UserAvatar } from '../user-avatar';
|
||||
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||
|
||||
export function ThreadSelectorContainer({ children }: { children: ReactNode }) {
|
||||
return <Box className={css.ThreadSelectorContainer}>{children}</Box>;
|
||||
}
|
||||
|
||||
type ThreadSelectorProps = {
|
||||
room: Room;
|
||||
senderId: string;
|
||||
threadDetail: IThreadBundledRelationship;
|
||||
outlined?: boolean;
|
||||
};
|
||||
|
||||
export function ThreadSelector({ room, senderId, threadDetail, outlined }: ThreadSelectorProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
|
||||
|
||||
const latestEvent = threadDetail.latest_event;
|
||||
const latestSenderId = latestEvent.sender;
|
||||
const latestSenderAvatarMxc = getMemberAvatarMxc(room, latestSenderId);
|
||||
const latestDisplayName =
|
||||
getMemberDisplayName(room, latestSenderId) ??
|
||||
getMxIdLocalPart(latestSenderId) ??
|
||||
latestSenderId;
|
||||
|
||||
const latestEventTs = latestEvent.origin_server_ts;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classNames(
|
||||
css.ThreadSelector,
|
||||
outlined && css.ThreadSectorOutlined,
|
||||
ContainerColor({ variant: 'SurfaceVariant' })
|
||||
)}
|
||||
alignItems="Center"
|
||||
gap="200"
|
||||
>
|
||||
<Box gap="100" alignItems="Inherit">
|
||||
<Avatar size="200" radii="300">
|
||||
<UserAvatar
|
||||
userId={senderId}
|
||||
src={
|
||||
senderAvatarMxc
|
||||
? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
|
||||
: undefined
|
||||
}
|
||||
alt={senderId}
|
||||
renderFallback={() => <Icon size="200" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
{latestSenderId && (
|
||||
<Avatar size="200" radii="300">
|
||||
<UserAvatar
|
||||
userId={latestSenderId}
|
||||
src={
|
||||
latestSenderAvatarMxc
|
||||
? mxcUrlToHttp(mx, latestSenderAvatarMxc, useAuthentication, 48, 48, 'crop') ??
|
||||
undefined
|
||||
: undefined
|
||||
}
|
||||
alt={senderId}
|
||||
renderFallback={() => <Icon size="200" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
</Box>
|
||||
<Box gap="200" alignItems="Inherit">
|
||||
<Text className={css.ThreadRepliesCount} size="L400">
|
||||
{threadDetail.count} {threadDetail.count === 1 ? 'Reply' : 'Replies'}
|
||||
</Text>
|
||||
<Text size="T200" truncate>
|
||||
{/* TODO: date */}
|
||||
Last Reply by <b>{latestDisplayName}</b> at {new Date(latestEventTs).getTime()}
|
||||
</Text>
|
||||
<Icon size="100" src={Icons.ChevronRight} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { color, config } from 'folds';
|
||||
|
||||
export const ThreadSelectorContainer = style({
|
||||
paddingTop: config.space.S100,
|
||||
});
|
||||
|
||||
export const ThreadSelector = style({
|
||||
padding: config.space.S200,
|
||||
borderRadius: config.radii.R400,
|
||||
});
|
||||
|
||||
export const ThreadSectorOutlined = style({
|
||||
borderWidth: config.borderWidth.B300,
|
||||
});
|
||||
|
||||
export const ThreadRepliesCount = style({
|
||||
color: color.Primary.Main,
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
|
@ -127,7 +127,7 @@ import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/u
|
|||
import { useTheme } from '../../hooks/useTheme';
|
||||
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
||||
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||
import { ThreadSelector, ThreadSelectorContainer } from '../../components/thread-selector';
|
||||
import { ThreadSelector, ThreadSelectorContainer } from './message/thread-selector';
|
||||
|
||||
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
||||
({ position, className, ...props }, ref) => (
|
||||
|
|
@ -1113,7 +1113,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||
|
||||
{threadDetail && (
|
||||
<ThreadSelectorContainer>
|
||||
<ThreadSelector room={room} senderId={senderId} threadDetail={threadDetail} />
|
||||
<ThreadSelector
|
||||
room={room}
|
||||
threadDetail={threadDetail}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
outlined={messageLayout === MessageLayout.Bubble}
|
||||
/>
|
||||
</ThreadSelectorContainer>
|
||||
)}
|
||||
</Message>
|
||||
|
|
|
|||
105
src/app/features/room/message/thread-selector/ThreadSelector.tsx
Normal file
105
src/app/features/room/message/thread-selector/ThreadSelector.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { Avatar, Box, Icon, Icons, Line, Text } from 'folds';
|
||||
import React, { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { IThreadBundledRelationship, Room } from 'matrix-js-sdk';
|
||||
import * as css from './styles.css';
|
||||
import { UserAvatar } from '../../../../components/user-avatar';
|
||||
import { getMemberAvatarMxc, getMemberDisplayName } from '../../../../utils/room';
|
||||
import { useMatrixClient } from '../../../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../../../hooks/useMediaAuthentication';
|
||||
import { getMxIdLocalPart, mxcUrlToHttp } from '../../../../utils/matrix';
|
||||
import { Time } from '../../../../components/message';
|
||||
|
||||
export function ThreadSelectorContainer({ children }: { children: ReactNode }) {
|
||||
return <Box className={css.ThreadSelectorContainer}>{children}</Box>;
|
||||
}
|
||||
|
||||
type ThreadSelectorProps = {
|
||||
room: Room;
|
||||
threadDetail: IThreadBundledRelationship;
|
||||
outlined?: boolean;
|
||||
hour24Clock: boolean;
|
||||
dateFormatString: string;
|
||||
};
|
||||
|
||||
export function ThreadSelector({
|
||||
room,
|
||||
threadDetail,
|
||||
outlined,
|
||||
hour24Clock,
|
||||
dateFormatString,
|
||||
}: ThreadSelectorProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
|
||||
const latestEvent = threadDetail.latest_event;
|
||||
|
||||
const latestSenderId = latestEvent.sender;
|
||||
const latestSenderAvatarMxc = getMemberAvatarMxc(room, latestSenderId);
|
||||
const latestDisplayName =
|
||||
getMemberDisplayName(room, latestSenderId) ??
|
||||
getMxIdLocalPart(latestSenderId) ??
|
||||
latestSenderId;
|
||||
|
||||
const latestEventTs = latestEvent.origin_server_ts;
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
className={classNames(css.ThreadSelector, outlined && css.ThreadSectorOutlined)}
|
||||
alignItems="Center"
|
||||
gap="300"
|
||||
>
|
||||
<Box className={css.ThreadRepliesCount} shrink="No" alignItems="Center" gap="100">
|
||||
<Icon size="100" src={Icons.Thread} filled />
|
||||
<Text size="L400">
|
||||
{threadDetail.count} {threadDetail.count === 1 ? 'Thread Reply' : 'Thread Replies'}
|
||||
</Text>
|
||||
</Box>
|
||||
{latestSenderId && (
|
||||
<>
|
||||
<Line
|
||||
className={css.ThreadSelectorDivider}
|
||||
direction="Vertical"
|
||||
variant="SurfaceVariant"
|
||||
/>
|
||||
<Box gap="200" alignItems="Inherit">
|
||||
<Box gap="100" alignItems="Inherit">
|
||||
<Avatar size="200" radii="400">
|
||||
<UserAvatar
|
||||
userId={latestSenderId}
|
||||
src={
|
||||
latestSenderAvatarMxc
|
||||
? mxcUrlToHttp(
|
||||
mx,
|
||||
latestSenderAvatarMxc,
|
||||
useAuthentication,
|
||||
48,
|
||||
48,
|
||||
'crop'
|
||||
) ?? undefined
|
||||
: undefined
|
||||
}
|
||||
alt={latestSenderId}
|
||||
renderFallback={() => <Icon size="200" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Text size="T200" truncate>
|
||||
<span>
|
||||
Latest by <strong>{latestDisplayName}</strong> at{' '}
|
||||
</span>
|
||||
<Time
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
ts={latestEventTs}
|
||||
/>
|
||||
</Text>
|
||||
<Icon size="100" src={Icons.ChevronRight} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
37
src/app/features/room/message/thread-selector/styles.css.ts
Normal file
37
src/app/features/room/message/thread-selector/styles.css.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { color, config, toRem } from 'folds';
|
||||
import { ContainerColor } from '../../../../styles/ContainerColor.css';
|
||||
|
||||
export const ThreadSelectorContainer = style({
|
||||
marginTop: config.space.S200,
|
||||
});
|
||||
|
||||
export const ThreadSelector = style([
|
||||
ContainerColor({ variant: 'SurfaceVariant' }),
|
||||
{
|
||||
padding: `${config.space.S200} ${config.space.S300}`,
|
||||
borderRadius: config.radii.R400,
|
||||
cursor: 'pointer',
|
||||
|
||||
selectors: {
|
||||
'&:hover, &:focus-visible': {
|
||||
backgroundColor: color.SurfaceVariant.ContainerHover,
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: color.SurfaceVariant.ContainerActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const ThreadSectorOutlined = style({
|
||||
borderWidth: config.borderWidth.B300,
|
||||
});
|
||||
|
||||
export const ThreadSelectorDivider = style({
|
||||
height: toRem(16),
|
||||
});
|
||||
|
||||
export const ThreadRepliesCount = style({
|
||||
color: color.Primary.Main,
|
||||
});
|
||||
|
|
@ -80,7 +80,7 @@ import {
|
|||
} from '../../../hooks/useMemberPowerTag';
|
||||
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
|
||||
import { useRoomMyThreads } from '../../../hooks/useRoomThreads';
|
||||
import { ThreadSelector, ThreadSelectorContainer } from '../../../components/thread-selector';
|
||||
import { ThreadSelector, ThreadSelectorContainer } from '../message/thread-selector';
|
||||
|
||||
type ThreadMessageProps = {
|
||||
room: Room;
|
||||
|
|
@ -287,9 +287,10 @@ export const ThreadsMenu = forwardRef<HTMLDivElement, ThreadsMenuProps>(
|
|||
<ThreadSelectorContainer>
|
||||
<ThreadSelector
|
||||
room={room}
|
||||
senderId={event.getSender()!}
|
||||
threadDetail={threadDetail}
|
||||
outlined
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
/>
|
||||
</ThreadSelectorContainer>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const useRoomMyThreads = (room: Room): MatrixEvent[] | undefined => {
|
|||
null,
|
||||
30,
|
||||
Direction.Backward,
|
||||
ThreadFilterType.My
|
||||
ThreadFilterType.All
|
||||
),
|
||||
[mx, room]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue