Skip to content

Commit

Permalink
feat: Add aria attributes to RangeInput (#1560)
Browse files Browse the repository at this point in the history
* Add min and aria min label and tests

* Add max values and tests

* Use min value that corresponds to step in test

* Apply aria value now based on current value

* Fix value calculation and typing

* Add .idea to .gitignore

* Add option for aria- labels and tests
	labels: aria-valuemin, aria-valuemax, aria-valuenow
	tests for correct default value and passed in aria- labels

* Fix default value formula

* refer to aria props via snakecase - e.g. 'aria-valuenow' - strings rather than React camel case

Co-authored-by: Hana Worku <hana@truss.works>
  • Loading branch information
kimallen and haworku authored Sep 20, 2021
1 parent f6f972d commit 0034835
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ storybook-static/
# editor config

.vscode
.idea

# happo output directory

Expand Down
60 changes: 52 additions & 8 deletions src/components/forms/RangeInput/RangeInput.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { render, screen, waitFor } from '@testing-library/react'

import { RangeInput } from './RangeInput'

Expand All @@ -17,25 +18,68 @@ describe('RangeInput component', () => {
expect(rangeElement).toBeInTheDocument()
expect(rangeElement).toHaveAttribute('id', 'range-slider-id')
expect(rangeElement).toHaveAttribute('name', 'rangeName')
expect(rangeElement).toHaveAttribute('aria-valuemin', '0')
expect(rangeElement).toHaveAttribute('aria-valuemax', '100')
expect(rangeElement).toHaveAttribute('aria-valuenow', '50')
expect(rangeElement).toHaveClass('usa-range')
expect(rangeElement).toHaveClass('additional-class')
})

it('renders with custom range values', () => {
const min = -15
const max = 60
const { queryByTestId } = render(
<RangeInput
id="range-slider-id"
name="rangeName"
min={0}
max={60}
min={min}
max={max}
step={15}
defaultValue={45}
/>
)
expect(queryByTestId('range')).toHaveAttribute('min', '0')
expect(queryByTestId('range')).toHaveAttribute('max', '60')
expect(queryByTestId('range')).toHaveAttribute('step', '15')
expect(queryByTestId('range')).toHaveAttribute('value', '45')

const rangeElement = queryByTestId('range')

expect(rangeElement).toHaveAttribute('min', '-15')
expect(rangeElement).toHaveAttribute('max', '60')
expect(rangeElement).toHaveAttribute('aria-valuemin', String(min))
expect(rangeElement).toHaveAttribute('aria-valuemax', String(max))
expect(rangeElement).toHaveAttribute(
'aria-valuenow',
String(min + (max - min) / 2)
)
expect(rangeElement).toHaveAttribute('step', '15')
})

it('renders with default value', () => {
const { queryByTestId } = render(
<RangeInput id="range-slider-id" name="rangeName" defaultValue={75} />
)

expect(queryByTestId('range')).toHaveAttribute('aria-valuenow', '75')
})

it('renders with custom aria values and updates aria-valuenow on keyboard actions', () => {
render(
<RangeInput
id="range-slider-id"
name="rangeName"
aria-valuemin={12}
aria-valuemax={58}
aria-valuenow={23}
/>
)
const rangeInput = screen.getByTestId('range')
expect(rangeInput).toHaveAttribute('aria-valuemin', '12')
expect(rangeInput).toHaveAttribute('aria-valuemax', '58')
expect(rangeInput).toHaveAttribute('aria-valuenow', '23')

userEvent.type(rangeInput, '{arrowright}')

waitFor(() => expect(rangeInput).toHaveAttribute('aria-valuenow', '24'))
userEvent.type(rangeInput, '{arrowleft}')
userEvent.type(rangeInput, '{arrowleft}')
waitFor(() => expect(rangeInput).toHaveAttribute('aria-valuenow', '22'))
})

it('renders with step attribute set to value any', () => {
Expand Down
34 changes: 32 additions & 2 deletions src/components/forms/RangeInput/RangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import classnames from 'classnames'
interface RangeInputProps {
id: string
name: string
min?: number
max?: number
inputRef?:
| string
| ((instance: HTMLInputElement | null) => void)
Expand All @@ -17,9 +19,33 @@ export const RangeInput = ({
inputRef,
...inputProps
}: RangeInputProps & JSX.IntrinsicElements['input']): React.ReactElement => {
// Range defaults to min = 0, max = 100, step = 1, and value = (max/2) if not specified.

const classes = classnames('usa-range', className)
// input range defaults to min = 0, max = 100, step = 1, and value = (max/2) if not specified.
const defaultMin = 0
const defaultMax = 100
const { min, max, defaultValue } = inputProps
const rangeMin = min || defaultMin
const rangeMax = max || defaultMax
const ariaMin = inputProps['aria-valuemin'] || rangeMin
const ariaMax = inputProps['aria-valuemax'] || rangeMax
const calculatedAriaValueNow =
inputProps['aria-valuenow'] ||
defaultValue ||
(rangeMax < rangeMin ? rangeMin : rangeMin + (rangeMax - rangeMin) / 2)
const convertValueType = (
value: string | number | readonly string[]
): number | undefined => {
if (typeof value === 'number' || typeof value === 'string') {
return Number(value)
}
return undefined
}
const [ariaValue, setAriaValue] = React.useState<number | undefined>(
convertValueType(calculatedAriaValueNow)
)
const onValueChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
if (!inputProps['aria-valuenow']) setAriaValue(e.target.valueAsNumber)
}

return (
<input
Expand All @@ -28,6 +54,10 @@ export const RangeInput = ({
ref={inputRef}
type="range"
{...inputProps}
aria-valuemin={ariaMin}
aria-valuemax={ariaMax}
aria-valuenow={ariaValue}
onChange={(e): void => onValueChange(e)}
/>
)
}
Expand Down

0 comments on commit 0034835

Please sign in to comment.