nicolium: make closing the hotkeys modal navigate back to what opened it

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-17 18:50:11 +01:00
parent 3957b40572
commit 87d23e0b50
2 changed files with 64 additions and 56 deletions

View File

@ -91,7 +91,7 @@ const UI: React.FC = React.memo(() => {
};
const handleOpenHotkeysModal = () => {
openModal('HOTKEYS');
openModal('HOTKEYS', undefined, document.getElementById('skip-link-hotkeys') || undefined);
};
/** Load initial data when a user is logged in */
@ -164,7 +164,7 @@ const UI: React.FC = React.memo(() => {
<button onClick={handleSkipToContent}>
<FormattedMessage id='skip_links.skip_to_content' defaultMessage='Skip to content' />
</button>
<button onClick={handleOpenHotkeysModal}>
<button id='skip-link-hotkeys' onClick={handleOpenHotkeysModal}>
<FormattedMessage
id='navigation.keyboard_shortcuts'
defaultMessage='Keyboard shortcuts'

View File

@ -2,7 +2,6 @@ import { create } from 'zustand';
import { mutative } from 'zustand-mutative';
import type { ICryptoAddress } from '@/features/crypto-donate/components/crypto-address';
import type { ModalType } from '@/features/ui/components/modal-root';
import type { AltTextModalProps } from '@/modals/alt-text-modal';
import type { AntennaEditorModalProps } from '@/modals/antenna-editor-modal';
import type { BlockMuteModalProps } from '@/modals/block-mute-modal';
@ -41,57 +40,66 @@ import type { TextFieldModalProps } from '@/modals/text-field-modal';
import type { UnauthorizedModalProps } from '@/modals/unauthorized-modal';
type OpenModalProps =
| [type: 'ALT_TEXT', props: AltTextModalProps]
| [type: 'ANTENNA_EDITOR', props: AntennaEditorModalProps]
| [type: 'BIRTHDAYS' | 'CREATE_GROUP' | 'HOTKEYS']
| [type: 'BOOST', props: BoostModalProps]
| [type: 'CIRCLE_EDITOR', props: CircleEditorModalProps]
| [type: 'COMPARE_HISTORY', props: CompareHistoryModalProps]
| [type: 'COMPONENT', props: ComponentModalProps]
| [type: 'COMPOSE', props?: ComposeModalProps]
| [type: 'COMPOSE_INTERACTION_POLICY', props?: ComposeInteractionPolicyModalProps]
| [type: 'CONFIRM', props: ConfirmationModalProps]
| [type: 'ALT_TEXT', props: AltTextModalProps, element?: HTMLElement]
| [type: 'ANTENNA_EDITOR', props: AntennaEditorModalProps, element?: HTMLElement]
| [type: 'BIRTHDAYS' | 'CREATE_GROUP' | 'HOTKEYS', props?: undefined, element?: HTMLElement]
| [type: 'BOOST', props: BoostModalProps, element?: HTMLElement]
| [type: 'CIRCLE_EDITOR', props: CircleEditorModalProps, element?: HTMLElement]
| [type: 'COMPARE_HISTORY', props: CompareHistoryModalProps, element?: HTMLElement]
| [type: 'COMPONENT', props: ComponentModalProps, element?: HTMLElement]
| [type: 'COMPOSE', props?: ComposeModalProps, element?: HTMLElement]
| [
type: 'COMPOSE_INTERACTION_POLICY',
props?: ComposeInteractionPolicyModalProps,
element?: HTMLElement,
]
| [type: 'CONFIRM', props: ConfirmationModalProps, element?: HTMLElement]
| [type: 'CRYPTO_DONATE', props: ICryptoAddress]
| [type: 'DISLIKES', props: DislikesModalProps]
| [type: 'DROPDOWN_MENU', props: DropdownMenuModalProps]
| [type: 'EDIT_ANNOUNCEMENT', props?: EditAnnouncementModalProps]
| [type: 'EDIT_BOOKMARK_FOLDER', props: EditBookmarkFolderModalProps]
| [type: 'EDIT_DOMAIN', props?: EditDomainModalProps]
| [type: 'EDIT_FEDERATION', props: EditFederationModalProps]
| [type: 'EDIT_RULE', props?: EditRuleModalProps]
| [type: 'EMBED', props: EmbedModalProps]
| [type: 'EVENT_MAP', props: EventMapModalProps]
| [type: 'EVENT_PARTICIPANTS', props: EventParticipantsModalProps]
| [type: 'FAMILIAR_FOLLOWERS', props: FamiliarFollowersModalProps]
| [type: 'FAVOURITES', props: FavouritesModalProps]
| [type: 'JOIN_EVENT', props: JoinEventModalProps]
| [type: 'LIST_ADDER', props: ListAdderModalProps]
| [type: 'LIST_EDITOR', props: ListEditorModalProps]
| [type: 'MEDIA', props: MediaModalProps]
| [type: 'MENTIONS', props: MentionsModalProps]
| [type: 'MISSING_DESCRIPTION', props: MissingDescriptionModalProps]
| [type: 'BLOCK_MUTE', props: BlockMuteModalProps]
| [type: 'REACTIONS', props: ReactionsModalProps]
| [type: 'REBLOGS', props: ReblogsModalProps]
| [type: 'REPLY_MENTIONS', props: ReplyMentionsModalProps]
| [type: 'REPORT', props: ReportModalProps]
| [type: 'SELECT_BOOKMARK_FOLDER', props: SelectBookmarkFolderModalProps]
| [type: 'SELECT_DRIVE_FILE', props: SelectDriveFileModalProps]
| [type: 'TEXT_FIELD', props: TextFieldModalProps]
| [type: 'UNAUTHORIZED', props?: UnauthorizedModalProps];
| [type: 'DISLIKES', props: DislikesModalProps, element?: HTMLElement]
| [type: 'DROPDOWN_MENU', props: DropdownMenuModalProps, element?: HTMLElement]
| [type: 'EDIT_ANNOUNCEMENT', props?: EditAnnouncementModalProps, element?: HTMLElement]
| [type: 'EDIT_BOOKMARK_FOLDER', props: EditBookmarkFolderModalProps, element?: HTMLElement]
| [type: 'EDIT_DOMAIN', props?: EditDomainModalProps, element?: HTMLElement]
| [type: 'EDIT_FEDERATION', props: EditFederationModalProps, element?: HTMLElement]
| [type: 'EDIT_RULE', props?: EditRuleModalProps, element?: HTMLElement]
| [type: 'EMBED', props: EmbedModalProps, element?: HTMLElement]
| [type: 'EVENT_MAP', props: EventMapModalProps, element?: HTMLElement]
| [type: 'EVENT_PARTICIPANTS', props: EventParticipantsModalProps, element?: HTMLElement]
| [type: 'FAMILIAR_FOLLOWERS', props: FamiliarFollowersModalProps, element?: HTMLElement]
| [type: 'FAVOURITES', props: FavouritesModalProps, element?: HTMLElement]
| [type: 'JOIN_EVENT', props: JoinEventModalProps, element?: HTMLElement]
| [type: 'LIST_ADDER', props: ListAdderModalProps, element?: HTMLElement]
| [type: 'LIST_EDITOR', props: ListEditorModalProps, element?: HTMLElement]
| [type: 'MEDIA', props: MediaModalProps, element?: HTMLElement]
| [type: 'MENTIONS', props: MentionsModalProps, element?: HTMLElement]
| [type: 'MISSING_DESCRIPTION', props: MissingDescriptionModalProps, element?: HTMLElement]
| [type: 'BLOCK_MUTE', props: BlockMuteModalProps, element?: HTMLElement]
| [type: 'REACTIONS', props: ReactionsModalProps, element?: HTMLElement]
| [type: 'REBLOGS', props: ReblogsModalProps, element?: HTMLElement]
| [type: 'REPLY_MENTIONS', props: ReplyMentionsModalProps, element?: HTMLElement]
| [type: 'REPORT', props: ReportModalProps, element?: HTMLElement]
| [type: 'SELECT_BOOKMARK_FOLDER', props: SelectBookmarkFolderModalProps, element?: HTMLElement]
| [type: 'SELECT_DRIVE_FILE', props: SelectDriveFileModalProps, element?: HTMLElement]
| [type: 'TEXT_FIELD', props: TextFieldModalProps, element?: HTMLElement]
| [type: 'UNAUTHORIZED', props?: UnauthorizedModalProps, element?: HTMLElement];
type Modals = Array<{
type ModalType = OpenModalProps[0];
type Modal = {
modalType: ModalType;
modalProps?: Record<string, any>;
}>;
element?: HTMLElement;
};
type Modals = Array<Modal>;
type State = {
modals: Modals;
actions: {
/** Open a modal of the given type */
openModal: (...[modalType, modalProps]: OpenModalProps) => void;
openModal: (...args: OpenModalProps) => void;
/** Close the modal */
closeModal: (modalType?: ModalType, all?: boolean) => void;
closeModal: (modalType?: ModalType | null, all?: boolean) => void;
};
};
@ -100,33 +108,33 @@ const useModalsStore = create<State>()(
(set) => ({
modals: [],
actions: {
openModal: (...[modalType, modalProps]) => {
set((state: State) => {
state.modals.push({ modalType, modalProps });
openModal: (...[modalType, modalProps, element]) => {
set((state) => {
state.modals.push({ modalType, modalProps, element } as any);
});
},
closeModal: (modalType, all) => {
set((state: State) => {
set((state) => {
if (state.modals.length === 0) {
return;
}
let closedModal: Record<string, any> | undefined;
let closedModal: (typeof state.modals)[number] | undefined;
if (all) {
closedModal = state.modals[0].modalProps;
closedModal = state.modals[0];
state.modals = [];
} else if (modalType === undefined) {
closedModal = state.modals[state.modals.length - 1].modalProps;
} else if (!modalType) {
closedModal = state.modals[state.modals.length - 1];
state.modals = state.modals.slice(0, -1);
} else if (state.modals.some((modal) => modalType === modal.modalType)) {
const lastIndex = state.modals.findLastIndex(
(modal) => modalType === modal.modalType,
);
closedModal = state.modals[lastIndex].modalProps;
closedModal = state.modals[lastIndex];
state.modals = state.modals.slice(0, lastIndex);
}
if (closedModal?.element) {
const element = closedModal.element;
setTimeout(() => element.focus(), 0);
const element = closedModal?.element || closedModal?.modalProps?.element;
if (element) {
setTimeout(() => (element as HTMLElement).focus(), 0);
}
});
},