nicolium: more accessibility tweaks

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-18 16:01:06 +01:00
parent 224a221647
commit 239b660e89
11 changed files with 120 additions and 40 deletions

View File

@ -83,7 +83,9 @@ const FilterBar = () => {
});
} else {
items.push({
text: <Icon className='size-4' src={require('@phosphor-icons/core/regular/at.svg')} />,
text: (
<Icon className='size-4' src={require('@phosphor-icons/core/regular/at.svg')} aria-hidden />
),
title: intl.formatMessage(messages.mentions),
action: onClick('mention'),
name: 'mention',
@ -94,6 +96,7 @@ const FilterBar = () => {
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/bell-simple-ringing.svg')}
aria-hidden
/>
),
title: intl.formatMessage(messages.statuses),
@ -101,13 +104,25 @@ const FilterBar = () => {
name: 'status',
});
items.push({
text: <Icon className='size-4' src={require('@phosphor-icons/core/regular/star.svg')} />,
text: (
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/star.svg')}
aria-hidden
/>
),
title: intl.formatMessage(messages.favourites),
action: onClick('favourite'),
name: 'favourite',
});
items.push({
text: <Icon className='size-4' src={require('@phosphor-icons/core/regular/repeat.svg')} />,
text: (
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/repeat.svg')}
aria-hidden
/>
),
title: intl.formatMessage(messages.boosts),
action: onClick('reblog'),
name: 'reblog',
@ -115,7 +130,11 @@ const FilterBar = () => {
if (features.polls)
items.push({
text: (
<Icon className='size-4' src={require('@phosphor-icons/core/regular/chart-bar.svg')} />
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/chart-bar.svg')}
aria-hidden
/>
),
title: intl.formatMessage(messages.polls),
action: onClick('poll'),
@ -127,6 +146,7 @@ const FilterBar = () => {
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/calendar-dots.svg')}
aria-hidden
/>
),
title: intl.formatMessage(messages.events),
@ -134,7 +154,13 @@ const FilterBar = () => {
name: 'events',
});
items.push({
text: <Icon className='size-4' src={require('@phosphor-icons/core/regular/user-plus.svg')} />,
text: (
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/user-plus.svg')}
aria-hidden
/>
),
title: intl.formatMessage(messages.follows),
action: onClick('follow'),
name: 'follow',

View File

@ -20,7 +20,11 @@ const AltIndicator: React.FC<IAltIndicator> = React.forwardRef<HTMLSpanElement,
ref={ref}
>
{warning && (
<Icon className='size-4' src={require('@phosphor-icons/core/regular/warning.svg')} />
<Icon
className='size-4'
src={require('@phosphor-icons/core/regular/warning.svg')}
aria-hidden
/>
)}
{message ?? (
<FormattedMessage id='upload_form.description_missing.indicator' defaultMessage='Alt' />

View File

@ -1,6 +1,6 @@
import clsx from 'clsx';
import React, { useState, useRef, useLayoutEffect, CSSProperties } from 'react';
import { FormattedMessage } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import AltIndicator from '@/components/alt-indicator';
import Blurhash from '@/components/blurhash';
@ -30,6 +30,10 @@ import type { MediaAttachment } from 'pl-api';
const ATTACHMENT_LIMIT = 4;
const MAX_FILENAME_LENGTH = 45;
const messages = defineMessages({
altText: { id: 'media_gallery.alt_indicator', defaultMessage: 'Show image description' },
});
interface Dimensions {
w: CSSProperties['width'];
h: CSSProperties['height'];
@ -90,6 +94,7 @@ const Item: React.FC<IItem> = ({
total,
visible,
}) => {
const intl = useIntl();
const { autoPlayGif } = useSettings();
const { mediaPreview } = useFrontendConfig();
@ -231,6 +236,7 @@ const Item: React.FC<IItem> = ({
</Stack>
}
isFlush
title={intl.formatMessage(messages.altText)}
>
<AltIndicator className='absolute bottom-2 left-2 z-10 opacity-80 transition-opacity hover:opacity-100' />
</Popover>

View File

@ -150,7 +150,7 @@ const StatusReactionsBar: React.FC<IStatusReactionsBar> = ({ status, collapsed }
className='⁂-status-reactions-bar__picker-button emoji-picker-dropdown'
title={intl.formatMessage(messages.addEmoji)}
>
<Icon src={require('@phosphor-icons/core/regular/smiley-sticker.svg')} />
<Icon src={require('@phosphor-icons/core/regular/smiley-sticker.svg')} aria-hidden />
</button>
</EmojiPickerDropdown>
)}

View File

@ -40,6 +40,7 @@ import StatusInfo from './statuses/status-info';
import Tombstone from './tombstone';
const messages = defineMessages({
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
reblogged_by: { id: 'status.reblogged_by', defaultMessage: '{name} reposted' },
});
@ -47,37 +48,49 @@ interface IAccountInfo {
status: SelectedStatus;
}
const AccountInfo: React.FC<IAccountInfo> = React.memo(({ status }) => (
<div className='flex flex-row-reverse items-center gap-1 self-baseline'>
<Link
to='/@{$username}/posts/$statusId'
params={{ username: status.account.acct, statusId: status.id }}
className='hover:underline'
onClick={(event) => {
event.stopPropagation();
}}
>
<RelativeTimestamp
timestamp={status.created_at}
theme='muted'
size='sm'
className='whitespace-nowrap'
/>
</Link>
<StatusTypeIcon visibility={status.visibility} />
<StatusLanguagePicker status={status} />
{!!status.edited_at && (
<>
<span className='⁂-separator' />
<Icon
className='size-4 text-gray-700 dark:text-gray-600'
src={require('@phosphor-icons/core/regular/pencil-simple.svg')}
const AccountInfo: React.FC<IAccountInfo> = React.memo(({ status }) => {
const intl = useIntl();
return (
<div className='flex flex-row-reverse items-center gap-1 self-baseline'>
<Link
to='/@{$username}/posts/$statusId'
params={{ username: status.account.acct, statusId: status.id }}
className='hover:underline'
onClick={(event) => {
event.stopPropagation();
}}
>
<RelativeTimestamp
timestamp={status.created_at}
theme='muted'
size='sm'
className='whitespace-nowrap'
/>
</>
)}
</div>
));
</Link>
<StatusTypeIcon visibility={status.visibility} />
<StatusLanguagePicker status={status} />
{!!status.edited_at && (
<>
<span className='⁂-separator' />
<Icon
className='size-4 text-gray-700 dark:text-gray-600'
src={require('@phosphor-icons/core/regular/pencil-simple.svg')}
title={intl.formatMessage(messages.edited, {
date: intl.formatDate(new Date(status.edited_at), {
hour12: true,
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: '2-digit',
}),
})}
/>
</>
)}
</div>
);
});
interface IStatusFollowedTagInfo {
status: SelectedStatus;

View File

@ -31,6 +31,7 @@ interface IPopover {
referenceElementClassName?: string;
offsetOptions?: OffsetOptions;
placements?: Array<Placement>;
title?: string;
}
/**
@ -47,6 +48,7 @@ const Popover: React.FC<IPopover> = ({
isFlush = false,
offsetOptions = 10,
placements = ['top', 'bottom'],
title,
}) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
@ -95,7 +97,11 @@ const Popover: React.FC<IPopover> = ({
ref: refs.setReference,
...getReferenceProps(),
className: clsx(children.props.className, referenceElementClassName),
'aria-haspopup': interaction === 'click' ? 'dialog' : undefined,
'aria-expanded': isOpen,
role: interaction === 'click' ? 'button' : undefined,
tabIndex: interaction === 'click' ? 0 : undefined,
title: title || children.props.title,
})}
{isMounted && (
@ -109,6 +115,7 @@ const Popover: React.FC<IPopover> = ({
left: x ?? 0,
...styles,
}}
role='dialog'
>
<div
className={clsx(

View File

@ -237,8 +237,11 @@ const Upload: React.FC<IUpload> = ({
</span>
{onDescriptionChange && !description && (
<button onClick={handleOpenAltTextModal}>
<AltIndicator warning title={intl.formatMessage(messages.descriptionMissingTitle)} />
<button
onClick={handleOpenAltTextModal}
title={intl.formatMessage(messages.descriptionMissingTitle)}
>
<AltIndicator warning />
</button>
)}
</HStack>

View File

@ -131,6 +131,7 @@ const messages = defineMessages({
notePlaceholder: { id: 'account_note.placeholder', defaultMessage: 'Add a note' },
noteSaved: { id: 'account_note.success', defaultMessage: 'Note saved' },
noteSaveFailed: { id: 'account_note.fail', defaultMessage: 'Failed to save note' },
headerAlt: { id: 'account.header.alt.popover', defaultMessage: 'Show profile header alt text' },
});
interface IMovedNote {
@ -745,6 +746,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
</Stack>
}
isFlush
title={intl.formatMessage(messages.headerAlt)}
>
<AltIndicator
className='ml-6 mt-6 w-fit'

View File

@ -53,6 +53,10 @@ const messages = defineMessages({
id: 'compose_event.fetch_fail',
defaultMessage: 'Failed to fetch edited event information',
},
eventHeaderDescription: {
id: 'compose_event.header_description',
defaultMessage: 'Add header alt text.',
},
});
interface IEditEvent {
@ -262,11 +266,13 @@ const EditEvent: React.FC<IEditEvent> = ({ statusId }) => {
<IconButton
src={require('@phosphor-icons/core/regular/x.svg')}
onClick={handleClearBanner}
title={intl.formatMessage(messages.resetLocation)}
/>
<button
type='button'
className='absolute bottom-1 left-1'
onClick={handleChangeDescriptionClick}
title={intl.formatMessage(messages.eventHeaderDescription)}
>
<AltIndicator warning={!banner.description} />
</button>

View File

@ -21,6 +21,11 @@ const messages = defineMessages({
defaultMessage: 'Image description',
},
changeHeaderDescriptionConfirm: { id: 'group.upload_banner.alt.confirm', defaultMessage: 'Save' },
clearHeader: { id: 'group.upload_banner.clear', defaultMessage: 'Clear header image' },
changeDescription: {
id: 'group.upload_banner.change_description',
defaultMessage: 'Change alt text',
},
});
interface IMediaInput {
@ -111,6 +116,7 @@ const HeaderPicker = React.forwardRef<HTMLInputElement, IMediaInput>(
theme='dark'
className='absolute right-2 top-2 z-10 hover:scale-105 hover:bg-gray-900'
iconClassName='h-5 w-5'
title={intl.formatMessage(messages.clearHeader)}
/>
)}
{onChangeDescription && src && (
@ -118,6 +124,7 @@ const HeaderPicker = React.forwardRef<HTMLInputElement, IMediaInput>(
type='button'
className='absolute left-2 top-2'
onClick={handleChangeDescriptionClick}
title={intl.formatMessage(messages.changeDescription)}
>
<AltIndicator warning={!description} />
</button>

View File

@ -34,6 +34,7 @@
"account.follows.empty": "This user doesn't follow anyone yet.",
"account.follows_you": "Follows you",
"account.header.alt": "Profile header",
"account.header.alt.popover": "Show profile header alt text",
"account.header.description": "Header description",
"account.hide_reblogs": "Hide reposts from @{name}",
"account.instance_favicon": "Visit {domain} timeline",
@ -552,6 +553,7 @@
"compose_event.fields.name_placeholder": "Name",
"compose_event.fields.start_time_label": "Event start date",
"compose_event.fields.start_time_placeholder": "Event begins on…",
"compose_event.header_description": "Add header alt text.",
"compose_event.participation_requests.authorize": "Authorize",
"compose_event.participation_requests.authorize.fail": "Failed to authorize event participation request",
"compose_event.participation_requests.authorize.success": "Event participation request authorized successfully",
@ -567,6 +569,7 @@
"compose_event.update": "Update",
"compose_event.upload_banner": "Upload event banner",
"compose_form.approval_required": "The reply needs to be approved by the post author.",
"compose_form.approval_required.quote": "The quote needs to be approved by the post author.",
"compose_form.content_type.change": "Change content type",
"compose_form.direct_message_warning": "This post will only be sent to the mentioned users.",
"compose_form.drive_button": "Select from drive",
@ -1060,6 +1063,8 @@
"group.upload_banner.alt.confirm": "Save",
"group.upload_banner.alt.heading": "Change header description",
"group.upload_banner.alt.placeholder": "Image description",
"group.upload_banner.change_description": "Change alt text",
"group.upload_banner.clear": "Clear header image",
"group.upload_banner.title": "Upload background picture",
"groups.discover.search.results.member_count": "{members, plural, one {member} other {members}}",
"groups.empty.subtitle": "Start discovering groups to join or create your own.",
@ -1276,6 +1281,7 @@
"media.default_description.gifv": "GIFV",
"media.default_description.image": "Image",
"media.default_description.video": "Video",
"media_gallery.alt_indicator": "Show image description",
"media_panel.empty_message": "No media found.",
"media_panel.title": "Media",
"mfa.confirm.success_message": "MFA confirmed",