Enable some more oxlint rules (#34193)

* Enable some more oxlint rules

* Iterate

* Iterate

* Update packages/shared-components/src/room/timeline/event-tile/EventTileView/E2ePadlock/E2ePadlock.tsx

Co-authored-by: Hubert Chathi <hubert@uhoreg.ca>

---------

Co-authored-by: Hubert Chathi <hubert@uhoreg.ca>
This commit is contained in:
Michael Telatynski
2026-08-10 12:25:55 +00:00
committed by GitHub
co-authored by Hubert Chathi
parent 4807ff1568
commit d54cddcb1d
26 changed files with 115 additions and 76 deletions
@@ -41,6 +41,7 @@ export default class NonUrgentToastContainer extends React.PureComponent<EmptyOb
public render(): React.ReactNode { public render(): React.ReactNode {
const toasts = this.state.toasts.map((t, i) => { const toasts = this.state.toasts.map((t, i) => {
return ( return (
// oxlint-disable-next-line react/no-array-index-key
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}> <div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
{React.createElement(t, {})} {React.createElement(t, {})}
</div> </div>
@@ -43,6 +43,7 @@ export default class Waveform extends React.PureComponent<IProps> {
}); });
return ( return (
<span <span
// oxlint-disable-next-line react/no-array-index-key
key={i} key={i}
style={ style={
{ {
@@ -264,34 +264,50 @@ export default class LoginWithQRFlow extends React.Component<Props> {
); );
break; break;
case Phase.ShowingQR: { case Phase.ShowingQR: {
let steps: ReactNode[]; let steps: ReactNode;
if (this.props.intent === RendezvousIntent.LOGIN_ON_NEW_DEVICE) { if (this.props.intent === RendezvousIntent.LOGIN_ON_NEW_DEVICE) {
steps = [ steps = (
_t("auth|qr_code_login|open_element_mobile_device", { <>
brand: SdkConfig.get().brand, <li>
}), {_t("auth|qr_code_login|open_element_mobile_device", {
_t("auth|qr_code_login|tap_avatar_link_new_device", { brand: SdkConfig.get().brand,
linkNewDevice: <strong>{_t("settings|sessions|sign_in_with_qr")}</strong>, })}
}), </li>
_t("auth|qr_code_login|choose_desktop_computer", { <li>
desktopComputer: <strong>{_t("auth|qr_code_login|desktop_computer")}</strong>, {_t("auth|qr_code_login|tap_avatar_link_new_device", {
}), linkNewDevice: <strong>{_t("settings|sessions|sign_in_with_qr")}</strong>,
_t("auth|qr_code_login|select_ready_to_scan", { })}
readyToScan: <strong>{_t("auth|qr_code_login|ready_to_scan")}</strong>, </li>
}), <li>
_t("auth|qr_code_login|follow_remaining_instructions"), {_t("auth|qr_code_login|choose_desktop_computer", {
]; desktopComputer: <strong>{_t("auth|qr_code_login|desktop_computer")}</strong>,
})}
</li>
<li>
{_t("auth|qr_code_login|select_ready_to_scan", {
readyToScan: <strong>{_t("auth|qr_code_login|ready_to_scan")}</strong>,
})}
</li>
<li>{_t("auth|qr_code_login|follow_remaining_instructions")}</li>
</>
);
} else { } else {
steps = [ steps = (
_t("auth|qr_code_login|open_element_other_device", { <>
brand: SdkConfig.get().brand, <li>
}), {_t("auth|qr_code_login|open_element_other_device", {
_t("auth|qr_code_login|select_qr_code", { brand: SdkConfig.get().brand,
scanQRCode: <strong>{_t("auth|qr_code_login|scan_qr_code")}</strong>, })}
}), </li>
_t("auth|qr_code_login|point_the_camera"), <li>
_t("auth|qr_code_login|follow_remaining_instructions"), {_t("auth|qr_code_login|select_qr_code", {
]; scanQRCode: <strong>{_t("auth|qr_code_login|scan_qr_code")}</strong>,
})}
</li>
<li>{_t("auth|qr_code_login|point_the_camera")}</li>
<li>{_t("auth|qr_code_login|follow_remaining_instructions")}</li>
</>
);
} }
main = ( main = (
@@ -308,11 +324,7 @@ export default class LoginWithQRFlow extends React.Component<Props> {
<Spinner /> <Spinner />
)} )}
</div> </div>
<ol> <ol>{steps}</ol>
{steps.map((step, i) => (
<li key={this.props.intent + i}>{step}</li>
))}
</ol>
</> </>
); );
break; break;
@@ -39,7 +39,7 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
}; };
private renderTimeline(): ReactNode[] { private renderTimeline(): ReactNode[] {
return EchoStore.instance.contexts.map((c, i) => { return EchoStore.instance.contexts.map((c) => {
if (!c.firstFailedTime) return null; // not useful if (!c.firstFailedTime) return null; // not useful
if (!(c instanceof RoomEchoContext)) if (!(c instanceof RoomEchoContext))
throw new Error("Cannot render unknown context: " + c.constructor.name); throw new Error("Cannot render unknown context: " + c.constructor.name);
@@ -51,7 +51,7 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
); );
const entries = c.transactions const entries = c.transactions
.filter((t) => t.status === TransactionStatus.Error || t.didPreviouslyFail) .filter((t) => t.status === TransactionStatus.Error || t.didPreviouslyFail)
.map((t, j) => { .map((t) => {
let button = <Spinner size={19} />; let button = <Spinner size={19} />;
if (t.status === TransactionStatus.Error) { if (t.status === TransactionStatus.Error) {
button = ( button = (
@@ -61,14 +61,14 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
); );
} }
return ( return (
<div className="mx_ServerOfflineDialog_content_context_txn" key={`txn-${j}`}> <div className="mx_ServerOfflineDialog_content_context_txn" key={t.auditName}>
<span className="mx_ServerOfflineDialog_content_context_txn_desc">{t.auditName}</span> <span className="mx_ServerOfflineDialog_content_context_txn_desc">{t.auditName}</span>
{button} {button}
</div> </div>
); );
}); });
return ( return (
<div className="mx_ServerOfflineDialog_content_context" key={`context-${i}`}> <div className="mx_ServerOfflineDialog_content_context" key={c.room.roomId}>
<div className="mx_ServerOfflineDialog_content_context_timestamp"> <div className="mx_ServerOfflineDialog_content_context_timestamp">
{formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps"))} {formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps"))}
</div> </div>
@@ -92,11 +92,11 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
// Both are the same type (both timeline or both non-timeline), sort lexicographically // Both are the same type (both timeline or both non-timeline), sort lexicographically
return lexicographicCompare(capA, capB); return lexicographicCompare(capA, capB);
}); });
const checkboxRows = orderedCapabilities.map(([cap, isChecked], i) => { const checkboxRows = orderedCapabilities.map(([cap, isChecked]) => {
const text = CapabilityText.for(cap, this.props.widgetKind); const text = CapabilityText.for(cap, this.props.widgetKind);
return ( return (
<div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap + i}> <div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap}>
<StyledCheckbox <StyledCheckbox
checked={isChecked} checked={isChecked}
onChange={() => this.onToggle(cap)} onChange={() => this.onToggle(cap)}
@@ -215,6 +215,7 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
/> />
<h2>{_t("poll|options_heading")}</h2> <h2>{_t("poll|options_heading")}</h2>
{this.state.options.map((op, i) => ( {this.state.options.map((op, i) => (
// oxlint-disable-next-line react/no-array-index-key
<div key={`option_${i}`} className="mx_PollCreateDialog_option"> <div key={`option_${i}`} className="mx_PollCreateDialog_option">
<Field <Field
id={`pollcreate_option_${i}`} id={`pollcreate_option_${i}`}
@@ -495,6 +495,7 @@ export class ImageBodyBaseInner extends React.Component<ImageBodyBaseProps, ISta
<div <div
className="mx_MImageBody_thumbnail_container" className="mx_MImageBody_thumbnail_container"
style={{ maxHeight, maxWidth, aspectRatio: `${infoWidth}/${infoHeight}` }} style={{ maxHeight, maxWidth, aspectRatio: `${infoWidth}/${infoHeight}` }}
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={tooltipProps ? 0 : undefined} tabIndex={tooltipProps ? 0 : undefined}
> >
{placeholder} {placeholder}
@@ -347,10 +347,10 @@ export default class AliasSettings extends React.Component<IProps, IState> {
<option value="" key="unset"> <option value="" key="unset">
{_t("room_settings|alias_not_specified")} {_t("room_settings|alias_not_specified")}
</option> </option>
{this.getAliases().map((alias, i) => { {this.getAliases().map((alias) => {
if (alias === this.state.canonicalAlias) found = true; if (alias === this.state.canonicalAlias) found = true;
return ( return (
<option value={alias} key={i}> <option value={alias} key={alias}>
{alias} {alias}
</option> </option>
); );
@@ -414,7 +414,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
)} )}
<datalist id="mx_AliasSettings_altRecommendations"> <datalist id="mx_AliasSettings_altRecommendations">
{this.getLocalNonAltAliases().map((alias) => { {this.getLocalNonAltAliases().map((alias) => {
return <option value={alias} key={alias} />; return <option value={alias} key={alias} aria-label={alias} />;
})} })}
</datalist> </datalist>
<EditableAliasesList <EditableAliasesList
@@ -314,7 +314,11 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
}); });
return completions.length > 0 ? ( return completions.length > 0 ? (
<div key={i} className="mx_Autocomplete_ProviderSection" role="presentation"> <div
key={completionResult.provider.getName()}
className="mx_Autocomplete_ProviderSection"
role="presentation"
>
<div className="mx_Autocomplete_provider_name">{completionResult.provider.getName()}</div> <div className="mx_Autocomplete_provider_name">{completionResult.provider.getName()}</div>
{completionResult.provider.renderCompletions(completions)} {completionResult.provider.renderCompletions(completions)}
</div> </div>
@@ -218,6 +218,7 @@ function Indicators({ count, currentIndex }: IndicatorsProps): JSX.Element {
return ( return (
<div className="mx_PinnedMessageBanner_Indicators"> <div className="mx_PinnedMessageBanner_Indicators">
{Array.from({ length: numberOfIndicators }).map((_, i) => ( {Array.from({ length: numberOfIndicators }).map((_, i) => (
// oxlint-disable-next-line react/no-array-index-key
<Indicator key={i} active={i === index} hidden={isLastCycle && lastCycleIndex <= i} /> <Indicator key={i} active={i === index} hidden={isLastCycle && lastCycleIndex <= i} />
))} ))}
</div> </div>
@@ -655,6 +655,7 @@ class RoomPreviewBar extends React.Component<IProps, IState> {
if (!Array.isArray(subTitle)) { if (!Array.isArray(subTitle)) {
subTitle = [subTitle]; subTitle = [subTitle];
} }
// oxlint-disable-next-line react/no-array-index-key
subTitleElements = subTitle.map((t, i) => <p key={`subTitle${i}`}>{t}</p>); subTitleElements = subTitle.map((t, i) => <p key={`subTitle${i}`}>{t}</p>);
} }
@@ -11,6 +11,7 @@ import React, { type CSSProperties, memo, type RefObject, type ReactNode } from
import { useIsExpanded } from "../hooks/useIsExpanded"; import { useIsExpanded } from "../hooks/useIsExpanded";
import { useSelection } from "../hooks/useSelection"; import { useSelection } from "../hooks/useSelection";
import { _t } from "../../../../../i18n";
const HEIGHT_BREAKING_POINT = 24; const HEIGHT_BREAKING_POINT = 24;
@@ -38,6 +39,7 @@ export const Editor = memo(function Editor({ disabled, placeholder, leftComponen
ref={ref} ref={ref}
contentEditable={!disabled} contentEditable={!disabled}
role="textbox" role="textbox"
aria-label={_t("a11y|message_composer")}
aria-multiline="true" aria-multiline="true"
aria-autocomplete="list" aria-autocomplete="list"
aria-haspopup="listbox" aria-haspopup="listbox"
@@ -124,10 +124,10 @@ const DeviceDetails: React.FC<Props> = ({
{!accountManagementEndpoint && ( {!accountManagementEndpoint && (
<section className="mx_DeviceDetails_section"> <section className="mx_DeviceDetails_section">
<p className="mx_DeviceDetails_sectionHeading">{_t("settings|sessions|details_heading")}</p> <p className="mx_DeviceDetails_sectionHeading">{_t("settings|sessions|details_heading")}</p>
{metadata.map(({ heading, values, id }, index) => ( {metadata.map(({ heading, values, id }) => (
<table <table
className="mx_DeviceDetails_metadataTable" className="mx_DeviceDetails_metadataTable"
key={index} key={id}
data-testid={`device-detail-metadata-${id}`} data-testid={`device-detail-metadata-${id}`}
> >
{heading && ( {heading && (
@@ -387,7 +387,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps, RolesR
} }
const powerSelectors = Object.keys(powerLevelDescriptors) const powerSelectors = Object.keys(powerLevelDescriptors)
.map((key, index) => { .map((key) => {
const descriptor = powerLevelDescriptors[key]; const descriptor = powerLevelDescriptors[key];
if (isSpaceRoom && descriptor.hideForSpace) { if (isSpaceRoom && descriptor.hideForSpace) {
return null; return null;
@@ -395,7 +395,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps, RolesR
const value = parseIntWithDefault(get(plContent, key), descriptor.defaultValue); const value = parseIntWithDefault(get(plContent, key), descriptor.defaultValue);
return ( return (
<div key={index} className=""> <div key={key}>
<PowerSelector <PowerSelector
label={descriptor.desc} label={descriptor.desc}
value={value} value={value}
@@ -45,6 +45,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
if (avatar) { if (avatar) {
avatarSection = ( avatarSection = (
<React.Fragment> <React.Fragment>
{/* oxlint-disable-next-line jsx-a11y/control-has-associated-label */}
<AccessibleButton <AccessibleButton
className="mx_SpaceBasicSettings_avatar" className="mx_SpaceBasicSettings_avatar"
onClick={() => avatarUploadRef.current?.click()} onClick={() => avatarUploadRef.current?.click()}
@@ -65,8 +65,13 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
? _t("encryption|verification|confirm_the_emojis") ? _t("encryption|verification|confirm_the_emojis")
: _t("encryption|verification|sas_emoji_caption_user"); : _t("encryption|verification|sas_emoji_caption_user");
} else if (this.props.sas.decimal) { } else if (this.props.sas.decimal) {
const numberBlocks = this.props.sas.decimal.map((num, i) => <span key={i}>{num}</span>); sasDisplay = (
sasDisplay = <div className="mx_VerificationShowSas_decimalSas">{numberBlocks}</div>; <div className="mx_VerificationShowSas_decimalSas">
<span>{this.props.sas.decimal[0]}</span>
<span>{this.props.sas.decimal[1]}</span>
<span>{this.props.sas.decimal[2]}</span>
</div>
);
sasCaption = this.props.isSelf sasCaption = this.props.isSelf
? _t("encryption|verification|sas_caption_self") ? _t("encryption|verification|sas_caption_self")
: _t("encryption|verification|sas_caption_user"); : _t("encryption|verification|sas_caption_user");
@@ -44,8 +44,8 @@ export default class AudioFeedArrayForLegacyCall extends React.Component<IProps,
}; };
public render(): JSX.Element[] { public render(): JSX.Element[] {
return this.state.feeds.map((feed, i) => { return this.state.feeds.map((feed) => {
return <AudioFeed feed={feed} key={i} />; return <AudioFeed feed={feed} key={feed.deviceId + feed.userId} />;
}); });
} }
} }
+1
View File
@@ -18,6 +18,7 @@ export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.
return ( return (
<> <>
{array.map((element, index) => ( {array.map((element, index) => (
// oxlint-disable-next-line react/no-array-index-key
<React.Fragment key={index}> <React.Fragment key={index}>
{element} {element}
{index === array.length - 1 ? null : joiner} {index === array.length - 1 ? null : joiner}
@@ -16,18 +16,18 @@ describe("KeyboardLandmarkUtils", () => {
it("Landmarks are cycled through correctly without an opened room", () => { it("Landmarks are cycled through correctly without an opened room", () => {
render( render(
<div> <div>
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active"> <button className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
SPACE_BUTTON SPACE_BUTTON
</div> </button>
<div tabIndex={0} id="room-list-search-button" data-testid="mx_RoomListSearch_search"> <button id="room-list-search-button" data-testid="mx_RoomListSearch_search">
ROOM_SEARCH ROOM_SEARCH
</div> </button>
<div tabIndex={0} className="mx_RoomListItemView" data-testid="mx_RoomListItemView"> <button className="mx_RoomListItemView" data-testid="mx_RoomListItemView">
ROOM_TILE ROOM_TILE
</div> </button>
<div tabIndex={0} className="mx_HomePage" data-testid="mx_HomePage"> <button className="mx_HomePage" data-testid="mx_HomePage">
HOME_PAGE HOME_PAGE
</div> </button>
</div>, </div>,
); );
// ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> HOME <-> ACTIVE_SPACE_BUTTON // ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> HOME <-> ACTIVE_SPACE_BUTTON
@@ -69,21 +69,19 @@ describe("KeyboardLandmarkUtils", () => {
defaultDispatcher.register(callback); defaultDispatcher.register(callback);
render( render(
<div> <div>
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active"> <button className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
SPACE_BUTTON SPACE_BUTTON
</div> </button>
<div tabIndex={0} id="room-list-search-button" data-testid="mx_RoomListSearch_search"> <button id="room-list-search-button" data-testid="mx_RoomListSearch_search">
ROOM_SEARCH ROOM_SEARCH
</div> </button>
<div tabIndex={0} className="mx_RoomListItemView_selected" data-testid="mx_RoomListItemView_selected"> <button className="mx_RoomListItemView_selected" data-testid="mx_RoomListItemView_selected">
ROOM_TILE ROOM_TILE
</div> </button>
<div tabIndex={0} className="mx_Room" data-testid="mx_Room"> <button className="mx_Room" data-testid="mx_Room">
ROOM ROOM
<div tabIndex={0} className="mx_MessageComposer"> <div className="mx_MessageComposer">COMPOSER</div>
COMPOSER </button>
</div>
</div>
</div>, </div>,
); );
// ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> MESSAGE_COMPOSER <-> ACTIVE_SPACE_BUTTON // ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> MESSAGE_COMPOSER <-> ACTIVE_SPACE_BUTTON
+10 -6
View File
@@ -205,6 +205,14 @@ export default defineConfig({
// Prevent unnecessary runtime dependencies between files // Prevent unnecessary runtime dependencies between files
"typescript/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }], "typescript/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }],
"jsx-a11y/control-has-associated-label": [
"error",
{
labelAttributes: ["label", "value"],
depth: 3,
},
],
// Disable some perf rules // Disable some perf rules
"no-await-in-loop": "off", "no-await-in-loop": "off",
@@ -212,7 +220,7 @@ export default defineConfig({
"unicorn/switch-case-braces": "off", "unicorn/switch-case-braces": "off",
"sort-keys": "off", "sort-keys": "off",
"typescript/require-array-sort-compare": "off", "typescript/require-array-sort-compare": "off",
"eslint/no-extra-boolean-cast": "off", "no-extra-boolean-cast": "off",
// These would be nice to enable at some point // These would be nice to enable at some point
"unicorn/prefer-set-has": "off", "unicorn/prefer-set-has": "off",
@@ -232,21 +240,17 @@ export default defineConfig({
"typescript/no-implied-eval": "off", "typescript/no-implied-eval": "off",
"typescript/no-misused-spread": "off", "typescript/no-misused-spread": "off",
"promise/valid-params": "off", "promise/valid-params": "off",
"no-extra-boolean-cast": "off",
"react-perf/jsx-no-new-function-as-prop": "off", "react-perf/jsx-no-new-function-as-prop": "off",
"react-perf/jsx-no-new-object-as-prop": "off", "react-perf/jsx-no-new-object-as-prop": "off",
"react-perf/jsx-no-jsx-as-prop": "off", "react-perf/jsx-no-jsx-as-prop": "off",
"jsx-a11y/prefer-tag-over-role": "off", "jsx-a11y/prefer-tag-over-role": "off",
"jsx-a11y/no-autofocus": "off", "jsx-a11y/no-autofocus": "off",
"react/no-children-prop": "off", "react/no-children-prop": "off",
"jsx-a11y/no-noninteractive-tabindex": "off",
"react-perf/jsx-no-new-array-as-prop": "off", "react-perf/jsx-no-new-array-as-prop": "off",
"react/no-did-update-set-state": "off", "react/no-did-update-set-state": "off",
"react/no-did-mount-set-state": "off", "react/no-did-mount-set-state": "off",
"jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off", "jsx-a11y/no-noninteractive-element-interactions": "off",
"react/no-array-index-key": "off",
"jsx-a11y/control-has-associated-label": "off",
"jsx-a11y/media-has-caption": "off", "jsx-a11y/media-has-caption": "off",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off", "jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
"jsx-a11y/aria-activedescendant-has-tabindex": "off", "jsx-a11y/aria-activedescendant-has-tabindex": "off",
@@ -522,7 +526,6 @@ export default defineConfig({
"typescript/no-empty-object-type": "off", "typescript/no-empty-object-type": "off",
"typescript/unbound-method": "off", "typescript/unbound-method": "off",
"typescript/no-floating-promises": "off", "typescript/no-floating-promises": "off",
"typescript/no-misused-spread": "off",
"vitest/require-mock-type-parameters": "off", "vitest/require-mock-type-parameters": "off",
"vitest/no-disabled-tests": "off", "vitest/no-disabled-tests": "off",
"vitest/no-conditional-expect": "off", "vitest/no-conditional-expect": "off",
@@ -565,6 +568,7 @@ export default defineConfig({
"no-new": "off", "no-new": "off",
"react/iframe-missing-sandbox": "off", "react/iframe-missing-sandbox": "off",
"promise/no-promise-in-callback": "off", "promise/no-promise-in-callback": "off",
// This would be good to enable in the future // This would be good to enable in the future
"typescript/await-thenable": "off", "typescript/await-thenable": "off",
"promise/no-callback-in-promise": "off", "promise/no-callback-in-promise": "off",
@@ -165,6 +165,7 @@ const groupedVariant: ListTestVariant = {
<div <div
className="mx_group_header" className="mx_group_header"
data-testid={`group-header-${header.id}`} data-testid={`group-header-${header.id}`}
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={context.tabIndexKey === header.id ? 0 : -1} tabIndex={context.tabIndexKey === header.id ? 0 : -1}
onFocus={(e) => onFocus(header, e)} onFocus={(e) => onFocus(header, e)}
> >
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { type JSX } from "react"; import { type JSX } from "react";
import { Text, type HTMLReactParserOptions } from "html-react-parser"; import { Text, type HTMLReactParserOptions } from "html-react-parser";
type Replacer = HTMLReactParserOptions["replace"]; type Replacer = HTMLReactParserOptions["replace"];
@@ -28,7 +28,7 @@ export function applyReplacerOnString(
const arr = Array.isArray(input) ? input : [input]; const arr = Array.isArray(input) ? input : [input];
return arr.map((item, index): JSX.Element => { return arr.map((item, index): JSX.Element => {
if (typeof item === "string") { if (typeof item === "string") {
return <React.Fragment key={index}>{(replacer(new Text(item), 0) as JSX.Element) || item}</React.Fragment>; return (replacer(new Text(item), 0) as JSX.Element) || item;
} }
return item; return item;
}); });
@@ -31,6 +31,7 @@ export function SasEmoji({ emoji, className }: Props): JSX.Element {
const { language } = useI18n(); const { language } = useI18n();
const emojiBlocks = emoji.map((emoji, i) => ( const emojiBlocks = emoji.map((emoji, i) => (
// oxlint-disable-next-line react/no-array-index-key
<div className={styles.segment} key={i}> <div className={styles.segment} key={i}>
<div className={styles.emoji} aria-hidden={true}> <div className={styles.emoji} aria-hidden={true}>
{emoji} {emoji}
@@ -102,6 +102,7 @@ export const RoomListPrimaryFilters = memo(function RoomListPrimaryFilters({
> >
{visibleFilterIds.map((filterId, index) => ( {visibleFilterIds.map((filterId, index) => (
<ChatFilter <ChatFilter
// oxlint-disable-next-line react/no-array-index-key
key={`${filterId}-${index}`} key={`${filterId}-${index}`}
role="option" role="option"
tabIndex={0} tabIndex={0}
@@ -69,6 +69,8 @@ export function E2ePadlock({ icon, title, className }: Readonly<E2ePadlockProps>
data-testid="e2e-padlock" data-testid="e2e-padlock"
className={classNames(styles.e2ePadlock, iconClasses[icon], className)} className={classNames(styles.e2ePadlock, iconClasses[icon], className)}
role="img" role="img"
// This is only tab indexed to act as a tooltip trigger
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0} tabIndex={0}
aria-label={_t("timeline|e2e_state")} aria-label={_t("timeline|e2e_state")}
> >
@@ -257,6 +257,7 @@ export function FileBodyView({ vm, refIFrame, refLink, className }: Readonly<Fil
* We'll use it to learn how the download button * We'll use it to learn how the download button
* would have been styled if it was rendered inline. * would have been styled if it was rendered inline.
* this violates multiple eslint rules so ignore it completely */} * this violates multiple eslint rules so ignore it completely */}
{/* oxlint-disable-next-line jsx-a11y/control-has-associated-label */}
<Button size="md" kind="secondary" Icon={DownloadIcon} as="a" ref={refLink} /> <Button size="md" kind="secondary" Icon={DownloadIcon} as="a" ref={refLink} />
</div> </div>
{/* {/*