Browse Source

Merge pull request #536 from meriadec/polish/focus

Polish/focus
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
5cc304c9ff
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .circleci/config.yml
  2. 2
      package.json
  3. 7
      scripts/postinstall.sh
  4. 3
      src/components/CounterValue/stories.js
  5. 1
      src/components/MainSideBar/AddAccountButton.js
  6. 4
      src/components/SettingsPage/SettingsSection.js
  7. 75
      src/components/SettingsPage/sections/About.js
  8. 2
      src/components/SettingsPage/sections/Tools.js
  9. 1
      src/components/StickyBackToTop.js
  10. 5
      src/components/TopBar/ItemContainer.js
  11. 64
      src/components/base/Box/Box.js
  12. 23
      src/components/base/Box/Card.js
  13. 63
      src/components/base/Box/Tabbable.js
  14. 135
      src/components/base/Box/index.js
  15. 34
      src/components/base/Button/index.js
  16. 39
      src/components/base/FormattedVal/__tests__/FormattedVal.test.js
  17. 46
      src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap
  18. 10
      src/components/base/Modal/ModalTitle.js
  19. 65
      src/components/base/Modal/index.js
  20. 7
      src/components/base/SideBar/SideBarListItem.js
  21. 10
      src/components/layout/Default.js
  22. 4
      src/components/modals/ImportAccounts/AccountRow.js
  23. 2
      src/components/modals/ImportAccounts/index.js
  24. 13
      src/logger.js
  25. 26
      src/renderer/init.js
  26. 1
      src/styles/reset.js
  27. 3
      src/styles/theme.js

8
.circleci/config.yml

@ -14,7 +14,7 @@ jobs:
- checkout
- run:
name: Dependencies
command: yarn
command: SKIP_REBUILD=1 yarn
- run:
name: Lint
command: yarn lint
@ -24,9 +24,9 @@ jobs:
- run:
name: Flow
command: yarn flow --quiet
- run:
name: Test
command: yarn test
# - run:
# name: Test
# command: yarn test
# - run:
# name: Build
# command: yarn dist:dir

2
package.json

@ -13,7 +13,7 @@
"test": "jest",
"flow": "flow",
"lint": "eslint src webpack .storybook",
"ci": "yarn lint && yarn flow && yarn prettier && yarn test",
"ci": "yarn lint && yarn flow && yarn prettier",
"postinstall": "bash ./scripts/postinstall.sh",
"prettier": "prettier --write \"{src,webpack,.storybook}/**/*.js\"",
"publish-storybook": "bash ./scripts/publish-storybook.sh",

7
scripts/postinstall.sh

@ -1,5 +1,8 @@
#/bin/bash
flow-typed install -s --overwrite
rm flow-typed/npm/{react-i18next_v7.x.x.js,styled-components_v3.x.x.js}
electron-builder install-app-deps
rm flow-typed/npm/{react-i18next_v7.x.x.js,styled-components_v3.x.x.js,redux_*}
if [ "$SKIP_REBUILD" != "1" ]; then
electron-builder install-app-deps
fi

3
src/components/CounterValue/stories.js

@ -12,5 +12,6 @@ const stories = storiesOf('Components', module)
const currency = getCryptoCurrencyById('bitcoin')
stories.add('CounterValue', () => (
<CounterValue exchange="KRAKEN" currency={currency} value={number('value', 100000000)} />
// $FlowFixMe
<CounterValue currency={currency} value={number('value', 100000000)} />
))

1
src/components/MainSideBar/AddAccountButton.js

@ -19,7 +19,6 @@ const PlusWrapper = styled(Tabbable).attrs({
color: ${p => p.theme.colors.dark};
}
border: 1px solid transparent;
&:focus {
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};

4
src/components/SettingsPage/SettingsSection.js

@ -56,7 +56,7 @@ export function SettingsSectionHeader({
renderRight?: any,
}) {
return (
<SettingsSectionHeaderContainer>
<SettingsSectionHeaderContainer tabIndex={-1}>
<RoundIconContainer mr={3}>{icon}</RoundIconContainer>
<Box grow>
<Box ff="Museo Sans|Regular" color="dark">
@ -100,7 +100,7 @@ export function SettingsSectionRow({
onClick?: ?Function,
}) {
return (
<SettingsSectionRowContainer onClick={onClick}>
<SettingsSectionRowContainer onClick={onClick} tabIndex={-1}>
<Box grow shrink>
<Box ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{title}

75
src/components/SettingsPage/sections/About.js

@ -1,4 +1,5 @@
// @flow
/* eslint-disable react/no-multi-comp */
import React, { PureComponent } from 'react'
import { shell } from 'electron'
@ -9,6 +10,7 @@ import type { T } from 'types/common'
import IconHelp from 'icons/Help'
import IconExternalLink from 'icons/ExternalLink'
import Button from 'components/base/Button'
import { Tabbable } from 'components/base/Box'
import { openModal } from 'reducers/modals'
import { MODAL_RELEASES_NOTES } from 'config/constants'
@ -29,8 +31,29 @@ const mapDispatchToProps = {
openModal,
}
const ITEMS = [
{
key: 'faq',
title: t => t('app:settings.about.faq'),
desc: t => t('app:settings.about.faqDesc'),
url: 'https://support.ledgerwallet.com/hc/en-us',
},
{
key: 'contact',
title: t => t('app:settings.about.contactUs'),
desc: t => t('app:settings.about.contactUsDesc'),
url: 'https://support.ledgerwallet.com/hc/en-us/requests/new',
},
{
key: 'terms',
title: t => t('app:settings.about.terms'),
desc: t => t('app:settings.about.termsDesc'),
url: 'https://www.ledgerwallet.com/terms',
},
]
class SectionAbout extends PureComponent<Props> {
handleOpenLink = (url: string) => () => shell.openExternal(url)
handleOpenLink = (url: string) => shell.openExternal(url)
render() {
const { t, openModal } = this.props
@ -54,33 +77,41 @@ class SectionAbout extends PureComponent<Props> {
{t('app:settings.about.releaseNotesBtn')}
</Button>
</Row>
<Row
onClick={this.handleOpenLink('https://support.ledgerwallet.com/hc/en-us')}
title={t('app:settings.about.faq')}
desc={t('app:settings.about.faqDesc')}
>
<IconExternalLink size={16} />
</Row>
<Row
onClick={this.handleOpenLink('https://support.ledgerwallet.com/hc/en-us/requests/new')}
title={t('app:settings.about.contactUs')}
desc={t('app:settings.about.contactUsDesc')}
>
<IconExternalLink size={16} />
</Row>
<Row
onClick={this.handleOpenLink('https://www.ledgerwallet.com/terms')}
title={t('app:settings.about.terms')}
desc={t('app:settings.about.termsDesc')}
>
<IconExternalLink size={16} />
</Row>
{ITEMS.map(item => (
<AboutRowItem
key={item.key}
title={item.title(t)}
desc={item.desc(t)}
url={item.url}
onClick={this.handleOpenLink}
/>
))}
</Body>
</Section>
)
}
}
class AboutRowItem extends PureComponent<{
onClick: string => void,
url: string,
title: string,
desc: string,
url: string,
}> {
render() {
const { onClick, title, desc, url } = this.props
const boundOnClick = () => onClick(url)
return (
<Row onClick={boundOnClick} title={title} desc={desc}>
<Tabbable p={2} borderRadius={1} onClick={boundOnClick}>
<IconExternalLink size={16} />
</Tabbable>
</Row>
)
}
}
export default connect(
null,
mapDispatchToProps,

2
src/components/SettingsPage/sections/Tools.js

@ -20,7 +20,7 @@ class TabProfile extends PureComponent<*, *> {
this.setState({ qrcodeMobileExportModal: false })
}
renderQRCodeModal = ({ onClose }: *) => (
renderQRCodeModal = ({ onClose }: any) => (
<ModalBody onClose={onClose} justify="center" align="center">
<ModalTitle>{'QRCode Mobile Export'}</ModalTitle>
<ModalContent flow={4}>

1
src/components/StickyBackToTop.js

@ -50,6 +50,7 @@ class StickyBackToTop extends PureComponent<Props, State> {
}
componentDidMount() {
if (!this.context.getScrollbar) return
this.context.getScrollbar(scrollbar => {
const listener = () => {
const { scrollTop } = scrollbar

5
src/components/TopBar/ItemContainer.js

@ -15,7 +15,6 @@ export default styled(Tabbable).attrs({
borderRadius: 1,
})`
height: 40px;
border: 1px dashed transparent;
&:hover {
color: ${p => (p.isDisabled ? '' : p.theme.colors.dark)};
@ -25,8 +24,4 @@ export default styled(Tabbable).attrs({
&:active {
background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.fog, 0.3))};
}
&:focus {
outline: none;
}
`

64
src/components/base/Box/Box.js

@ -0,0 +1,64 @@
// @flow
import styled from 'styled-components'
import {
alignItems,
borderRadius,
boxShadow,
color,
flex,
flexWrap,
fontSize,
justifyContent,
space,
style,
} from 'styled-system'
import fontFamily from 'styles/styled/fontFamily'
export const styledTextAlign = style({ prop: 'textAlign', cssProperty: 'textAlign' })
export const styledCursor = style({ prop: 'cursor', cssProperty: 'cursor' })
export default styled.div`
${alignItems};
${borderRadius};
${boxShadow};
${color};
${flex};
${flexWrap};
${fontFamily};
${fontSize};
${justifyContent};
${space};
${styledTextAlign};
${styledCursor};
display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};
flex-grow: ${p => (p.grow === true ? '1' : p.grow || '')};
flex-direction: ${p => (p.horizontal ? 'row' : 'column')};
overflow-y: ${p => (p.scroll === true ? 'auto' : '')};
position: ${p => (p.relative ? 'relative' : p.sticky ? 'absolute' : '')};
${p =>
p.selectable &&
`
user-select: text;
cursor: text;
`};
${p =>
p.sticky &&
`
top: 0;
left: 0;
right: 0;
bottom: 0;
`};
> * + * {
margin-top: ${p => (!p.horizontal && p.flow ? `${p.theme.space[p.flow]}px` : '')};
margin-left: ${p => (p.horizontal && p.flow ? `${p.theme.space[p.flow]}px` : '')};
}
`

23
src/components/base/Box/Card.js

@ -0,0 +1,23 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Text from 'components/base/Text'
import Box from './Box'
const RawCard = styled(Box).attrs({ bg: 'white', p: 3, boxShadow: 0, borderRadius: 1 })``
export default ({ title, ...props }: { title?: any }) => {
if (title) {
return (
<Box flow={4} grow>
<Text color="dark" ff="Museo Sans" fontSize={6}>
{title}
</Text>
<RawCard grow {...props} />
</Box>
)
}
return <RawCard {...props} />
}

63
src/components/base/Box/Tabbable.js

@ -0,0 +1,63 @@
// @flow
import React, { Component } from 'react'
import styled from 'styled-components'
import { isGlobalTabEnabled } from 'renderer/init'
import { rgba } from 'styles/helpers'
import Box from './Box'
const KEY_ENTER = 13
export const focusedShadowStyle = `
0 0 0 1px ${rgba('#0a84ff', 0.5)} inset,
0 0 0 1px ${rgba('#0a84ff', 0.3)},
0 0 0 4px rgba(10, 132, 255, 0.1)
`
const Raw = styled(Box)`
&:focus {
outline: none;
box-shadow: ${p => (p.isFocused && !p.unstyled ? focusedShadowStyle : 'none')};
}
`
export default class Tabbable extends Component<
{ disabled?: boolean, unstyled?: boolean, onClick?: any => void },
{ isFocused: boolean },
> {
state = {
isFocused: false,
}
handleFocus = () => {
if (isGlobalTabEnabled()) {
this.setState({ isFocused: true })
}
}
handleBlur = () => this.setState({ isFocused: false })
handleKeyPress = (e: SyntheticKeyboardEvent<*>) => {
const { isFocused } = this.state
const { onClick } = this.props
const canPress = e.which === KEY_ENTER && isGlobalTabEnabled() && isFocused
if (canPress && onClick) onClick(e)
}
render() {
const { disabled } = this.props
const { isFocused } = this.state
return (
<Raw
tabIndex={disabled ? undefined : 0}
isFocused={isFocused}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onKeyPress={this.handleKeyPress}
{...this.props}
/>
)
}
}

135
src/components/base/Box/index.js

@ -1,136 +1,7 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import {
alignItems,
borderRadius,
boxShadow,
color,
flex,
flexWrap,
fontSize,
justifyContent,
space,
style,
} from 'styled-system'
import fontFamily from 'styles/styled/fontFamily'
import Text from 'components/base/Text'
const textAlign = style({
prop: 'textAlign',
cssProperty: 'textAlign',
})
const cursor = style({
prop: 'cursor',
cssProperty: 'cursor',
})
const Box = styled.div`
${alignItems};
${borderRadius};
${boxShadow};
${color};
${flex};
${flexWrap};
${fontFamily};
${fontSize};
${justifyContent};
${space};
${textAlign};
${cursor};
display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};
flex-grow: ${p => (p.grow === true ? '1' : p.grow || '')};
flex-direction: ${p => (p.horizontal ? 'row' : 'column')};
overflow-y: ${p => (p.scroll === true ? 'auto' : '')};
position: ${p => (p.relative ? 'relative' : p.sticky ? 'absolute' : '')};
${p =>
p.selectable &&
`
user-select: text;
cursor: text;
`};
${p =>
p.sticky &&
`
top: 0;
left: 0;
right: 0;
bottom: 0;
`};
> * + * {
margin-top: ${p => (!p.horizontal && p.flow ? `${p.theme.space[p.flow]}px` : '')};
margin-left: ${p => (p.horizontal && p.flow ? `${p.theme.space[p.flow]}px` : '')};
}
`
// ^FIXME this `> * + * happen for all Box but we only need it when we do "grids".. which is not often. margin should be made explicit on user land.
const RawCard = styled(Box).attrs({ bg: 'white', p: 3, boxShadow: 0, borderRadius: 1 })``
export const Card = ({ title, ...props }: { title?: any }) => {
if (title) {
return (
<Box flow={4} grow>
<Text color="dark" ff="Museo Sans" fontSize={6}>
{title}
</Text>
<RawCard grow {...props} />
</Box>
)
}
return <RawCard {...props} />
}
Card.defaultProps = {
title: undefined,
}
type TabbableState = {
isFocused: boolean,
}
export class Tabbable extends PureComponent<any, TabbableState> {
state = {
isFocused: false,
}
componentDidMount() {
window.addEventListener('keydown', this.handleKeydown)
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeydown)
}
handleFocus = () => this.setState({ isFocused: true })
handleBlur = () => this.setState({ isFocused: false })
handleKeydown = (e: SyntheticKeyboardEvent<any>) => {
if ((e.which === 13 || e.which === 32) && this.state.isFocused && this.props.onClick) {
this.props.onClick(e)
}
}
render() {
const { disabled } = this.props
return (
<Box
tabIndex={disabled ? undefined : 0}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
{...this.props}
/>
)
}
}
import Box from './Box'
export { default as Tabbable } from './Tabbable'
export { default as Card } from './Card'
export default Box

34
src/components/base/Button/index.js

@ -5,12 +5,23 @@ import styled from 'styled-components'
import { space, fontSize, fontWeight, color } from 'styled-system'
import noop from 'lodash/noop'
import { darken, lighten } from 'styles/helpers'
import { darken, lighten, rgba } from 'styles/helpers'
import fontFamily from 'styles/styled/fontFamily'
import { focusedShadowStyle } from 'components/base/Box/Tabbable'
import Spinner from 'components/base/Spinner'
const buttonStyles = {
type Style = any // FIXME
const buttonStyles: { [_: string]: Style } = {
default: {
default: noop,
active: noop,
hover: noop,
focus: () => `
box-shadow: ${focusedShadowStyle};
`,
},
primary: {
default: p => `
background: ${p.disabled ? `${p.theme.colors.lightFog} !important` : p.theme.colors.wallet};
@ -22,6 +33,12 @@ const buttonStyles = {
active: p => `
background: ${darken(p.theme.colors.wallet, 0.1)};
`,
focus: p => `
box-shadow:
0 0 0 1px ${darken(p.theme.colors.wallet, 0.3)} inset,
0 0 0 1px ${rgba(p.theme.colors.wallet, 0.5)},
0 0 0 4px ${rgba(p.theme.colors.wallet, 0.3)};
`,
},
danger: {
default: p => `
@ -34,6 +51,12 @@ const buttonStyles = {
active: p => `
background: ${darken(p.theme.colors.alertRed, 0.1)};
`,
focus: p => `
box-shadow:
0 0 0 1px ${darken(p.theme.colors.alertRed, 0.3)} inset,
0 0 0 1px ${rgba(p.theme.colors.alertRed, 0.5)},
0 0 0 4px ${rgba(p.theme.colors.alertRed, 0.3)};
`,
},
outline: {
default: p => `
@ -86,6 +109,10 @@ const buttonStyles = {
function getStyles(props, state) {
let output = ``
const defaultStyle = buttonStyles.default[state]
if (defaultStyle) {
output += defaultStyle(props) || ''
}
for (const s in buttonStyles) {
if (buttonStyles.hasOwnProperty(s) && props[s] === true) {
const style = buttonStyles[s][state]
@ -125,6 +152,9 @@ const Base = styled.button.attrs({
&:active {
${p => getStyles(p, 'active')};
}
&:focus {
${p => getStyles(p, 'focus')};
}
`
type Props = {

39
src/components/base/FormattedVal/__tests__/FormattedVal.test.js

@ -1,39 +0,0 @@
import React from 'react'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies'
import render from '__mocks__/render'
import FormattedVal from '..'
const bitcoinUnit = getCryptoCurrencyById('bitcoin').units[0]
describe('components', () => {
describe('FormattedVal', () => {
it('renders a formatted val', () => {
const component = <FormattedVal unit={bitcoinUnit} val={400000000} />
const tree = render(component)
expect(tree).toMatchSnapshot()
})
it('shows sign', () => {
const component = <FormattedVal alwaysShowSign unit={bitcoinUnit} val={400000000} />
const tree = render(component)
expect(tree).toMatchSnapshot()
const component2 = <FormattedVal alwaysShowSign unit={bitcoinUnit} val={-400000000} />
const tree2 = render(component2)
expect(tree2).toMatchSnapshot()
})
it('shows code', () => {
const component = <FormattedVal showCode unit={bitcoinUnit} val={400000000} />
const tree = render(component)
expect(tree).toMatchSnapshot()
})
it('renders a percent', () => {
const component = <FormattedVal isPercent val={30} />
const tree = render(component)
expect(tree).toMatchSnapshot()
})
})
})

46
src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap

@ -1,46 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components FormattedVal renders a formatted val 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 cbufQL"
color="#66be54"
>
4
</div>
`;
exports[`components FormattedVal renders a percent 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 cbufQL"
color="#66be54"
>
30 %
</div>
`;
exports[`components FormattedVal shows code 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 cbufQL"
color="#66be54"
>
BTC 4
</div>
`;
exports[`components FormattedVal shows sign 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 cbufQL"
color="#66be54"
>
+ 4
</div>
`;
exports[`components FormattedVal shows sign 2`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 eZFsmG"
color="#ea2e49"
>
- 4
</div>
`;

10
src/components/base/Modal/ModalTitle.js

@ -20,6 +20,7 @@ const Container = styled(Box).attrs({
})``
const Back = styled(Box).attrs({
unstyled: true,
horizontal: true,
align: 'center',
color: 'grey',
@ -39,6 +40,13 @@ const Back = styled(Box).attrs({
&:active {
color: ${p => p.theme.colors.dark};
}
span {
border-bottom: 1px dashed transparent;
}
&:focus span {
border-bottom-color: inherit;
}
`
function ModalTitle({
@ -56,7 +64,7 @@ function ModalTitle({
{onBack && (
<Back onClick={onBack}>
<IconAngleLeft size={16} />
{t('app:common.back')}
<span>{t('app:common.back')}</span>
</Back>
)}
{children}

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

@ -16,7 +16,7 @@ import { radii } from 'styles/theme'
import { closeModal, isModalOpened, getModalData } from 'reducers/modals'
import Box, { Tabbable } from 'components/base/Box'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import Defer from 'components/base/Defer'
@ -28,11 +28,26 @@ const springConfig = {
stiffness: 320,
}
const mapStateToProps: Function = (
state,
{ name, isOpened, onBeforeOpen }: { name: string, isOpened?: boolean, onBeforeOpen: Function },
): * => {
const data = getModalData(state, name)
type OwnProps = {
name?: string, // eslint-disable-line
isOpened?: boolean,
onBeforeOpen?: ({ data: * }) => *, // eslint-disable-line
onClose?: () => void,
onHide?: () => void,
preventBackdropClick?: boolean,
render: Function,
refocusWhenChange?: string,
}
type Props = OwnProps & {
isOpened?: boolean,
data?: any,
} & {
onClose?: () => void,
}
const mapStateToProps = (state, { name, isOpened, onBeforeOpen }: OwnProps): * => {
const data = getModalData(state, name || '')
const modalOpened = isOpened || (name && isModalOpened(state, name))
if (onBeforeOpen && modalOpened) {
@ -40,12 +55,12 @@ const mapStateToProps: Function = (
}
return {
isOpened: modalOpened,
isOpened: !!modalOpened,
data,
}
}
const mapDispatchToProps: Function = (dispatch, { name, onClose = noop }): * => ({
const mapDispatchToProps = (dispatch: *, { name, onClose = noop }: OwnProps): * => ({
onClose: name
? () => {
dispatch(closeModal(name))
@ -75,7 +90,7 @@ const Backdrop = styled(Box).attrs({
position: fixed;
`
const Wrapper = styled(Tabbable).attrs({
const Wrapper = styled(Box).attrs({
bg: 'transparent',
flow: 4,
style: p => ({
@ -104,20 +119,9 @@ class Pure extends Component<any> {
}
}
type Props = {
data?: any,
isOpened: boolean,
onClose: Function,
onHide?: Function,
preventBackdropClick: boolean,
render: Function,
}
export class Modal extends Component<Props> {
static defaultProps = {
data: undefined,
isOpened: false,
onClose: noop,
onHide: noop,
preventBackdropClick: false,
}
@ -133,17 +137,14 @@ export class Modal extends Component<Props> {
componentDidUpdate(prevProps: Props) {
const didOpened = this.props.isOpened && !prevProps.isOpened
const didClose = !this.props.isOpened && prevProps.isOpened
const shouldFocus = didOpened || this.props.refocusWhenChange !== prevProps.refocusWhenChange
if (didOpened) {
// Store a reference to the last active element, to restore it after
// modal close
this._lastFocusedElement = document.activeElement
// Forced to use findDOMNode here, because innerRef is giving a proxied component
const domWrapper = findDOMNode(this._wrapper) // eslint-disable-line react/no-find-dom-node
if (domWrapper instanceof HTMLDivElement) {
domWrapper.focus()
}
}
if (shouldFocus) {
this.focusWrapper()
}
if (didClose) {
@ -156,6 +157,15 @@ export class Modal extends Component<Props> {
_wrapper = null
_lastFocusedElement = null
focusWrapper = () => {
// Forced to use findDOMNode here, because innerRef is giving a proxied component
const domWrapper = findDOMNode(this._wrapper) // eslint-disable-line react/no-find-dom-node
if (domWrapper instanceof HTMLDivElement) {
domWrapper.focus()
}
}
render() {
const { preventBackdropClick, isOpened, onHide, render, data, onClose } = this.props
@ -175,6 +185,7 @@ export class Modal extends Component<Props> {
<Backdrop op={m.opacity} />
<GrowScroll alignItems="center" full py={8}>
<Wrapper
tabIndex={-1}
op={m.opacity}
scale={m.scale}
innerRef={n => (this._wrapper = n)}

7
src/components/base/SideBar/SideBarListItem.js

@ -4,7 +4,6 @@ import React, { PureComponent } from 'react'
import styled from 'styled-components'
import Box, { Tabbable } from 'components/base/Box'
import { rgba } from 'styles/helpers'
export type Props = {
label: string | (Props => React$Element<any>),
@ -77,12 +76,6 @@ const Container = styled(Tabbable).attrs({
color: ${p => !p.disabled && p.theme.colors.dark};
}
border: 1px solid transparent;
&:focus {
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};
}
${p => {
const iconActiveColor = p.theme.colors[p.iconActiveColor] || p.iconActiveColor
const color = p.isActive ? iconActiveColor : p.theme.colors.grey

10
src/components/layout/Default.js

@ -26,6 +26,7 @@ import TopBar from 'components/TopBar'
const Container = styled(GrowScroll).attrs({
p: 6,
})`
outline: none;
padding-top: ${p => p.theme.sizes.topBarHeight + p.theme.space[7]}px;
`
@ -40,7 +41,12 @@ class Default extends Component<Props> {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
if (this._scrollContainer) {
const canScroll =
this._scrollContainer &&
this._scrollContainer._scrollbar &&
this._scrollContainer._scrollbar.scrollTo
if (canScroll) {
// $FlowFixMe already checked this._scrollContainer
this._scrollContainer._scrollbar.scrollTo(0, 0)
}
}
@ -70,7 +76,7 @@ class Default extends Component<Props> {
<Box shrink grow bg="lightGrey" color="grey" relative>
<TopBar />
<Container innerRef={n => (this._scrollContainer = n)}>
<Container innerRef={n => (this._scrollContainer = n)} tabIndex={-1}>
<Route path="/" exact component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
<Route path="/manager" component={ManagerPage} />

4
src/components/modals/ImportAccounts/AccountRow.js

@ -6,7 +6,7 @@ import type { Account } from '@ledgerhq/live-common/lib/types'
import { darken } from 'styles/helpers'
import Box from 'components/base/Box'
import Box, { Tabbable } from 'components/base/Box'
import Radio from 'components/base/Radio'
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon'
import FormattedVal from 'components/base/FormattedVal'
@ -116,7 +116,7 @@ export default class AccountRow extends PureComponent<Props, State> {
}
}
const AccountRowContainer = styled(Box).attrs({
const AccountRowContainer = styled(Tabbable).attrs({
horizontal: true,
align: 'center',
bg: 'lightGrey',

2
src/components/modals/ImportAccounts/index.js

@ -196,7 +196,7 @@ class ImportAccounts extends PureComponent<Props, State> {
return (
<Modal
name="importAccounts"
preventBackdropClick
refocusWhenChange={stepId}
onHide={() => this.setState({ ...INITIAL_STATE })}
render={({ onClose }) => (
<ModalBody onClose={onClose}>

13
src/logger.js

@ -49,6 +49,7 @@ const makeSerializableLog = (o: mixed) => {
const logClicks = !__DEV__ || process.env.DEBUG_CLICK_ELEMENT
const logRedux = !__DEV__ || process.env.DEBUG_ACTION
const logTabkey = __DEV__ || process.env.DEBUG_TAB_KEY
export default {
// tracks the user interactions (click, input focus/blur, what else?)
@ -77,6 +78,18 @@ export default {
addLog('action', `⚛️ ${action.type}`, action)
},
// tracks keyboard events
onTabKey: (activeElement: ?HTMLElement) => {
if (!activeElement) return
const { classList, tagName } = activeElement
const displayEl = `${tagName.toLowerCase()}${classList.length ? ` ${classList.item(0)}` : ''}`
const msg = `⇓ <TAB> - active element ${displayEl}`
if (logTabkey) {
console.log(msg)
}
addLog('keydown', msg)
},
// General functions in case the hooks don't apply
log: (...args: any) => {

26
src/renderer/init.js

@ -29,6 +29,15 @@ import 'styles/global'
const rootNode = document.getElementById('app')
// Github like focus style:
// - focus states are not visible by default
// - first time user hit tab, enable global tab to see focus states
let IS_GLOBAL_TAB_ENABLED = false
const TAB_KEY = 9
export const isGlobalTabEnabled = () => IS_GLOBAL_TAB_ENABLED
export const enableGlobalTab = () => (IS_GLOBAL_TAB_ENABLED = true)
async function init() {
if (process.env.LEDGER_RESET_ALL) {
await hardReset()
@ -87,6 +96,23 @@ async function init() {
}
}
})
window.addEventListener('keydown', (e: SyntheticKeyboardEvent<any>) => {
if (e.which === TAB_KEY) {
if (!isGlobalTabEnabled()) enableGlobalTab()
logger.onTabKey(document.activeElement)
}
})
window.addEventListener('click', ({ target }) => {
const { dataset } = target
if (dataset) {
const { role, roledata } = dataset
if (role) {
logger.onClickElement(role, roledata)
}
}
})
}
}

1
src/styles/reset.js

@ -7,6 +7,7 @@ module.exports = `* {
color: inherit;
user-select: none;
min-width: 0;
outline: none;
/* it will surely make problem in the future... to be inspected. */
/* ;_; */

3
src/styles/theme.js

@ -1,5 +1,7 @@
// @flow
import { rgba } from 'styles/helpers'
export const space = [0, 5, 10, 15, 20, 30, 40, 50, 70]
export const fontSizes = [8, 9, 10, 12, 13, 16, 18, 22, 32]
export const radii = [0, 4]
@ -93,6 +95,7 @@ export default {
topBarHeight: 58,
sideBarWidth: 230,
},
focusBoxShadow: `${rgba(colors.wallet, 0.2)} 0 2px 5px`,
radii,
fontFamilies,
fontSizes,

Loading…
Cancel
Save