Files
cinny/src/app/pages/client/sidebar/UnverifiedTab.tsx
T
Lotus CIandClaude Opus 5.5 885ec7c82e
CI / Build & Quality Checks (pull_request) Successful in 1m47s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m18s
fix(a11y): axe findings: dialog names, aria-pressed, labels, avatar alt, placeholder contrast (#278)
- Modal500 takes a label: Room/Space Settings ("<name> settings") and
  Settings dialogs have accessible names.
- aria-selected on plain buttons (inbox filter chips, date/time picker
  chips, verification/usage/notification menu items) -> aria-pressed; the
  Permissions power chips open a menu -> aria-haspopup + aria-expanded.
- Labels tied to the room pack name input and the Export From/To dates.
- UserAvatar defaults to alt="" (decorative next to a name / in a
  labelled button) unless a caller passes one.
- Composer placeholder: Slate's inline opacity 0.333 overrode the #222
  AA-contrast class (2.66:1); it is dropped so the class (0.75) applies.
- e2e/a11y.spec.ts also audits Room Settings → Permissions, Emojis &
  Stickers and Export, checks the dialog name, and audits the inbox.

Fixes #278

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 15:35:59 -04:00

100 lines
3.3 KiB
TypeScript

import React, { useState } from 'react';
import { Badge, color, Icon, Icons, Text } from 'folds';
import {
SidebarAvatar,
SidebarItem,
SidebarItemBadge,
SidebarItemTooltip,
} from '../../../components/sidebar';
import { useDeviceIds, useDeviceList, useSplitCurrentDevice } from '../../../hooks/useDeviceList';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import * as css from './UnverifiedTab.css';
import {
useDeviceVerificationStatus,
useUnverifiedDeviceCount,
VerificationStatus,
} from '../../../hooks/useDeviceVerificationStatus';
import { useCrossSigningActive } from '../../../hooks/useCrossSigning';
import { Modal500 } from '../../../components/Modal500';
import { Settings, SettingsPages } from '../../../features/settings';
function UnverifiedIndicator() {
const mx = useMatrixClient();
const crypto = mx.getCrypto();
const [devices] = useDeviceList();
const [currentDevice, otherDevices] = useSplitCurrentDevice(devices);
const verificationStatus = useDeviceVerificationStatus(
crypto,
mx.getSafeUserId(),
currentDevice?.device_id,
);
const unverified = verificationStatus === VerificationStatus.Unverified;
const otherDevicesId = useDeviceIds(otherDevices);
const unverifiedDeviceCount = useUnverifiedDeviceCount(
crypto,
mx.getSafeUserId(),
otherDevicesId,
);
const [settings, setSettings] = useState(false);
const closeSettings = () => setSettings(false);
const hasUnverified =
unverified || (unverifiedDeviceCount !== undefined && unverifiedDeviceCount > 0);
return (
<>
{hasUnverified && (
<SidebarItem active={settings} className={css.UnverifiedTab}>
<SidebarItemTooltip tooltip={unverified ? 'Unverified Device' : 'Unverified Devices'}>
{(triggerRef) => (
<SidebarAvatar
className={unverified ? css.UnverifiedAvatar : css.UnverifiedOtherAvatar}
as="button"
aria-label={
unverified
? 'Unverified device — open settings'
: 'Other unverified devices — open settings'
}
ref={triggerRef}
outlined
onClick={() => setSettings(true)}
>
<Icon
style={{ color: unverified ? color.Critical.Main : color.Warning.Main }}
src={Icons.ShieldUser}
/>
</SidebarAvatar>
)}
</SidebarItemTooltip>
{!unverified && unverifiedDeviceCount && unverifiedDeviceCount > 0 && (
<SidebarItemBadge hasCount>
<Badge variant="Warning" size="400" fill="Solid" radii="Pill" outlined={false}>
<Text as="span" size="L400">
{unverifiedDeviceCount}
</Text>
</Badge>
</SidebarItemBadge>
)}
</SidebarItem>
)}
{settings && (
<Modal500 requestClose={closeSettings} label="Settings">
<Settings initialPage={SettingsPages.DevicesPage} requestClose={closeSettings} />
</Modal500>
)}
</>
);
}
export function UnverifiedTab() {
const crossSigningActive = useCrossSigningActive();
if (!crossSigningActive) return null;
return <UnverifiedIndicator />;
}