Merge pull request #6664 from matrix-org/palid/fix/difficult-to-grab-scrollbar

Fix difficult to grab scrollbar
This commit is contained in:
Dariusz Niemczyk 2021-08-24 15:09:46 +02:00 committed by GitHub
commit 168fe20559
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 32 deletions

View file

@ -32,6 +32,10 @@ $roomListCollapsedWidth: 68px;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
&[data-collapsed] {
max-width: $roomListCollapsedWidth;
}
} }
} }
@ -40,12 +44,12 @@ $roomListCollapsedWidth: 68px;
.mx_LeftPanel { .mx_LeftPanel {
background-color: $roomlist-bg-color; background-color: $roomlist-bg-color;
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
min-width: 206px;
// Create a row-based flexbox for the GroupFilterPanel and the room list // Create a row-based flexbox for the GroupFilterPanel and the room list
display: flex; display: flex;
contain: content; contain: content;
position: relative; position: relative;
flex-grow: 1;
// Note: The 'room list' in this context is actually everything that isn't the tag // Note: The 'room list' in this context is actually everything that isn't the tag
// panel, such as the menu options, breadcrumbs, filtering, etc // panel, such as the menu options, breadcrumbs, filtering, etc
@ -178,6 +182,8 @@ $roomListCollapsedWidth: 68px;
} }
.mx_LeftPanel_roomListWrapper { .mx_LeftPanel_roomListWrapper {
// Make the y-scrollbar more responsive
padding-right: 2px;
overflow: hidden; overflow: hidden;
margin-top: 10px; // so we're not up against the search/filter margin-top: 10px; // so we're not up against the search/filter
flex: 1 0 0; // needed in Safari to properly set flex-basis flex: 1 0 0; // needed in Safari to properly set flex-basis
@ -199,6 +205,7 @@ $roomListCollapsedWidth: 68px;
// These styles override the defaults for the minimized (66px) layout // These styles override the defaults for the minimized (66px) layout
&.mx_LeftPanel_minimized { &.mx_LeftPanel_minimized {
flex-grow: 0;
min-width: unset; min-width: unset;
width: unset !important; width: unset !important;

View file

@ -151,7 +151,8 @@ class LoggedInView extends React.Component<IProps, IState> {
private dispatcherRef: string; private dispatcherRef: string;
protected readonly _matrixClient: MatrixClient; protected readonly _matrixClient: MatrixClient;
protected readonly _roomView: React.RefObject<any>; protected readonly _roomView: React.RefObject<any>;
protected readonly _resizeContainer: React.RefObject<ResizeHandle>; protected readonly _resizeContainer: React.RefObject<HTMLDivElement>;
protected readonly resizeHandler: React.RefObject<HTMLDivElement>;
protected compactLayoutWatcherRef: string; protected compactLayoutWatcherRef: string;
protected backgroundImageWatcherRef: string; protected backgroundImageWatcherRef: string;
protected resizer: Resizer; protected resizer: Resizer;
@ -176,6 +177,7 @@ class LoggedInView extends React.Component<IProps, IState> {
this._roomView = React.createRef(); this._roomView = React.createRef();
this._resizeContainer = React.createRef(); this._resizeContainer = React.createRef();
this.resizeHandler = React.createRef();
} }
componentDidMount() { componentDidMount() {
@ -280,6 +282,7 @@ class LoggedInView extends React.Component<IProps, IState> {
isItemCollapsed: domNode => { isItemCollapsed: domNode => {
return domNode.classList.contains("mx_LeftPanel_minimized"); return domNode.classList.contains("mx_LeftPanel_minimized");
}, },
handler: this.resizeHandler.current,
}; };
const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig); const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig);
resizer.setClassNames({ resizer.setClassNames({
@ -681,16 +684,17 @@ class LoggedInView extends React.Component<IProps, IState> {
backgroundImage={this.state.backgroundImage} backgroundImage={this.state.backgroundImage}
/> />
<div <div
ref={this._resizeContainer}
className="mx_LeftPanel_wrapper--user" className="mx_LeftPanel_wrapper--user"
ref={this._resizeContainer}
data-collapsed={this.props.collapseLhs ? true : undefined}
> >
<LeftPanel <LeftPanel
isMinimized={this.props.collapseLhs || false} isMinimized={this.props.collapseLhs || false}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
/> />
<ResizeHandle id="lp-resizer" />
</div> </div>
</div> </div>
<ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />
<div className="mx_RoomView_wrapper"> <div className="mx_RoomView_wrapper">
{ pageElement } { pageElement }
</div> </div>

View file

@ -1,27 +1,27 @@
import React from 'react'; // eslint-disable-line no-unused-vars import React from 'react'; // eslint-disable-line no-unused-vars
import PropTypes from 'prop-types';
//see src/resizer for the actual resizing code, this is just the DOM for the resize handle //see src/resizer for the actual resizing code, this is just the DOM for the resize handle
const ResizeHandle = (props) => { interface IResizeHandleProps {
vertical?: boolean;
reverse?: boolean;
id?: string;
passRef?: React.RefObject<HTMLDivElement>;
}
const ResizeHandle: React.FC<IResizeHandleProps> = ({ vertical, reverse, id, passRef }) => {
const classNames = ['mx_ResizeHandle']; const classNames = ['mx_ResizeHandle'];
if (props.vertical) { if (vertical) {
classNames.push('mx_ResizeHandle_vertical'); classNames.push('mx_ResizeHandle_vertical');
} else { } else {
classNames.push('mx_ResizeHandle_horizontal'); classNames.push('mx_ResizeHandle_horizontal');
} }
if (props.reverse) { if (reverse) {
classNames.push('mx_ResizeHandle_reverse'); classNames.push('mx_ResizeHandle_reverse');
} }
return ( return (
<div className={classNames.join(' ')} data-id={props.id}><div /></div> <div ref={passRef} className={classNames.join(' ')} data-id={id}><div /></div>
); );
}; };
ResizeHandle.propTypes = {
vertical: PropTypes.bool,
reverse: PropTypes.bool,
id: PropTypes.string,
};
export default ResizeHandle; export default ResizeHandle;

View file

@ -40,8 +40,13 @@ class CollapseItem extends ResizeItem<ICollapseConfig> {
} }
export default class CollapseDistributor extends FixedDistributor<ICollapseConfig, CollapseItem> { export default class CollapseDistributor extends FixedDistributor<ICollapseConfig, CollapseItem> {
static createItem(resizeHandle: HTMLDivElement, resizer: Resizer<ICollapseConfig>, sizer: Sizer) { static createItem(
return new CollapseItem(resizeHandle, resizer, sizer); resizeHandle: HTMLDivElement,
resizer: Resizer<ICollapseConfig>,
sizer: Sizer,
container?: HTMLElement,
): CollapseItem {
return new CollapseItem(resizeHandle, resizer, sizer, container);
} }
private readonly toggleSize: number; private readonly toggleSize: number;
@ -55,12 +60,9 @@ export default class CollapseDistributor extends FixedDistributor<ICollapseConfi
public resize(newSize: number) { public resize(newSize: number) {
const isCollapsedSize = newSize < this.toggleSize; const isCollapsedSize = newSize < this.toggleSize;
if (isCollapsedSize && !this.isCollapsed) { if (isCollapsedSize !== this.isCollapsed) {
this.isCollapsed = true; this.isCollapsed = isCollapsedSize;
this.item.notifyCollapsed(true); this.item.notifyCollapsed(isCollapsedSize);
} else if (!isCollapsedSize && this.isCollapsed) {
this.item.notifyCollapsed(false);
this.isCollapsed = false;
} }
if (!isCollapsedSize) { if (!isCollapsedSize) {
super.resize(newSize); super.resize(newSize);

View file

@ -26,15 +26,20 @@ export default class ResizeItem<C extends IConfig = IConfig> {
handle: HTMLElement, handle: HTMLElement,
public readonly resizer: Resizer<C>, public readonly resizer: Resizer<C>,
public readonly sizer: Sizer, public readonly sizer: Sizer,
public readonly container?: HTMLElement,
) { ) {
this.reverse = resizer.isReverseResizeHandle(handle); this.reverse = resizer.isReverseResizeHandle(handle);
if (container) {
this.domNode = <HTMLElement>(container);
} else {
this.domNode = <HTMLElement>(this.reverse ? handle.nextElementSibling : handle.previousElementSibling); this.domNode = <HTMLElement>(this.reverse ? handle.nextElementSibling : handle.previousElementSibling);
}
this.id = handle.getAttribute("data-id"); this.id = handle.getAttribute("data-id");
} }
private copyWith(handle: HTMLElement, resizer: Resizer, sizer: Sizer) { private copyWith(handle: HTMLElement, resizer: Resizer, sizer: Sizer, container?: HTMLElement) {
const Ctor = this.constructor as typeof ResizeItem; const Ctor = this.constructor as typeof ResizeItem;
return new Ctor(handle, resizer, sizer); return new Ctor(handle, resizer, sizer, container);
} }
private advance(forwards: boolean) { private advance(forwards: boolean) {

View file

@ -35,6 +35,7 @@ export interface IConfig {
onResizeStart?(): void; onResizeStart?(): void;
onResizeStop?(): void; onResizeStop?(): void;
onResized?(size: number, id: string, element: HTMLElement): void; onResized?(size: number, id: string, element: HTMLElement): void;
handler?: HTMLDivElement;
} }
export default class Resizer<C extends IConfig = IConfig> { export default class Resizer<C extends IConfig = IConfig> {
@ -46,8 +47,17 @@ export default class Resizer<C extends IConfig = IConfig> {
public container: HTMLElement, public container: HTMLElement,
private readonly distributorCtor: { private readonly distributorCtor: {
new(item: ResizeItem): FixedDistributor<C, any>; new(item: ResizeItem): FixedDistributor<C, any>;
createItem(resizeHandle: HTMLDivElement, resizer: Resizer, sizer: Sizer): ResizeItem; createItem(
createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer; resizeHandle: HTMLDivElement,
resizer: Resizer,
sizer: Sizer,
container: HTMLElement
): ResizeItem;
createSizer(
containerElement: HTMLElement,
vertical: boolean,
reverse: boolean
): Sizer;
}, },
public readonly config?: C, public readonly config?: C,
) { ) {
@ -68,12 +78,14 @@ export default class Resizer<C extends IConfig = IConfig> {
} }
public attach() { public attach() {
this.container.addEventListener("mousedown", this.onMouseDown, false); const attachment = this?.config?.handler.parentElement ?? this.container;
attachment.addEventListener("mousedown", this.onMouseDown, false);
window.addEventListener("resize", this.onResize); window.addEventListener("resize", this.onResize);
} }
public detach() { public detach() {
this.container.removeEventListener("mousedown", this.onMouseDown, false); const attachment = this?.config?.handler.parentElement ?? this.container;
attachment.removeEventListener("mousedown", this.onMouseDown, false);
window.removeEventListener("resize", this.onResize); window.removeEventListener("resize", this.onResize);
} }
@ -113,7 +125,8 @@ export default class Resizer<C extends IConfig = IConfig> {
// use closest in case the resize handle contains // use closest in case the resize handle contains
// child dom nodes that can be the target // child dom nodes that can be the target
const resizeHandle = event.target && (<HTMLDivElement>event.target).closest(`.${this.classNames.handle}`); const resizeHandle = event.target && (<HTMLDivElement>event.target).closest(`.${this.classNames.handle}`);
if (!resizeHandle || resizeHandle.parentElement !== this.container) { const hasHandler = this?.config?.handler;
if (!resizeHandle || (!hasHandler && resizeHandle.parentElement !== this.container)) {
return; return;
} }
// prevent starting a drag operation // prevent starting a drag operation
@ -174,13 +187,17 @@ export default class Resizer<C extends IConfig = IConfig> {
const vertical = resizeHandle.classList.contains(this.classNames.vertical); const vertical = resizeHandle.classList.contains(this.classNames.vertical);
const reverse = this.isReverseResizeHandle(resizeHandle); const reverse = this.isReverseResizeHandle(resizeHandle);
const Distributor = this.distributorCtor; const Distributor = this.distributorCtor;
const useItemContainer = this.config && this.config.handler ? this.container : undefined;
const sizer = Distributor.createSizer(this.container, vertical, reverse); const sizer = Distributor.createSizer(this.container, vertical, reverse);
const item = Distributor.createItem(resizeHandle, this, sizer); const item = Distributor.createItem(resizeHandle, this, sizer, useItemContainer);
const distributor = new Distributor(item); const distributor = new Distributor(item);
return { sizer, distributor }; return { sizer, distributor };
} }
private getResizeHandles() { private getResizeHandles() {
if (this?.config?.handler) {
return [this.config.handler];
}
if (!this.container.children) return []; if (!this.container.children) return [];
return Array.from(this.container.querySelectorAll(`.${this.classNames.handle}`)) as HTMLElement[]; return Array.from(this.container.querySelectorAll(`.${this.classNames.handle}`)) as HTMLElement[];
} }