Navigation: Support quick chat picker (#6635)
This commit is contained in:
parent
184c2a88e8
commit
1140242b0e
@ -107,3 +107,4 @@ export { default as FrozenAccountModal } from '../components/modals/frozenAccoun
|
|||||||
export { default as ProfileRatingModal } from '../components/modals/profileRating/ProfileRatingModal';
|
export { default as ProfileRatingModal } from '../components/modals/profileRating/ProfileRatingModal';
|
||||||
export { default as QuickPreviewModal } from '../components/modals/quickPreview/QuickPreviewModal';
|
export { default as QuickPreviewModal } from '../components/modals/quickPreview/QuickPreviewModal';
|
||||||
export { default as StealthModeModal } from '../components/modals/storyStealthMode/StealthModeModal';
|
export { default as StealthModeModal } from '../components/modals/storyStealthMode/StealthModeModal';
|
||||||
|
export { default as QuickChatPickerModal } from '../components/modals/quickChatPicker/QuickChatPickerModal';
|
||||||
|
|||||||
@ -67,7 +67,9 @@
|
|||||||
.picker-list {
|
.picker-list {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding-block: 0.125rem;
|
||||||
padding-inline: 0.5rem;
|
padding-inline: 0.5rem;
|
||||||
|
|
||||||
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
.root {
|
.root {
|
||||||
|
scroll-margin-block: 0.25rem;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -9,7 +11,7 @@
|
|||||||
|
|
||||||
min-height: 2.5rem;
|
min-height: 2.5rem;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
border-radius: var(--border-radius-default);
|
border-radius: 1.25rem;
|
||||||
|
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
@ -40,10 +42,14 @@
|
|||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
&:hover,
|
&:hover {
|
||||||
&:focus-visible {
|
|
||||||
background-color: var(--color-item-hover);
|
background-color: var(--color-item-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
z-index: 1;
|
||||||
|
outline: 2px solid var(--color-borders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import { resolveTransitionName } from '../../util/resolveTransitionName';
|
import { resolveTransitionName } from '../../util/resolveTransitionName';
|
||||||
import { captureControlledSwipe } from '../../util/swipeController';
|
import { captureControlledSwipe } from '../../util/swipeController';
|
||||||
|
import { isComposerHasSelection } from '../middle/composer/helpers/selection';
|
||||||
|
|
||||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||||
@ -110,6 +111,7 @@ function LeftColumn({
|
|||||||
openChat,
|
openChat,
|
||||||
openLeftColumnContent,
|
openLeftColumnContent,
|
||||||
openSettingsScreen,
|
openSettingsScreen,
|
||||||
|
openQuickChatPicker,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [contactsFilter, setContactsFilter] = useState<string>('');
|
const [contactsFilter, setContactsFilter] = useState<string>('');
|
||||||
@ -436,8 +438,16 @@ function LeftColumn({
|
|||||||
openLeftColumnContent({ contentKey: LeftColumnContent.Settings });
|
openLeftColumnContent({ contentKey: LeftColumnContent.Settings });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleQuickChatPicker = useLastCallback((e: KeyboardEvent) => {
|
||||||
|
if (isComposerHasSelection()) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
openQuickChatPicker();
|
||||||
|
});
|
||||||
|
|
||||||
useHotkeys(useMemo(() => ({
|
useHotkeys(useMemo(() => ({
|
||||||
'Mod+Shift+F': handleHotkeySearch,
|
'Mod+Shift+F': handleHotkeySearch,
|
||||||
|
'Mod+K': handleQuickChatPicker,
|
||||||
// https://support.mozilla.org/en-US/kb/take-screenshots-firefox
|
// https://support.mozilla.org/en-US/kb/take-screenshots-firefox
|
||||||
...(!IS_FIREFOX && {
|
...(!IS_FIREFOX && {
|
||||||
'Mod+Shift+S': handleHotkeySavedMessages,
|
'Mod+Shift+S': handleHotkeySavedMessages,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID } from '../../../../config';
|
||||||
|
|
||||||
const MAX_NESTING_PARENTS = 5;
|
const MAX_NESTING_PARENTS = 5;
|
||||||
|
|
||||||
export function isSelectionInsideInput(selectionRange: Range, inputId: string) {
|
export function isSelectionInsideInput(selectionRange: Range, inputId: string) {
|
||||||
@ -11,3 +13,14 @@ export function isSelectionInsideInput(selectionRange: Range, inputId: string) {
|
|||||||
|
|
||||||
return Boolean(parentNode && parentNode.id === inputId);
|
return Boolean(parentNode && parentNode.id === inputId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isComposerHasSelection() {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
const isComposerFocused = activeElement?.id === EDITABLE_INPUT_ID
|
||||||
|
|| activeElement?.id === EDITABLE_INPUT_MODAL_ID;
|
||||||
|
|
||||||
|
if (!isComposerFocused) return false;
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
return Boolean(selection && !selection.isCollapsed);
|
||||||
|
}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ import PasskeyModal from './passkey/PasskeyModal.async';
|
|||||||
import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
|
import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
|
||||||
import PriceConfirmModal from './priceConfirm/PriceConfirmModal.async';
|
import PriceConfirmModal from './priceConfirm/PriceConfirmModal.async';
|
||||||
import ProfileRatingModal from './profileRating/ProfileRatingModal.async';
|
import ProfileRatingModal from './profileRating/ProfileRatingModal.async';
|
||||||
|
import QuickChatPickerModal from './quickChatPicker/QuickChatPickerModal.async';
|
||||||
import QuickPreviewModal from './quickPreview/QuickPreviewModal.async';
|
import QuickPreviewModal from './quickPreview/QuickPreviewModal.async';
|
||||||
import ReportAdModal from './reportAd/ReportAdModal.async';
|
import ReportAdModal from './reportAd/ReportAdModal.async';
|
||||||
import ReportModal from './reportModal/ReportModal.async';
|
import ReportModal from './reportModal/ReportModal.async';
|
||||||
@ -126,7 +127,8 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'quickPreview' |
|
'quickPreview' |
|
||||||
'storyStealthModal' |
|
'storyStealthModal' |
|
||||||
'isPasskeyModalOpen' |
|
'isPasskeyModalOpen' |
|
||||||
'birthdaySetupModal'
|
'birthdaySetupModal' |
|
||||||
|
'isQuickChatPickerOpen'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -201,6 +203,7 @@ const MODALS: ModalRegistry = {
|
|||||||
storyStealthModal: StealthModeModal,
|
storyStealthModal: StealthModeModal,
|
||||||
isPasskeyModalOpen: PasskeyModal,
|
isPasskeyModalOpen: PasskeyModal,
|
||||||
birthdaySetupModal: BirthdaySetupModal,
|
birthdaySetupModal: BirthdaySetupModal,
|
||||||
|
isQuickChatPickerOpen: QuickChatPickerModal,
|
||||||
};
|
};
|
||||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { OwnProps } from './QuickChatPickerModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const QuickChatPickerModalAsync = (props: OwnProps) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const QuickChatPickerModal = useModuleLoader(Bundles.Extra, 'QuickChatPickerModal', !modal);
|
||||||
|
|
||||||
|
return QuickChatPickerModal ? <QuickChatPickerModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuickChatPickerModalAsync;
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import RecipientPicker from '../../common/RecipientPicker';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const QuickChatPickerModal = ({
|
||||||
|
modal,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const { closeQuickChatPicker, openChat } = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
|
const handleSelectRecipient = useLastCallback((peerId: string) => {
|
||||||
|
openChat({ id: peerId });
|
||||||
|
closeQuickChatPicker();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecipientPicker
|
||||||
|
isOpen={isOpen}
|
||||||
|
searchPlaceholder={lang('Search')}
|
||||||
|
onSelectRecipient={handleSelectRecipient}
|
||||||
|
onClose={closeQuickChatPicker}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(QuickChatPickerModal);
|
||||||
@ -951,3 +951,13 @@ addCallback((global: GlobalState) => {
|
|||||||
prevIsScreenLocked = global.passcode.isScreenLocked;
|
prevIsScreenLocked = global.passcode.isScreenLocked;
|
||||||
prevBlurredTabsCount = blurredTabsCount;
|
prevBlurredTabsCount = blurredTabsCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openQuickChatPicker', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
isQuickChatPickerOpen: true,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addTabStateResetterAction('closeQuickChatPicker', 'isQuickChatPickerOpen');
|
||||||
|
|||||||
@ -2556,6 +2556,9 @@ export interface ActionPayloads {
|
|||||||
openGiftRecipientPicker: WithTabId | undefined;
|
openGiftRecipientPicker: WithTabId | undefined;
|
||||||
closeGiftRecipientPicker: WithTabId | undefined;
|
closeGiftRecipientPicker: WithTabId | undefined;
|
||||||
|
|
||||||
|
openQuickChatPicker: WithTabId | undefined;
|
||||||
|
closeQuickChatPicker: WithTabId | undefined;
|
||||||
|
|
||||||
openWebAppsCloseConfirmationModal: WithTabId | undefined;
|
openWebAppsCloseConfirmationModal: WithTabId | undefined;
|
||||||
|
|
||||||
closeWebAppsCloseConfirmationModal: ({
|
closeWebAppsCloseConfirmationModal: ({
|
||||||
|
|||||||
@ -681,6 +681,8 @@ export type TabState = {
|
|||||||
|
|
||||||
isGiftRecipientPickerOpen?: boolean;
|
isGiftRecipientPickerOpen?: boolean;
|
||||||
|
|
||||||
|
isQuickChatPickerOpen?: boolean;
|
||||||
|
|
||||||
isFrozenAccountModalOpen?: boolean;
|
isFrozenAccountModalOpen?: boolean;
|
||||||
|
|
||||||
starsGiftingPickerModal?: {
|
starsGiftingPickerModal?: {
|
||||||
|
|||||||
@ -41,6 +41,8 @@ const useKeyboardListNavigation = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const focusedElement = document.activeElement;
|
const focusedElement = document.activeElement;
|
||||||
const elementChildren = Array.from(itemSelector ? element.querySelectorAll(itemSelector) : element.children);
|
const elementChildren = Array.from(itemSelector ? element.querySelectorAll(itemSelector) : element.children);
|
||||||
|
|
||||||
@ -59,7 +61,8 @@ const useKeyboardListNavigation = (
|
|||||||
const item = elementChildren[newIndex] as HTMLElement;
|
const item = elementChildren[newIndex] as HTMLElement;
|
||||||
if (item) {
|
if (item) {
|
||||||
setFocusedIndex(newIndex);
|
setFocusedIndex(newIndex);
|
||||||
item.focus();
|
item.focus({ preventScroll: true });
|
||||||
|
item.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user