mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
feat(scrollbars): Custom scrollbars via ScrollRegion
Summary: ScrollRegion with support for tooltips shown as you scroll, custom tooltips for thread list and message list. fix(specs): all other scrollbars hidden to prevent incompatibility fix scrollbar sizing when used in conjunction with resizableregion Test Plan: Need to write tests and docs for ScrollRegion - no tests yet Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1597
This commit is contained in:
parent
bc916a2530
commit
fb3f7fc410
|
@ -16,6 +16,7 @@ module.exports =
|
||||||
MultiselectList: require '../src/components/multiselect-list'
|
MultiselectList: require '../src/components/multiselect-list'
|
||||||
MultiselectActionBar: require '../src/components/multiselect-action-bar'
|
MultiselectActionBar: require '../src/components/multiselect-action-bar'
|
||||||
ResizableRegion: require '../src/components/resizable-region'
|
ResizableRegion: require '../src/components/resizable-region'
|
||||||
|
ScrollRegion: require '../src/components/scroll-region'
|
||||||
InjectedComponentSet: require '../src/components/injected-component-set'
|
InjectedComponentSet: require '../src/components/injected-component-set'
|
||||||
InjectedComponent: require '../src/components/injected-component'
|
InjectedComponent: require '../src/components/injected-component'
|
||||||
TokenizingTextField: require '../src/components/tokenizing-text-field'
|
TokenizingTextField: require '../src/components/tokenizing-text-field'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
React = require 'react'
|
React = require 'react'
|
||||||
{Actions} = require("nylas-exports")
|
{Actions} = require("nylas-exports")
|
||||||
|
{ScrollRegion} = require("nylas-component-kit")
|
||||||
SidebarDividerItem = require("./account-sidebar-divider-item")
|
SidebarDividerItem = require("./account-sidebar-divider-item")
|
||||||
SidebarTagItem = require("./account-sidebar-tag-item")
|
SidebarTagItem = require("./account-sidebar-tag-item")
|
||||||
SidebarSheetItem = require("./account-sidebar-sheet-item")
|
SidebarSheetItem = require("./account-sidebar-sheet-item")
|
||||||
|
@ -26,11 +27,11 @@ class AccountSidebar extends React.Component
|
||||||
@unsubscribe() if @unsubscribe
|
@unsubscribe() if @unsubscribe
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
<div id="account-sidebar" className="account-sidebar">
|
<ScrollRegion id="account-sidebar" className="account-sidebar">
|
||||||
<div className="account-sidebar-sections">
|
<div className="account-sidebar-sections">
|
||||||
{@_sections()}
|
{@_sections()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScrollRegion>
|
||||||
|
|
||||||
_sections: =>
|
_sections: =>
|
||||||
return @state.sections.map (section) =>
|
return @state.sections.map (section) =>
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
#account-sidebar {
|
#account-sidebar {
|
||||||
order: 1;
|
order: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
background-color: @source-list-bg;
|
background-color: @source-list-bg;
|
||||||
|
|
||||||
section {
|
section {
|
||||||
|
|
|
@ -4,11 +4,46 @@ classNames = require 'classnames'
|
||||||
MessageItem = require "./message-item"
|
MessageItem = require "./message-item"
|
||||||
{Utils, Actions, MessageStore, ComponentRegistry} = require("nylas-exports")
|
{Utils, Actions, MessageStore, ComponentRegistry} = require("nylas-exports")
|
||||||
{Spinner,
|
{Spinner,
|
||||||
|
ScrollRegion,
|
||||||
ResizableRegion,
|
ResizableRegion,
|
||||||
RetinaImg,
|
RetinaImg,
|
||||||
InjectedComponentSet,
|
InjectedComponentSet,
|
||||||
InjectedComponent} = require('nylas-component-kit')
|
InjectedComponent} = require('nylas-component-kit')
|
||||||
|
|
||||||
|
class MessageListScrollTooltip extends React.Component
|
||||||
|
@displayName: 'MessageListScrollTooltip'
|
||||||
|
@propTypes:
|
||||||
|
viewportCenter: React.PropTypes.number.isRequired
|
||||||
|
totalHeight: React.PropTypes.number.isRequired
|
||||||
|
|
||||||
|
componentWillMount: =>
|
||||||
|
@setupForProps(@props)
|
||||||
|
|
||||||
|
componentWillReceiveProps: (newProps) =>
|
||||||
|
@setupForProps(newProps)
|
||||||
|
|
||||||
|
shouldComponentUpdate: (newProps, newState) =>
|
||||||
|
not _.isEqual(@state,newState)
|
||||||
|
|
||||||
|
setupForProps: (props) ->
|
||||||
|
# Technically, we could have MessageList provide the currently visible
|
||||||
|
# item index, but the DOM approach is simple and self-contained.
|
||||||
|
#
|
||||||
|
els = document.querySelectorAll('.message-item-wrap')
|
||||||
|
idx = _.findIndex els, (el) -> el.offsetTop > props.viewportCenter
|
||||||
|
if idx is -1
|
||||||
|
idx = els.length
|
||||||
|
|
||||||
|
@setState
|
||||||
|
idx: idx
|
||||||
|
count: els.length
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
<div className="scroll-tooltip">
|
||||||
|
{@state.idx} of {@state.count}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
class MessageList extends React.Component
|
class MessageList extends React.Component
|
||||||
@displayName: 'MessageList'
|
@displayName: 'MessageList'
|
||||||
@containerRequired: false
|
@containerRequired: false
|
||||||
|
@ -74,22 +109,23 @@ class MessageList extends React.Component
|
||||||
"ready": @state.ready
|
"ready": @state.ready
|
||||||
|
|
||||||
<div className="message-list" id="message-list">
|
<div className="message-list" id="message-list">
|
||||||
<div tabIndex="-1"
|
<ScrollRegion tabIndex="-1"
|
||||||
className={wrapClass}
|
className={wrapClass}
|
||||||
|
scrollTooltipComponent={MessageListScrollTooltip}
|
||||||
onScroll={_.debounce(@_cacheScrollPos, 100)}
|
onScroll={_.debounce(@_cacheScrollPos, 100)}
|
||||||
ref="messageWrap">
|
ref="messageWrap">
|
||||||
|
<div className="headers" style={position:'relative'}>
|
||||||
<InjectedComponentSet
|
<InjectedComponentSet
|
||||||
className="message-list-notification-bars"
|
className="message-list-notification-bars"
|
||||||
matching={role:"MessageListNotificationBar"}
|
matching={role:"MessageListNotificationBar"}
|
||||||
exposedProps={thread: @state.currentThread}/>
|
exposedProps={thread: @state.currentThread}/>
|
||||||
<InjectedComponentSet
|
<InjectedComponentSet
|
||||||
className="message-list-headers"
|
className="message-list-headers"
|
||||||
matching={role:"MessageListHeaders"}
|
matching={role:"MessageListHeaders"}
|
||||||
exposedProps={thread: @state.currentThread}/>
|
exposedProps={thread: @state.currentThread}/>
|
||||||
|
</div>
|
||||||
{@_messageComponents()}
|
{@_messageComponents()}
|
||||||
</div>
|
</ScrollRegion>
|
||||||
{@_renderReplyArea()}
|
{@_renderReplyArea()}
|
||||||
<Spinner visible={!@state.ready} />
|
<Spinner visible={!@state.ready} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,32 @@ classNames = require 'classnames'
|
||||||
ThreadListParticipants = require './thread-list-participants'
|
ThreadListParticipants = require './thread-list-participants'
|
||||||
ThreadListStore = require './thread-list-store'
|
ThreadListStore = require './thread-list-store'
|
||||||
|
|
||||||
|
class ThreadListScrollTooltip extends React.Component
|
||||||
|
@displayName: 'ThreadListScrollTooltip'
|
||||||
|
@propTypes:
|
||||||
|
viewportCenter: React.PropTypes.number.isRequired
|
||||||
|
totalHeight: React.PropTypes.number.isRequired
|
||||||
|
|
||||||
|
componentWillMount: =>
|
||||||
|
@setupForProps(@props)
|
||||||
|
|
||||||
|
componentWillReceiveProps: (newProps) =>
|
||||||
|
@setupForProps(newProps)
|
||||||
|
|
||||||
|
shouldComponentUpdate: (newProps, newState) =>
|
||||||
|
@state?.idx isnt newState.idx
|
||||||
|
|
||||||
|
setupForProps: (props) ->
|
||||||
|
idx = Math.floor(ThreadListStore.view().count() / @props.totalHeight * @props.viewportCenter)
|
||||||
|
@setState
|
||||||
|
idx: idx
|
||||||
|
item: ThreadListStore.view().get(idx)
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
<div className="scroll-tooltip">
|
||||||
|
{timestamp(@state.item?.lastMessageTimestamp)}
|
||||||
|
</div>
|
||||||
|
|
||||||
class ThreadList extends React.Component
|
class ThreadList extends React.Component
|
||||||
@displayName: 'ThreadList'
|
@displayName: 'ThreadList'
|
||||||
|
|
||||||
|
@ -99,6 +125,7 @@ class ThreadList extends React.Component
|
||||||
commands={@commands}
|
commands={@commands}
|
||||||
itemPropsProvider={@itemPropsProvider}
|
itemPropsProvider={@itemPropsProvider}
|
||||||
className="thread-list"
|
className="thread-list"
|
||||||
|
scrollTooltipComponent={ThreadListScrollTooltip}
|
||||||
collection="thread" />
|
collection="thread" />
|
||||||
|
|
||||||
# Additional Commands
|
# Additional Commands
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
.thread-list, .draft-list {
|
.thread-list, .draft-list {
|
||||||
order: 3;
|
order: 3;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position:relative;
|
position:absolute;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
|
|
||||||
.list-item {
|
.list-item {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
_ = require 'underscore'
|
_ = require 'underscore'
|
||||||
React = require 'react/addons'
|
React = require 'react/addons'
|
||||||
|
ScrollRegion = require './scroll-region'
|
||||||
|
|
||||||
RangeChunkSize = 10
|
RangeChunkSize = 10
|
||||||
|
|
||||||
|
@ -85,14 +86,11 @@ class ListTabular extends React.Component
|
||||||
# If our view has been swapped out for an entirely different one,
|
# If our view has been swapped out for an entirely different one,
|
||||||
# reset our scroll position to the top.
|
# reset our scroll position to the top.
|
||||||
if prevProps.dataView isnt @props.dataView
|
if prevProps.dataView isnt @props.dataView
|
||||||
container = React.findDOMNode(@refs.container)
|
@refs.container.scrollTop = 0
|
||||||
container.scrollTop = 0
|
|
||||||
@updateRangeState()
|
@updateRangeState()
|
||||||
|
|
||||||
updateScrollState: =>
|
updateScrollState: =>
|
||||||
window.requestAnimationFrame =>
|
window.requestAnimationFrame =>
|
||||||
container = React.findDOMNode(@refs.container)
|
|
||||||
|
|
||||||
# Create an event that fires when we stop receiving scroll events.
|
# Create an event that fires when we stop receiving scroll events.
|
||||||
# There is no "scrollend" event, but we really need one.
|
# There is no "scrollend" event, but we really need one.
|
||||||
clearTimeout(@_scrollTimer) if @_scrollTimer
|
clearTimeout(@_scrollTimer) if @_scrollTimer
|
||||||
|
@ -104,7 +102,7 @@ class ListTabular extends React.Component
|
||||||
|
|
||||||
# If we've shifted enough pixels from our previous scrollTop to require
|
# If we've shifted enough pixels from our previous scrollTop to require
|
||||||
# new rows to be rendered, update our state!
|
# new rows to be rendered, update our state!
|
||||||
if Math.abs(@state.scrollTop - container.scrollTop) >= @_rowHeight() * RangeChunkSize
|
if Math.abs(@state.scrollTop - @refs.container.scrollTop) >= @_rowHeight() * RangeChunkSize
|
||||||
@updateRangeState()
|
@updateRangeState()
|
||||||
|
|
||||||
onDoneReceivingScrollEvents: =>
|
onDoneReceivingScrollEvents: =>
|
||||||
|
@ -113,8 +111,7 @@ class ListTabular extends React.Component
|
||||||
@updateRangeState()
|
@updateRangeState()
|
||||||
|
|
||||||
updateRangeState: =>
|
updateRangeState: =>
|
||||||
container = @refs.container
|
scrollTop = @refs.container.scrollTop
|
||||||
scrollTop = React.findDOMNode(container)?.scrollTop
|
|
||||||
|
|
||||||
rowHeight = @_rowHeight()
|
rowHeight = @_rowHeight()
|
||||||
|
|
||||||
|
@ -159,12 +156,12 @@ class ListTabular extends React.Component
|
||||||
height: @props.dataView.count() * @_rowHeight()
|
height: @props.dataView.count() * @_rowHeight()
|
||||||
pointerEvents: if @state.scrollInProgress then 'none' else 'auto'
|
pointerEvents: if @state.scrollInProgress then 'none' else 'auto'
|
||||||
|
|
||||||
<div ref="container" onScroll={@updateScrollState} tabIndex="-1" className="list-container list-tabular">
|
<ScrollRegion ref="container" onScroll={@updateScrollState} tabIndex="-1" className="list-container list-tabular" scrollTooltipComponent={@props.scrollTooltipComponent} >
|
||||||
{@_headers()}
|
{@_headers()}
|
||||||
<div className="list-rows" style={innerStyles}>
|
<div className="list-rows" style={innerStyles}>
|
||||||
{@_rows()}
|
{@_rows()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScrollRegion>
|
||||||
|
|
||||||
_rowHeight: =>
|
_rowHeight: =>
|
||||||
39
|
39
|
||||||
|
|
|
@ -32,6 +32,7 @@ class MultiselectList extends React.Component
|
||||||
columns: React.PropTypes.array.isRequired
|
columns: React.PropTypes.array.isRequired
|
||||||
dataStore: React.PropTypes.object.isRequired
|
dataStore: React.PropTypes.object.isRequired
|
||||||
itemPropsProvider: React.PropTypes.func.isRequired
|
itemPropsProvider: React.PropTypes.func.isRequired
|
||||||
|
scrollTooltipComponent: React.PropTypes.func
|
||||||
|
|
||||||
constructor: (@props) ->
|
constructor: (@props) ->
|
||||||
@state = @_getStateFromStores()
|
@state = @_getStateFromStores()
|
||||||
|
@ -120,6 +121,7 @@ class MultiselectList extends React.Component
|
||||||
<ListTabular
|
<ListTabular
|
||||||
ref="list"
|
ref="list"
|
||||||
columns={@props.columns}
|
columns={@props.columns}
|
||||||
|
scrollTooltipComponent={@props.scrollTooltipComponent}
|
||||||
dataView={@state.dataView}
|
dataView={@state.dataView}
|
||||||
itemPropsProvider={@itemPropsProvider}
|
itemPropsProvider={@itemPropsProvider}
|
||||||
onSelect={@_onClickItem}
|
onSelect={@_onClickItem}
|
||||||
|
|
144
src/components/scroll-region.cjsx
Normal file
144
src/components/scroll-region.cjsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
_ = require 'underscore'
|
||||||
|
React = require 'react/addons'
|
||||||
|
{Utils} = require 'nylas-exports'
|
||||||
|
classNames = require 'classnames'
|
||||||
|
|
||||||
|
###
|
||||||
|
The ScrollRegion component attaches a custom scrollbar.
|
||||||
|
###
|
||||||
|
|
||||||
|
class ScrollRegion extends React.Component
|
||||||
|
@displayName: "ScrollRegion"
|
||||||
|
|
||||||
|
@propTypes:
|
||||||
|
onScroll: React.PropTypes.func
|
||||||
|
className: React.PropTypes.string
|
||||||
|
scrollTooltipComponent: React.PropTypes.func
|
||||||
|
|
||||||
|
constructor: (@props) ->
|
||||||
|
@state =
|
||||||
|
totalHeight:0
|
||||||
|
viewportHeight: 0
|
||||||
|
viewportOffset: 0
|
||||||
|
dragging: false
|
||||||
|
scrolling: false
|
||||||
|
|
||||||
|
Object.defineProperty(@, 'scrollTop', {
|
||||||
|
get: -> React.findDOMNode(@refs.content).scrollTop
|
||||||
|
set: (val) -> React.findDOMNode(@refs.content).scrollTop = val
|
||||||
|
})
|
||||||
|
|
||||||
|
componentDidMount: =>
|
||||||
|
@_recomputeDimensions()
|
||||||
|
|
||||||
|
componentDidUpdate: =>
|
||||||
|
@_recomputeDimensions()
|
||||||
|
|
||||||
|
componentWillUnmount: =>
|
||||||
|
@_onHandleUp()
|
||||||
|
|
||||||
|
shouldComponentUpdate: (newProps, newState) =>
|
||||||
|
not Utils.isEqualReact(newProps, @props) or not Utils.isEqualReact(newState, @state)
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
containerClasses = "#{@props.className ? ''} " + classNames
|
||||||
|
'scroll-region': true
|
||||||
|
'dragging': @state.dragging
|
||||||
|
'scrolling': @state.scrolling
|
||||||
|
|
||||||
|
otherProps = _.omit(@props, _.keys(@constructor.propTypes))
|
||||||
|
|
||||||
|
tooltip = []
|
||||||
|
if @props.scrollTooltipComponent
|
||||||
|
tooltip = <@props.scrollTooltipComponent viewportCenter={@state.viewportOffset + @state.viewportHeight / 2} totalHeight={@state.totalHeight} />
|
||||||
|
|
||||||
|
<div className={containerClasses} {...otherProps}>
|
||||||
|
<div className="scrollbar-track" style={@_scrollbarWrapStyles()} onMouseEnter={@_recomputeDimensions}>
|
||||||
|
<div className="scrollbar-track-inner" ref="track" onClick={@_onScrollJump}>
|
||||||
|
<div className="scrollbar-handle" onMouseDown={@_onHandleDown} style={@_scrollbarHandleStyles()} ref="handle" onClick={@_onHandleClick} >
|
||||||
|
<div className="tooltip">{tooltip}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="scroll-region-content" onScroll={@_onScroll} ref="content">
|
||||||
|
{@props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_scrollbarWrapStyles: =>
|
||||||
|
position:'absolute'
|
||||||
|
top: 0
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
zIndex: 2
|
||||||
|
|
||||||
|
_scrollbarHandleStyles: =>
|
||||||
|
handleHeight = @_getHandleHeight()
|
||||||
|
handleTop = (@state.viewportOffset / (@state.totalHeight - @state.viewportHeight)) * (@state.trackHeight - handleHeight)
|
||||||
|
|
||||||
|
position:'relative'
|
||||||
|
height: handleHeight
|
||||||
|
top: handleTop
|
||||||
|
|
||||||
|
_getHandleHeight: =>
|
||||||
|
Math.min(@state.totalHeight, Math.max(40, (@state.trackHeight / @state.totalHeight) * @state.trackHeight))
|
||||||
|
|
||||||
|
_recomputeDimensions: =>
|
||||||
|
return unless @refs.content
|
||||||
|
|
||||||
|
contentNode = React.findDOMNode(@refs.content)
|
||||||
|
trackNode = React.findDOMNode(@refs.track)
|
||||||
|
|
||||||
|
totalHeight = contentNode.scrollHeight
|
||||||
|
trackHeight = trackNode.clientHeight
|
||||||
|
viewportHeight = contentNode.clientHeight
|
||||||
|
viewportOffset = contentNode.scrollTop
|
||||||
|
|
||||||
|
if @state.totalHeight != totalHeight or
|
||||||
|
@state.trackHeight != trackHeight or
|
||||||
|
@state.viewportOffset != viewportOffset or
|
||||||
|
@state.viewportHeight != viewportHeight
|
||||||
|
@setState({totalHeight, trackHeight, viewportOffset, viewportHeight})
|
||||||
|
|
||||||
|
_onHandleDown: (event) =>
|
||||||
|
handleNode = React.findDOMNode(@refs.handle)
|
||||||
|
@_trackOffset = React.findDOMNode(@refs.track).getBoundingClientRect().top
|
||||||
|
@_mouseOffsetWithinHandle = event.pageY - handleNode.getBoundingClientRect().top
|
||||||
|
window.addEventListener("mousemove", @_onHandleMove)
|
||||||
|
window.addEventListener("mouseup", @_onHandleUp)
|
||||||
|
@setState(dragging: true)
|
||||||
|
|
||||||
|
_onHandleMove: (event) =>
|
||||||
|
trackY = event.pageY - @_trackOffset - @_mouseOffsetWithinHandle
|
||||||
|
trackPxToViewportPx = (@state.totalHeight - @state.viewportHeight) / (@state.trackHeight - @_getHandleHeight())
|
||||||
|
|
||||||
|
contentNode = React.findDOMNode(@refs.content)
|
||||||
|
contentNode.scrollTop = trackY * trackPxToViewportPx
|
||||||
|
|
||||||
|
_onHandleUp: (event) =>
|
||||||
|
window.removeEventListener("mousemove", @_onHandleMove)
|
||||||
|
window.removeEventListener("mouseup", @_onHandleUp)
|
||||||
|
@setState(dragging: false)
|
||||||
|
|
||||||
|
_onHandleClick: (event) =>
|
||||||
|
# Avoid event propogating up to track
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
_onScrollJump: (event) =>
|
||||||
|
@_mouseOffsetWithinHandle = @_getHandleHeight() / 2
|
||||||
|
@_onHandleMove(event)
|
||||||
|
|
||||||
|
_onScroll: (event) =>
|
||||||
|
@_recomputeDimensions()
|
||||||
|
@props.onScroll?(event)
|
||||||
|
|
||||||
|
if not @state.scrolling
|
||||||
|
@setState(scrolling: true)
|
||||||
|
|
||||||
|
@_onStoppedScroll ?= _.debounce =>
|
||||||
|
@setState(scrolling: false)
|
||||||
|
, 250
|
||||||
|
@_onStoppedScroll()
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = ScrollRegion
|
|
@ -71,11 +71,6 @@
|
||||||
|
|
||||||
|
|
||||||
.list-container {
|
.list-container {
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
.list-item {
|
.list-item {
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-base;
|
||||||
line-height: @line-height-computed;
|
line-height: @line-height-computed;
|
||||||
|
@ -107,6 +102,7 @@
|
||||||
.list-tabular {
|
.list-tabular {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.list-tabular-item {
|
.list-tabular-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
104
static/components/scroll-region.less
Normal file
104
static/components/scroll-region.less
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
@import "ui-variables";
|
||||||
|
|
||||||
|
@tooltipBorderColor: rgba(54, 56, 57, 0.9);
|
||||||
|
@tooltipBackground: -webkit-gradient(linear, left top, left bottom, from(rgba(99, 102, 103, 0.9)), to(rgba(82, 85, 86, 0.9)));
|
||||||
|
|
||||||
|
.scroll-tooltip {
|
||||||
|
background: @tooltipBackground;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 2px 7px rgba(0, 0, 0, 0.25);
|
||||||
|
padding: @padding-base-vertical @padding-base-horizontal @padding-base-vertical @padding-base-horizontal;
|
||||||
|
border: 1px solid @tooltipBorderColor;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
position: relative;
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
.scroll-tooltip:after, .scroll-tooltip:before {
|
||||||
|
left: 100%;
|
||||||
|
top: 50%;
|
||||||
|
border: solid transparent;
|
||||||
|
content: " ";
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.scroll-tooltip:after {
|
||||||
|
border-color: transparent;
|
||||||
|
border-left-color: lighten(rgba(99, 102, 103, 1), 3%);
|
||||||
|
border-width: 8px;
|
||||||
|
margin-top: -8px;
|
||||||
|
}
|
||||||
|
.scroll-tooltip:before {
|
||||||
|
border-color: transparent;
|
||||||
|
border-left-color: darken(@tooltipBorderColor, 20%);
|
||||||
|
border-width: 9px;
|
||||||
|
margin-top: -9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-region {
|
||||||
|
position:relative;
|
||||||
|
|
||||||
|
.scroll-region-content {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-track {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
transition-delay: 0.5s;
|
||||||
|
padding:3px;
|
||||||
|
width:17px;
|
||||||
|
background: @list-bg;
|
||||||
|
border-left: 1px solid @border-color-divider;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to read the track height with padding applied. */
|
||||||
|
.scrollbar-track-inner {
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-handle {
|
||||||
|
background-color: lighten(@gray, 40%);
|
||||||
|
border:1px solid lighten(@gray, 30%);
|
||||||
|
border-radius:8px;
|
||||||
|
.tooltip {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-100%, -50%);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scroll-region.scrolling {
|
||||||
|
.scrollbar-track {
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scroll-region.dragging {
|
||||||
|
.scrollbar-track {
|
||||||
|
opacity: 1;
|
||||||
|
.scrollbar-handle {
|
||||||
|
cursor: default;
|
||||||
|
background-color: lighten(@gray, 30%);
|
||||||
|
border:1px solid lighten(@gray, 20%);
|
||||||
|
.tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
@import "components/tokenizing-text-field";
|
@import "components/tokenizing-text-field";
|
||||||
@import "components/extra";
|
@import "components/extra";
|
||||||
@import "components/list-tabular";
|
@import "components/list-tabular";
|
||||||
|
@import "components/scroll-region";
|
||||||
@import "components/spinner";
|
@import "components/spinner";
|
||||||
@import "components/generated-form";
|
@import "components/generated-form";
|
||||||
@import "components/empty-state";
|
@import "components/empty-state";
|
||||||
|
|
|
@ -198,7 +198,7 @@ body.is-blurred {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.flexbox-handle-right {
|
&.flexbox-handle-right {
|
||||||
right:-3px;
|
right:-4px;
|
||||||
padding-right:3px;
|
padding-right:3px;
|
||||||
}
|
}
|
||||||
&.flexbox-handle-left {
|
&.flexbox-handle-left {
|
||||||
|
|
Loading…
Reference in a new issue