Skip to content

Commit

Permalink
Merge pull request #347 from lifeomic/spinbutton-a11y
Browse files Browse the repository at this point in the history
Spinbutton a11y
  • Loading branch information
Shawn Zhu authored May 4, 2023
2 parents 54ca68f + 0fbdce2 commit c958b6a
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@testing-library/jest-dom": "^5.5.0",
"@testing-library/react": "^10.0.3",
"@testing-library/react-hooks": "^3.2.1",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.0",
"@types/node": "^16.0.0",
"@types/react": "^16.9.34",
Expand Down
38 changes: 38 additions & 0 deletions src/components/SpinButton/SpinButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';

import { SpinButton } from './SpinButton';
import { Box } from '../Box';
import { TextField } from '../TextField';

export default {
title: 'Components/SpinButton',
component: SpinButton,
argTypes: {
onClick: { action: 'clicked' },
},
} as ComponentMeta<typeof SpinButton>;

const Template: ComponentStory<typeof SpinButton> = (args) => (
<SpinButton {...args}></SpinButton>
);

export const Default = Template.bind({});

export const KeyboardSupport = () => {
const [valueNow, setValueNow] = useState(0);
return (
<Box style={{ overflow: 'auto', width: '80%' }} direction="row">
<TextField
label="Current Number"
disabled={true}
value={valueNow}
secondaryLabel="use the SpinButton to adjust value"
/>
<SpinButton
onDecrement={() => setValueNow(valueNow - 1)}
onIncrement={() => setValueNow(valueNow + 1)}
/>
</Box>
);
};
49 changes: 49 additions & 0 deletions src/components/SpinButton/SpinButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from 'react';
import userEvent from '@testing-library/user-event';

import { renderWithTheme } from '../../testUtils/renderWithTheme';
import { SpinButton } from './index';

const testId = 'Button';

test('it renders two buttons', async () => {
const noop = () => {};

const { findByTestId } = renderWithTheme(
<SpinButton onDecrement={noop} onIncrement={noop} data-testid={testId} />
);

const spinButton = await findByTestId(testId);

const arrowButtons = spinButton.querySelectorAll('[type="button"]');
expect(arrowButtons.length).toEqual(2);
});

test('support keyboard ArrowUp and ArrowDown', async () => {
const onIncrement = jest.fn();
const onDecrement = jest.fn();

const { findByTestId } = renderWithTheme(
<SpinButton
onDecrement={onDecrement}
onIncrement={onIncrement}
data-testid={testId}
/>
);

const spinButton = await findByTestId(testId);

spinButton.focus();
await userEvent.keyboard('{ArrowUp}');
expect(onIncrement).toHaveBeenCalledTimes(1);

spinButton.focus();
await userEvent.keyboard('{ArrowDown}');
expect(onDecrement).toHaveBeenCalledTimes(1);

spinButton.focus();
await userEvent.keyboard('{ArrowLeft}');
// nothing should change
expect(onIncrement).toHaveBeenCalledTimes(1);
expect(onDecrement).toHaveBeenCalledTimes(1);
});
105 changes: 105 additions & 0 deletions src/components/SpinButton/SpinButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as React from 'react';
import clsx from 'clsx';
import { ChevronUp, ChevronDown } from '@lifeomic/chromicons';

import { makeStyles } from '../../styles';
import { Box } from '../Box';
import { IconButton } from '../IconButton';

export const SpinButtonStylesKey = 'ChromaSpinButton';

export const useStyles = makeStyles(
(theme) => ({
root: {
alignItems: 'center',
borderRadius: theme.pxToRem(4),
color: theme.palette.common.white,
cursor: 'pointer',
display: 'inline-flex',
whiteSpace: 'nowrap',
margin: 0,
},
button: {
borderRadius: theme.pxToRem(8),
padding: theme.spacing(0, 0.5),
'&:hover, &:focus': {
background: theme.palette.graphite[50],
},
},
size0: {
'& > svg': {
width: theme.pxToRem(24),
height: theme.pxToRem(18),
},
},
}),
{ name: SpinButtonStylesKey }
);

export interface SpinButtonProps
extends React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> {
onDecrement: (
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>
) => void;
onIncrement: (
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>
) => void;
ref?: React.Ref<HTMLDivElement>;
decrementDisabled?: boolean;
incrementDisabled?: boolean;
}

export const SpinButton = React.forwardRef<HTMLDivElement, SpinButtonProps>(
(props, ref) => {
const {
onDecrement,
onIncrement,
decrementDisabled = false,
incrementDisabled = false,
...rootProps
} = props;
const classes = useStyles({});

const handleKeyBoard = (e: React.KeyboardEvent<HTMLElement>) => {
if (e.key === 'ArrowUp') {
onIncrement(e);
} else if (e.key === 'ArrowDown') {
onDecrement(e);
}
};

return (
<Box
ref={ref}
className={clsx(classes.root)}
gap={0.125}
direction="column"
tabIndex={0}
onKeyDown={handleKeyBoard}
{...rootProps}
>
<IconButton
type="button"
className={clsx(classes.size0, classes.button)}
icon={ChevronUp}
aria-label="Up"
tabIndex={-1}
onClick={onIncrement}
disabled={incrementDisabled}
></IconButton>
<IconButton
type="button"
className={clsx(classes.size0, classes.button)}
icon={ChevronDown}
aria-label="Down"
tabIndex={-1}
onClick={onDecrement}
disabled={decrementDisabled}
></IconButton>
</Box>
);
}
);
1 change: 1 addition & 0 deletions src/components/SpinButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SpinButton, SpinButtonStylesKey, SpinButtonProps } from './SpinButton';
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5852,6 +5852,11 @@
"@babel/runtime" "^7.10.3"
"@testing-library/dom" "^7.22.3"

"@testing-library/user-event@^14.4.3":
version "14.4.3"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591"
integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==

"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
Expand Down

0 comments on commit c958b6a

Please sign in to comment.