diff --git a/.changeset/tag-focus.md b/.changeset/tag-focus.md new file mode 100644 index 0000000000..397c1855ed --- /dev/null +++ b/.changeset/tag-focus.md @@ -0,0 +1,5 @@ +--- +'react-magma-dom': patch +--- + +fix(Tag): Update tag focus state diff --git a/packages/react-magma-dom/src/components/Tag/Tag.stories.tsx b/packages/react-magma-dom/src/components/Tag/Tag.stories.tsx index ff74e5b3fe..06fef30e19 100644 --- a/packages/react-magma-dom/src/components/Tag/Tag.stories.tsx +++ b/packages/react-magma-dom/src/components/Tag/Tag.stories.tsx @@ -3,80 +3,117 @@ import { Card, CardBody } from '../Card'; import { AccountCircleIcon } from 'react-magma-icons'; import { Story, Meta } from '@storybook/react/types-6-0'; import { Tag, TagColor, TagProps, TagSize } from '.'; +import { Button } from '../Button'; -const Template: Story = args => ( - <> - <> - Default - - Primary - - - High Contrast - - - Low Contrast - - -

- }> - Default Icon - - } color={TagColor.primary}> - Primary Icon - - } color={TagColor.highContrast}> - High Contrast Icon - - } color={TagColor.lowContrast}> - Low Contrast Icon - -

-

- - Default Small - - - Primary Small - - - High Contrast Small - - - Low Contrast Small - -

-

- } size={TagSize.small}> - Default Small Icon - - } - size={TagSize.small} - color={TagColor.primary} - > - Primary Small Icon - - } - size={TagSize.small} - color={TagColor.highContrast} - > - High Contrast Small Icon - - } - size={TagSize.small} - color={TagColor.lowContrast} - > - Low Contrast Small Icon - -

- -); +const Template: Story = args => { + return ( + + +

+ Default + + Primary + + + High Contrast + + + Low Contrast + +

+

+ }> + Default Icon + + } color={TagColor.primary}> + Primary Icon + + } + color={TagColor.highContrast} + > + High Contrast Icon + + } + color={TagColor.lowContrast} + > + Low Contrast Icon + +

+

+ + Default Small + + + Primary Small + + + High Contrast Small + + + Low Contrast Small + +

+

+ } size={TagSize.small}> + Default Small Icon + + } + size={TagSize.small} + color={TagColor.primary} + > + Primary Small Icon + + } + size={TagSize.small} + color={TagColor.highContrast} + > + High Contrast Small Icon + + } + size={TagSize.small} + color={TagColor.lowContrast} + > + Low Contrast Small Icon + +

+

+ { + console.log('clicked'); + }} + > + Clickable Tag + + { + console.log('clicked'); + }} + > + Deletetable Tag + +

+
+
+ ); +}; export default { title: 'Tag', @@ -108,103 +145,95 @@ export default { } as Meta; export const Default = Template.bind({}); -Default.args = {}; - -export const Disabled = Template.bind({}); -Disabled.args = { - disabled: true, -}; - -export const Inverse = Template.bind({}); -Inverse.args = { - isInverse: true, +Default.args = { + disabled: false, + isInverse: false, }; -export const InverseDisabled = Template.bind({}); -InverseDisabled.args = { - isInverse: true, - disabled: true, -}; - -Inverse.decorators = [ - Story => ( - - - - - - ), -]; -InverseDisabled.decorators = [ - Story => ( - - - - - - ), -]; - export const OnClick = args => { const [counter, setCounter] = React.useState(0); function updateCounter() { setCounter(count => count + 1); } return ( - <> -

- Counter: {counter} -

- - Text Label - - + + +

+ Counter: {counter} +

+ + Text Label + +
+
); }; OnClick.args = {}; export const WithDelete = args => { - const [isVisible, setIsVisible] = React.useState(true); + const [isVisibleDefault, setIsVisibleDefault] = React.useState(true); + const [isVisibleIcon, setIsVisibleIcon] = React.useState(true); + const [isVisibleSmall, setIsVisibleSmall] = React.useState(true); + const [isVisibleSmallIcon, setIsVisibleSmallIcon] = React.useState(true); - function deleteMe() { - setIsVisible(false); - } return ( - <> -

- {isVisible && ( - - Delete - - )} -

-

- {isVisible && ( - }> - Delete Icon - - )} -

-

- {isVisible && ( - - Delete Small - - )} -

-

- {isVisible && ( - } - > - Delete Icon Small - - )} -

- + + +

+ {isVisibleDefault && ( + setIsVisibleDefault(false)}> + Delete + + )} +

+

+ {isVisibleIcon && ( + setIsVisibleIcon(false)} + icon={} + > + Delete Icon + + )} +

+

+ {isVisibleSmall && ( + setIsVisibleSmall(false)} + > + Delete Small + + )} +

+

+ {isVisibleSmallIcon && ( + setIsVisibleSmallIcon(false)} + icon={} + > + Delete Icon Small + + )} +

+ + +
+
); }; diff --git a/packages/react-magma-dom/src/components/Tag/Tag.test.js b/packages/react-magma-dom/src/components/Tag/Tag.test.js index 6df3aa6fd6..0b15bc84e2 100644 --- a/packages/react-magma-dom/src/components/Tag/Tag.test.js +++ b/packages/react-magma-dom/src/components/Tag/Tag.test.js @@ -2,9 +2,8 @@ import React from 'react'; import { axe } from '../../../axe-helper'; import { magma } from '../../theme/magma'; import { Tag, TagColor, TagSize } from '.'; -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, getByTestId } from '@testing-library/react'; import { transparentize } from 'polished'; -import { I18nContext, deleteAriaLabel } from '../../i18n'; import { AccountCircleIcon, CancelIcon } from 'react-magma-icons'; const TEXT = 'Text Label'; @@ -23,7 +22,28 @@ describe('Tag', () => { expect(getByTestId(testId)).toBeInTheDocument(); }); - describe('Default background tests', () => { + it('Should not have a focus state', () => { + const testId = 'tag-id'; + const { getByTestId } = render( + + {TEXT} + + ); + const tag = getByTestId(testId); + + expect(tag).not.toHaveStyleRule('outline-offset', '2px', { + target: ':focus', + }); + expect(tag).not.toHaveStyleRule( + 'outline', + `2px solid ${magma.colors.focus}`, + { + target: ':focus', + } + ); + }); + + describe('Default background', () => { it('Should render a default Tag with a gray background', () => { const { getByText } = render({TEXT}); const tag = getByText('Text Label').parentElement; @@ -57,7 +77,7 @@ describe('Tag', () => { }); }); - describe('Disabled background tests', () => { + describe('Disabled background', () => { it('Should render a Tag with a disabled background', () => { const { getByText } = render({TEXT}); const tag = getByText('Text Label').parentElement; @@ -116,7 +136,7 @@ describe('Tag', () => { }); }); - describe('Disabled Inverse background tests', () => { + describe('Disabled Inverse background', () => { it('Should render a inverse Tag with a disabled background', () => { const { getByText } = render( @@ -175,7 +195,7 @@ describe('Tag', () => { }); }); - describe('Inverse background tests', () => { + describe('Inverse background', () => { it('Should render a default inverse Tag with a gray background', () => { const { getByText } = render({TEXT}); const tag = getByText('Text Label').parentElement; @@ -217,7 +237,7 @@ describe('Tag', () => { }); }); - describe('Size tests', () => { + describe('Size', () => { it('Should render a small Tag size', () => { const { getByText } = render( @@ -255,23 +275,113 @@ describe('Tag', () => { }); }); - describe('Events tests', () => { + describe('Clickable Tag', () => { + const testId = 'clickableTag'; + it('Should render a clickable tag', () => { const isClickable = jest.fn(); const { getByText } = render({TEXT}); - const button = getByText(TEXT); + const tag = getByText(TEXT); - fireEvent.click(button); + fireEvent.click(tag); expect(isClickable).toHaveBeenCalled(); }); - it('Should render a deletable tag', () => { + it('Should have a focus state', () => { const isClickable = jest.fn(); - const { getByText } = render({TEXT}); - const button = getByText(TEXT); + const { getByTestId } = render( + + {TEXT} + + ); + const tag = getByTestId(testId); - fireEvent.click(button); - expect(isClickable).toHaveBeenCalled(); + expect(tag).toHaveStyleRule('outline-offset', '2px', { + target: ':focus', + }); + expect(tag).toHaveStyleRule( + 'outline', + `2px solid ${magma.colors.focus}`, + { + target: ':focus', + } + ); + }); + + it('Should have a focus state when isInverse', () => { + const isClickable = jest.fn(); + const { getByTestId } = render( + + {TEXT} + + ); + const tag = getByTestId(testId); + + expect(tag).toHaveStyleRule('outline-offset', '2px', { + target: ':focus', + }); + expect(tag).toHaveStyleRule( + 'outline', + `2px solid ${magma.colors.focusInverse}`, + { + target: ':focus', + } + ); + }); + }); + + describe('Deletable Tag', () => { + const testId = 'deleteTag'; + + it('Should render a deletable tag', () => { + const onTagDelete = jest.fn(); + const { getByText } = render({TEXT}); + const tag = getByText(TEXT); + + fireEvent.click(tag); + expect(onTagDelete).toHaveBeenCalled(); + }); + + it('Should have a focus state', () => { + const onTagDelete = jest.fn(); + const { getByTestId } = render( + + {TEXT} + + ); + const tag = getByTestId(testId); + + expect(tag).toHaveStyleRule('outline-offset', '2px', { + target: ':focus', + }); + expect(tag).toHaveStyleRule( + 'outline', + `2px solid ${magma.colors.focus}`, + { + target: ':focus', + } + ); + }); + + it('Should have a focus state when isInverse', () => { + const onTagDelete = jest.fn(); + const { getByTestId } = render( + + {TEXT} + + ); + const tag = getByTestId(testId); + + expect(tag).toHaveStyleRule('outline-offset', '2px', { + target: ':focus', + }); + expect(tag).toHaveStyleRule( + 'outline', + `2px solid ${magma.colors.focusInverse}`, + { + target: ':focus', + } + ); }); }); diff --git a/packages/react-magma-dom/src/components/Tag/Tag.tsx b/packages/react-magma-dom/src/components/Tag/Tag.tsx index fa0710d86c..6da53ed284 100644 --- a/packages/react-magma-dom/src/components/Tag/Tag.tsx +++ b/packages/react-magma-dom/src/components/Tag/Tag.tsx @@ -298,6 +298,14 @@ const StyledButton = styled.button<{ }>` ${TagStyling}; cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; + &:focus { + outline-offset: 2px; + outline: 2px solid + ${props => + props.isInverse + ? props.theme.colors.focusInverse + : props.theme.colors.focus}; + } `; const StyledSpan = styled.span<{