Focus the thread panel when clicking on an item in the TAC (#12410)
* Focus the thread panel when clicking on an item in the TAC actually the 'close' button in the threads panel as it's the only interactive element: we can improve this later when we use landmarks & generally have better a11y. * Undo minor refactoring as none of it is test3ed, it's not worth it. * add unit test * Add matrixchat tests * Needs awaits * ts-ignore * Fix test (I think...) * Remove unnecessary value set * Not how assignments work
This commit is contained in:
parent
0daf0cfa80
commit
59395abb6b
12 changed files with 136 additions and 11 deletions
|
@ -116,7 +116,7 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
|
|||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";
|
||||
import Views from "../../Views";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { FocusNextType, ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
|
||||
import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
|
||||
import { DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
|
||||
|
@ -229,7 +229,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
private screenAfterLogin?: IScreen;
|
||||
private tokenLogin?: boolean;
|
||||
private focusComposer: boolean;
|
||||
// What to focus on next component update, if anything
|
||||
private focusNext: FocusNextType;
|
||||
private subTitleStatus: string;
|
||||
private prevWindowWidth: number;
|
||||
private voiceBroadcastResumer?: VoiceBroadcastResumer;
|
||||
|
@ -298,8 +299,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
this.themeWatcher.start();
|
||||
this.fontWatcher.start();
|
||||
|
||||
this.focusComposer = false;
|
||||
|
||||
// object field used for tracking the status info appended to the title tag.
|
||||
// we don't do it as react state as i'm scared about triggering needless react refreshes.
|
||||
this.subTitleStatus = "";
|
||||
|
@ -483,9 +482,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
|
||||
}
|
||||
}
|
||||
if (this.focusComposer) {
|
||||
if (this.focusNext === "composer") {
|
||||
dis.fire(Action.FocusSendMessageComposer);
|
||||
this.focusComposer = false;
|
||||
this.focusNext = undefined;
|
||||
} else if (this.focusNext === "threadsPanel") {
|
||||
dis.fire(Action.FocusThreadsPanel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -985,7 +986,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
// switch view to the given room
|
||||
private async viewRoom(roomInfo: ViewRoomPayload): Promise<void> {
|
||||
this.focusComposer = true;
|
||||
this.focusNext = roomInfo.focusNext ?? "composer";
|
||||
|
||||
if (roomInfo.room_alias) {
|
||||
logger.log(`Switching to room alias ${roomInfo.room_alias} at event ${roomInfo.event_id}`);
|
||||
|
|
|
@ -1268,7 +1268,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
case Action.FocusAComposer: {
|
||||
dis.dispatch<FocusComposerPayload>({
|
||||
...(payload as FocusComposerPayload),
|
||||
// re-dispatch to the correct composer
|
||||
// re-dispatch to the correct composer (the send message will still be on screen even when editing a message)
|
||||
action: this.state.editState ? Action.FocusEditMessageComposer : Action.FocusSendMessageComposer,
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -37,6 +37,9 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
|
|||
import Spinner from "../views/elements/Spinner";
|
||||
import Heading from "../views/typography/Heading";
|
||||
import { clearRoomNotification } from "../../utils/notifications";
|
||||
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -229,6 +232,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
const roomContext = useContext(RoomContext);
|
||||
const timelinePanel = useRef<TimelinePanel | null>(null);
|
||||
const card = useRef<HTMLDivElement | null>(null);
|
||||
const closeButonRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [filterOption, setFilterOption] = useState<ThreadFilterType>(ThreadFilterType.All);
|
||||
const [room, setRoom] = useState<Room | null>(null);
|
||||
|
@ -255,6 +259,14 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
}
|
||||
}, [timelineSet, timelinePanel]);
|
||||
|
||||
useDispatcher(dis, (payload) => {
|
||||
// This actually foucses the close button on the threads panel, as its the only interactive element,
|
||||
// but at least it puts the user in the right area of the app.
|
||||
if (payload.action === Action.FocusThreadsPanel) {
|
||||
closeButonRef.current?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
|
@ -276,6 +288,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
onClose={onClose}
|
||||
withoutScrollContainer={true}
|
||||
ref={card}
|
||||
closeButtonRef={closeButonRef}
|
||||
>
|
||||
{card.current && <Measured sensor={card.current} onMeasurement={setNarrow} />}
|
||||
{timelineSet ? (
|
||||
|
|
|
@ -35,6 +35,8 @@ interface IProps {
|
|||
onKeyDown?(ev: KeyboardEvent): void;
|
||||
cardState?: any;
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
// Ref for the 'close' button the the card
|
||||
closeButtonRef?: Ref<HTMLDivElement>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
@ -54,7 +56,21 @@ export const Group: React.FC<IGroupProps> = ({ className, title, children }) =>
|
|||
};
|
||||
|
||||
const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
|
||||
({ closeLabel, onClose, onBack, className, header, footer, withoutScrollContainer, children, onKeyDown }, ref) => {
|
||||
(
|
||||
{
|
||||
closeLabel,
|
||||
onClose,
|
||||
onBack,
|
||||
className,
|
||||
header,
|
||||
footer,
|
||||
withoutScrollContainer,
|
||||
children,
|
||||
onKeyDown,
|
||||
closeButtonRef,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
let backButton;
|
||||
const cardHistory = RightPanelStore.instance.roomPhaseHistory;
|
||||
if (cardHistory.length > 1) {
|
||||
|
@ -75,6 +91,7 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
|
|||
className="mx_BaseCard_close"
|
||||
onClick={onClose}
|
||||
title={closeLabel || _t("action|close")}
|
||||
ref={closeButtonRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -159,6 +159,7 @@ function ThreadsActivityCentreRow({ room, onClick, notificationLevel }: ThreadsA
|
|||
show_room_tile: true, // make sure the room is visible in the list
|
||||
room_id: room.roomId,
|
||||
metricsTrigger: "WebThreadsActivityCentre",
|
||||
focusNext: "threadsPanel",
|
||||
});
|
||||
}}
|
||||
label={room.name}
|
||||
|
|
|
@ -27,7 +27,7 @@ import { notificationLevelToIndicator } from "../../../../utils/notifications";
|
|||
|
||||
interface ThreadsActivityCentreButtonProps extends ComponentProps<typeof IconButton> {
|
||||
/**
|
||||
* Display the `Treads` label next to the icon.
|
||||
* Display the `Threads` label next to the icon.
|
||||
*/
|
||||
displayLabel?: boolean;
|
||||
/**
|
||||
|
|
|
@ -91,6 +91,11 @@ export enum Action {
|
|||
*/
|
||||
FocusAComposer = "focus_a_composer",
|
||||
|
||||
/**
|
||||
* Focuses the threads panel.
|
||||
*/
|
||||
FocusThreadsPanel = "focus_threads_panel",
|
||||
|
||||
/**
|
||||
* Opens the user menu (previously known as the top left menu). No additional payload information required.
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,8 @@ import { IOpts } from "../../createRoom";
|
|||
import { JoinRoomPayload } from "./JoinRoomPayload";
|
||||
import { AtLeastOne } from "../../@types/common";
|
||||
|
||||
export type FocusNextType = "composer" | "threadsPanel" | undefined;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface BaseViewRoomPayload extends Pick<ActionPayload, "action"> {
|
||||
action: Action.ViewRoom;
|
||||
|
@ -61,5 +63,6 @@ export type ViewRoomPayload = BaseViewRoomPayload &
|
|||
// the number of API calls required.
|
||||
room_id?: string;
|
||||
room_alias?: string;
|
||||
focusNext: FocusNextType; // wat to focus after room switch. Defaults to 'composer' if undefined.
|
||||
}>;
|
||||
/* eslint-enable camelcase */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue