Phone: 44 px hit areas for the chat path's remaining icon buttons; pin variant, import and ::after content
Some checks failed
Build pull request / Build pull request (pull_request) Has been cancelled

MessageEditor, RoomNavItem, MembersDrawer, ReactionViewer, RoomPinMenu, RoomViewTyping, CallChatView and JumpToTime
get PhoneHit (alone) or PhoneHitSpread32 (two neighbours in a row). phoneTapTargets.test.ts now lists them, plus
PasswordInput, Desk, SkyStateControl and PasswordLoginForm, which had no test, checks each class is imported from
styles/PhoneHit.css, and pins the ::after content (a mutant without it passed before).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Void 2026-11-13 05:40:21 +00:00
commit fc92809eb9
9 changed files with 55 additions and 7 deletions

View file

@ -66,6 +66,7 @@ import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
import { livekitSupport } from '../../hooks/useLivekitSupport';
import { StateEvent } from '../../../types/matrix/room';
import { webRTCSupported } from '../../utils/rtc';
import { PhoneHitSpread32 } from '../../styles/PhoneHit.css';
type RoomNavItemMenuProps = {
room: Room;
@ -229,6 +230,7 @@ function CallChatToggle() {
return (
<IconButton
className={PhoneHitSpread32}
onClick={() => setChat(!chat)}
aria-pressed={chat}
aria-label="Toggle Chat"
@ -436,6 +438,7 @@ export function RoomNavItem({
}
>
<IconButton
className={PhoneHitSpread32}
onClick={handleOpenMenu}
aria-pressed={!!menuAnchor}
aria-controls={`menu-${room.roomId}`}

View file

@ -6,6 +6,7 @@ import { Page, PageHeader } from '../../components/page';
import { callChatAtom } from '../../state/callEmbed';
import { RoomView } from './RoomView';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { PhoneHit } from '../../styles/PhoneHit.css';
export function CallChatView() {
const { eventId } = useParams();
@ -41,7 +42,12 @@ export function CallChatView() {
}
>
{(triggerRef) => (
<IconButton ref={triggerRef} variant="Surface" onClick={handleClose}>
<IconButton
className={PhoneHit}
ref={triggerRef}
variant="Surface"
onClick={handleClose}
>
<Icon src={Icons.Cross} />
</IconButton>
)}

View file

@ -59,6 +59,7 @@ import { useSpaceOptionally } from '../../hooks/useSpace';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { PhoneHit } from '../../styles/PhoneHit.css';
type MemberDrawerHeaderProps = {
room: Room;
@ -87,6 +88,7 @@ function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) {
>
{(triggerRef) => (
<IconButton
className={PhoneHit}
ref={triggerRef}
variant="Background"
onClick={() => setPeopleDrawer(false)}
@ -359,6 +361,7 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
<ScrollTopContainer scrollRef={scrollRef} anchorRef={scrollTopAnchorRef}>
<IconButton
className={PhoneHit}
onClick={() => virtualizer.scrollToOffset(0)}
variant="Surface"
radii="Pill"

View file

@ -10,6 +10,7 @@ import { getMxIdLocalPart } from '../../utils/matrix';
import * as css from './RoomViewTyping.css';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
import { PhoneHit } from '../../styles/PhoneHit.css';
export type RoomViewTypingProps = {
room: Room;
@ -111,7 +112,13 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>(
</>
)}
</Text>
<IconButton title="Drop Typing Status" size="300" radii="Pill" onClick={handleDropAll}>
<IconButton
className={PhoneHit}
title="Drop Typing Status"
size="300"
radii="Pill"
onClick={handleDropAll}
>
<Icon size="50" src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -31,6 +31,7 @@ import { getToday, getYesterday, timeDayMonthYear, timeHourMinute } from '../../
import { DatePicker, TimePicker } from '../../../components/time-date';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type JumpToTimeProps = {
onCancel: () => void;
@ -108,7 +109,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
<Box grow="Yes">
<Text size="H4">Jump to Time</Text>
</Box>
<IconButton size="300" onClick={onCancel} radii="300">
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -54,6 +54,7 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getEditedEvent, getMentionContent, trimReplyFromFormattedBody } from '../../../utils/room';
import { mobileOrTablet } from '../../../utils/user-agent';
import { useComposingCheck } from '../../../hooks/useComposingCheck';
import { PhoneHitSpread32 } from '../../../styles/PhoneHit.css';
type MessageEditorProps = {
roomId: string;
@ -289,6 +290,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
</Box>
<Box gap="Inherit">
<IconButton
className={PhoneHitSpread32}
variant="SurfaceVariant"
size="300"
radii="300"
@ -322,6 +324,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
}
>
<IconButton
className={PhoneHitSpread32}
aria-pressed={anchor !== undefined}
onClick={
((evt) =>

View file

@ -28,6 +28,7 @@ import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { useOpenUserRoomProfile } from '../../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../../hooks/useSpace';
import { getMouseEventCords } from '../../../utils/dom';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export type ReactionViewerProps = {
room: Room;
@ -100,7 +101,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
<Box grow="Yes">
<Text size="H3" truncate>{`Reacted with :${selectedShortcode}:`}</Text>
</Box>
<IconButton size="300" onClick={requestClose}>
<IconButton className={PhoneHit} size="300" onClick={requestClose}>
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -86,6 +86,7 @@ import {
useGetMemberPowerTag,
} from '../../../hooks/useMemberPowerTag';
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type PinnedMessageProps = {
room: Room;
@ -146,6 +147,7 @@ function PinnedMessage({
</Chip>
{canPinEvent && (
<IconButton
className={PhoneHit}
data-event-id={eventId}
variant="Secondary"
size="300"
@ -457,7 +459,7 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
<Text size="H5">Pinned Messages</Text>
</Box>
<Box shrink="No">
<IconButton size="300" onClick={requestClose} radii="300">
<IconButton className={PhoneHit} size="300" onClick={requestClose} radii="300">
<Icon src={Icons.Cross} size="400" />
</IconButton>
</Box>

View file

@ -126,6 +126,8 @@ describe('phone icon-button hit areas', () => {
const phone = [...css.matchAll(/@media ([^{]+) \{\n([\s\S]*?\n)\}(?:\n|$)/g)];
expect(phone.map((m) => m[1])).toEqual([PHONE]);
expect(css).toMatch(/::after \{[^}]*width: max\(100%, 44px\);[^}]*height: max\(100%, 44px\);/);
// no content, no box: the pseudo-element is not rendered at all
expect(css).toMatch(/::after \{[^}]*content: "";/);
expect(css).toMatch(/::after \{[^}]*position: absolute;/);
// centred on the button: top and left at half, pulled back by half its own size
expect(css).toMatch(/::after \{[^}]*top: 50%;/);
@ -190,13 +192,33 @@ describe('icon buttons in the room view', () => {
],
'features/room/RoomInput.tsx': Array(6).fill('PhoneHitSpread32'),
'features/room/RoomViewHeader.tsx': Array(6).fill('PhoneHitSpread40'),
// The chat path's other buttons. Two neighbours in a row: Spread32. One on its own: the plain PhoneHit.
'features/room/message/MessageEditor.tsx': ['PhoneHitSpread32', 'PhoneHitSpread32'], // toolbar, emoji
'features/room-nav/RoomNavItem.tsx': ['PhoneHitSpread32', 'PhoneHitSpread32'], // chat toggle, options
'features/room/MembersDrawer.tsx': ['PhoneHit', 'PhoneHit'], // close, scroll to top
'features/room/reaction-viewer/ReactionViewer.tsx': ['PhoneHit'],
'features/room/room-pin-menu/RoomPinMenu.tsx': ['PhoneHit', 'PhoneHit'], // unpin, close
'features/room/RoomViewTyping.tsx': ['PhoneHit'],
'features/room/CallChatView.tsx': ['PhoneHit'],
'features/room/jump-to-time/JumpToTime.tsx': ['PhoneHit'],
// Buttons that were fixed with #125 and had no test.
'components/password-input/PasswordInput.tsx': ['PhoneHit'],
'features/desk/Desk.tsx': ['PhoneHit'],
'features/scene/SkyStateControl.tsx': ['PhoneHit'],
'pages/auth/login/PasswordLoginForm.tsx': ['PhoneHit'],
};
it.each(Object.entries(expected))(
'%s: each IconButton carries its own PhoneHit class',
(file, want) => {
const tags = iconButtonTags(readFileSync(`${process.cwd()}/src/app/${file}`, 'utf8'));
expect(tags.map((t) => t.match(/\bPhoneHit\w+/)?.[0] ?? null)).toEqual(want);
const src = readFileSync(`${process.cwd()}/src/app/${file}`, 'utf8');
expect(iconButtonTags(src).map((t) => t.match(/\bPhoneHit\w*/)?.[0] ?? null)).toEqual(want);
// the name is the one PhoneHit.css.ts exports, not a local of the same name
[...new Set(want)].forEach((name) =>
expect(src).toMatch(
new RegExp(`import \\{[^}]*\\b${name}\\b[^}]*\\} from '(\\.\\./)+styles/PhoneHit\\.css'`)
)
);
}
);
});