diff --git a/documentation-site/components/yard/config/radio.ts b/documentation-site/components/yard/config/radio.ts index 5a6337232a..64661f2c78 100644 --- a/documentation-site/components/yard/config/radio.ts +++ b/documentation-site/components/yard/config/radio.ts @@ -72,6 +72,13 @@ const RadioGroupConfig: TConfig = { 'baseui/radio': {named: ['Radio']}, }, }, + name: { + value: 'number', + type: PropTypes.String, + description: + 'String value for the name of RadioGroup, it is used to group buttons. If missed default is random ID string.', + hidden: false, + }, align: { value: 'ALIGN.vertical', type: PropTypes.Enum, @@ -94,13 +101,6 @@ const RadioGroupConfig: TConfig = { type: PropTypes.Boolean, description: 'Sets radio group into error state.', }, - name: { - value: undefined, - type: PropTypes.String, - description: - 'String value for the name of RadioGroup, it is used to group buttons. If missed default is random ID string.', - hidden: true, - }, required: { value: false, type: PropTypes.Boolean, diff --git a/documentation-site/components/yard/editor.tsx b/documentation-site/components/yard/editor.tsx index be3eab1813..2f081261e1 100644 --- a/documentation-site/components/yard/editor.tsx +++ b/documentation-site/components/yard/editor.tsx @@ -84,6 +84,7 @@ const Editor: React.FC = ({ }} /> highlightCode(code, editorTheme, transformToken)} diff --git a/documentation-site/components/yard/index.tsx b/documentation-site/components/yard/index.tsx index 73d65a4dde..bb9946c5ce 100644 --- a/documentation-site/components/yard/index.tsx +++ b/documentation-site/components/yard/index.tsx @@ -16,7 +16,7 @@ import { ThemeProvider, } from 'baseui'; import {Card} from 'baseui/card'; -import {Spinner} from 'baseui/spinner'; +import {StyledSpinnerNext as Spinner} from 'baseui/spinner'; import {useRouter} from 'next/router'; import {useView, Compiler, Error} from 'react-view'; diff --git a/documentation-site/components/yard/knob.tsx b/documentation-site/components/yard/knob.tsx index 55014102b3..90868fd71e 100644 --- a/documentation-site/components/yard/knob.tsx +++ b/documentation-site/components/yard/knob.tsx @@ -161,7 +161,7 @@ const Knob: React.SFC<{ {numberOfOptions < 7 ? ( { const [value, setValue] = React.useState('1'); return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/basic.tsx b/documentation-site/examples/radio/basic.tsx index 38f5dd4329..43a2060cbc 100644 --- a/documentation-site/examples/radio/basic.tsx +++ b/documentation-site/examples/radio/basic.tsx @@ -5,7 +5,7 @@ export default () => { const [value, setValue] = React.useState('1'); return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/disabled.js b/documentation-site/examples/radio/disabled.js index 49f193bafc..30de69b245 100644 --- a/documentation-site/examples/radio/disabled.js +++ b/documentation-site/examples/radio/disabled.js @@ -3,7 +3,7 @@ import * as React from 'react'; import {Radio, RadioGroup} from 'baseui/radio'; export default () => ( - + Checked Unchecked diff --git a/documentation-site/examples/radio/disabled.tsx b/documentation-site/examples/radio/disabled.tsx index a862421d06..6ada7c8465 100644 --- a/documentation-site/examples/radio/disabled.tsx +++ b/documentation-site/examples/radio/disabled.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import {Radio, RadioGroup} from 'baseui/radio'; export default () => ( - + Checked Unchecked diff --git a/documentation-site/examples/radio/error.js b/documentation-site/examples/radio/error.js index 1847884443..df05c694e5 100644 --- a/documentation-site/examples/radio/error.js +++ b/documentation-site/examples/radio/error.js @@ -7,7 +7,7 @@ export default () => { return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/error.tsx b/documentation-site/examples/radio/error.tsx index ac970a6544..f82665d727 100644 --- a/documentation-site/examples/radio/error.tsx +++ b/documentation-site/examples/radio/error.tsx @@ -6,7 +6,7 @@ export default () => { return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/horizontal-align.js b/documentation-site/examples/radio/horizontal-align.js index aed61b4369..4a7c7bc635 100644 --- a/documentation-site/examples/radio/horizontal-align.js +++ b/documentation-site/examples/radio/horizontal-align.js @@ -7,7 +7,7 @@ export default () => { return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/horizontal-align.tsx b/documentation-site/examples/radio/horizontal-align.tsx index e0289ea35c..dc3df59246 100644 --- a/documentation-site/examples/radio/horizontal-align.tsx +++ b/documentation-site/examples/radio/horizontal-align.tsx @@ -6,7 +6,7 @@ export default () => { return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/overrides.js b/documentation-site/examples/radio/overrides.js index 98b2f11d1b..cf0db63d1e 100644 --- a/documentation-site/examples/radio/overrides.js +++ b/documentation-site/examples/radio/overrides.js @@ -7,7 +7,7 @@ export default () => { const [value, setValue] = React.useState('1'); return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/overrides.tsx b/documentation-site/examples/radio/overrides.tsx index 4767c7dde4..ca696bd709 100644 --- a/documentation-site/examples/radio/overrides.tsx +++ b/documentation-site/examples/radio/overrides.tsx @@ -6,7 +6,7 @@ export default () => { const [value, setValue] = React.useState('1'); return ( setValue(e.target.value)} value={value} > diff --git a/documentation-site/examples/radio/stateful.js b/documentation-site/examples/radio/stateful.js index b9ad47283f..24ddafcb03 100644 --- a/documentation-site/examples/radio/stateful.js +++ b/documentation-site/examples/radio/stateful.js @@ -3,10 +3,7 @@ import * as React from 'react'; import {Radio, StatefulRadioGroup} from 'baseui/radio'; export default () => ( - + First Second Third diff --git a/documentation-site/examples/radio/stateful.tsx b/documentation-site/examples/radio/stateful.tsx index b2bd8d4d2c..49821d57cb 100644 --- a/documentation-site/examples/radio/stateful.tsx +++ b/documentation-site/examples/radio/stateful.tsx @@ -2,10 +2,7 @@ import * as React from 'react'; import {Radio, StatefulRadioGroup} from 'baseui/radio'; export default () => ( - + First Second Third diff --git a/package.json b/package.json index 3c02c8ff53..8b27788afa 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,9 @@ "@octokit/rest": "^16.33.1", "@storybook/react": "^5.0.0", "@svgr/cli": "^4.3.2", + "@testing-library/jest-dom": "^5.1.1", "@testing-library/react": "^9.1.4", + "@testing-library/user-event": "^8.1.0", "@types/babel__code-frame": "^7.0.1", "@types/babel__core": "^7.1.3", "@types/babel__generator": "^7.0.2", diff --git a/src/button/button.js b/src/button/button.js index 206a1a98c6..fd818a74ab 100644 --- a/src/button/button.js +++ b/src/button/button.js @@ -15,12 +15,17 @@ import {getSharedProps} from './utils.js'; import ButtonInternals from './button-internals.js'; import {defaultProps} from './default-props.js'; import {getOverrides} from '../helpers/overrides.js'; +import {isFocusVisible, forkFocus, forkBlur} from '../utils/focusVisible.js'; import type {ButtonPropsT} from './types.js'; -// eslint-disable-next-line flowtype/no-weak-types -class Button extends React.Component { +class Button extends React.Component< + // eslint-disable-next-line flowtype/no-weak-types + ButtonPropsT & {forwardedRef: any}, + {isFocusVisible: boolean}, +> { static defaultProps = defaultProps; + state = {isFocusVisible: false}; internalOnClick = (...args: *) => { const {isLoading, onClick} = this.props; @@ -30,6 +35,18 @@ class Button extends React.Component { onClick && onClick(...args); }; + handleFocus = (event: SyntheticEvent<>) => { + if (isFocusVisible(event)) { + this.setState({isFocusVisible: true}); + } + }; + + handleBlur = (event: SyntheticEvent<>) => { + if (this.state.isFocusVisible !== false) { + this.setState({isFocusVisible: false}); + } + }; + render() { const { overrides = {}, @@ -61,7 +78,10 @@ class Button extends React.Component { overrides.LoadingSpinnerContainer, StyledLoadingSpinnerContainer, ); - const sharedProps = getSharedProps(this.props); + const sharedProps = { + ...getSharedProps(this.props), + $isFocusVisible: this.state.isFocusVisible, + }; return ( { {...baseButtonProps} // Applies last to override passed in onClick onClick={this.internalOnClick} + onFocus={forkFocus(baseButtonProps, this.handleFocus)} + onBlur={forkBlur(baseButtonProps, this.handleBlur)} > {isLoading ? ( diff --git a/src/button/styled-components.js b/src/button/styled-components.js index 6700599553..e26ef4dc86 100644 --- a/src/button/styled-components.js +++ b/src/button/styled-components.js @@ -11,7 +11,16 @@ import type {SharedStylePropsT} from './types.js'; export const BaseButton = styled( 'button', - ({$theme, $size, $kind, $shape, $isLoading, $isSelected, $disabled}) => ({ + ({ + $theme, + $size, + $kind, + $shape, + $isLoading, + $isSelected, + $disabled, + $isFocusVisible, + }) => ({ display: 'inline-flex', // need to maintain button width while showing loading spinner flexDirection: $isLoading ? 'column' : 'row', @@ -25,8 +34,9 @@ export const BaseButton = styled( borderTopStyle: 'none', borderRightStyle: 'none', borderBottomStyle: 'none', + outline: $isFocusVisible ? `3px solid ${$theme.colors.accent}` : 'none', + outlineOffset: '-3px', textDecoration: 'none', - outline: 'none', WebkitAppearance: 'none', transitionProperty: 'background', transitionDuration: $theme.animation.timing100, @@ -258,11 +268,6 @@ function getKindStyles({$theme, $isLoading, $isSelected, $kind, $disabled}) { ? $theme.colors.buttonPrimaryActive : $theme.colors.buttonPrimaryHover, }, - ':focus': { - backgroundColor: $isLoading - ? $theme.colors.buttonPrimaryActive - : $theme.colors.buttonPrimaryHover, - }, ':active': { backgroundColor: $theme.colors.buttonPrimaryActive, }, @@ -282,11 +287,6 @@ function getKindStyles({$theme, $isLoading, $isSelected, $kind, $disabled}) { ? $theme.colors.buttonSecondaryActive : $theme.colors.buttonSecondaryHover, }, - ':focus': { - backgroundColor: $isLoading - ? $theme.colors.buttonSecondaryActive - : $theme.colors.buttonSecondaryHover, - }, ':active': { backgroundColor: $theme.colors.buttonSecondaryActive, }, @@ -306,11 +306,6 @@ function getKindStyles({$theme, $isLoading, $isSelected, $kind, $disabled}) { ? $theme.colors.buttonTertiaryActive : $theme.colors.buttonTertiaryHover, }, - ':focus': { - backgroundColor: $isLoading - ? $theme.colors.buttonTertiaryActive - : $theme.colors.buttonTertiaryHover, - }, ':active': { backgroundColor: $theme.colors.buttonTertiaryActive, }, @@ -330,11 +325,6 @@ function getKindStyles({$theme, $isLoading, $isSelected, $kind, $disabled}) { ? $theme.colors.buttonMinimalActive : $theme.colors.buttonMinimalHover, }, - ':focus': { - backgroundColor: $isLoading - ? $theme.colors.buttonMinimalActive - : $theme.colors.buttonMinimalHover, - }, ':active': { backgroundColor: $theme.colors.buttonMinimalActive, }, diff --git a/src/button/types.js b/src/button/types.js index efce87b79e..dfd2810425 100644 --- a/src/button/types.js +++ b/src/button/types.js @@ -45,4 +45,5 @@ export type SharedStylePropsT = { $size?: $Keys, $isLoading?: boolean, $disabled?: boolean, + $isFocusVisible: boolean, }; diff --git a/src/checkbox/checkbox.js b/src/checkbox/checkbox.js index 1fe7f879ec..90f562bb58 100644 --- a/src/checkbox/checkbox.js +++ b/src/checkbox/checkbox.js @@ -18,6 +18,7 @@ import { ToggleTrack as StyledToggleTrack, } from './styled-components.js'; import {STYLE_TYPE} from './constants.js'; +import {isFocusVisible} from '../utils/focusVisible.js'; class StatelessCheckbox extends React.Component { static defaultProps: DefaultPropsT = { @@ -41,6 +42,7 @@ class StatelessCheckbox extends React.Component { state = { isFocused: this.props.autoFocus || false, + isFocusVisible: false, isHovered: false, isActive: false, }; @@ -83,11 +85,17 @@ class StatelessCheckbox extends React.Component { onFocus = (e: SyntheticInputEvent) => { this.setState({isFocused: true}); this.props.onFocus(e); + if (isFocusVisible(e)) { + this.setState({isFocusVisible: true}); + } }; onBlur = (e: SyntheticInputEvent) => { this.setState({isFocused: false}); this.props.onBlur(e); + if (this.state.isFocusVisible !== false) { + this.setState({isFocusVisible: false}); + } }; isToggle = () => { @@ -147,6 +155,7 @@ class StatelessCheckbox extends React.Component { }; const sharedProps = { $isFocused: this.state.isFocused, + $isFocusVisible: this.state.isFocusVisible, $isHovered: this.state.isHovered, $isActive: this.state.isActive, $isError: isError, diff --git a/src/checkbox/styled-components.js b/src/checkbox/styled-components.js index 0012a62d12..a72fd11592 100644 --- a/src/checkbox/styled-components.js +++ b/src/checkbox/styled-components.js @@ -12,7 +12,14 @@ import {STYLE_TYPE} from './constants.js'; import type {SharedStylePropsT} from './types.js'; function getBorderColor(props) { - const {$disabled, $checked, $isError, $isIndeterminate, $theme} = props; + const { + $disabled, + $checked, + $isError, + $isIndeterminate, + $theme, + $isFocusVisible, + } = props; const {colors} = $theme; if ($disabled) { return colors.tickFillDisabled; @@ -20,6 +27,8 @@ function getBorderColor(props) { return 'transparent'; } else if ($isError) { return colors.borderError; + } else if ($isFocusVisible) { + return colors.borderSelected; } else { return colors.tickBorder; } @@ -63,7 +72,6 @@ function getBackgroundColor(props) { $disabled, $checked, $isIndeterminate, - $isFocused, $isError, $isHovered, $isActive, @@ -82,7 +90,7 @@ function getBackgroundColor(props) { return colors.tickFill; } } else if ($isError && ($isIndeterminate || $checked)) { - if ($isActive || $isFocused) { + if ($isActive) { return colors.tickFillErrorSelectedHoverActive; } else if ($isHovered) { return colors.tickFillErrorSelectedHover; @@ -90,7 +98,7 @@ function getBackgroundColor(props) { return colors.tickFillErrorSelected; } } else if ($isError) { - if ($isActive || $isFocused) { + if ($isActive) { return colors.tickFillErrorHoverActive; } else if ($isHovered) { return colors.tickFillErrorHover; @@ -98,7 +106,7 @@ function getBackgroundColor(props) { return colors.tickFillError; } } else if ($isIndeterminate || $checked) { - if ($isActive || $isFocused) { + if ($isActive) { return colors.tickFillSelectedHoverActive; } else if ($isHovered) { return colors.tickFillSelectedHover; @@ -106,7 +114,7 @@ function getBackgroundColor(props) { return colors.tickFillSelected; } } else { - if ($isActive || $isFocused) { + if ($isActive) { return isToggle ? colors.sliderTrackFillActive : colors.tickFillActive; } else if ($isHovered) { return isToggle ? colors.sliderTrackFillHover : colors.tickFillHover; @@ -140,7 +148,14 @@ export const Root = styled('label', props => { }); export const Checkmark = styled('span', props => { - const {$checked, $disabled, $isError, $isIndeterminate, $theme} = props; + const { + $checked, + $disabled, + $isError, + $isIndeterminate, + $theme, + $isFocusVisible, + } = props; const {sizing, animation} = $theme; const tickColor = $disabled @@ -167,6 +182,7 @@ export const Checkmark = styled('span', props => { flex: '0 0 auto', transitionDuration: animation.timing100, transitionTimingFunction: animation.easeOutCurve, + transitionProperty: 'background-image, border-color, background-color', width: sizing.scale700, height: sizing.scale700, left: '4px', @@ -179,6 +195,10 @@ export const Checkmark = styled('span', props => { borderTopRightRadius: borderRadius, borderBottomRightRadius: borderRadius, borderBottomLeftRadius: borderRadius, + outline: + $isFocusVisible && $checked + ? `3px solid ${$theme.colors.accent}` + : 'none', display: 'inline-block', verticalAlign: 'middle', backgroundImage: $isIndeterminate @@ -234,7 +254,10 @@ export const Toggle = styled('div', props => { borderTopRightRadius: borderRadius, borderBottomRightRadius: borderRadius, borderBottomLeftRadius: borderRadius, - boxShadow: props.$theme.lighting.shadow400, + boxShadow: props.$isFocusVisible + ? `0 0 0 3px ${props.$theme.colors.accent}` + : props.$theme.lighting.shadow400, + outline: 'none', display: 'flex', justifyContent: 'center', height: props.$theme.sizing.scale800, @@ -257,10 +280,12 @@ export const Toggle = styled('div', props => { borderTopRightRadius: '50%', borderBottomRightRadius: '50%', borderBottomLeftRadius: '50%', - boxShadow: - (props.$isFocused || props.$isHovered) && !props.$disabled - ? props.$theme.lighting.shadow500 - : props.$theme.lighting.shadow400, + boxShadow: props.$isFocusVisible + ? `0 0 0 3px ${props.$theme.colors.accent}` + : props.$isHovered && !props.$disabled + ? props.$theme.lighting.shadow500 + : props.$theme.lighting.shadow400, + outline: 'none', height: props.$theme.sizing.scale700, width: props.$theme.sizing.scale700, transform: props.$checked diff --git a/src/checkbox/types.js b/src/checkbox/types.js index 6919eb6854..4c25bc5f75 100644 --- a/src/checkbox/types.js +++ b/src/checkbox/types.js @@ -88,6 +88,7 @@ export type PropsT = { export type StatelessStateT = { isFocused: boolean, + isFocusVisible: boolean, isHovered: boolean, isActive: boolean, }; @@ -159,6 +160,7 @@ export type StatefulCheckboxPropsT = { export type SharedStylePropsT = { $isFocused: boolean, + $isFocusVisible: boolean, $isHovered: boolean, $isActive: boolean, $isError: boolean, diff --git a/src/radio/__tests__/radiogroup.test.js b/src/radio/__tests__/radiogroup.test.js index 89e98ebb73..f4ab29d218 100644 --- a/src/radio/__tests__/radiogroup.test.js +++ b/src/radio/__tests__/radiogroup.test.js @@ -8,8 +8,11 @@ LICENSE file in the root directory of this source tree. import * as React from 'react'; import {shallow} from 'enzyme'; +import {render} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; -import {RadioGroup, Radio} from '../index.js'; +import {StatefulRadioGroup, RadioGroup, Radio} from '../index.js'; describe('radio-group', () => { it('sets expected child radio checked', () => { @@ -54,3 +57,79 @@ describe('radio-group', () => { }); }); }); + +describe('radio-group focus and a11y management', () => { + it('sets the initial state', () => { + const {getByDisplayValue} = render( + + + + + , + ); + + const one = getByDisplayValue('1'); + const two = getByDisplayValue('2'); + const three = getByDisplayValue('3'); + + expect(one).not.toBeChecked(); + expect(two).not.toBeChecked(); + expect(three).toBeChecked(); + + expect(one).toHaveAttribute('tabindex', '-1'); + expect(two).toHaveAttribute('tabindex', '-1'); + expect(three).toHaveAttribute('tabindex', '0'); + + expect(one).not.toHaveFocus(); + expect(two).not.toHaveFocus(); + expect(three).not.toHaveFocus(); + }); + + it('focus selected radio', () => { + const {getByDisplayValue} = render( + + + + + , + ); + + const one = getByDisplayValue('1'); + const two = getByDisplayValue('2'); + const three = getByDisplayValue('3'); + + userEvent.tab(); + + expect(one).not.toHaveFocus(); + expect(two).not.toHaveFocus(); + expect(three).toHaveFocus(); + }); + + it('focus first radio if no value is selected', () => { + const {getByDisplayValue} = render( + + + + + , + ); + + const one = getByDisplayValue('1'); + const two = getByDisplayValue('2'); + const three = getByDisplayValue('3'); + + expect(one).not.toBeChecked(); + expect(two).not.toBeChecked(); + expect(three).not.toBeChecked(); + + expect(one).toHaveAttribute('tabindex', '0'); + expect(two).toHaveAttribute('tabindex', '-1'); + expect(three).toHaveAttribute('tabindex', '-1'); + + userEvent.tab(); + + expect(one).toHaveFocus(); + expect(two).not.toHaveFocus(); + expect(three).not.toHaveFocus(); + }); +}); diff --git a/src/radio/index.d.ts b/src/radio/index.d.ts index 4591839124..b7cbe2ec68 100644 --- a/src/radio/index.d.ts +++ b/src/radio/index.d.ts @@ -94,6 +94,8 @@ export interface RadioProps { disabled?: boolean; inputRef?: React.Ref; isError?: boolean; + isFocused?: boolean; + isFocusVisible?: boolean; labelPlacement?: 'top' | 'right' | 'bottom' | 'left'; name?: string; onChange?: React.ChangeEventHandler; @@ -106,11 +108,11 @@ export interface RadioProps { overrides?: RadioOverrides & RadioGroupOverrides; required?: boolean; value?: string; + tabIndex?: string; } export interface RadioState { isActive: boolean; - isFocused: boolean; isHovered: boolean; } diff --git a/src/radio/radio.js b/src/radio/radio.js index ece6ea280b..84bef277fa 100644 --- a/src/radio/radio.js +++ b/src/radio/radio.js @@ -48,7 +48,6 @@ class Radio extends React.Component { state = { isActive: false, - isFocused: this.props.autoFocus || false, isHovered: false, }; @@ -78,16 +77,6 @@ class Radio extends React.Component { this.props.onMouseUp && this.props.onMouseUp(e); }; - onFocus = (e: SyntheticInputEvent) => { - this.setState({isFocused: true}); - this.props.onFocus && this.props.onFocus(e); - }; - - onBlur = (e: SyntheticInputEvent) => { - this.setState({isFocused: false}); - this.props.onBlur && this.props.onBlur(e); - }; - render() { const {overrides = {}} = this.props; const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); @@ -113,7 +102,8 @@ class Radio extends React.Component { $hasDescription: !!this.props.description, $isActive: this.state.isActive, $isError: this.props.isError, - $isFocused: this.state.isFocused, + $isFocused: this.props.isFocused, + $isFocusVisible: this.props.isFocused && this.props.isFocusVisible, $isHovered: this.state.isHovered, $labelPlacement: this.props.labelPlacement, $required: this.props.required, @@ -143,15 +133,15 @@ class Radio extends React.Component { { static defaultProps: DefaultPropsT = { @@ -30,6 +31,30 @@ class StatelessRadioGroup extends React.Component { overrides: {}, }; + state = {isFocusVisible: false, focusedRadioIndex: -1}; + + handleFocus = ( + event: SyntheticInputEvent, + index: number, + ) => { + if (isFocusVisible(event)) { + this.setState({isFocusVisible: true}); + } + this.setState({focusedRadioIndex: index}); + this.props.onFocus && this.props.onFocus(event); + }; + + handleBlur = ( + event: SyntheticInputEvent, + index: number, + ) => { + if (this.state.isFocusVisible !== false) { + this.setState({isFocusVisible: false}); + } + this.setState({focusedRadioIndex: -1}); + this.props.onBlur && this.props.onBlur(event); + }; + render() { const {overrides = {}} = this.props; const [RadioGroupRoot, radioGroupRootProps] = getOverrides( @@ -59,22 +84,28 @@ class StatelessRadioGroup extends React.Component { $required={this.props.required} {...radioGroupRootProps} > - {React.Children.map(this.props.children, child => { + {React.Children.map(this.props.children, (child, index) => { if (!React.isValidElement(child)) { return null; } - + const checked = this.props.value === child.props.value; return React.cloneElement(child, { align: this.props.align, autoFocus: this.props.autoFocus, - checked: this.props.value === child.props.value, + checked, disabled: this.props.disabled || child.props.disabled, isError: this.props.isError, + isFocused: this.state.focusedRadioIndex === index, + isFocusVisible: this.state.isFocusVisible, + tabIndex: + (index === 0 && !this.props.value) || checked ? '0' : '-1', labelPlacement: this.props.labelPlacement, name: this.props.name, - onBlur: this.props.onBlur, + onBlur: (e: SyntheticInputEvent) => + this.handleBlur(e, index), + onFocus: (e: SyntheticInputEvent) => + this.handleFocus(e, index), onChange: this.props.onChange, - onFocus: this.props.onFocus, onMouseEnter: this.props.onMouseEnter, onMouseLeave: this.props.onMouseLeave, // will need to remove overrides pass-through on next major version diff --git a/src/radio/styled-components.js b/src/radio/styled-components.js index 066ca0b5e1..90fcc7a5c2 100644 --- a/src/radio/styled-components.js +++ b/src/radio/styled-components.js @@ -24,6 +24,7 @@ function getOuterColor(props) { if (props.$disabled) return colors.tickFillDisabled; if (!props.$checked) { if (props.$disabled) return colors.tickMarkFillDisabled; + if (props.$isFocusVisible) return colors.borderSelected; if (props.$isError) return colors.tickBorderError; return colors.tickBorder; } else { @@ -171,6 +172,10 @@ export const RadioMarkOuter = styled('div', props => { borderTopRightRadius: '50%', borderBottomRightRadius: '50%', borderBottomLeftRadius: '50%', + boxShadow: + props.$isFocusVisible && props.$checked + ? `0 0 0 3px ${props.$theme.colors.accent}` + : 'none', display: 'flex', height: sizing.scale700, justifyContent: 'center', @@ -178,6 +183,7 @@ export const RadioMarkOuter = styled('div', props => { marginRight: sizing.scale0, marginBottom: sizing.scale0, marginLeft: sizing.scale0, + outline: 'none', verticalAlign: 'middle', width: sizing.scale700, flexShrink: 0, diff --git a/src/radio/types.js b/src/radio/types.js index 2d4b1621b9..8397124393 100644 --- a/src/radio/types.js +++ b/src/radio/types.js @@ -105,6 +105,10 @@ export type RadioPropsT = { inputRef: React.ElementRef<*>, /** Renders checkbox in errored state. */ isError?: boolean, + /** Is radio focused / active? */ + isFocused?: boolean, + /** Is parent RadioGroup focused by keyboard? */ + isFocusVisible?: boolean, /** How to position the label relative to the checkbox itself. */ labelPlacement?: 'top' | 'right' | 'bottom' | 'left', /** Passed to the input element name attribute */ @@ -128,11 +132,12 @@ export type RadioPropsT = { required?: boolean, /** Passed to the input element value attribute */ value?: string, + /** Passed to the input element, typically managed by RadioGroup */ + tabIndex?: string, }; export type RadioStateT = { isActive: boolean, - isFocused: boolean, isHovered: boolean, }; @@ -143,7 +148,10 @@ export type StateReducerT = ( event: SyntheticInputEvent, ) => StateT; -export type StatelessStateT = {}; +export type StatelessStateT = { + isFocusVisible: boolean, + focusedRadioIndex: number, +}; export type DefaultStatefulPropsT = { initialState: StateT, @@ -186,6 +194,7 @@ export type StylePropsT = { $isActive: boolean, $isError: boolean, $isFocused: boolean, + $isFocusVisible: boolean, $isHovered: boolean, $labelPlacement: LabelPlacementT, $required: boolean, diff --git a/src/slider/slider.js b/src/slider/slider.js index 9c2406384b..e8c93788af 100644 --- a/src/slider/slider.js +++ b/src/slider/slider.js @@ -47,7 +47,7 @@ const ThumbLabel = ({index, values, rangeRef, Component, ...props}) => { class Slider extends React.Component< PropsT, - {focusVisible: boolean, focusedThumbIndex: number}, + {isFocusVisible: boolean, focusedThumbIndex: number}, > { static defaultProps = { overrides: {}, @@ -58,7 +58,7 @@ class Slider extends React.Component< max: 100, step: 1, }; - state = {focusVisible: false, focusedThumbIndex: -1}; + state = {isFocusVisible: false, focusedThumbIndex: -1}; rangeRef = React.createRef(); getSharedProps() { const {disabled, step, min, max, value}: PropsT = this.props; @@ -68,10 +68,27 @@ class Slider extends React.Component< $min: min, $max: max, $value: limitValue(value), - $isFocusVisible: this.state.focusVisible, + $isFocusVisible: this.state.isFocusVisible, }; } + handleFocus = (event: SyntheticEvent<>) => { + if (isFocusVisible(event)) { + this.setState({isFocusVisible: true}); + } + const index = + // eslint-disable-next-line flowtype/no-weak-types + (event.target: any).parentNode.firstChild === event.target ? 0 : 1; + this.setState({focusedThumbIndex: index}); + }; + + handleBlur = (event: SyntheticEvent<>) => { + if (this.state.isFocusVisible !== false) { + this.setState({isFocusVisible: false}); + } + this.setState({focusedThumbIndex: -1}); + }; + render() { const { overrides = {}, @@ -105,22 +122,6 @@ class Slider extends React.Component< ); const sharedProps = this.getSharedProps(); - const handleFocus = event => { - if (isFocusVisible(event)) { - this.setState({focusVisible: true}); - } - const index = - // eslint-disable-next-line flowtype/no-weak-types - (event.target: any).parentNode.firstChild === event.target ? 0 : 1; - this.setState({focusedThumbIndex: index}); - }; - - const handleBlur = event => { - if (this.state.focusVisible !== false) { - this.setState({focusVisible: false}); - } - this.setState({focusedThumbIndex: -1}); - }; return ( {theme => ( @@ -128,8 +129,8 @@ class Slider extends React.Component< data-baseweb="slider" {...sharedProps} {...rootProps} - onFocus={forkFocus(rootProps, handleFocus)} - onBlur={forkBlur(rootProps, handleBlur)} + onFocus={forkFocus(rootProps, this.handleFocus)} + onBlur={forkBlur(rootProps, this.handleBlur)} > diff --git a/vrt/__image_snapshots__/modal-rtl__desktop-snap.png b/vrt/__image_snapshots__/modal-rtl__desktop-snap.png index 17f2caa2a7..7ab97f5317 100644 Binary files a/vrt/__image_snapshots__/modal-rtl__desktop-snap.png and b/vrt/__image_snapshots__/modal-rtl__desktop-snap.png differ diff --git a/vrt/__image_snapshots__/modal-rtl__mobile-snap.png b/vrt/__image_snapshots__/modal-rtl__mobile-snap.png index 49b88c2b54..47d7490e48 100644 Binary files a/vrt/__image_snapshots__/modal-rtl__mobile-snap.png and b/vrt/__image_snapshots__/modal-rtl__mobile-snap.png differ diff --git a/vrt/__image_snapshots__/modal-uncloseable__dark-snap.png b/vrt/__image_snapshots__/modal-uncloseable__dark-snap.png index de48ddfce9..634ced96c7 100644 Binary files a/vrt/__image_snapshots__/modal-uncloseable__dark-snap.png and b/vrt/__image_snapshots__/modal-uncloseable__dark-snap.png differ diff --git a/vrt/__image_snapshots__/modal-uncloseable__desktop-snap.png b/vrt/__image_snapshots__/modal-uncloseable__desktop-snap.png index a0a4c018c5..0d68693a48 100644 Binary files a/vrt/__image_snapshots__/modal-uncloseable__desktop-snap.png and b/vrt/__image_snapshots__/modal-uncloseable__desktop-snap.png differ diff --git a/vrt/__image_snapshots__/modal-uncloseable__mobile-snap.png b/vrt/__image_snapshots__/modal-uncloseable__mobile-snap.png index f9e96b2e6d..10298a3bf3 100644 Binary files a/vrt/__image_snapshots__/modal-uncloseable__mobile-snap.png and b/vrt/__image_snapshots__/modal-uncloseable__mobile-snap.png differ diff --git a/vrt/__image_snapshots__/modal__dark-snap.png b/vrt/__image_snapshots__/modal__dark-snap.png index 96eddbe7d8..0ba42da80f 100644 Binary files a/vrt/__image_snapshots__/modal__dark-snap.png and b/vrt/__image_snapshots__/modal__dark-snap.png differ diff --git a/vrt/__image_snapshots__/modal__desktop-snap.png b/vrt/__image_snapshots__/modal__desktop-snap.png index df46455dfc..828d5c7437 100644 Binary files a/vrt/__image_snapshots__/modal__desktop-snap.png and b/vrt/__image_snapshots__/modal__desktop-snap.png differ diff --git a/vrt/__image_snapshots__/modal__mobile-snap.png b/vrt/__image_snapshots__/modal__mobile-snap.png index 4894260e5d..2e78e29903 100644 Binary files a/vrt/__image_snapshots__/modal__mobile-snap.png and b/vrt/__image_snapshots__/modal__mobile-snap.png differ diff --git a/yarn.lock b/yarn.lock index eea5838f16..40a460ea15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,6 +1446,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.8.3": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" + integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.0.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.7.0": version "7.7.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0" @@ -1930,6 +1937,16 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@jest/types@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395" + integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@marionebl/sander@^0.6.0": version "0.6.1" resolved "https://registry.yarnpkg.com/@marionebl/sander/-/sander-0.6.1.tgz#1958965874f24bc51be48875feb50d642fc41f7b" @@ -2801,6 +2818,22 @@ pretty-format "^24.8.0" wait-for-expect "^1.3.0" +"@testing-library/jest-dom@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz#e88a5c08f9b9f36b384f948a0532eae2abbc8204" + integrity sha512-7xnmBFcUmmUVAUhFiZ/u3CxFh1e46THAwra4SiiKNCW4By26RedCRwEk0rtleFPZG0wlTSNOKDvJjWYy93dp0w== + dependencies: + "@babel/runtime" "^7.8.3" + "@types/testing-library__jest-dom" "^5.0.0" + chalk "^3.0.0" + css "^2.2.4" + css.escape "^1.5.1" + jest-diff "^25.1.0" + jest-matcher-utils "^25.1.0" + lodash "^4.17.15" + pretty-format "^25.1.0" + redent "^3.0.0" + "@testing-library/react@^9.1.4": version "9.1.4" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.1.4.tgz#4cc1a228a944c0f468ee501e7da1651d8bbd9902" @@ -2810,6 +2843,11 @@ "@testing-library/dom" "^6.1.0" "@types/testing-library__react" "^9.1.0" +"@testing-library/user-event@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-8.1.0.tgz#6362a9a01c5a4f612e8ef6eafd69e71e355c9f09" + integrity sha512-42VHeLj4877FYIlehDQEP24rmixcfkRpPSil8icNKFJfU4pNArI0EPaKCoAaDr/p6NThW+QHFRiwws80PUNFtg== + "@types/babel-types@*": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.7.tgz#667eb1640e8039436028055737d2b9986ee336e3" @@ -2879,6 +2917,11 @@ resolved "https://registry.yarnpkg.com/@types/card-validator/-/card-validator-4.1.0.tgz#039fb05b2e254d62cebb9d1bd0efb2f817ea1afa" integrity sha512-d24YOrxtrFXPW/IP7/jaGUTi/sTpPbosnAETitzwh2QNpPCMOdi23O/dPEi2ydbYIT41HN/QY9zyIm6m3VEV4A== +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/cookie@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" @@ -2956,6 +2999,14 @@ resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== +"@types/jest@*": + version "25.1.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.2.tgz#1c4c8770c27906c7d8def5d2033df9dbd39f60da" + integrity sha512-EsPIgEsonlXmYV7GzUqcvORsSS9Gqxw/OvkGwHfAdpjduNRxMlhsav0O5Kb0zijc/eXSO/uW6SJt9nwull8AUQ== + dependencies: + jest-diff "^25.1.0" + pretty-format "^25.1.0" + "@types/jest@^24.0.18": version "24.0.18" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.18.tgz#9c7858d450c59e2164a8a9df0905fc5091944498" @@ -3130,6 +3181,13 @@ dependencies: pretty-format "^24.3.0" +"@types/testing-library__jest-dom@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.0.1.tgz#cc7f384535a3d9597e27f58d38a795f5c137cc53" + integrity sha512-GiPXQBVF9O4DG9cssD2d266vozBJvC5Tnv6aeH5ujgYJgys1DYm9AFCz7YC+STR5ksGxq3zCt+yP8T1wbk2DFg== + dependencies: + "@types/jest" "*" + "@types/testing-library__react@^9.1.0": version "9.1.1" resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.1.tgz#4bcb8bba54b07fbb6c084f2f00e7f9410e587c10" @@ -3160,6 +3218,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^15.0.0": + version "15.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.3.tgz#41453a0bc7ab393e995d1f5451455638edbd2baf" + integrity sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/typescript-estree@1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.11.0.tgz#b7b5782aab22e4b3b6d84633652c9f41e62d37d5" @@ -3773,6 +3838,11 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" @@ -3797,6 +3867,14 @@ ansi-styles@^4.0.0: dependencies: color-convert "^2.0.1" +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + ansi-to-html@^0.6.11: version "0.6.13" resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.13.tgz#c72eae8b63e5ca0643aab11bfc6e6f2217425833" @@ -6227,6 +6305,14 @@ chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + change-case@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" @@ -7450,7 +7536,12 @@ css-what@^2.1.2: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css@2.2.4: +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@2.2.4, css@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== @@ -8065,6 +8156,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff-sequences@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32" + integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw== + diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -10560,6 +10656,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" @@ -11113,6 +11214,11 @@ indent-string@^3.0.0, indent-string@^3.2.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -12035,6 +12141,16 @@ jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-diff@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad" + integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" + jest-docblock@23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7" @@ -12132,6 +12248,11 @@ jest-get-type@^24.9.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876" + integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw== + jest-haste-map@^24.0.0: version "24.0.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.0.0.tgz#e9ef51b2c9257384b4d6beb83bd48c65b37b5e6e" @@ -12224,6 +12345,16 @@ jest-matcher-utils@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-matcher-utils@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz#fa5996c45c7193a3c24e73066fc14acdee020220" + integrity sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ== + dependencies: + chalk "^3.0.0" + jest-diff "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" + jest-message-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7" @@ -13823,6 +13954,11 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" +min-indent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" + integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= + mini-css-extract-plugin@0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.3.tgz#98d60fcc5d228c3e36a9bd15a1d6816d6580beb8" @@ -15831,6 +15967,16 @@ pretty-format@^24.3.0, pretty-format@^24.8.0, pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8" + integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ== + dependencies: + "@jest/types" "^25.1.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -16482,6 +16628,11 @@ react-is@^16.10.2, react-is@^16.9.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa" integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw== +react-is@^16.12.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + react-is@^16.3.2: version "16.8.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb" @@ -16927,6 +17078,14 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + redeyed@~2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" @@ -18508,6 +18667,13 @@ strip-indent@^2.0.0: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -18633,6 +18799,13 @@ supports-color@^6.0.0, supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7"