Phone hit: page back buttons and Lobby/Notifications lone buttons
All checks were successful
Build pull request / Build pull request (pull_request) Successful in 3m59s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Void 2026-11-13 07:16:02 +00:00
commit cd90a044ed
12 changed files with 53 additions and 12 deletions

View file

@ -10,6 +10,7 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
import { allRoomsAtom } from '../../state/room-list/roomList';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { BackRouteHandler } from '../../components/BackRouteHandler';
import { PhoneHit } from '../../styles/PhoneHit.css';
type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] };
export function JoinBeforeNavigate({
@ -38,7 +39,7 @@ export function JoinBeforeNavigate({
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -38,6 +38,7 @@ import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt';
import { PhoneHit } from '../../styles/PhoneHit.css';
type LobbyMenuProps = {
powerLevels: IPowerLevels;
@ -165,7 +166,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
<Box shrink="No">
<BackRouteHandler>
{(onBack) => (
<IconButton fill="None" onClick={onBack}>
<IconButton className={PhoneHit} fill="None" onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}
@ -240,6 +241,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
>
{(triggerRef) => (
<IconButton
className={PhoneHit}
fill="None"
onClick={handleOpenMenu}
ref={triggerRef}

View file

@ -17,6 +17,7 @@ import {
} from '../../../components/page';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { CreateChat } from '../../../features/create-chat';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function DirectCreate() {
const mx = useMatrixClient();
@ -44,7 +45,7 @@ export function DirectCreate() {
<Box grow="Yes" alignItems="Center" gap="200">
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -18,6 +18,7 @@ import * as css from './style.css';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function FeaturedRooms() {
const { featuredCommunities } = useClientConfig();
@ -33,7 +34,7 @@ export function FeaturedRooms() {
<Box shrink="No">
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -45,6 +45,7 @@ import { getMxIdServer } from '../../../utils/matrix';
import { stopPropagation } from '../../../utils/keyboard';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams =>
useMemo(
@ -499,7 +500,7 @@ export function PublicRooms() {
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -12,6 +12,7 @@ import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { CreateRoomForm } from '../../../features/create-room';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function HomeCreateRoom() {
const screenSize = useScreenSizeContext();
@ -25,7 +26,7 @@ export function HomeCreateRoom() {
<Box grow="Yes" alignItems="Center" gap="200">
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -5,6 +5,7 @@ import { MessageSearch } from '../../../features/message-search';
import { useHomeRooms } from './useHomeRooms';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function HomeSearch() {
const scrollRef = useRef<HTMLDivElement>(null);
@ -19,7 +20,7 @@ export function HomeSearch() {
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -63,6 +63,7 @@ import { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
import { useReportRoomSupported } from '../../../hooks/useReportRoomSupported';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const COMPACT_CARD_WIDTH = 548;
@ -745,7 +746,7 @@ export function Invites() {
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -97,6 +97,7 @@ import {
} from '../../../hooks/useMemberPowerTag';
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type RoomNotificationsGroup = {
roomId: string;
@ -642,7 +643,7 @@ export function Notifications() {
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}
@ -694,6 +695,7 @@ export function Notifications() {
onVisibilityChange={handleScrollTopVisibility}
>
<IconButton
className={PhoneHit}
onClick={() => virtualizer.scrollToOffset(0)}
variant="SurfaceVariant"
radii="Pill"

View file

@ -11,6 +11,7 @@ import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function SpaceSearch() {
const mx = useMatrixClient();
@ -34,7 +35,7 @@ export function SpaceSearch() {
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<IconButton className={PhoneHit} onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -0,0 +1,29 @@
import { describe, it } from 'vitest';
import { expectPhoneHitClasses, expectSizeTie } from './phoneHitTestKit';
// Page back buttons (and two lone buttons beside them). Every one is alone in its row, 40 px drawn (default
// IconButton and Icon), so plain PhoneHit; the vertical stacks of the page have no variant.
// null = deliberately without: LobbyHeader's Members button renders only when screenSize !== Mobile.
const files: Record<string, (string | null)[]> = {
'features/lobby/LobbyHeader.tsx': ['PhoneHit', null, 'PhoneHit'],
'features/join-before-navigate/JoinBeforeNavigate.tsx': ['PhoneHit'],
'pages/client/space/Search.tsx': ['PhoneHit'],
'pages/client/explore/Server.tsx': ['PhoneHit'],
'pages/client/explore/Featured.tsx': ['PhoneHit'],
'pages/client/home/CreateRoom.tsx': ['PhoneHit'],
'pages/client/home/Search.tsx': ['PhoneHit'],
// the second is the 30 px scroll-to-top pill, floating alone at the top centre
'pages/client/inbox/Notifications.tsx': ['PhoneHit', 'PhoneHit'],
'pages/client/inbox/Invites.tsx': ['PhoneHit'],
'pages/client/direct/DirectCreate.tsx': ['PhoneHit'],
};
describe.each(Object.entries(files))('phone hit area: %s', (file, want) => {
it('gives each IconButton its variant', () => {
expectPhoneHitClasses(file, want);
});
// plain PhoneHit has no size to tie; this fails when a sized variant appears whose number is not the drawn px
it('ties any sized variant to the drawn size', () => {
expectSizeTie(file);
});
});

View file

@ -90,10 +90,10 @@ const phoneHitOf = (tag: string) => tag.match(/className=\{[^}]*?\b(PhoneHit\w*)
/** The class each IconButton must carry, in source order: the variant is chosen by the button's drawn size and
* whether a clipping container or neighbours surround it, so a swapped variant is a wrong hit area. Also checks
* the name is the one PhoneHit.css.ts exports, not a local of the same name. */
export const expectPhoneHitClasses = (file: string, want: string[]) => {
export const expectPhoneHitClasses = (file: string, want: (string | null)[]) => {
const src = readSource(file);
expect(iconButtonTags(src).map(phoneHitOf)).toEqual(want);
[...new Set(want)].forEach((name) =>
[...new Set(want.filter((w): w is string => w !== null))].forEach((name) =>
expect(src).toMatch(
new RegExp(
`import \\{(?:[^}]*,)?\\s*${name}\\s*(?:,[^}]*)?\\} from '(\\.\\./)+styles/PhoneHit\\.css'`