Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Lukebailey/infra 386 create a a11y checkbox #53

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
17cffa8
feat: add react-aria and react-stately
lukerohanbailey Jan 5, 2022
23e5e0d
feat: add simple react-aria checkbox
lukerohanbailey Jan 5, 2022
e254aa5
feat: add simple checkbox story
lukerohanbailey Jan 5, 2022
1ce89d5
feat: add disabled and enabled checkbox styles
lukerohanbailey Jan 5, 2022
5c819c1
feat: add state styling to checkbox
lukerohanbailey Jan 5, 2022
d86c95d
style: update All Checkbox component name to Checkbox States
lukerohanbailey Jan 5, 2022
59efa9f
fix: update autoFocus attribute element to match the order on figma
lukerohanbailey Jan 5, 2022
c66b5cd
chore: move stitches to devdependencies
lukerohanbailey Jan 6, 2022
ca95774
fix: add react-aria and react-stately to build-cjs
lukerohanbailey Jan 6, 2022
0a41a35
feat: add aria-label to checkbox
lukerohanbailey Jan 6, 2022
93013ed
chore: add onChange handler to checkbox
lukerohanbailey Jan 6, 2022
177e1c9
build: add gsap to project
lukerohanbailey Jan 7, 2022
4f84d58
feat: add basic gsap setup and microinteraction to checkbox
lukerohanbailey Jan 7, 2022
3c53864
build: add .vscode to .gitignore
lukerohanbailey Jan 7, 2022
dac2cfd
fix: remove GSAPTimeline type import
lukerohanbailey Jan 7, 2022
969c328
feat: add animatiable check icon
lukerohanbailey Jan 9, 2022
ab4d3dc
feat: animate checkbox check icon based on isSelected state
lukerohanbailey Jan 9, 2022
7bf7cdd
feat: improve checkbox animation timings
lukerohanbailey Jan 10, 2022
fc43aa6
Merge branch 'lukebailey/infra-387-explore-and-create-a-micro-interac…
lukerohanbailey Jan 11, 2022
9d2b028
fix: destructure checkbox types from ComponentPropsWithRef, CheckboxP…
lukerohanbailey Jan 11, 2022
6786ee4
fix: invalid styling
lukerohanbailey Jan 11, 2022
661b8d3
Merge branch 'lukebailey/infra-386-create-a-a11y-checkbox' into lukeb…
lukerohanbailey Jan 11, 2022
aa97e6c
fix: aria-label console warnings
lukerohanbailey Jan 11, 2022
8b505fa
feat: update checkbox animation timings
lukerohanbailey Jan 11, 2022
f4a25d7
Merge branch 'lukebailey/infra-387-explore-and-create-a-micro-interac…
lukerohanbailey Jan 11, 2022
965235e
style: destructure ToggleProps from useToggleState hook
lukerohanbailey Jan 11, 2022
8d2a0f1
fix: move check timeline play call to a useEffect hook
lukerohanbailey Jan 12, 2022
5c060e9
fix: type naming conventions and allow label to accept any ReactNode
lukerohanbailey Jan 13, 2022
55ab973
fix: remove .vscode/ from .gitignore
lukerohanbailey Jan 13, 2022
e8cf1a9
feat: replace gsap animations with CSS transitions
lukerohanbailey Jan 14, 2022
dce6b65
fix: remove restprops usage
lukerohanbailey Jan 14, 2022
4280b21
fix: prevent pressed stateon disabled checkboxes
lukerohanbailey Jan 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ junit.xml
.idea

# logs
*.log
*.log
.vscode/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not add .vscode to the ignore. We do like to share some configuration in projects. This project doesn't have anything shared yet, but I'd imagine we would do some similiar sharing that we do in https://github.com/contra/contra-web-app/tree/master/.vscode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! This has been fixed in 55ab973

9 changes: 8 additions & 1 deletion bin/build-cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ const sharedConfig = {
platform: 'node',
sourcemap: true,
bundle: true,
external: ['react', 'react-dom', '@stitches/react'],
external: [
'react',
'react-dom',
'@stitches/react',
'react-aria',
'react-stately',
'gsap',
],
target: ['node12'],
inject: ['./bin/util/react-shim.js'],
};
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@commitlint/cli": "^14.1.0",
"@commitlint/config-conventional": "^14.1.0",
"@skypack/package-check": "^0.2.2",
"@stitches/react": "^1.2.5",
"@storybook/addon-actions": "^6.3.12",
"@storybook/addon-essentials": "^6.3.12",
"@storybook/addon-links": "^6.3.12",
Expand Down Expand Up @@ -85,6 +86,8 @@
"react": "^16.14.0 || ^17.0.0"
},
"dependencies": {
"@stitches/react": "^1.2.5"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we moved this from dependencies to devDependencies. If we are going to do this, then we need to add @stitches/react to peerDependencies which would constitute a major/breaking change.

"gsap": "^3.9.1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some slight concerns about bringing gsap in as a dependency. gsap is large library, and I'm curious how much we need it as opposed to handling things in a more light weight manner.

Would love to hear some thoughts on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this maybe a thing! totally fine to remove GSAP and go with CSS transitions for the UI kit. See comment #53 (comment)

"react-aria": "^3.12.0",
"react-stately": "^3.11.0"
}
}
30 changes: 30 additions & 0 deletions src/animationIcons/CheckIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { SVGProps } from 'react';
import { styled } from '../../stitches.config';

const Path = styled('path', {
'input:checked ~ div &': {
transition: 'stroke-dashoffset 1s cubic-bezier(0.16, 1, 0.3, 1)',
},
transition: 'stroke-dashoffset 0.2s cubic-bezier(0.11, 0, 0.5, 0)',
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use variants to essentially add a isChecked prop that would adjust the transition style.

The reason I would do this is that the style then relies on a prop and not the structure of the DOM.

Right now this styling is very conditional based on where it is used and isn't really sharable.

Thoughts @Boeing787 and @lukerohanbailey?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! See comment #53 (comment)


export const CheckIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
fill="none"
focusable="false"
height="1em"
role="img"
viewBox="0 0 16 16"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<Path
d="M0.709339 7.44716L5.70834 12.4462L15.3004 2.8541"
stroke="currentColor"
strokeDasharray="21 21"
strokeMiterlimit="10"
strokeWidth="2"
/>
</svg>
);
123 changes: 123 additions & 0 deletions src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { CheckboxProps, ToggleProps } from '@react-types/checkbox';
lukerohanbailey marked this conversation as resolved.
Show resolved Hide resolved
import { gsap } from 'gsap';
import type { ChangeEvent, ComponentPropsWithRef } from 'react';
import { useEffect, useRef } from 'react';
import { useCheckbox } from 'react-aria';
import { useToggleState } from 'react-stately';
import { styled } from '../../stitches.config';
import { CheckIcon } from '../animationIcons/CheckIcon';

const Label = styled('label', {
'& input:checked:disabled + div': {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we needing to target specifically with div here? Can we tighten this to reference IconContainer specifically?

color: '$gray50',
},
display: 'block',
position: 'relative',
});

const Input = styled('input', {
'&:checked': {
backgroundColor: '$brandYellow',
borderColor: '$brandYellow',
},
'&:checked&:disabled': {
backgroundColor: '$yellow20',
borderColor: '$yellow20',
},
'&:disabled': {
borderColor: '$gray30',
},
'&:focus': {
outline: '2px solid $brandYellow',
outlineOffset: '2px',
},
'&:indeterminate': {
borderColor: '$brandYellow',
position: 'relative',
},
'&:indeterminate:after': {
backgroundColor: '$brandYellow',
content: '""',
height: '12px',
left: '50%',
position: 'absolute',
top: '50%',
transform: 'translate(-50%, -50%)',
width: '12px',
},
'&:invalid': {
borderColor: '$uiErrorRegular',
},
appearance: 'none',
backgroundColor: '$brandWhite',
borderColor: '$gray50',
borderRadius: '$4',
borderStyle: '$solid',
borderWidth: '$thin',
height: '24px',
margin: '0',
position: 'relative',
width: '24px',
});

const IconContainer = styled('div', {
left: '50%',
position: 'absolute',
top: '50%',
transform: 'translate(-50%, -50%)',
});

type CustomCheckboxProps = {
text?: string;
};

export const Checkbox = ({
validationState,
text,
defaultSelected,
isIndeterminate,
...restProps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually highly advise not using rest props on component libraries like this, and to specifically list all props out so you can make sure you are never spreading props improperly to base react components (if you did you'll get those annoying react warnings about a prop not being recognized).

In general it would be very easy for a consumer to pass a prop that could ultimately end up getting inappropriately passed all the way down to base component that it shouldnt.

}: CheckboxProps &
ComponentPropsWithRef<'input'> &
CustomCheckboxProps &
ToggleProps) => {
lukerohanbailey marked this conversation as resolved.
Show resolved Hide resolved
const ref = useRef<HTMLInputElement>(null);
const state = useToggleState({ defaultSelected, validationState });
lukerohanbailey marked this conversation as resolved.
Show resolved Hide resolved
const { inputProps } = useCheckbox(
{ isIndeterminate, ...restProps },
state,
ref
);
const checkedTl = useRef<GSAPTimeline>(gsap.timeline({ paused: true }));

useEffect(() => {
checkedTl.current
.to(ref.current, {
duration: 0.1,
ease: 'quad.easeIn',
scale: 0.9,
})
.to(ref.current, {
duration: 0.1,
ease: 'quad.easeOut',
scale: 1,
});
}, []);

const onChange = (event: ChangeEvent<HTMLInputElement>) => {
lukerohanbailey marked this conversation as resolved.
Show resolved Hide resolved
const checked = event.target.checked;
state.setSelected(checked);

checkedTl.current.play(0);
};

return (
<Label>
<Input {...inputProps} {...restProps} onChange={onChange} ref={ref} />
lukerohanbailey marked this conversation as resolved.
Show resolved Hide resolved
<IconContainer>
<CheckIcon strokeDashoffset={state.isSelected ? '' : '21'} />
</IconContainer>
{text}
</Label>
);
};
34 changes: 34 additions & 0 deletions src/components/stories/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { styled } from '../../../stitches.config';
import { Checkbox } from '../Checkbox';

const Wrapper = styled('div', {
display: 'flex',
flexFlow: 'row wrap',
gap: '$16',
width: '100%',
});

export const CheckboxStates = () => (
<Wrapper>
<Checkbox aria-label="Example checkbox: disabled" disabled />
<Checkbox aria-label="Example checkbox: unchecked" />
<Checkbox aria-label="Example checkbox: checked" defaultSelected />
<Checkbox
aria-label="Example checkbox: focused"
autoFocus
defaultSelected
/>
<Checkbox
aria-label="Example checkbox: unchecked"
defaultSelected
disabled
/>
<Checkbox aria-label="test" isIndeterminate />
<Checkbox aria-label="test" required validationState="invalid" />
</Wrapper>
);

export default {
component: CheckboxStates,
title: 'Checkbox',
};
9 changes: 9 additions & 0 deletions stitches.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ export const {
} = createStitches({
media: {},
theme: {
borderStyles: {
solid: 'solid',
},
borderWidths: {
thin: '2px',
},
colors: colorPrimitives,
radii: {
4: '4px',
},
space: {
4: '4px',
8: '8px',
Expand Down