Left Column: Support resizing; Better support for large displays (#1484)
This commit is contained in:
parent
03ea0cb152
commit
5180eb0d2e
@ -1 +1 @@
|
||||
> 2%, last 2 edge versions
|
||||
> 2%, last 2 edge versions, iOS >= 13.4, firefox >= 68, firefoxandroid >= 68, last 2 safari major versions
|
||||
|
||||
51
package-lock.json
generated
51
package-lock.json
generated
@ -5329,17 +5329,44 @@
|
||||
"dev": true
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.3.1.tgz",
|
||||
"integrity": "sha512-L8AmtKzdiRyYg7BUXJTzigmhbQRCXFKz6SA1Lqo0+AR2FBbQ4aTAPFSDlOutnFkjhiz8my4agGXog1xlMjPJ6A==",
|
||||
"version": "10.3.7",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.3.7.tgz",
|
||||
"integrity": "sha512-EmGpu0nnQVmMhX8ROoJ7Mx8mKYPlcUHuxkwrRYEYMz85lu7H09v8w6R1P0JPdn/hKU32GjpLBFEOuIlDWCRWvg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browserslist": "^4.16.6",
|
||||
"caniuse-lite": "^1.0.30001243",
|
||||
"colorette": "^1.2.2",
|
||||
"browserslist": "^4.17.3",
|
||||
"caniuse-lite": "^1.0.30001264",
|
||||
"fraction.js": "^4.1.1",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^0.2.1",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"browserslist": {
|
||||
"version": "4.17.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz",
|
||||
"integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001264",
|
||||
"electron-to-chromium": "^1.3.857",
|
||||
"escalade": "^3.1.1",
|
||||
"node-releases": "^1.1.77",
|
||||
"picocolors": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.864",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.864.tgz",
|
||||
"integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.77",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz",
|
||||
"integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"aws-sign2": {
|
||||
@ -6085,9 +6112,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001249",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001249.tgz",
|
||||
"integrity": "sha512-vcX4U8lwVXPdqzPWi6cAJ3FnQaqXbBqy/GZseKNQzRj37J7qZdGcBtxq/QLFNLLlfsoXLUdHw8Iwenri86Tagw==",
|
||||
"version": "1.0.30001265",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
|
||||
"integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
@ -15697,6 +15724,12 @@
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
||||
"dev": true
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
|
||||
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz",
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.1",
|
||||
"@typescript-eslint/parser": "^4.29.1",
|
||||
"@webpack-cli/serve": "^1.5.1",
|
||||
"autoprefixer": "^10.3.1",
|
||||
"autoprefixer": "^10.3.7",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"browserlist": "^1.0.1",
|
||||
|
||||
@ -11,16 +11,33 @@
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 1680px;
|
||||
max-width: 1920px;
|
||||
height: 100%;
|
||||
z-index: var(--z-ui-loader-mask);
|
||||
display: flex;
|
||||
|
||||
|
||||
@media (min-width: 600px) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 100%;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
background: var(--color-background);
|
||||
min-width: 15.5rem;
|
||||
max-width: 26.5rem;
|
||||
min-width: 18rem;
|
||||
width: 26.5rem;
|
||||
max-width: 33vw;
|
||||
|
||||
@media (min-width: 926px) {
|
||||
width: 25vw;
|
||||
max-width: 40vw;
|
||||
}
|
||||
|
||||
@media (min-width: 1276px) {
|
||||
max-width: 33vw;
|
||||
}
|
||||
|
||||
@media (min-width: 1680px) {
|
||||
border-left: 1px solid var(--color-borders);
|
||||
|
||||
@ -32,6 +32,7 @@ type StateProps = Pick<GlobalState, 'uiReadyState' | 'shouldSkipHistoryAnimation
|
||||
hasCustomBackground?: boolean;
|
||||
hasCustomBackgroundColor: boolean;
|
||||
isRightColumnShown?: boolean;
|
||||
leftColumnWidth?: number;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'setIsUiReady'>;
|
||||
@ -83,6 +84,7 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
hasCustomBackgroundColor,
|
||||
isRightColumnShown,
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
setIsUiReady,
|
||||
}) => {
|
||||
const [isReady, markReady] = useFlag();
|
||||
@ -131,7 +133,11 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className={buildClassName('mask', transitionClassNames)}>
|
||||
{page === 'main' ? (
|
||||
<>
|
||||
<div className="left" />
|
||||
<div
|
||||
className="left"
|
||||
// @ts-ignore teact feature
|
||||
style={leftColumnWidth ? `width: ${leftColumnWidth}px` : undefined}
|
||||
/>
|
||||
<div
|
||||
className={buildClassName(
|
||||
'middle',
|
||||
@ -162,6 +168,7 @@ export default withGlobal<OwnProps>(
|
||||
hasCustomBackground: Boolean(background),
|
||||
hasCustomBackgroundColor: Boolean(backgroundColor),
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
leftColumnWidth: global.leftColumnWidth,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['setIsUiReady']),
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
#LeftColumn {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#NewChat {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect, useState,
|
||||
FC, memo, useCallback, useEffect, useRef, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
@ -10,6 +10,7 @@ import { LAYERS_ANIMATION_NAME } from '../../util/environment';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||
import { useResize } from '../../hooks/useResize';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import LeftMain from './main/LeftMain';
|
||||
@ -24,11 +25,12 @@ type StateProps = {
|
||||
searchDate?: number;
|
||||
activeChatFolder: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
leftColumnWidth?: number;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'setGlobalSearchQuery' | 'setGlobalSearchChatId' | 'resetChatCreation' | 'setGlobalSearchDate' |
|
||||
'loadPasswordInfo' | 'clearTwoFaError'
|
||||
'loadPasswordInfo' | 'clearTwoFaError' | 'setLeftColumnWidth' | 'resetLeftColumnWidth'
|
||||
)>;
|
||||
|
||||
enum ContentType {
|
||||
@ -50,13 +52,18 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
searchDate,
|
||||
activeChatFolder,
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
setGlobalSearchQuery,
|
||||
setGlobalSearchChatId,
|
||||
resetChatCreation,
|
||||
setGlobalSearchDate,
|
||||
loadPasswordInfo,
|
||||
clearTwoFaError,
|
||||
setLeftColumnWidth,
|
||||
resetLeftColumnWidth,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const resizeRef = useRef<HTMLDivElement>(null);
|
||||
const [content, setContent] = useState<LeftColumnContent>(LeftColumnContent.ChatList);
|
||||
const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main);
|
||||
const [contactsFilter, setContactsFilter] = useState<string>('');
|
||||
@ -257,81 +264,95 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [clearTwoFaError, loadPasswordInfo, settingsScreen]);
|
||||
|
||||
const {
|
||||
initResize, resetResize, handleMouseUp,
|
||||
} = useResize(resizeRef, setLeftColumnWidth, resetLeftColumnWidth, leftColumnWidth);
|
||||
|
||||
const handleSettingsScreenSelect = (screen: SettingsScreens) => {
|
||||
setContent(LeftColumnContent.Settings);
|
||||
setSettingsScreen(screen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition
|
||||
<div
|
||||
id="LeftColumn"
|
||||
name={shouldSkipHistoryAnimations ? 'none' : LAYERS_ANIMATION_NAME}
|
||||
renderCount={RENDER_COUNT}
|
||||
activeKey={contentType}
|
||||
shouldCleanup
|
||||
cleanupExceptionKey={ContentType.Main}
|
||||
ref={resizeRef}
|
||||
>
|
||||
{(isActive) => {
|
||||
switch (contentType) {
|
||||
case ContentType.Archived:
|
||||
return (
|
||||
<ArchivedChats
|
||||
isActive={isActive}
|
||||
onReset={handleReset}
|
||||
onContentChange={setContent}
|
||||
/>
|
||||
);
|
||||
case ContentType.Settings:
|
||||
return (
|
||||
<Settings
|
||||
isActive={isActive}
|
||||
currentScreen={settingsScreen}
|
||||
foldersState={foldersState}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
);
|
||||
case ContentType.NewChannel:
|
||||
return (
|
||||
<NewChat
|
||||
key={lastResetTime}
|
||||
isActive={isActive}
|
||||
isChannel
|
||||
content={content}
|
||||
onContentChange={setContent}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case ContentType.NewGroup:
|
||||
return (
|
||||
<NewChat
|
||||
key={lastResetTime}
|
||||
isActive={isActive}
|
||||
content={content}
|
||||
onContentChange={setContent}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<LeftMain
|
||||
content={content}
|
||||
searchQuery={searchQuery}
|
||||
searchDate={searchDate}
|
||||
contactsFilter={contactsFilter}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onContentChange={setContent}
|
||||
onSearchQuery={handleSearchQuery}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
</Transition>
|
||||
<Transition
|
||||
name={shouldSkipHistoryAnimations ? 'none' : LAYERS_ANIMATION_NAME}
|
||||
renderCount={RENDER_COUNT}
|
||||
activeKey={contentType}
|
||||
shouldCleanup
|
||||
cleanupExceptionKey={ContentType.Main}
|
||||
>
|
||||
{(isActive) => {
|
||||
switch (contentType) {
|
||||
case ContentType.Archived:
|
||||
return (
|
||||
<ArchivedChats
|
||||
isActive={isActive}
|
||||
onReset={handleReset}
|
||||
onContentChange={setContent}
|
||||
/>
|
||||
);
|
||||
case ContentType.Settings:
|
||||
return (
|
||||
<Settings
|
||||
isActive={isActive}
|
||||
currentScreen={settingsScreen}
|
||||
foldersState={foldersState}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
);
|
||||
case ContentType.NewChannel:
|
||||
return (
|
||||
<NewChat
|
||||
key={lastResetTime}
|
||||
isActive={isActive}
|
||||
isChannel
|
||||
content={content}
|
||||
onContentChange={setContent}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case ContentType.NewGroup:
|
||||
return (
|
||||
<NewChat
|
||||
key={lastResetTime}
|
||||
isActive={isActive}
|
||||
content={content}
|
||||
onContentChange={setContent}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<LeftMain
|
||||
content={content}
|
||||
searchQuery={searchQuery}
|
||||
searchDate={searchDate}
|
||||
contactsFilter={contactsFilter}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onContentChange={setContent}
|
||||
onSearchQuery={handleSearchQuery}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
</Transition>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={initResize}
|
||||
onMouseUp={handleMouseUp}
|
||||
onDoubleClick={resetResize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -346,13 +367,14 @@ export default memo(withGlobal(
|
||||
activeChatFolder,
|
||||
},
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
} = global;
|
||||
return {
|
||||
searchQuery: query, searchDate: date, activeChatFolder, shouldSkipHistoryAnimations,
|
||||
searchQuery: query, searchDate: date, activeChatFolder, shouldSkipHistoryAnimations, leftColumnWidth,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'setGlobalSearchQuery', 'setGlobalSearchChatId', 'resetChatCreation', 'setGlobalSearchDate',
|
||||
'loadPasswordInfo', 'clearTwoFaError',
|
||||
'loadPasswordInfo', 'clearTwoFaError', 'setLeftColumnWidth', 'resetLeftColumnWidth',
|
||||
]),
|
||||
)(LeftColumn));
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
#Main {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
@ -11,13 +10,25 @@
|
||||
@media (max-width: 600px) {
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
}
|
||||
|
||||
@media (min-width: 926px) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#LeftColumn {
|
||||
flex: 1;
|
||||
min-width: 18rem;
|
||||
min-width: 12rem;
|
||||
width: 25vw;
|
||||
max-width: 26.5rem;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
@ -27,8 +38,12 @@
|
||||
border-left: 1px solid var(--color-borders);
|
||||
}
|
||||
|
||||
@media (max-width: 1275px) {
|
||||
flex: 2;
|
||||
@media (min-width: 926px) {
|
||||
max-width: 40vw;
|
||||
}
|
||||
|
||||
@media (min-width: 1276px) {
|
||||
max-width: 33vw;
|
||||
}
|
||||
|
||||
@media (max-width: 925px) {
|
||||
@ -36,7 +51,7 @@
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
width: 26.5rem;
|
||||
width: 26.5rem !important;
|
||||
|
||||
transform: translate3d(-5rem, 0, 0);
|
||||
transition: transform var(--layer-transition);
|
||||
@ -92,7 +107,7 @@
|
||||
|
||||
@media (max-width: 600px) {
|
||||
max-width: none;
|
||||
width: 100vw;
|
||||
width: 100vw !important;
|
||||
transform: translate3d(-20vw, 0, 0);
|
||||
|
||||
@supports (left: env(safe-area-inset-left)) {
|
||||
@ -125,21 +140,10 @@
|
||||
}
|
||||
|
||||
#MiddleColumn {
|
||||
flex: 3;
|
||||
border-left: 1px solid var(--color-borders);
|
||||
max-width: 75vw;
|
||||
|
||||
@media (max-width: 1275px) {
|
||||
max-width: calc(100vw - 26.5rem);
|
||||
}
|
||||
|
||||
@media (max-width: 66.25rem) {
|
||||
max-width: 60vw;
|
||||
}
|
||||
|
||||
@media (min-width: 1680px) {
|
||||
border-right: 1px solid var(--color-borders);
|
||||
max-width: calc(1680px - 26.5rem);
|
||||
}
|
||||
|
||||
@media (max-width: 925px) {
|
||||
|
||||
@ -76,6 +76,7 @@
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 0;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
overflow: hidden;
|
||||
|
||||
@ -76,6 +76,10 @@
|
||||
.ListItem-button {
|
||||
cursor: pointer;
|
||||
|
||||
body.cursor-ew-resize & {
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover, &:focus {
|
||||
--background-color: var(--color-chat-hover);
|
||||
|
||||
@ -164,6 +164,7 @@ function updateCache() {
|
||||
'recentEmojis',
|
||||
'push',
|
||||
'shouldShowContextMenuHint',
|
||||
'leftColumnWidth',
|
||||
]),
|
||||
isChatInfoShown: reduceShowChatInfo(global),
|
||||
users: reduceUsers(global),
|
||||
|
||||
@ -83,6 +83,7 @@ export type GlobalState = {
|
||||
currentUserId?: number;
|
||||
lastSyncTime?: number;
|
||||
serverTimeOffset: number;
|
||||
leftColumnWidth?: number;
|
||||
|
||||
// TODO Move to `auth`.
|
||||
isLoggingOut?: boolean;
|
||||
@ -447,7 +448,7 @@ export type ActionTypes = (
|
||||
// ui
|
||||
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
|
||||
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' |
|
||||
'setNewChatMembersDialogState' | 'disableHistoryAnimations' |
|
||||
'setNewChatMembersDialogState' | 'disableHistoryAnimations' | 'setLeftColumnWidth' | 'resetLeftColumnWidth' |
|
||||
// auth
|
||||
'setAuthPhoneNumber' | 'setAuthCode' | 'setAuthPassword' | 'signUp' | 'returnToAuthPhoneNumber' | 'signOut' |
|
||||
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' |
|
||||
|
||||
64
src/hooks/useResize.ts
Normal file
64
src/hooks/useResize.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { RefObject } from 'react';
|
||||
import { useState, useEffect } from '../lib/teact/teact';
|
||||
import useFlag from './useFlag';
|
||||
|
||||
export const useResize = (
|
||||
elementRef: RefObject<HTMLElement>,
|
||||
onResize: (width: number) => void,
|
||||
onReset: NoneToVoidFunction,
|
||||
initialWidth?: number,
|
||||
) => {
|
||||
const [isActive, markIsActive, unmarkIsActive] = useFlag();
|
||||
const [initialMouseX, setInitialMouseX] = useState<number>();
|
||||
const [initialElementWidth, setInitialElementWidth] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!elementRef.current || !initialWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
elementRef.current.style.width = `${initialWidth}px`;
|
||||
}, [elementRef, initialWidth]);
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.body.classList.remove('no-selection', 'cursor-ew-resize');
|
||||
};
|
||||
|
||||
const initResize = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
document.body.classList.add('no-selection', 'cursor-ew-resize');
|
||||
|
||||
setInitialMouseX(event.clientX);
|
||||
setInitialElementWidth(elementRef.current!.offsetWidth);
|
||||
markIsActive();
|
||||
};
|
||||
|
||||
const resetResize = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
elementRef.current!.style.width = '';
|
||||
onReset();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive) return;
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
const newWidth = Math.ceil(initialElementWidth + event.clientX - initialMouseX);
|
||||
elementRef.current!.style.width = `${newWidth}px`;
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
handleMouseUp();
|
||||
document.removeEventListener('mousemove', handleMouseMove, false);
|
||||
document.removeEventListener('mouseup', stopDrag, false);
|
||||
document.removeEventListener('blur', stopDrag, false);
|
||||
onResize(elementRef.current!.offsetWidth);
|
||||
unmarkIsActive();
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove, false);
|
||||
document.addEventListener('mouseup', stopDrag, false);
|
||||
document.addEventListener('blur', stopDrag, false);
|
||||
}, [initialElementWidth, initialMouseX, elementRef, onResize, isActive, unmarkIsActive]);
|
||||
|
||||
return { initResize, resetResize, handleMouseUp };
|
||||
};
|
||||
@ -17,6 +17,22 @@ addReducer('toggleChatInfo', (global) => {
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('setLeftColumnWidth', (global, actions, payload) => {
|
||||
const leftColumnWidth = payload;
|
||||
|
||||
return {
|
||||
...global,
|
||||
leftColumnWidth,
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('resetLeftColumnWidth', (global) => {
|
||||
return {
|
||||
...global,
|
||||
leftColumnWidth: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('toggleManagement', (global): GlobalState | undefined => {
|
||||
const { chatId } = selectCurrentMessageList(global) || {};
|
||||
|
||||
|
||||
@ -194,6 +194,7 @@ $color-user-8: #faa774;
|
||||
--z-sticky-date: 9;
|
||||
--z-register-add-avatar: 5;
|
||||
--z-media-viewer-head: 3;
|
||||
--z-resize-handle: 2;
|
||||
--z-below: -1;
|
||||
|
||||
--spinner-white-data: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEwLjggMjIuNEM2IDIxLjkgMi4xIDE4IDEuNiAxMy4yLjkgNy4xIDUuNCAxLjkgMTEuMyAxLjVjLjQgMCAuNy0uMy43LS43IDAtLjQtLjQtLjgtLjgtLjhDNC44LjQtLjIgNS45IDAgMTIuNS4yIDE4LjYgNS40IDIzLjggMTEuNSAyNGM2LjYuMiAxMi00LjggMTIuNC0xMS4yIDAtLjQtLjMtLjgtLjgtLjgtLjQgMC0uNy4zLS43LjctLjMgNS45LTUuNSAxMC40LTExLjYgOS43eiIgZmlsbD0iI2ZmZmZmZiIvPjwvc3ZnPg==);
|
||||
|
||||
@ -49,9 +49,13 @@ body.cursor-grabbing, body.cursor-grabbing * {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
|
||||
body.cursor-ew-resize {
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
max-width: 1680px;
|
||||
max-width: 1920px;
|
||||
margin: 0 auto;
|
||||
@media (max-width: 600px) {
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
@ -75,6 +79,21 @@ body.cursor-grabbing, body.cursor-grabbing * {
|
||||
-webkit-user-select: none !important;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -.1875rem;
|
||||
bottom: 0;
|
||||
width: .1875rem;
|
||||
z-index: var(--z-resize-handle);
|
||||
cursor: ew-resize;
|
||||
|
||||
@media (min-width: 926px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
See the article for more information on this visually-hidden pattern.
|
||||
https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user