revert: remove redundant QuickSwitcher (Ctrl+K already does this better)

The existing SearchModalRenderer (Ctrl+K) is already a polished room/DM
switcher with avatars, unread badges, fuzzy search, and keyboard nav.
Our QuickSwitcher was an inferior duplicate. Removing it entirely:
- Delete QuickSwitcher.tsx
- Remove QuickSwitcherFeature from ClientNonUIFeatures
- Remove quickSwitcherKey from settingsAtom
- Remove Keyboard Shortcuts section from General settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 01:03:50 -04:00
parent 977fa8aa1b
commit e18e089043
7 changed files with 3 additions and 269 deletions
Submodule .claude/worktrees/agent-a4cb7be95d74ab7e7 added at f7c39e20a9
Submodule .claude/worktrees/agent-aaee4049508b9f175 added at f7c39e20a9
Submodule .claude/worktrees/agent-ac5c7e09bae2b0939 added at f7c39e20a9
-206
View File
@@ -1,206 +0,0 @@
import React, {
ChangeEventHandler,
KeyboardEventHandler,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { useAtomValue } from 'jotai';
import { Avatar, Box, Icon, Icons, Input, Text, config } from 'folds';
import { Room } from 'matrix-js-sdk';
import { useMatrixClient } from '../hooks/useMatrixClient';
import { allRoomsAtom } from '../state/room-list/roomList';
import { useRoomNavigate } from '../hooks/useRoomNavigate';
import { useMediaAuthentication } from '../hooks/useMediaAuthentication';
import { mDirectAtom } from '../state/mDirectList';
import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../utils/room';
import { RoomAvatar, RoomIcon } from './room-avatar';
import { nameInitials } from '../utils/common';
const MAX_RESULTS = 10;
function RoomFallback({ room }: { room: Room }) {
return (
<Text as="span" size="H6">
{nameInitials(room.name)}
</Text>
);
}
type QuickSwitcherProps = {
onClose: () => void;
};
export function QuickSwitcher({ onClose }: QuickSwitcherProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const allRoomIds = useAtomValue(allRoomsAtom);
const mDirects = useAtomValue(mDirectAtom);
const { navigateRoom } = useRoomNavigate();
const [query, setQuery] = useState('');
const [selectedIdx, setSelectedIdx] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
const filteredRooms = query.trim()
? allRoomIds
.map((id) => ({ id, room: mx.getRoom(id) }))
.filter(({ room }) => room && room.name.toLowerCase().includes(query.trim().toLowerCase()))
.slice(0, MAX_RESULTS)
: allRoomIds
.map((id) => ({ id, room: mx.getRoom(id) }))
.filter(({ room }) => !!room)
.slice(0, MAX_RESULTS);
const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
setQuery(evt.currentTarget.value);
setSelectedIdx(0);
};
const navigateToRoom = useCallback(
(roomId: string) => {
navigateRoom(roomId);
onClose();
},
[navigateRoom, onClose],
);
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
(evt) => {
if (evt.key === 'ArrowDown') {
evt.preventDefault();
setSelectedIdx((i) => Math.min(i + 1, filteredRooms.length - 1));
} else if (evt.key === 'ArrowUp') {
evt.preventDefault();
setSelectedIdx((i) => Math.max(i - 1, 0));
} else if (evt.key === 'Enter') {
const item = filteredRooms[selectedIdx];
if (item) navigateToRoom(item.id);
} else if (evt.key === 'Escape') {
onClose();
}
},
[filteredRooms, selectedIdx, navigateToRoom, onClose],
);
return (
<>
{/* Backdrop */}
<div
role="presentation"
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.5)',
zIndex: 9899,
}}
onClick={onClose}
/>
{/* Modal */}
<div
role="dialog"
aria-label="Quick Room Switcher"
aria-modal="true"
style={{
position: 'fixed',
top: '20%',
left: '50%',
transform: 'translateX(-50%)',
width: 'min(560px, 90vw)',
zIndex: 9900,
background: 'var(--bg-surface)',
borderRadius: config.radii.R400,
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
overflow: 'hidden',
}}
>
{/* Search input */}
<Box style={{ padding: config.space.S300 }}>
<Input
ref={inputRef}
value={query}
onChange={handleChange}
onKeyDown={handleKeyDown}
before={<Icon size="200" src={Icons.Search} />}
placeholder="Search rooms…"
size="500"
variant="Background"
outlined
/>
</Box>
{/* Results list */}
{filteredRooms.length > 0 && (
<Box direction="Column" style={{ paddingBottom: config.space.S200 }}>
{filteredRooms.map(({ id, room }, idx) => {
if (!room) return null;
const dm = mDirects.has(id);
const avatarUrl = dm
? getDirectRoomAvatarUrl(mx, room, 32, useAuthentication)
: getRoomAvatarUrl(mx, room, 32, useAuthentication);
const isSelected = idx === selectedIdx;
return (
<button
key={id}
type="button"
aria-selected={isSelected}
onClick={() => navigateToRoom(id)}
onMouseEnter={() => setSelectedIdx(idx)}
style={{
display: 'flex',
alignItems: 'center',
gap: config.space.S200,
padding: `${config.space.S200} ${config.space.S300}`,
background: isSelected
? 'var(--bg-surface-hover, rgba(255,255,255,0.08))'
: 'transparent',
border: 'none',
cursor: 'pointer',
width: '100%',
textAlign: 'left',
color: 'inherit',
}}
>
<Avatar size="200" radii={dm ? '400' : '300'}>
{dm || room.isSpaceRoom() ? (
<RoomAvatar
roomId={id}
src={avatarUrl}
alt={room.name}
renderFallback={() => <RoomFallback room={room} />}
/>
) : (
<RoomIcon
size="200"
joinRule={room.getJoinRule()}
roomType={room.getType()}
/>
)}
</Avatar>
<Text truncate size="T400">
{room.name}
</Text>
</button>
);
})}
</Box>
)}
{filteredRooms.length === 0 && (
<Box alignItems="Center" justifyContent="Center" style={{ padding: config.space.S500 }}>
<Text size="T300" align="Center">
{query.trim() ? `No rooms matching "${query}"` : 'No rooms'}
</Text>
</Box>
)}
</div>
</>
);
}
@@ -1316,43 +1316,6 @@ function Messages() {
);
}
function KeyboardShortcuts() {
const [quickSwitcherKey, setQuickSwitcherKey] = useSetting(settingsAtom, 'quickSwitcherKey');
const qsBind = useKeyBind(setQuickSwitcherKey);
return (
<Box direction="Column" gap="100">
<Text size="L400">Keyboard Shortcuts</Text>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
title="Quick Room Switcher"
description="Open the quick room switcher overlay. Ctrl/Cmd + this key."
after={
<Button
size="300"
variant={qsBind.listening ? 'Warning' : 'Secondary'}
fill={qsBind.listening ? 'Solid' : 'Soft'}
radii="300"
outlined
onClick={qsBind.startListening}
style={{ minWidth: '90px' }}
>
<Text size="B300">
{qsBind.listening ? 'Press a key…' : `Ctrl+${keyLabel(quickSwitcherKey)}`}
</Text>
</Button>
}
/>
</SequenceCard>
</Box>
);
}
type GeneralProps = {
requestClose: () => void;
};
@@ -1383,7 +1346,6 @@ export function General({ requestClose }: GeneralProps) {
<Messages />
<Privacy />
<Calls />
<KeyboardShortcuts />
</Box>
</PageContent>
</Scroll>
@@ -27,7 +27,6 @@ import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { usePresenceUpdater } from '../../hooks/usePresenceUpdater';
import { QuickSwitcher } from '../../components/QuickSwitcher';
function SystemEmojiFeature() {
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
@@ -262,27 +261,6 @@ function MessageNotifications() {
);
}
function QuickSwitcherFeature() {
const [open, setOpen] = useState(false);
const [quickSwitcherKey] = useSetting(settingsAtom, 'quickSwitcherKey');
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.code === quickSwitcherKey) {
e.preventDefault();
setOpen((prev) => !prev);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [quickSwitcherKey]);
if (!open) return null;
return <QuickSwitcher onClose={() => setOpen(false)} />;
}
type ClientNonUIFeaturesProps = {
children: ReactNode;
};
@@ -296,7 +274,6 @@ export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
<PresenceUpdater />
<InviteNotifications />
<MessageNotifications />
<QuickSwitcherFeature />
{children}
</>
);
-2
View File
@@ -82,7 +82,6 @@ export interface Settings {
nightLightOpacity: number;
deafenKey: string;
quickSwitcherKey: string;
}
const defaultSettings: Settings = {
@@ -134,7 +133,6 @@ const defaultSettings: Settings = {
nightLightOpacity: 30,
deafenKey: 'KeyM',
quickSwitcherKey: 'KeyP',
};
export const getSettings = (): Settings => {