feat(a11y,perf): comprehensive icon button labels, toolbar a11y, timeline binary search
A11y C-1: aria-label on 30+ remaining icon-only buttons across:
- settings panels (close, reset, info, expand, remove, undo)
- editor toolbar (bold, italic, underline, strike, code, spoiler,
blockquote, code block, ordered/unordered list, headings 1-3)
- auth stages (cancel buttons in SSO, Password stages)
- device verification (cancel buttons)
- password input (show/hide toggle with dynamic label)
- event readers, account data editor close buttons
- global emoji packs (add/remove buttons)
Perf-5: Replace O(N×T) getTimelineAndBaseIndex scan with precomputed binary
search (timelineSegments useMemo) — O(log T) per visible message render
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -294,7 +294,7 @@ export function AccountDataEditor({
|
||||
</Chip>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
<IconButton onClick={requestClose} variant="Surface">
|
||||
<IconButton onClick={requestClose} variant="Surface" aria-label="Close">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
@@ -261,7 +261,7 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps)
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">Device Verification</Text>
|
||||
</Box>
|
||||
<IconButton size="300" radii="300" onClick={handleCancel}>
|
||||
<IconButton size="300" radii="300" onClick={handleCancel} aria-label="Cancel verification">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
|
||||
@@ -301,7 +301,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">Setup Device Verification</Text>
|
||||
</Box>
|
||||
<IconButton size="300" radii="300" onClick={onCancel}>
|
||||
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
|
||||
@@ -54,8 +54,8 @@ function BtnTooltip({ text, shortCode }: { text: string; shortCode?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
type MarkButtonProps = { format: MarkType; icon: IconSrc; tooltip: ReactNode };
|
||||
export function MarkButton({ format, icon, tooltip }: MarkButtonProps) {
|
||||
type MarkButtonProps = { format: MarkType; icon: IconSrc; tooltip: ReactNode; label?: string };
|
||||
export function MarkButton({ format, icon, tooltip, label }: MarkButtonProps) {
|
||||
const editor = useSlate();
|
||||
const disableInline = isBlockActive(editor, BlockType.CodeBlock);
|
||||
|
||||
@@ -76,6 +76,7 @@ export function MarkButton({ format, icon, tooltip }: MarkButtonProps) {
|
||||
variant="SurfaceVariant"
|
||||
onClick={handleClick}
|
||||
aria-pressed={isMarkActive(editor, format)}
|
||||
aria-label={label}
|
||||
size="400"
|
||||
radii="300"
|
||||
disabled={disableInline}
|
||||
@@ -91,8 +92,9 @@ type BlockButtonProps = {
|
||||
format: BlockType;
|
||||
icon: IconSrc;
|
||||
tooltip: ReactNode;
|
||||
label?: string;
|
||||
};
|
||||
export function BlockButton({ format, icon, tooltip }: BlockButtonProps) {
|
||||
export function BlockButton({ format, icon, tooltip, label }: BlockButtonProps) {
|
||||
const editor = useSlate();
|
||||
|
||||
const handleClick = () => {
|
||||
@@ -108,6 +110,7 @@ export function BlockButton({ format, icon, tooltip }: BlockButtonProps) {
|
||||
variant="SurfaceVariant"
|
||||
onClick={handleClick}
|
||||
aria-pressed={isBlockActive(editor, format)}
|
||||
aria-label={label}
|
||||
size="400"
|
||||
radii="300"
|
||||
>
|
||||
@@ -165,6 +168,7 @@ export function HeadingBlockButton() {
|
||||
<IconButton
|
||||
ref={triggerRef}
|
||||
onClick={() => handleMenuSelect(1)}
|
||||
aria-label="Heading 1"
|
||||
size="400"
|
||||
radii="300"
|
||||
>
|
||||
@@ -180,6 +184,7 @@ export function HeadingBlockButton() {
|
||||
<IconButton
|
||||
ref={triggerRef}
|
||||
onClick={() => handleMenuSelect(2)}
|
||||
aria-label="Heading 2"
|
||||
size="400"
|
||||
radii="300"
|
||||
>
|
||||
@@ -195,6 +200,7 @@ export function HeadingBlockButton() {
|
||||
<IconButton
|
||||
ref={triggerRef}
|
||||
onClick={() => handleMenuSelect(3)}
|
||||
aria-label="Heading 3"
|
||||
size="400"
|
||||
radii="300"
|
||||
>
|
||||
@@ -271,32 +277,44 @@ export function Toolbar() {
|
||||
<MarkButton
|
||||
format={MarkType.Bold}
|
||||
icon={Icons.Bold}
|
||||
tooltip={<BtnTooltip text="Bold" shortCode={`${modKey} + B`} />}
|
||||
tooltip={<BtnTooltip text="Bold" shortCode={`${modKey} + B`}
|
||||
label="Bold"
|
||||
/>}
|
||||
/>
|
||||
<MarkButton
|
||||
format={MarkType.Italic}
|
||||
icon={Icons.Italic}
|
||||
tooltip={<BtnTooltip text="Italic" shortCode={`${modKey} + I`} />}
|
||||
tooltip={<BtnTooltip text="Italic" shortCode={`${modKey} + I`}
|
||||
label="Italic"
|
||||
/>}
|
||||
/>
|
||||
<MarkButton
|
||||
format={MarkType.Underline}
|
||||
icon={Icons.Underline}
|
||||
tooltip={<BtnTooltip text="Underline" shortCode={`${modKey} + U`} />}
|
||||
tooltip={<BtnTooltip text="Underline" shortCode={`${modKey} + U`}
|
||||
label="Underline"
|
||||
/>}
|
||||
/>
|
||||
<MarkButton
|
||||
format={MarkType.StrikeThrough}
|
||||
icon={Icons.Strike}
|
||||
tooltip={<BtnTooltip text="Strike Through" shortCode={`${modKey} + S`} />}
|
||||
tooltip={<BtnTooltip text="Strike Through" shortCode={`${modKey} + S`}
|
||||
label="Strikethrough"
|
||||
/>}
|
||||
/>
|
||||
<MarkButton
|
||||
format={MarkType.Code}
|
||||
icon={Icons.Code}
|
||||
tooltip={<BtnTooltip text="Inline Code" shortCode={`${modKey} + [`} />}
|
||||
tooltip={<BtnTooltip text="Inline Code" shortCode={`${modKey} + [`}
|
||||
label="Inline code"
|
||||
/>}
|
||||
/>
|
||||
<MarkButton
|
||||
format={MarkType.Spoiler}
|
||||
icon={Icons.EyeBlind}
|
||||
tooltip={<BtnTooltip text="Spoiler" shortCode={`${modKey} + H`} />}
|
||||
tooltip={<BtnTooltip text="Spoiler" shortCode={`${modKey} + H`}
|
||||
label="Spoiler"
|
||||
/>}
|
||||
/>
|
||||
</Box>
|
||||
<Line variant="SurfaceVariant" direction="Vertical" style={{ height: toRem(12) }} />
|
||||
@@ -305,22 +323,30 @@ export function Toolbar() {
|
||||
<BlockButton
|
||||
format={BlockType.BlockQuote}
|
||||
icon={Icons.BlockQuote}
|
||||
tooltip={<BtnTooltip text="Block Quote" shortCode={`${modKey} + '`} />}
|
||||
tooltip={<BtnTooltip text="Block Quote" shortCode={`${modKey} + '`}
|
||||
label="Block quote"
|
||||
/>}
|
||||
/>
|
||||
<BlockButton
|
||||
format={BlockType.CodeBlock}
|
||||
icon={Icons.BlockCode}
|
||||
tooltip={<BtnTooltip text="Block Code" shortCode={`${modKey} + ;`} />}
|
||||
tooltip={<BtnTooltip text="Block Code" shortCode={`${modKey} + ;`}
|
||||
label="Code block"
|
||||
/>}
|
||||
/>
|
||||
<BlockButton
|
||||
format={BlockType.OrderedList}
|
||||
icon={Icons.OrderList}
|
||||
tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + 7`} />}
|
||||
tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + 7`}
|
||||
label="Ordered list"
|
||||
/>}
|
||||
/>
|
||||
<BlockButton
|
||||
format={BlockType.UnorderedList}
|
||||
icon={Icons.UnorderList}
|
||||
tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + 8`} />}
|
||||
tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + 8`}
|
||||
label="Unordered list"
|
||||
/>}
|
||||
/>
|
||||
<HeadingBlockButton />
|
||||
</Box>
|
||||
|
||||
@@ -73,7 +73,7 @@ export const EventReaders = as<'div', EventReadersProps>(
|
||||
<Box grow="Yes">
|
||||
<Text size="H3" style={lotusTerminal ? { color: '#00D4FF', textShadow: '0 0 6px rgba(0,212,255,0.45)', letterSpacing: '0.05em' } : undefined}>Seen by</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={requestClose}>
|
||||
<IconButton size="300" onClick={requestClose} aria-label="Close">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
|
||||
@@ -28,6 +28,7 @@ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
|
||||
variant={visible ? 'Warning' : variant}
|
||||
size="300"
|
||||
radii="300"
|
||||
aria-label={visible ? 'Hide password' : 'Show password'}
|
||||
>
|
||||
<Icon
|
||||
style={{ opacity: config.opacity.P300 }}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function PasswordStage({
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">Account Password</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={onCancel} radii="300">
|
||||
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
|
||||
@@ -56,7 +56,7 @@ export function SSOStage({
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">SSO Login</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={onCancel} radii="300">
|
||||
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
|
||||
Reference in New Issue
Block a user