pl-fe: chat accessibility improvements

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-09 18:27:30 +01:00
parent 1fc9ddbd64
commit 5763e4053b
6 changed files with 50 additions and 27 deletions

View File

@ -1,8 +1,14 @@
import React, { HTMLAttributes } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import IconButton from '@/components/ui/icon-button';
import { useSettings } from '@/stores/settings';
const messages = defineMessages({
expand: { id: 'chat_pane.header.expand', defaultMessage: 'Expand chats' },
collapse: { id: 'chat_pane.header.collapse', defaultMessage: 'Collapse chats' },
});
interface IChatPaneHeader {
isOpen: boolean;
isToggleable?: boolean;
@ -11,6 +17,7 @@ interface IChatPaneHeader {
unreadCount?: number;
secondaryAction?(): void;
secondaryActionIcon?: string;
secondaryActionTitle?: string;
}
const ChatPaneHeader = (props: IChatPaneHeader) => {
@ -20,11 +27,13 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
onToggle,
secondaryAction,
secondaryActionIcon,
secondaryActionTitle,
title,
unreadCount,
...rest
} = props;
const intl = useIntl();
const { demetricator } = useSettings();
const ButtonComp = isToggleable ? 'button' : 'div';
@ -54,10 +63,11 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
</ButtonComp>
<div className='⁂-chat-widget__header__actions'>
{secondaryAction ? (
{secondaryAction && secondaryActionIcon ? (
<IconButton
onClick={secondaryAction}
src={secondaryActionIcon as string}
src={secondaryActionIcon}
title={secondaryActionTitle}
/>
) : null}
@ -65,6 +75,7 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
onClick={onToggle}
src={require('@phosphor-icons/core/regular/caret-up.svg')}
className='⁂-chat-widget__header__open-button'
title={isOpen ? intl.formatMessage(messages.collapse) : intl.formatMessage(messages.expand)}
/>
</div>
</div>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Avatar from '@/components/ui/avatar';
import HStack from '@/components/ui/hstack';
@ -15,19 +15,12 @@ import { useModalsActions } from '@/stores/modals';
import ChatPaneHeader from './chat-pane-header';
const messages = defineMessages({
blockMessage: { id: 'chat_settings.block.message', defaultMessage: 'Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.' },
blockHeading: { id: 'chat_settings.block.heading', defaultMessage: 'Block @{acct}' },
blockConfirm: { id: 'chat_settings.block.confirm', defaultMessage: 'Block' },
unblockMessage: { id: 'chat_settings.unblock.message', defaultMessage: 'Unblocking will allow this profile to direct message you and view your content.' },
back: { id: 'card.back.label', defaultMessage: 'Back' },
unblockHeading: { id: 'chat_settings.unblock.heading', defaultMessage: 'Unblock @{acct}' },
unblockConfirm: { id: 'chat_settings.unblock.confirm', defaultMessage: 'Unblock' },
leaveMessage: { id: 'chat_settings.leave.message', defaultMessage: 'Are you sure you want to leave this chat? Messages will be deleted for you and this chat will be removed from your inbox.' },
leaveHeading: { id: 'chat_settings.leave.heading', defaultMessage: 'Leave chat' },
leaveConfirm: { id: 'chat_settings.leave.confirm', defaultMessage: 'Leave chat' },
title: { id: 'chat_settings.title', defaultMessage: 'Chat Details' },
blockUser: { id: 'chat_settings.options.block_user', defaultMessage: 'Block @{acct}' },
unblockUser: { id: 'chat_settings.options.unblock_user', defaultMessage: 'Unblock @{acct}' },
leaveChat: { id: 'chat_settings.options.leave_chat', defaultMessage: 'Leave chat' },
});
const ChatSettings = () => {
@ -61,7 +54,7 @@ const ChatSettings = () => {
const handleUnblockUser = () => {
openModal('CONFIRM', {
heading: intl.formatMessage(messages.unblockHeading, { acct: chat?.account.acct }),
message: intl.formatMessage(messages.unblockMessage),
message: <FormattedMessage id='chat_settings.unblock.message' defaultMessage='Unblocking will allow this profile to direct message you and view your content.' />,
confirm: intl.formatMessage(messages.unblockConfirm),
onConfirm: () => unblockAccount(),
});
@ -70,7 +63,7 @@ const ChatSettings = () => {
const handleLeaveChat = () => {
openModal('CONFIRM', {
heading: intl.formatMessage(messages.leaveHeading),
message: intl.formatMessage(messages.leaveMessage),
message: <FormattedMessage id='chat_settings.leave.message' defaultMessage='Are you sure you want to leave this chat? Messages will be deleted for you and this chat will be removed from your inbox.' />,
confirm: intl.formatMessage(messages.leaveConfirm),
onConfirm: () => deleteChat.mutate(),
});
@ -88,7 +81,7 @@ const ChatSettings = () => {
onToggle={minimizeChatPane}
title={
<HStack alignItems='center' space={2}>
<button onClick={closeSettings}>
<button onClick={closeSettings} title={intl.formatMessage(messages.back)}>
<Icon
src={require('@phosphor-icons/core/regular/arrow-left.svg')}
className='size-6 text-gray-600 dark:text-gray-400 rtl:rotate-180'
@ -96,7 +89,7 @@ const ChatSettings = () => {
</button>
<Text weight='semibold'>
{intl.formatMessage(messages.title)}
<FormattedMessage id='chat_settings.title' defaultMessage='Chat details' />
</Text>
</HStack>
}
@ -113,14 +106,20 @@ const ChatSettings = () => {
<Stack space={5}>
<button onClick={isBlocked ? handleUnblockUser : handleBlockUser} className='flex w-full items-center space-x-2 text-sm font-bold text-primary-600 dark:text-primary-400'>
<Icon src={require('@phosphor-icons/core/regular/prohibit.svg')} className='size-5' />
<span>{intl.formatMessage(isBlocked ? messages.unblockUser : messages.blockUser, { acct: chat.account.acct })}</span>
<Icon src={require('@phosphor-icons/core/regular/prohibit.svg')} className='size-5' aria-hidden />
<span>
{isBlocked
? <FormattedMessage id='chat_settings.options.unblock_user' defaultMessage='Unblock @{acct}' values={{ acct: chat.account.acct }} />
: <FormattedMessage id='chat_settings.options.block_user' defaultMessage='Block @{acct}' values={{ acct: chat.account.acct }} />
}
</span>
</button>
{features.chatsDelete && (
<button onClick={handleLeaveChat} className='flex w-full items-center space-x-2 text-sm font-bold text-danger-600'>
<Icon src={require('@phosphor-icons/core/regular/sign-out.svg')} className='size-5' />
<span>{intl.formatMessage(messages.leaveChat)}</span>
<Icon src={require('@phosphor-icons/core/regular/sign-out.svg')} className='size-5' aria-hidden />
<span><FormattedMessage id='chat_settings.options.leave_chat' defaultMessage='Leave chat' /></span>
</button>
)}
</Stack>

View File

@ -1,5 +1,6 @@
import { Link, type LinkProps } from '@tanstack/react-router';
import React, { useRef } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import Avatar from '@/components/ui/avatar';
import HStack from '@/components/ui/hstack';
@ -14,6 +15,12 @@ import Chat from '../chat';
import ChatPaneHeader from './chat-pane-header';
import ChatSettings from './chat-settings';
const messages = defineMessages({
back: { id: 'card.back.label', defaultMessage: 'Back' },
chatInfo: { id: 'chat_pane.header.chat_info', defaultMessage: 'Chat info' },
newChat: { id: 'chat_pane.header.new_chat', defaultMessage: 'New chat' },
});
const LinkWrapper = ({ enabled, children, ...rest }: LinkProps & { enabled: boolean; children: React.ReactNode }): JSX.Element => {
if (!enabled) {
return <>{children}</>;
@ -29,6 +36,7 @@ const LinkWrapper = ({ enabled, children, ...rest }: LinkProps & { enabled: bool
/** Floating desktop chat window. */
const ChatWindow = () => {
const { chat, currentChatId, screen, changeScreen, isOpen, toggleChatPane } = useChatContext();
const intl = useIntl();
const inputRef = useRef<HTMLTextAreaElement | null>(null);
@ -45,8 +53,6 @@ const ChatWindow = () => {
changeScreen(ChatWidgetScreens.CHAT_SETTINGS, currentChatId);
};
const secondaryAction = () => isOpen ? openChatSettings : openSearch;
if (!chat) return null;
if (screen === ChatWidgetScreens.CHAT_SETTINGS) {
@ -59,7 +65,7 @@ const ChatWindow = () => {
title={
<HStack alignItems='center' space={2}>
{isOpen && (
<button onClick={closeChat}>
<button onClick={closeChat} title={intl.formatMessage(messages.back)}>
<Icon
src={require('@phosphor-icons/core/regular/arrow-left.svg')}
className='size-6 text-gray-600 dark:text-gray-400 rtl:rotate-180'
@ -85,8 +91,9 @@ const ChatWindow = () => {
</HStack>
</HStack>
}
secondaryAction={secondaryAction()}
secondaryAction={isOpen ? openChatSettings : openSearch}
secondaryActionIcon={isOpen ? require('@phosphor-icons/core/regular/info.svg') : require('@phosphor-icons/core/regular/pencil-simple.svg')}
secondaryActionTitle={isOpen ? intl.formatMessage(messages.chatInfo) : intl.formatMessage(messages.newChat)}
isToggleable={!isOpen}
isOpen={isOpen}
onToggle={toggleChatPane}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Icon from '@/components/ui/icon';
import Text from '@/components/ui/text';
@ -8,7 +8,7 @@ import { ChatWidgetScreens, useChatContext } from '@/contexts/chat-context';
import ChatPaneHeader from '../chat-pane-header';
const messages = defineMessages({
title: { id: 'chat_search.title', defaultMessage: 'Messages' },
back: { id: 'card.back.label', defaultMessage: 'Back' },
});
const ChatSearchHeader = () => {
@ -25,6 +25,7 @@ const ChatSearchHeader = () => {
onClick={() => {
changeScreen(ChatWidgetScreens.INBOX);
}}
title={intl.formatMessage(messages.back)}
>
<Icon
src={require('@phosphor-icons/core/regular/arrow-left.svg')}
@ -33,7 +34,7 @@ const ChatSearchHeader = () => {
</button>
<Text size='sm' weight='bold' truncate>
{intl.formatMessage(messages.title)}
<FormattedMessage id='chat_search.title' defaultMessage='Messages' />
</Text>
</div>
}

View File

@ -13,6 +13,7 @@ const Pane: React.FC<IPane> = ({ isOpen = false, children }) => (
<div
className={clsx('⁂-chat-widget', { '⁂-chat-widget--open': isOpen })}
data-testid='pane'
aria-expanded={isOpen}
>
{children}
</div>

View File

@ -336,6 +336,10 @@
"chat_pane.blankslate.action": "Message someone",
"chat_pane.blankslate.body": "Search for someone to chat with.",
"chat_pane.blankslate.title": "No messages yet",
"chat_pane.header.chat_info": "Chat info",
"chat_pane.header.collapse": "Collapse chats",
"chat_pane.header.expand": "Expand chats",
"chat_pane.header.new_chat": "New chat",
"chat_search.blankslate.body": "Search for someone to chat with.",
"chat_search.blankslate.title": "Start a chat",
"chat_search.empty_results_blankslate.body": "Try searching for another name.",
@ -351,7 +355,7 @@
"chat_settings.options.block_user": "Block @{acct}",
"chat_settings.options.leave_chat": "Leave chat",
"chat_settings.options.unblock_user": "Unblock @{acct}",
"chat_settings.title": "Chat Details",
"chat_settings.title": "Chat details",
"chat_settings.unblock.confirm": "Unblock",
"chat_settings.unblock.heading": "Unblock @{acct}",
"chat_settings.unblock.message": "Unblocking will allow this profile to direct message you and view your content.",