Browse Source

Clean custom scrollbar

master
Loëck Vézien 7 years ago
parent
commit
6b29736975
No known key found for this signature in database GPG Key ID: CBCDCE384E853AC4
  1. 2
      package.json
  2. 16
      src/components/SelectAccount/index.js
  3. 3
      src/components/SideBar/index.js
  4. 3
      src/components/Wrapper.js
  5. 91
      src/components/base/GrowScroll/index.js
  6. 13
      src/components/base/GrowScroll/stories.js
  7. 1
      src/components/base/Modal/index.js
  8. 143
      src/components/base/Select/index.js
  9. 23
      src/styles/global.js
  10. 18
      yarn.lock

2
package.json

@ -75,10 +75,12 @@
"react-router": "^4.2.0", "react-router": "^4.2.0",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-router-redux": "5.0.0-alpha.9", "react-router-redux": "5.0.0-alpha.9",
"react-smooth-scrollbar": "^8.0.6",
"redux": "^3.7.2", "redux": "^3.7.2",
"redux-actions": "^2.2.1", "redux-actions": "^2.2.1",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"smooth-scrollbar": "^8.2.5",
"source-map-support": "^0.5.3", "source-map-support": "^0.5.3",
"styled-components": "^3.1.4", "styled-components": "^3.1.4",
"styled-system": "^1.1.1" "styled-system": "^1.1.1"

16
src/components/SelectAccount/index.js

@ -21,13 +21,6 @@ const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: Object.entries(getAccounts(state)).map(([, account]: [string, any]) => account), accounts: Object.entries(getAccounts(state)).map(([, account]: [string, any]) => account),
}) })
type Props = {
accounts: Array<Account>,
onChange?: () => Account | void,
value?: Account | null,
t: T,
}
const renderItem = item => ( const renderItem = item => (
<Box horizontal align="center"> <Box horizontal align="center">
<Box grow> <Box grow>
@ -43,7 +36,14 @@ const renderItem = item => (
</Box> </Box>
) )
export const SelectAccount = ({ accounts, value, onChange, t }: Props) => ( type Props = {
accounts: Array<Account>,
onChange?: () => Account | void,
value?: Account | null,
t: T,
}
export const SelectAccount = ({ accounts, onChange, value, t }: Props) => (
<Select <Select
value={value && accounts.find(a => value && a.id === value.id)} value={value && accounts.find(a => value && a.id === value.id)}
renderSelected={renderItem} renderSelected={renderItem}

3
src/components/SideBar/index.js

@ -17,7 +17,8 @@ import { getAccounts } from 'reducers/accounts'
import { formatBTC } from 'helpers/format' import { formatBTC } from 'helpers/format'
import { rgba } from 'styles/helpers' import { rgba } from 'styles/helpers'
import Box, { GrowScroll } from 'components/base/Box' import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import Item from './Item' import Item from './Item'
const CapsSubtitle = styled(Box).attrs({ const CapsSubtitle = styled(Box).attrs({

3
src/components/Wrapper.js

@ -5,7 +5,8 @@ import { Route } from 'react-router'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import * as modals from 'components/modals' import * as modals from 'components/modals'
import Box, { GrowScroll } from 'components/base/Box' import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import AccountPage from 'components/AccountPage' import AccountPage from 'components/AccountPage'
import DashboardPage from 'components/DashboardPage' import DashboardPage from 'components/DashboardPage'

91
src/components/base/GrowScroll/index.js

@ -0,0 +1,91 @@
// @flow
import React, { PureComponent } from 'react'
import Scrollbar from 'react-smooth-scrollbar'
import noop from 'lodash/noop'
import Box from 'components/base/Box'
type Props = {
maxHeight?: number | string,
children: any,
offsetLimit: Object,
onUpdate: Function,
}
class GrowScroll extends PureComponent<Props> {
static defaultProps = {
onUpdate: noop,
offsetLimit: {
y: {
max: -3,
min: 3,
},
},
}
componentDidMount() {
const { offsetLimit } = this.props
if (this._scrollbar) {
this._scrollbar.addListener(function onScroll({ limit, offset }) {
if (limit.y > 0) {
const maxY = limit.y + offsetLimit.y.max
const minY = offsetLimit.y.min
if (offset.y > maxY) {
this.scrollTo(offset.x, maxY)
}
if (offset.y < minY) {
this.scrollTo(offset.x, minY)
}
}
})
}
this.handleUpdate(this.props)
}
componentWillReceiveProps(nextProps: Props) {
this.handleUpdate(nextProps)
}
handleUpdate = (props: Props) => {
if (this._scrollbar) {
props.onUpdate(this._scrollbar)
}
}
_scrollbar = undefined
render() {
const { onUpdate, children, maxHeight, ...props } = this.props
return (
<Box grow relative>
<Scrollbar
damping={1}
style={{
...(maxHeight
? {
maxHeight,
}
: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
}),
}}
ref={r => r && (this._scrollbar = r.scrollbar)}
>
<Box {...props}>{children}</Box>
</Scrollbar>
</Box>
)
}
}
export default GrowScroll

13
src/components/base/GrowScroll/stories.js

@ -0,0 +1,13 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
const stories = storiesOf('GrowScroll', module)
stories.add('basic', () => (
<Box style={{ height: 400, border: '1px solid black' }}>
<GrowScroll>{[...Array(1000).keys()].map(v => <div key={v}>{v}</div>)}</GrowScroll>
</Box>
))

1
src/components/base/Modal/index.js

@ -85,6 +85,7 @@ const Wrapper = styled(Box).attrs({
const Body = styled(Box).attrs({ const Body = styled(Box).attrs({
bg: p => p.theme.colors.white, bg: p => p.theme.colors.white,
p: 3, p: 3,
relative: true,
})` })`
border-radius: 5px; border-radius: 5px;
` `

143
src/components/base/Select/index.js

@ -4,10 +4,12 @@ import React, { PureComponent } from 'react'
import Downshift from 'downshift' import Downshift from 'downshift'
import styled from 'styled-components' import styled from 'styled-components'
import { space } from 'styled-system' import { space } from 'styled-system'
import get from 'lodash/get'
import type { Element } from 'react' import type { Element } from 'react'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import Icon from 'components/base/Icon' import Icon from 'components/base/Icon'
import Input from 'components/base/Input' import Input from 'components/base/Input'
import Search from 'components/base/Search' import Search from 'components/base/Search'
@ -70,8 +72,6 @@ const Dropdown = styled(Box).attrs({
left: 0; left: 0;
right: 0; right: 0;
border: 1px solid ${p => p.theme.colors.mouse}; border: 1px solid ${p => p.theme.colors.mouse};
max-height: 300px;
overflow-y: auto;
border-radius: 3px; border-radius: 3px;
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px; box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;
` `
@ -115,27 +115,59 @@ class Select extends PureComponent<Props> {
keyProp: undefined, keyProp: undefined,
} }
_scrollToSelectedItem = true
_useKeyboard = false
renderItems = (items: Array<Object>, selectedItem: any, downshiftProps: Object) => { renderItems = (items: Array<Object>, selectedItem: any, downshiftProps: Object) => {
const { renderItem, keyProp } = this.props const { renderItem, keyProp } = this.props
const { getItemProps, highlightedIndex } = downshiftProps const { getItemProps, highlightedIndex } = downshiftProps
const selectedItemIndex = items.indexOf(selectedItem)
return ( return (
<Dropdown> <Dropdown>
{items.length ? ( {items.length ? (
items.map((item, i) => ( <GrowScroll
<ItemWrapper key={keyProp ? item[keyProp] : item.key} {...getItemProps({ item })}> maxHeight={300}
<Item highlighted={i === highlightedIndex} horizontal flow={10}> onUpdate={scrollbar => {
<Box grow> const { contentEl } = scrollbar
{renderItem ? renderItem(item) : <span>{item.name_highlight || item.name}</span>} const children = get(contentEl, 'children[0].children[0].children', {})
</Box>
<Box> const currentHighlighted = children[highlightedIndex]
<IconSelected selected={selectedItem === item}> const currentSelectedItem = children[selectedItemIndex]
<Icon name="check" />
</IconSelected> if (this._useKeyboard && currentHighlighted) {
</Box> scrollbar.scrollIntoView(currentHighlighted, {
</Item> alignToTop: false,
</ItemWrapper> })
)) } else if (this._scrollToSelectedItem && currentSelectedItem) {
scrollbar.scrollIntoView(currentSelectedItem, {
alignToTop: false,
})
this._scrollToSelectedItem = false
}
}}
>
{items.map((item, i) => (
<ItemWrapper key={keyProp ? item[keyProp] : item.key} {...getItemProps({ item })}>
<Item highlighted={i === highlightedIndex} horizontal flow={10}>
<Box grow>
{renderItem ? (
renderItem(item)
) : (
<span>{item.name_highlight || item.name}</span>
)}
</Box>
<Box>
<IconSelected selected={selectedItem === item}>
<Icon name="check" />
</IconSelected>
</Box>
</Item>
</ItemWrapper>
))}
</GrowScroll>
) : ( ) : (
<ItemWrapper> <ItemWrapper>
<Item>{'No results'}</Item> <Item>{'No results'}</Item>
@ -174,42 +206,53 @@ class Select extends PureComponent<Props> {
openMenu, openMenu,
selectedItem, selectedItem,
...downshiftProps ...downshiftProps
}) => ( }) => {
<Container {...getRootProps({ refKey: 'innerRef' })} {...props}> if (!isOpen) {
{searchable ? ( this._scrollToSelectedItem = true
<Box relative> }
<Input keepEvent {...getInputProps({ placeholder })} onClick={openMenu} />
<FloatingTriangles> return (
<Triangles /> <Container
</FloatingTriangles> {...getRootProps({ refKey: 'innerRef' })}
</Box> {...props}
) : ( onKeyDown={() => (this._useKeyboard = true)}
<TriggerBtn {...getButtonProps()} tabIndex={0} horizontal align="center" flow={2}> onKeyUp={() => (this._useKeyboard = false)}
<Box grow> >
{selectedItem && renderSelected ? ( {searchable ? (
renderSelected(selectedItem) <Box relative>
) : ( <Input keepEvent {...getInputProps({ placeholder })} onClick={openMenu} />
<Text color="mouse">{placeholder}</Text> <FloatingTriangles>
)} <Triangles />
</FloatingTriangles>
</Box> </Box>
<Triangles />
</TriggerBtn>
)}
{isOpen &&
(searchable ? (
<Search
value={inputValue}
items={items}
fuseOptions={fuseOptions}
highlight={highlight}
renderHighlight={renderHighlight}
render={items => this.renderItems(items, selectedItem, downshiftProps)}
/>
) : ( ) : (
this.renderItems(items, selectedItem, downshiftProps) <TriggerBtn {...getButtonProps()} tabIndex={0} horizontal align="center" flow={2}>
))} <Box grow>
</Container> {selectedItem && renderSelected ? (
)} renderSelected(selectedItem)
) : (
<Text color="mouse">{placeholder}</Text>
)}
</Box>
<Triangles />
</TriggerBtn>
)}
{isOpen &&
(searchable ? (
<Search
value={inputValue}
items={items}
fuseOptions={fuseOptions}
highlight={highlight}
renderHighlight={renderHighlight}
render={items => this.renderItems(items, selectedItem, downshiftProps)}
/>
) : (
this.renderItems(items, selectedItem, downshiftProps)
))}
</Container>
)
}}
/> />
) )
} }

23
src/styles/global.js

@ -45,17 +45,22 @@ injectGlobal`
font-style: italic; font-style: italic;
} }
::-webkit-scrollbar { .scrollbar-thumb-y {
background-color: rgba(0, 0, 0, 0); width: 5px !important;
width: 6px;
} }
::-webkit-scrollbar:hover { .scrollbar-thumb-x {
background-color: rgba(0, 0, 0, 0.09); height: 5px !important;
} }
::-webkit-scrollbar-thumb:vertical { .scrollbar-track {
background: rgba(0, 0, 0, 0.5); background: transparent !important;
transition: opacity 0.2s ease-in-out !important;
} }
::-webkit-scrollbar-thumb:vertical:active { .scrollbar-track-y {
background: rgba(0, 0, 0, 0.61); right: 2px !important;
width: 5px !important;
}
.scrollbar-track-x {
bottom: 2px !important;
height: 5px !important;
} }
` `

18
yarn.lock

@ -2606,7 +2606,7 @@ core-js@^1.0.0:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.3: core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3:
version "2.5.3" version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
@ -7443,6 +7443,10 @@ react-router@^4.2.0:
prop-types "^15.5.4" prop-types "^15.5.4"
warning "^3.0.0" warning "^3.0.0"
react-smooth-scrollbar@^8.0.6:
version "8.0.6"
resolved "https://registry.yarnpkg.com/react-smooth-scrollbar/-/react-smooth-scrollbar-8.0.6.tgz#179072e6a547b3af589ea303c50fd86366275edc"
react-split-pane@^0.1.74: react-split-pane@^0.1.74:
version "0.1.74" version "0.1.74"
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.74.tgz#cf79fc98b51ab0763fdc778749b810a102b036ca" resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.74.tgz#cf79fc98b51ab0763fdc778749b810a102b036ca"
@ -8184,6 +8188,14 @@ slice-ansi@1.0.0:
dependencies: dependencies:
readable-stream "~1.0.31" readable-stream "~1.0.31"
smooth-scrollbar@^8.2.5:
version "8.2.5"
resolved "https://registry.yarnpkg.com/smooth-scrollbar/-/smooth-scrollbar-8.2.5.tgz#676a2595b1aad97fe0835d2425e403b0d9c70eb3"
dependencies:
core-js "^2.5.1"
lodash-es "^4.17.4"
tslib "^1.7.1"
snapdragon-node@^2.0.1: snapdragon-node@^2.0.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@ -8826,6 +8838,10 @@ truncate-utf8-bytes@^1.0.0:
dependencies: dependencies:
utf8-byte-length "^1.0.1" utf8-byte-length "^1.0.1"
tslib@^1.7.1:
version "1.9.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
tty-browserify@0.0.0: tty-browserify@0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"

Loading…
Cancel
Save