Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2839 from matrix-org/travis/breadcrumbs/visual
Browse files Browse the repository at this point in the history
Add custom tooltips and scrolling to breadcrumbs
  • Loading branch information
turt2live authored Mar 28, 2019
2 parents a0e3182 + 87ca306 commit acb33e1
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 18 deletions.
55 changes: 40 additions & 15 deletions res/css/views/rooms/_RoomBreadcrumbs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,29 @@ limitations under the License.
*/

.mx_RoomBreadcrumbs {
overflow-x: auto;
position: relative;
height: 32px;
height: 42px;
margin: 8px;
margin-bottom: 0;

overflow-x: hidden;
overflow-x: visible;
display: flex;
flex-direction: row;

> * {
margin-left: 4px;
.mx_AutoHideScrollbar_offset {
display: flex;
flex-direction: row;
height: 100%;
}

&::after {
content: "";
position: absolute;
width: 15px;
top: 0;
right: 0;
height: 100%;
background: linear-gradient(to right, $panel-gradient);
.mx_RoomBreadcrumbs_crumb {
margin-left: 4px;
height: 32px;
display: inline-block;
transition: transform 0.3s, width 0.3s;
}

.mx_RoomBreadcrumbs_animate {
margin-left: 0;
transition: transform 0.3s, width 0.3s;
width: 32px;
transform: scale(1);
}
Expand All @@ -50,5 +46,34 @@ limitations under the License.
width: 0;
transform: scale(0);
}


// Note: we have to manually control the gradient and stuff, but the IndicatorScrollbar
// will deal with left/right positioning for us. Normally we'd use position:sticky on
// a few key elements, however that doesn't work in horizontal scrolling scenarios.

.mx_IndicatorScrollbar_leftOverflowIndicator,
.mx_IndicatorScrollbar_rightOverflowIndicator {
display: none;
}

&.mx_IndicatorScrollbar_leftOverflow .mx_IndicatorScrollbar_leftOverflowIndicator,
&.mx_IndicatorScrollbar_rightOverflow .mx_IndicatorScrollbar_rightOverflowIndicator {
position: absolute;
top: 0;
bottom: 0;
width: 15px;
display: block;
pointer-events: none;
z-index: 100;
}

.mx_IndicatorScrollbar_leftOverflowIndicator {
background: linear-gradient(to left, $panel-gradient);
}

.mx_IndicatorScrollbar_rightOverflowIndicator {
background: linear-gradient(to right, $panel-gradient);
}
}

46 changes: 46 additions & 0 deletions src/components/structures/IndicatorScrollbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,29 @@ limitations under the License.
*/

import React from "react";
import PropTypes from "prop-types";
import AutoHideScrollbar from "./AutoHideScrollbar";

export default class IndicatorScrollbar extends React.Component {
static PropTypes = {
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element.
trackHorizontalOverflow: PropTypes.bool,
};

constructor(props) {
super(props);
this._collectScroller = this._collectScroller.bind(this);
this._collectScrollerComponent = this._collectScrollerComponent.bind(this);
this.checkOverflow = this.checkOverflow.bind(this);
this._scrollElement = null;
this._autoHideScrollbar = null;

this.state = {
leftIndicatorOffset: 0,
rightIndicatorOffset: 0,
};
}

_collectScroller(scroller) {
Expand All @@ -43,6 +56,10 @@ export default class IndicatorScrollbar extends React.Component {
const hasTopOverflow = this._scrollElement.scrollTop > 0;
const hasBottomOverflow = this._scrollElement.scrollHeight >
(this._scrollElement.scrollTop + this._scrollElement.clientHeight);
const hasLeftOverflow = this._scrollElement.scrollLeft > 0;
const hasRightOverflow = this._scrollElement.scrollWidth >
(this._scrollElement.scrollLeft + this._scrollElement.clientWidth);

if (hasTopOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow");
} else {
Expand All @@ -53,10 +70,30 @@ export default class IndicatorScrollbar extends React.Component {
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow");
}
if (hasLeftOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_leftOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_leftOverflow");
}
if (hasRightOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_rightOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
}

if (this._autoHideScrollbar) {
this._autoHideScrollbar.checkOverflow();
}

if (this.props.trackHorizontalOverflow) {
this.setState({
// Offset from absolute position of the container
leftIndicatorOffset: hasLeftOverflow ? `${this._scrollElement.scrollLeft}px` : '0',

// Negative because we're coming from the right
rightIndicatorOffset: hasRightOverflow ? `-${this._scrollElement.scrollLeft}px` : '0',
});
}
}

getScrollTop() {
Expand All @@ -70,12 +107,21 @@ export default class IndicatorScrollbar extends React.Component {
}

render() {
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
const leftOverflowIndicator = this.props.trackHorizontalOverflow
? <div className="mx_IndicatorScrollbar_leftOverflowIndicator" style={leftIndicatorStyle} /> : null;
const rightOverflowIndicator = this.props.trackHorizontalOverflow
? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null;

return (<AutoHideScrollbar
ref={this._collectScrollerComponent}
wrappedRef={this._collectScroller}
{... this.props}
>
{ leftOverflowIndicator }
{ this.props.children }
{ rightOverflowIndicator }
</AutoHideScrollbar>);
}
}
39 changes: 36 additions & 3 deletions src/components/views/rooms/RoomBreadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
import AccessibleButton from '../elements/AccessibleButton';
import RoomAvatar from '../avatars/RoomAvatar';
import classNames from 'classnames';
import sdk from "../../../index";

const MAX_ROOMS = 20;

Expand Down Expand Up @@ -101,7 +102,26 @@ export default class RoomBreadcrumbs extends React.Component {
dis.dispatch({action: "view_room", room_id: room.roomId});
}

_onMouseEnter(room) {
this._onHover(room);
}

_onMouseLeave(room) {
this._onHover(null); // clear hover states
}

_onHover(room) {
const rooms = this.state.rooms.slice();
for (const r of rooms) {
r.hover = room && r.room.roomId === room.roomId;
}
this.setState({rooms});
}

render() {
const Tooltip = sdk.getComponent('elements.Tooltip');
const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar');

// check for collapsed here and
// not at parent so we keep
// rooms in our state
Expand All @@ -110,18 +130,31 @@ export default class RoomBreadcrumbs extends React.Component {
return null;
}
const rooms = this.state.rooms;
const avatars = rooms.map(({room, animated}, i) => {
const avatars = rooms.map(({room, animated, hover}, i) => {
const isFirst = i === 0;
const classes = classNames({
"mx_RoomBreadcrumbs_crumb": true,
"mx_RoomBreadcrumbs_preAnimate": isFirst && !animated,
"mx_RoomBreadcrumbs_animate": isFirst,
});

let tooltip = null;
if (hover) {
tooltip = <Tooltip label={room.name} />;
}

return (
<AccessibleButton className={classes} key={room.roomId} title={room.name} onClick={() => this._viewRoom(room)}>
<AccessibleButton className={classes} key={room.roomId} onClick={() => this._viewRoom(room)}
onMouseEnter={() => this._onMouseEnter(room)} onMouseLeave={() => this._onMouseLeave(room)}>
<RoomAvatar room={room} width={32} height={32} />
{tooltip}
</AccessibleButton>
);
});
return (<div className="mx_RoomBreadcrumbs">{ avatars }</div>);
return (
<IndicatorScrollbar ref="scroller" className="mx_RoomBreadcrumbs" trackHorizontalOverflow={true}>
{ avatars }
</IndicatorScrollbar>
);
}
}

0 comments on commit acb33e1

Please sign in to comment.