Merge commit '58b3fc03796dbde54dbba9a4c554c2d8d24f81fd' into glitch-soc/merge-upstream

This commit is contained in:
Claire committed 2025-11-06 12:35:04 +01:00
commit 58b29be439
143 files changed
+821 -469

No files matched your search

@@ -31,6 +31,7 @@ export const Section: FC<SectionProps> = ({
className='about__section__title'
tabIndex={0}
onClick={handleClick}
type='button'
>
<Icon
id={collapsed ? 'chevron-right' : 'chevron-down'}
@@ -37,6 +37,7 @@ export const DomainPill: React.FC<{
onClick={handleClick}
aria-expanded={open}
aria-controls={accessibilityId}
type='button'
>
{domain}
</button>
@@ -154,6 +155,7 @@ export const DomainPill: React.FC<{
<button
onClick={handleExpandClick}
className='link-button'
type='button'
>
{x}
</button>
@@ -169,6 +171,7 @@ export const DomainPill: React.FC<{
<button
onClick={handleExpandClick}
className='link-button'
type='button'
>
{x}
</button>
@@ -489,6 +489,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
className='link-button'
onClick={handleDetectClick}
disabled={type !== 'image' || isDetecting}
type='button'
>
<FormattedMessage
id='alt_text_modal.add_text_from_image'
@@ -1,4 +1,4 @@
import { useEffect, useRef, useCallback, useState, useId } from 'react';
import { useEffect, useRef, useCallback, useState } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
@@ -22,6 +22,8 @@ import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer';
import { displayMedia, useBlurhash } from 'mastodon/initial_state';
import { playerSettings } from 'mastodon/settings';
import { AudioVisualizer } from './visualizer';
const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' },
pause: { id: 'video.pause', defaultMessage: 'Pause' },
@@ -116,7 +118,6 @@ export const Audio: React.FC<{
const seekRef = useRef<HTMLDivElement>(null);
const volumeRef = useRef<HTMLDivElement>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
const accessibilityId = useId();
const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } =
useAudioContext({ audioElementRef: audioRef });
@@ -538,19 +539,6 @@ export const Audio: React.FC<{
[togglePlay, toggleMute],
);
const springForBand0 = useSpring({
to: { r: 50 + (frequencyBands[0] ?? 0) * 10 },
config: config.wobbly,
});
const springForBand1 = useSpring({
to: { r: 50 + (frequencyBands[1] ?? 0) * 10 },
config: config.wobbly,
});
const springForBand2 = useSpring({
to: { r: 50 + (frequencyBands[2] ?? 0) * 10 },
config: config.wobbly,
});
const progress = Math.min((currentTime / loadedDuration) * 100, 100);
const effectivelyMuted = muted || volume === 0;
@@ -641,81 +629,7 @@ export const Audio: React.FC<{
</div>
<div className='audio-player__controls__play'>
<svg
className='audio-player__visualizer'
viewBox='0 0 124 124'
xmlns='http://www.w3.org/2000/svg'
>
<animated.circle
opacity={0.5}
cx={57}
cy={62.5}
r={springForBand0.r}
fill='var(--player-accent-color)'
/>
<animated.circle
opacity={0.5}
cx={65}
cy={57.5}
r={springForBand1.r}
fill='var(--player-accent-color)'
/>
<animated.circle
opacity={0.5}
cx={63}
cy={66.5}
r={springForBand2.r}
fill='var(--player-accent-color)'
/>
<g clipPath={`url(#${accessibilityId}-clip)`}>
<rect
x={14}
y={14}
width={96}
height={96}
fill={`url(#${accessibilityId}-pattern)`}
/>
<rect
x={14}
y={14}
width={96}
height={96}
fill='var(--player-background-color'
opacity={0.45}
/>
</g>
<defs>
<pattern
id={`${accessibilityId}-pattern`}
patternContentUnits='objectBoundingBox'
width='1'
height='1'
>
<use href={`#${accessibilityId}-image`} />
</pattern>
<clipPath id={`${accessibilityId}-clip`}>
<rect
x={14}
y={14}
width={96}
height={96}
rx={48}
fill='white'
/>
</clipPath>
<image
id={`${accessibilityId}-image`}
href={poster}
width={1}
height={1}
preserveAspectRatio='none'
/>
</defs>
</svg>
<AudioVisualizer frequencyBands={frequencyBands} poster={poster} />
<button
type='button'
@@ -0,0 +1,100 @@
import { useId } from 'react';
import type { FC } from 'react';
import { animated, config, useSpring } from '@react-spring/web';
interface AudioVisualizerProps {
frequencyBands?: number[];
poster?: string;
}
export const AudioVisualizer: FC<AudioVisualizerProps> = ({
frequencyBands = [],
poster,
}) => {
const accessibilityId = useId();
const springForBand0 = useSpring({
to: { r: 50 + (frequencyBands[0] ?? 0) * 10 },
config: config.wobbly,
});
const springForBand1 = useSpring({
to: { r: 50 + (frequencyBands[1] ?? 0) * 10 },
config: config.wobbly,
});
const springForBand2 = useSpring({
to: { r: 50 + (frequencyBands[2] ?? 0) * 10 },
config: config.wobbly,
});
return (
<svg
className='audio-player__visualizer'
viewBox='0 0 124 124'
xmlns='http://www.w3.org/2000/svg'
>
<animated.circle
opacity={0.5}
cx={57}
cy={62.5}
r={springForBand0.r}
fill='var(--player-accent-color)'
/>
<animated.circle
opacity={0.5}
cx={65}
cy={57.5}
r={springForBand1.r}
fill='var(--player-accent-color)'
/>
<animated.circle
opacity={0.5}
cx={63}
cy={66.5}
r={springForBand2.r}
fill='var(--player-accent-color)'
/>
<g clipPath={`url(#${accessibilityId}-clip)`}>
<rect
x={14}
y={14}
width={96}
height={96}
fill={`url(#${accessibilityId}-pattern)`}
/>
<rect
x={14}
y={14}
width={96}
height={96}
fill='var(--player-background-color'
opacity={0.45}
/>
</g>
<defs>
<pattern
id={`${accessibilityId}-pattern`}
patternContentUnits='objectBoundingBox'
width='1'
height='1'
>
<use href={`#${accessibilityId}-image`} />
</pattern>
<clipPath id={`${accessibilityId}-clip`}>
<rect x={14} y={14} width={96} height={96} rx={48} fill='white' />
</clipPath>
<image
id={`${accessibilityId}-image`}
href={poster}
width={1}
height={1}
preserveAspectRatio='none'
/>
</defs>
</svg>
);
};
@@ -557,7 +557,11 @@ export const Search: React.FC<{
)}
>
<span>{label}</span>
<button className='icon-button' onMouseDown={forget}>
<button
className='icon-button'
onMouseDown={forget}
type='button'
>
<Icon id='times' icon={CloseIcon} />
</button>
</div>
@@ -591,6 +595,7 @@ export const Search: React.FC<{
className={classNames('search__popout__menu__item', {
selected: selectedOption === i,
})}
type='button'
>
{label}
</button>
@@ -617,6 +622,7 @@ export const Search: React.FC<{
selectedOption ===
(quickActions.length || recent.length) + i,
})}
type='button'
>
{label}
</button>
@@ -10,6 +10,7 @@ import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import CloseIcon from '@/material-icons/400-20px/close.svg?react';
import SoundIcon from '@/material-icons/400-24px/audio.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
import { undoUploadCompose } from 'mastodon/actions/compose';
@@ -17,7 +18,18 @@ import { openModal } from 'mastodon/actions/modal';
import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon';
import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import {
createAppSelector,
useAppDispatch,
useAppSelector,
} from 'mastodon/store';
import { AudioVisualizer } from '../../audio/visualizer';
const selectUserAvatar = createAppSelector(
[(state) => state.accounts, (state) => state.meta.get('me') as string],
(accounts, myId) => accounts.get(myId)?.avatar_static,
);
export const Upload: React.FC<{
id: string;
@@ -38,6 +50,7 @@ export const Upload: React.FC<{
const sensitive = useAppSelector(
(state) => state.compose.get('spoiler') as boolean,
);
const userAvatar = useAppSelector(selectUserAvatar);
const handleUndoClick = useCallback(() => {
dispatch(undoUploadCompose(id));
@@ -67,6 +80,8 @@ export const Upload: React.FC<{
transform: CSS.Transform.toString(transform),
transition,
};
const preview_url = media.get('preview_url') as string | null;
const blurhash = media.get('blurhash') as string | null;
return (
<div
@@ -85,17 +100,19 @@ export const Upload: React.FC<{
<div
className='compose-form__upload__thumbnail'
style={{
backgroundImage: !sensitive
? `url(${media.get('preview_url') as string})`
: undefined,
backgroundImage:
!sensitive && preview_url ? `url(${preview_url})` : undefined,
backgroundPosition: `${x}% ${y}%`,
}}
>
{sensitive && (
<Blurhash
hash={media.get('blurhash') as string}
className='compose-form__upload__preview'
/>
{sensitive && blurhash && (
<Blurhash hash={blurhash} className='compose-form__upload__preview' />
)}
{!sensitive && !preview_url && (
<div className='compose-form__upload__visualizer'>
<AudioVisualizer poster={userAvatar} />
<Icon id='sound' icon={SoundIcon} />
</div>
)}
<div className='compose-form__upload__actions'>
@@ -1,11 +1,13 @@
import { useEffect, useState } from 'react';
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { FormattedDate, FormattedMessage } from 'react-intl';
import { dismissAnnouncement } from '@/mastodon/actions/announcements';
import type { ApiAnnouncementJSON } from '@/mastodon/api_types/announcements';
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { useAppDispatch } from '@/mastodon/store';
import { ReactionsBar } from './reactions';
@@ -22,13 +24,23 @@ export const Announcement: FC<AnnouncementProps> = ({
announcement,
active,
}) => {
const [unread, setUnread] = useState(!announcement.read);
const { read, id } = announcement;
// Dismiss announcement when it becomes active.
const dispatch = useAppDispatch();
useEffect(() => {
// Only update `unread` marker once the announcement is out of view
if (!active && unread !== !announcement.read) {
setUnread(!announcement.read);
if (active && !read) {
dispatch(dismissAnnouncement(id));
}
}, [announcement.read, active, unread]);
}, [active, id, dispatch, read]);
// But visually show the announcement as read only when it goes out of view.
const [unread, setUnread] = useState(!read);
useEffect(() => {
if (!active && unread !== !read) {
setUnread(!read);
}
}, [active, unread, read]);
return (
<AnimateEmojiProvider>
@@ -270,7 +270,7 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
</h3>
<div className='inline-follow-suggestions__header__actions'>
<button className='link-button' onClick={handleDismiss}>
<button className='link-button' onClick={handleDismiss} type='button'>
<FormattedMessage
id='follow_suggestions.dismiss'
defaultMessage="Don't show again"
@@ -309,6 +309,7 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
className='inline-follow-suggestions__body__scroll-button left'
onClick={handleLeftNav}
aria-label={intl.formatMessage(messages.previous)}
type='button'
>
<div className='inline-follow-suggestions__body__scroll-button__icon'>
<Icon id='' icon={ChevronLeftIcon} />
@@ -321,6 +322,7 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
className='inline-follow-suggestions__body__scroll-button right'
onClick={handleRightNav}
aria-label={intl.formatMessage(messages.next)}
type='button'
>
<div className='inline-follow-suggestions__body__scroll-button__icon'>
<Icon id='' icon={ChevronRightIcon} />
@@ -381,6 +381,7 @@ const LoginForm: React.FC<{
className={classNames('search__popout__menu__item', {
selected: selectedOption === i,
})}
type='button'
>
{option
.split(domainRegExp)
@@ -451,7 +452,7 @@ const InteractionModal: React.FC<{
);
} else {
signupButton = (
<button className='link-button' onClick={handleSignupClick}>
<button className='link-button' onClick={handleSignupClick} type='button'>
<FormattedMessage
id='sign_in_banner.create_account'
defaultMessage='Create account'
+147 -139
View File
@@ -22,6 +22,7 @@ import { Column } from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import type { List } from 'mastodon/models/list';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { messages as membersMessages } from './members';
@@ -72,36 +73,23 @@ const MembersLink: React.FC<{
);
};
const NewList: React.FC<{
multiColumn?: boolean;
}> = ({ multiColumn }) => {
const NewList: React.FC<{ list?: List | null }> = ({ list }) => {
const dispatch = useAppDispatch();
const { id } = useParams<{ id?: string }>();
const intl = useIntl();
const history = useHistory();
const list = useAppSelector((state) =>
id ? state.lists.get(id) : undefined,
);
const [title, setTitle] = useState('');
const [exclusive, setExclusive] = useState(false);
const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list');
const {
id,
title: initialTitle = '',
exclusive: initialExclusive = false,
replies_policy: initialRepliesPolicy = 'list',
} = list ?? {};
const [title, setTitle] = useState(initialTitle);
const [exclusive, setExclusive] = useState(initialExclusive);
const [repliesPolicy, setRepliesPolicy] =
useState<RepliesPolicyType>(initialRepliesPolicy);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (id) {
dispatch(fetchList(id));
}
}, [dispatch, id]);
useEffect(() => {
if (id && list) {
setTitle(list.title);
setExclusive(list.exclusive);
setRepliesPolicy(list.replies_policy);
}
}, [setTitle, setExclusive, setRepliesPolicy, id, list]);
const handleTitleChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
setTitle(value);
@@ -158,6 +146,138 @@ const NewList: React.FC<{
}
}, [history, dispatch, setSubmitting, id, title, exclusive, repliesPolicy]);
return (
<form className='simple_form app-form' onSubmit={handleSubmit}>
<div className='fields-group'>
<div className='input with_label'>
<div className='label_input'>
<label htmlFor='list_title'>
<FormattedMessage
id='lists.list_name'
defaultMessage='List name'
/>
</label>
<div className='label_input__wrapper'>
<input
id='list_title'
type='text'
value={title}
onChange={handleTitleChange}
maxLength={30}
required
placeholder=' '
/>
</div>
</div>
</div>
</div>
<div className='fields-group'>
<div className='input with_label'>
<div className='label_input'>
<label htmlFor='list_replies_policy'>
<FormattedMessage
id='lists.show_replies_to'
defaultMessage='Include replies from list members to'
/>
</label>
<div className='label_input__wrapper'>
<select
id='list_replies_policy'
value={repliesPolicy}
onChange={handleRepliesPolicyChange}
>
<FormattedMessage
id='lists.replies_policy.none'
defaultMessage='No one'
>
{(msg) => <option value='none'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='lists.replies_policy.list'
defaultMessage='Members of the list'
>
{(msg) => <option value='list'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='lists.replies_policy.followed'
defaultMessage='Any followed user'
>
{(msg) => <option value='followed'>{msg}</option>}
</FormattedMessage>
</select>
</div>
</div>
</div>
</div>
{id && (
<div className='fields-group'>
<MembersLink id={id} />
</div>
)}
<div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong>
<FormattedMessage
id='lists.exclusive'
defaultMessage='Hide members in Home'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='lists.exclusive_hint'
defaultMessage='If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle checked={exclusive} onChange={handleExclusiveChange} />
</div>
</div>
</label>
</div>
<div className='actions'>
<button className='button' type='submit'>
{submitting ? (
<LoadingIndicator />
) : id ? (
<FormattedMessage id='lists.save' defaultMessage='Save' />
) : (
<FormattedMessage id='lists.create' defaultMessage='Create' />
)}
</button>
</div>
</form>
);
};
const NewListWrapper: React.FC<{
multiColumn?: boolean;
}> = ({ multiColumn }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const { id } = useParams<{ id?: string }>();
const list = useAppSelector((state) =>
id ? state.lists.get(id) : undefined,
);
useEffect(() => {
if (id) {
dispatch(fetchList(id));
}
}, [dispatch, id]);
const isLoading = id && !list;
return (
<Column
bindToDocument={!multiColumn}
@@ -172,119 +292,7 @@ const NewList: React.FC<{
/>
<div className='scrollable'>
<form className='simple_form app-form' onSubmit={handleSubmit}>
<div className='fields-group'>
<div className='input with_label'>
<div className='label_input'>
<label htmlFor='list_title'>
<FormattedMessage
id='lists.list_name'
defaultMessage='List name'
/>
</label>
<div className='label_input__wrapper'>
<input
id='list_title'
type='text'
value={title}
onChange={handleTitleChange}
maxLength={30}
required
placeholder=' '
/>
</div>
</div>
</div>
</div>
<div className='fields-group'>
<div className='input with_label'>
<div className='label_input'>
<label htmlFor='list_replies_policy'>
<FormattedMessage
id='lists.show_replies_to'
defaultMessage='Include replies from list members to'
/>
</label>
<div className='label_input__wrapper'>
<select
id='list_replies_policy'
value={repliesPolicy}
onChange={handleRepliesPolicyChange}
>
<FormattedMessage
id='lists.replies_policy.none'
defaultMessage='No one'
>
{(msg) => <option value='none'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='lists.replies_policy.list'
defaultMessage='Members of the list'
>
{(msg) => <option value='list'>{msg}</option>}
</FormattedMessage>
<FormattedMessage
id='lists.replies_policy.followed'
defaultMessage='Any followed user'
>
{(msg) => <option value='followed'>{msg}</option>}
</FormattedMessage>
</select>
</div>
</div>
</div>
</div>
{id && (
<div className='fields-group'>
<MembersLink id={id} />
</div>
)}
<div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong>
<FormattedMessage
id='lists.exclusive'
defaultMessage='Hide members in Home'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='lists.exclusive_hint'
defaultMessage='If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle
checked={exclusive}
onChange={handleExclusiveChange}
/>
</div>
</div>
</label>
</div>
<div className='actions'>
<button className='button' type='submit'>
{submitting ? (
<LoadingIndicator />
) : id ? (
<FormattedMessage id='lists.save' defaultMessage='Save' />
) : (
<FormattedMessage id='lists.create' defaultMessage='Create' />
)}
</button>
</div>
</form>
{isLoading ? <LoadingIndicator /> : <NewList list={list} />}
</div>
<Helmet>
@@ -298,4 +306,4 @@ const NewList: React.FC<{
};
// eslint-disable-next-line import/no-default-export
export default NewList;
export default NewListWrapper;
@@ -113,7 +113,7 @@ export const MoreLink: React.FC = () => {
return (
<Dropdown items={menu} placement='bottom-start'>
<button className='column-link column-link--transparent'>
<button className='column-link column-link--transparent' type='button'>
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />
<FormattedMessage id='navigation_bar.more' defaultMessage='More' />
@@ -71,6 +71,7 @@ export const SignInBanner: React.FC = () => {
<button
className='button button--block'
onClick={openClosedRegistrationsModal}
type='button'
>
<FormattedMessage
id='sign_in_banner.create_account'
@@ -45,6 +45,7 @@ export const FilteredNotificationsIconButton: React.FC<{
title={intl.formatMessage(messages.filteredNotifications)}
onClick={handleClick}
className={className}
type='button'
>
<Icon id='filtered-notifications' icon={InventoryIcon} />
</button>
@@ -47,7 +47,7 @@ export const NotificationAnnualReport: React.FC<{
values={{ year }}
/>
</p>
<button onClick={handleClick} className='link-button'>
<button onClick={handleClick} className='link-button' type='button'>
<FormattedMessage
id='notification.annual_report.view'
defaultMessage='View #Wrapstodon'
@@ -50,6 +50,7 @@ const BarButton: React.FC<
className={selectedFilter === type ? 'active' : ''}
onClick={onClick}
title={title}
type='button'
>
{children}
</button>
@@ -245,6 +245,7 @@ export const Notifications: React.FC<{
title={intl.formatMessage(messages.markAsRead)}
onClick={handleMarkAsRead}
className='column-header__button'
type='button'
>
<Icon id='done-all' icon={DoneAllIcon} />
</button>
@@ -9,7 +9,7 @@ export const SearchSection: React.FC<{
<div className='search-results__section__header'>
<h3>{title}</h3>
{onClickMore && (
<button onClick={onClickMore}>
<button onClick={onClickMore} type='button'>
<FormattedMessage
id='search_results.see_all'
defaultMessage='See all'
@@ -232,12 +232,14 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
<button
onClick={handleSelectAll}
className={mappedType === 'all' ? 'active' : undefined}
type='button'
>
<FormattedMessage id='search_results.all' defaultMessage='All' />
</button>
<button
onClick={handleSelectAccounts}
className={mappedType === 'accounts' ? 'active' : undefined}
type='button'
>
<FormattedMessage
id='search_results.accounts'
@@ -247,6 +249,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
<button
onClick={handleSelectHashtags}
className={mappedType === 'hashtags' ? 'active' : undefined}
type='button'
>
<FormattedMessage
id='search_results.hashtags'
@@ -256,6 +259,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
<button
onClick={handleSelectStatuses}
className={mappedType === 'statuses' ? 'active' : undefined}
type='button'
>
<FormattedMessage
id='search_results.statuses'
@@ -25,7 +25,12 @@ export const ActionsModal: React.FC<{
if (isActionItem(option)) {
element = (
<button onClick={onClick} data-index={i} disabled={disabled}>
<button
onClick={onClick}
data-index={i}
disabled={disabled}
type='button'
>
<DropdownMenuItemContent item={option} />
</button>
);
@@ -113,7 +113,7 @@ export const BoostModal: React.FC<{
<div className='spacer' />
<button onClick={handleCancel} className='link-button'>
<button onClick={handleCancel} className='link-button' type='button'>
<FormattedMessage
id='confirmation_modal.cancel'
defaultMessage='Cancel'
@@ -58,7 +58,7 @@ export const ConfirmationModal: React.FC<
<div className='safety-action-modal__bottom'>
<div className='safety-action-modal__actions'>
<button onClick={onClose} className='link-button'>
<button onClick={onClose} className='link-button' type='button'>
{cancel ?? (
<FormattedMessage
id='confirmation_modal.cancel'
@@ -70,7 +70,11 @@ export const ConfirmationModal: React.FC<
{secondary && (
<>
<div className='spacer' />
<button onClick={handleSecondary} className='link-button'>
<button
onClick={handleSecondary}
className='link-button'
type='button'
>
{secondary}
</button>
</>
@@ -196,7 +196,7 @@ export const DomainBlockModal: React.FC<{
<div className='spacer' />
<button onClick={handleCancel} className='link-button'>
<button onClick={handleCancel} className='link-button' type='button'>
<FormattedMessage
id='confirmation_modal.cancel'
defaultMessage='Cancel'
@@ -247,6 +247,7 @@ export const MediaModal: FC<MediaModalProps> = forwardRef<
className='media-modal__nav media-modal__nav--prev'
onClick={handlePrevClick}
aria-label={intl.formatMessage(messages.previous)}
type='button'
>
<Icon id='chevron-left' icon={ChevronLeftIcon} />
</button>
@@ -257,6 +258,7 @@ export const MediaModal: FC<MediaModalProps> = forwardRef<
className='media-modal__nav media-modal__nav--next'
onClick={handleNextClick}
aria-label={intl.formatMessage(messages.next)}
type='button'
>
<Icon id='chevron-right' icon={ChevronRightIcon} />
</button>
@@ -354,6 +356,7 @@ const MediaPagination: FC<MediaPaginationProps> = ({
active: i === index,
})}
onClick={handleChangeIndex(i)}
type='button'
>
{i + 1}
</button>
@@ -127,7 +127,11 @@ const LoginOrSignUp: React.FC = () => {
);
} else {
signupButton = (
<button className='button' onClick={openClosedRegistrationsModal}>
<button
className='button'
onClick={openClosedRegistrationsModal}
type='button'
>
<FormattedMessage
id='sign_in_banner.create_account'
defaultMessage='Create account'
@@ -195,6 +199,7 @@ export const NavigationBar: React.FC = () => {
className={classNames('ui__navigation-bar__item', { active: open })}
onClick={handleClick}
aria-label={intl.formatMessage(messages.menu)}
type='button'
>
<Icon id='' icon={MenuIcon} />
</button>
@@ -875,6 +875,7 @@ export const Video: React.FC<{
<button
className='media-gallery__actions__pill'
onClick={toggleReveal}
type='button'
>
<FormattedMessage
id='media_gallery.hide'