mirror of
				https://github.com/vector-im/element-web.git
				synced 2025-10-25 22:31:51 +02:00 
			
		
		
		
	Merge pull request #2264 from matrix-org/bwindels/nativescrollbars
Redesign: use native auto-hiding scrollbars in room sub lists
This commit is contained in:
		
						commit
						b07514d21f
					
				| @ -1,5 +1,6 @@ | ||||
| // autogenerated by rethemendex.sh | ||||
| @import "./_common.scss"; | ||||
| @import "./structures/_AutoHideScrollbar.scss"; | ||||
| @import "./structures/_CompatibilityPage.scss"; | ||||
| @import "./structures/_ContextualMenu.scss"; | ||||
| @import "./structures/_CreateRoom.scss"; | ||||
|  | ||||
							
								
								
									
										56
									
								
								res/css/structures/_AutoHideScrollbar.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								res/css/structures/_AutoHideScrollbar.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| /* | ||||
| Copyright 2018 New Vector Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| /* | ||||
| 1. for browsers that support native overlay auto-hiding scrollbars | ||||
| */ | ||||
| .mx_AutoHideScrollbar { | ||||
|     overflow-y: auto; | ||||
|     -ms-overflow-style: -ms-autohiding-scrollbar; | ||||
| } | ||||
| /* | ||||
| 2. webkit also supports overflow:overlay where the scrollbars don't take any space | ||||
| in the layout but they don't autohide, so do that only on hover | ||||
| */ | ||||
| body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar { | ||||
|     overflow-y: hidden; | ||||
| } | ||||
| 
 | ||||
| body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar:hover { | ||||
|     overflow-y: overlay; | ||||
| } | ||||
| /* | ||||
| 3. as a last fallback, compensate for the scrollbar taking up space in the layout | ||||
| by playing with the paddings. the default below will add a right padding | ||||
| of the scrollbar width and clear that on hover. | ||||
| this won't work well on classes that also need to set their padding, | ||||
| so this needs to be overriden and adjust the padding with calc like so: | ||||
| ``` | ||||
| body.mx_scrollbar_nooverlay .componentClass.mx_AutoHideScrollbar_overflow:hover { | ||||
|     padding-right: calc(15px - var(--scrollbar-width)) !important; | ||||
| } | ||||
| ``` | ||||
| */ | ||||
| body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar { | ||||
|     box-sizing: border-box; | ||||
|     overflow-y: hidden; | ||||
|     padding-right: var(--scrollbar-width); | ||||
| } | ||||
| 
 | ||||
| body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover { | ||||
|     overflow-y: auto; | ||||
|     padding-right: 0; | ||||
| } | ||||
| @ -16,14 +16,12 @@ limitations under the License. | ||||
| 
 | ||||
| .mx_RoomSubList { | ||||
|     min-height: 31px; | ||||
|     flex: 0 0 auto; | ||||
|     flex: 0 1 auto; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .mx_RoomSubList_nonEmpty { | ||||
|     min-height: 80px; | ||||
|     flex: 1; | ||||
|     margin-bottom: 8px; | ||||
| } | ||||
| 
 | ||||
| @ -131,9 +129,18 @@ limitations under the License. | ||||
|     transform: rotateZ(-90deg); | ||||
| } | ||||
| 
 | ||||
| .mx_RoomSubList .gm-scrollbar-container { | ||||
| .mx_RoomSubList_scroll { | ||||
|     /* let rooms list grab all available space */ | ||||
|     flex: 1; | ||||
|     flex: 0 1 auto; | ||||
|     padding: 0 15px !important; | ||||
| } | ||||
| /* | ||||
| for browsers that don't support overlay scrollbars, | ||||
| subtract scrollbar width from right padding on hover when overflowing | ||||
| so the content doesn't jump when showing the scrollbars | ||||
| */ | ||||
| body.mx_scrollbar_nooverlay .mx_RoomSubList_scroll.mx_AutoHideScrollbar_overflow:hover { | ||||
|     padding-right: calc(15px - var(--scrollbar-width)) !important; | ||||
| } | ||||
| 
 | ||||
| .collapsed { | ||||
|  | ||||
| @ -15,7 +15,7 @@ limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| .mx_TopLeftMenuButton { | ||||
|     height: 52px; | ||||
|     flex: 0 0 52px; | ||||
|     border-bottom: 1px solid $panel-divider-color; | ||||
|     color: $topleftmenu-color; | ||||
|     background-color: $primary-bg-color; | ||||
|  | ||||
| @ -17,7 +17,7 @@ limitations under the License. | ||||
| 
 | ||||
| .mx_RoomList { | ||||
|     /* take up remaining space below TopLeftMenu */ | ||||
|     flex: 1; | ||||
|     flex: 1 1 auto; | ||||
|     /* use flexbox to layout sublists */ | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|  | ||||
| @ -20,7 +20,7 @@ limitations under the License. | ||||
|     align-items: center; | ||||
|     cursor: pointer; | ||||
|     height: 40px; | ||||
|     margin: 0 12px; | ||||
|     margin: 0; | ||||
|     padding: 2px 12px; | ||||
|     position: relative; | ||||
|     background-color: $secondary-accent-color; | ||||
|  | ||||
							
								
								
									
										118
									
								
								src/components/structures/AutoHideScrollbar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/components/structures/AutoHideScrollbar.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| /* | ||||
| Copyright 2018 New Vector Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from "react"; | ||||
| 
 | ||||
| // derived from code from github.com/noeldelgado/gemini-scrollbar
 | ||||
| // Copyright (c) Noel Delgado <pixelia.me@gmail.com> (pixelia.me)
 | ||||
| function getScrollbarWidth(alternativeOverflow) { | ||||
|     const div = document.createElement('div'); | ||||
|     div.style.position = 'absolute'; | ||||
|     div.style.top = '-9999px'; | ||||
|     div.style.width = '100px'; | ||||
|     div.style.height = '100px'; | ||||
|     div.style.overflow = "scroll"; | ||||
|     if (alternativeOverflow) { | ||||
|         div.style.overflow = alternativeOverflow; | ||||
|     } | ||||
|     div.style.msOverflowStyle = '-ms-autohiding-scrollbar'; | ||||
|     document.body.appendChild(div); | ||||
|     const scrollbarWidth = (div.offsetWidth - div.clientWidth); | ||||
|     document.body.removeChild(div); | ||||
|     return scrollbarWidth; | ||||
| } | ||||
| 
 | ||||
| function install() { | ||||
|     const scrollbarWidth = getScrollbarWidth(); | ||||
|     if (scrollbarWidth !== 0) { | ||||
|         const hasForcedOverlayScrollbar = getScrollbarWidth('overlay') === 0; | ||||
|         // overflow: overlay on webkit doesn't auto hide the scrollbar
 | ||||
|         if (hasForcedOverlayScrollbar) { | ||||
|             document.body.classList.add("mx_scrollbar_overlay_noautohide"); | ||||
|         } else { | ||||
|             document.body.classList.add("mx_scrollbar_nooverlay"); | ||||
|             const style = document.createElement('style'); | ||||
|             style.type = 'text/css'; | ||||
|             style.innerText = | ||||
|                 `body.mx_scrollbar_nooverlay { --scrollbar-width: ${scrollbarWidth}px; }`; | ||||
|             document.head.appendChild(style); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const installBodyClassesIfNeeded = (function() { | ||||
|     let installed = false; | ||||
|     return function() { | ||||
|         if (!installed) { | ||||
|             install(); | ||||
|             installed = true; | ||||
|         } | ||||
|     }; | ||||
| })(); | ||||
| 
 | ||||
| export default class AutoHideScrollbar extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|         this.onOverflow = this.onOverflow.bind(this); | ||||
|         this.onUnderflow = this.onUnderflow.bind(this); | ||||
|         this._collectContainerRef = this._collectContainerRef.bind(this); | ||||
|     } | ||||
| 
 | ||||
|     onOverflow() { | ||||
|         this.containerRef.classList.add("mx_AutoHideScrollbar_overflow"); | ||||
|         this.containerRef.classList.remove("mx_AutoHideScrollbar_underflow"); | ||||
|     } | ||||
| 
 | ||||
|     onUnderflow() { | ||||
|         this.containerRef.classList.remove("mx_AutoHideScrollbar_overflow"); | ||||
|         this.containerRef.classList.add("mx_AutoHideScrollbar_underflow"); | ||||
|     } | ||||
| 
 | ||||
|     _collectContainerRef(ref) { | ||||
|         if (ref && !this.containerRef) { | ||||
|             this.containerRef = ref; | ||||
|             const needsOverflowListener = | ||||
|                 document.body.classList.contains("mx_scrollbar_nooverlay"); | ||||
| 
 | ||||
|             if (needsOverflowListener) { | ||||
|                 this.containerRef.addEventListener("overflow", this.onOverflow); | ||||
|                 this.containerRef.addEventListener("underflow", this.onUnderflow); | ||||
|             } | ||||
|             if (ref.scrollHeight > ref.clientHeight) { | ||||
|                 this.onOverflow(); | ||||
|             } else { | ||||
|                 this.onUnderflow(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         if (this.containerRef) { | ||||
|             this.containerRef.removeEventListener("overflow", this.onOverflow); | ||||
|             this.containerRef.removeEventListener("underflow", this.onUnderflow); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         installBodyClassesIfNeeded(); | ||||
|         return (<div | ||||
|                     ref={this._collectContainerRef} | ||||
|                     className={["mx_AutoHideScrollbar", this.props.className].join(" ")} | ||||
|                 > | ||||
|             { this.props.children } | ||||
|         </div>); | ||||
|     } | ||||
| } | ||||
| @ -179,10 +179,9 @@ const LeftPanel = React.createClass({ | ||||
|         const RoomList = sdk.getComponent('rooms.RoomList'); | ||||
|         const TagPanel = sdk.getComponent('structures.TagPanel'); | ||||
|         const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton'); | ||||
|         const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu'); | ||||
|         const CallPreview = sdk.getComponent('voip.CallPreview'); | ||||
| 
 | ||||
|         let topBox = <TopLeftMenuButton collapsed={ this.props.collapsed }/>; | ||||
|         const topBox = <TopLeftMenuButton collapsed={ this.props.collapsed } />; | ||||
| /* | ||||
|         const SearchBox = sdk.getComponent('structures.SearchBox'); | ||||
|         const topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />; | ||||
|  | ||||
| @ -23,6 +23,7 @@ import dis from '../../dispatcher'; | ||||
| import Unread from '../../Unread'; | ||||
| import * as RoomNotifs from '../../RoomNotifs'; | ||||
| import * as FormattingUtils from '../../utils/FormattingUtils'; | ||||
| import AutoHideScrollbar from './AutoHideScrollbar'; | ||||
| import { KeyCode } from '../../Keyboard'; | ||||
| import { Group } from 'matrix-js-sdk'; | ||||
| import PropTypes from 'prop-types'; | ||||
| @ -348,19 +349,17 @@ const RoomSubList = React.createClass({ | ||||
|                     {this._getHeaderJsx()} | ||||
|                 </div>; | ||||
|             } else { | ||||
|                 const heightEstimation = (len * 40) + 31 + (8 + 8); | ||||
|                 const heightEstimation = (len * 44) + 31 + (8 + 8); | ||||
|                 const style = { | ||||
|                     flexGrow: `${heightEstimation}`, | ||||
|                     maxHeight: `${heightEstimation}px`, | ||||
|                 }; | ||||
|                 const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); | ||||
|                 const tiles = this.makeRoomTiles(); | ||||
|                 tiles.push(...this.props.extraTiles); | ||||
|                 return <div className={subListClasses} style={style}> | ||||
|                 return <div style={style} className={subListClasses}> | ||||
|                     {this._getHeaderJsx()} | ||||
|                     <GeminiScrollbarWrapper> | ||||
|                     <AutoHideScrollbar className="mx_RoomSubList_scroll"> | ||||
|                         { tiles } | ||||
|                     </GeminiScrollbarWrapper> | ||||
|                     </AutoHideScrollbar> | ||||
|                 </div>; | ||||
|             } | ||||
|         } else { | ||||
|  | ||||
| @ -26,7 +26,6 @@ import Avatar from '../../Avatar'; | ||||
| const AVATAR_SIZE = 28; | ||||
| 
 | ||||
| export default class TopLeftMenuButton extends React.Component { | ||||
| 
 | ||||
|     static propTypes = { | ||||
|         collapsed: PropTypes.bool.isRequired, | ||||
|     }; | ||||
|  | ||||
| @ -19,7 +19,6 @@ import dis from '../../../dispatcher'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| 
 | ||||
| export class TopLeftMenu extends React.Component { | ||||
| 
 | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.openSettings = this.openSettings.bind(this); | ||||
|  | ||||
| @ -74,7 +74,6 @@ class CollapseDistributor extends FixedDistributor { | ||||
| } | ||||
| 
 | ||||
| class PercentageDistributor { | ||||
| 
 | ||||
|     constructor(sizer, item, _config, items, container) { | ||||
|         this.container = container; | ||||
|         this.totalSize = sizer.getTotalSize(); | ||||
|  | ||||
| @ -34,7 +34,6 @@ class RoomSizer extends Sizer { | ||||
|             item.style.maxHeight = `${Math.round(size)}px`; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| class RoomDistributor extends FixedDistributor { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user