pl-fe: chats styles

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-01 21:33:35 +01:00
parent 5c883d4cfe
commit 3fc26881d8
3 changed files with 117 additions and 82 deletions

View File

@ -1,4 +1,5 @@
import { useNavigate } from '@tanstack/react-router';
import clsx from 'clsx';
import React, { useMemo } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -6,10 +7,7 @@ import DropdownMenu from 'pl-fe/components/dropdown-menu';
import { ParsedContent } from 'pl-fe/components/parsed-content';
import RelativeTimestamp from 'pl-fe/components/relative-timestamp';
import Avatar from 'pl-fe/components/ui/avatar';
import HStack from 'pl-fe/components/ui/hstack';
import IconButton from 'pl-fe/components/ui/icon-button';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
import { useChatContext } from 'pl-fe/contexts/chat-context';
import { useFeatures } from 'pl-fe/hooks/use-features';
@ -85,58 +83,46 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
data-testid='chat-list-item'
tabIndex={0}
>
<HStack alignItems='center' justifyContent='between' space={2} className='w-full'>
<HStack alignItems='center' space={2} className='overflow-hidden'>
<div>
<div className='⁂-chat-list-item__info'>
<Avatar
src={chat.account.avatar}
alt={chat.account.avatar_description}
size={40}
className='flex-none'
className='⁂-chat-list-item__avatar'
isCat={chat.account.is_cat}
username={chat.account.username}
/>
<Stack alignItems='start' className='overflow-hidden'>
<div className='flex w-full grow items-center space-x-1'>
<Text weight='bold' size='sm' align='left' truncate>{chat.account?.display_name || `@${chat.account.username}`}</Text>
<div className='⁂-chat-list-item__content'>
<div className='⁂-chat-list-item__name'>
<p>{chat.account?.display_name || `@${chat.account.username}`}</p>
{chat.account?.verified && <VerificationBadge />}
</div>
{(isBlocked || isBlocking) ? (
<Text
align='left'
size='sm'
weight='medium'
theme='muted'
truncate
className='pointer-events-none h-5 w-full italic'
data-testid='chat-last-message'
<p
className={clsx('⁂-chat-list-item__message', {
'⁂-chat-list-item__message--unread': !(isBlocked || isBlocking) && chat.last_message?.unread,
'⁂-chat-list-item__message--blocking': isBlocked || isBlocking,
})}
>
{isBlocked
? <FormattedMessage id='chat_list_item.blocked_you' defaultMessage='This user has blocked you' />
: <FormattedMessage id='chat_list_item.blocking' defaultMessage='You have blocked this user' />}
</Text>
{isBlocked ? (
<FormattedMessage id='chat_list_item.blocked_you' defaultMessage='This user has blocked you' />
) : isBlocking ? (
<FormattedMessage id='chat_list_item.blocking' defaultMessage='You have blocked this user' />
) : (
<>
{chat.last_message?.content && (
<Text
align='left'
size='sm'
weight='medium'
theme={chat.last_message.unread ? 'default' : 'muted'}
truncate
className='truncate-child pointer-events-none h-5 w-full'
data-testid='chat-last-message'
>
<ParsedContent html={chat.last_message?.content} emojis={chat.last_message.emojis} />
</Text>
chat.last_message?.content && (
<ParsedContent
html={chat.last_message?.content}
emojis={chat.last_message.emojis}
/>
)
)}
</>
)}
</Stack>
</HStack>
</p>
</div>
</div>
<HStack alignItems='center' space={2}>
<div className='⁂-chat-list-item__actions'>
{features.chatsDelete && (
<div className='⁂-chat-list-item__menu'>
<DropdownMenu items={menu}>
@ -152,7 +138,7 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
<>
{chat.last_message.unread && (
<div
className='size-2 rounded-full bg-secondary-500'
className='⁂-chat-list-item__unread'
data-testid='chat-unread-indicator'
/>
)}
@ -166,8 +152,8 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
/>
</>
)}
</HStack>
</HStack>
</div>
</div>
</div>
);
};

View File

@ -4,9 +4,6 @@ import { FormattedMessage } from 'react-intl';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
import { ParsedContent } from 'pl-fe/components/parsed-content';
import Avatar from 'pl-fe/components/ui/avatar';
import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import { useInstance } from 'pl-fe/hooks/use-instance';
import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config';
import { useShoutboxMessages } from 'pl-fe/stores/shoutbox';
@ -37,43 +34,34 @@ const ChatListShoutbox: React.FC<IChatListShoutboxInterface> = ({ onClick }) =>
key='shoutbox'
onClick={() => onClick('shoutbox')}
onKeyDown={handleKeyDown}
className='⁂-chat-list-item'
className='⁂-chat-list-item ⁂-chat-list-item--shoutbox'
data-testid='chat-list-item'
tabIndex={0}
>
<HStack alignItems='center' justifyContent='between' space={2} className='w-full'>
<HStack alignItems='center' space={2} className='overflow-hidden'>
<div>
<Avatar src={logo} alt='' size={40} className='flex-none' />
<Stack alignItems='start' className='overflow-hidden'>
<div className='flex w-full grow items-center space-x-1'>
<Text weight='bold' size='sm' align='left' truncate>
<div className='⁂-chat-list-item__content'>
<div className='⁂-chat-list-item__name'>
<p>
<FormattedMessage id='chat_list_item_shoutbox' defaultMessage='{instance} shoutbox' values={{ instance: instance.title }} />
</Text>
</p>
</div>
{lastMessage && (
<>
<Text
align='left'
size='sm'
weight='medium'
theme='muted'
truncate
className='truncate-child pointer-events-none h-5 w-full'
>
<p className='⁂-chat-list-item__message'>
{lastMessageAuthor && (
<Text weight='bold' size='sm' align='left' theme='muted' truncate tag='span'>
<span className='⁂-chat-list-item__message__author'>
{lastMessageAuthor.display_name || `@${lastMessageAuthor.username}`}:
{' '}
</Text>
</span>
)}
<ParsedContent html={lastMessage.text} />
</Text>
</p>
</>
)}
</Stack>
</HStack>
</HStack>
</div>
</div>
</div>
);
};

View File

@ -131,6 +131,55 @@
.-chat-list-item {
@apply flex w-full flex-col rounded-lg px-2 py-3 hover:bg-gray-100 focus:shadow-inset-ring dark:hover:bg-gray-800;
> div {
@apply flex items-center justify-between gap-2 w-full;
}
&--shoutbox > div {
@apply justify-normal;
}
&__info {
@apply flex items-center gap-2 overflow-hidden;
}
&__avatar {
@apply flex-none;
}
&__content {
@apply flex flex-col items-start overflow-hidden;
}
&__name {
@apply flex w-full grow items-center space-x-1;
p {
@include mixins.text($weight: bold, $size: sm, $align: left, $truncate: true);
}
}
&__message {
@include mixins.text($theme: muted, $size: sm, $align: left, $truncate: true);
@apply truncate-child pointer-events-none h-5 w-full;
&--unread {
@apply text-gray-900 dark:text-gray-100;
}
&:not(&--blocking) > * {
@apply truncate;
}
&__author {
@include mixins.text($weight: bold, $size: sm, $align: left, $theme: muted, $truncate: true);
}
}
&__actions {
@apply flex items-center gap-2;
}
&__menu {
@apply max-w-0 overflow-hidden text-gray-600 hover:text-gray-100;
@ -146,4 +195,16 @@
&:hover .-chat-list-item__menu {
@apply max-w-full;
}
&__unread {
@apply size-2 rounded-full bg-secondary-500;
}
&__timestamp {
@include mixins.text($theme: muted, $size: xs, $align: right, $truncate: true);
}
&__unread + &__timestamp {
@apply text-gray-900 dark:text-gray-100;
}
}