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 all 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ junit.xml
.idea

# logs
*.log
*.log
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
4 changes: 3 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,7 @@
"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.

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

const Path = styled('path', {
transition: `stroke-dashoffset 0.2s ${easeInQuad}`,
variants: {
isChecked: {
true: {
transition: `stroke-dashoffset 1s ${easeOutExpo}`,
},
},
},
});

type CheckIconProps = SVGProps<SVGSVGElement> & {
isChecked?: boolean;
};

export const CheckIcon = (props: CheckIconProps) => {
const { isChecked, ...svgProps } = props;

return (
<svg
fill="none"
focusable="false"
height="1em"
role="img"
viewBox="0 0 16 16"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...svgProps}
>
<Path
d="M0.709339 7.44716L5.70834 12.4462L15.3004 2.8541"
isChecked={isChecked}
stroke="currentColor"
strokeDasharray="21 21"
strokeMiterlimit="10"
strokeWidth="2"
/>
</svg>
);
};
131 changes: 131 additions & 0 deletions src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type {
CheckboxProps as RTCheckboxProps,
ToggleProps,
} from '@react-types/checkbox';
import type { ComponentPropsWithRef, ReactNode } from 'react';
import { useState, useRef } from 'react';
import { useCheckbox } from 'react-aria';
import { useToggleState } from 'react-stately';
import { styled } from '../../stitches.config';
import { CheckIcon } from '../animationIcons/CheckIcon';
import { easeOutExpo } from '../primitives/animation';

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',
},
appearance: 'none',
backgroundColor: '$brandWhite',
borderColor: '$gray50',
borderRadius: '$4',
borderStyle: '$solid',
borderWidth: '$thin',
height: '24px',
margin: '0',
position: 'relative',
transform: 'scale(1)',
transition: `transform 0.2s ${easeOutExpo}`,
variants: {
isPressed: {
true: {
transform: 'scale(0.9)',
},
},
},
width: '24px',
});

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

type CheckboxProps = RTCheckboxProps & {
label?: ReactNode;
};
export const Checkbox = ({
validationState,
label,
defaultSelected,
isIndeterminate,
disabled,
}: CheckboxProps &
ComponentPropsWithRef<'input'> &
RTCheckboxProps &
ToggleProps) => {
const ref = useRef<HTMLInputElement>(null);
const { isSelected, toggle, setSelected } = useToggleState({
defaultSelected,
validationState,
});
const { inputProps } = useCheckbox(
{ isIndeterminate },
{ isSelected, setSelected, toggle },
ref
);
const [isPressed, setIsPressed] = useState(false);

const handleMouseDown = () => {
if (disabled) return;
setIsPressed(true);
};

const handleMouseUp = () => {
if (disabled) return;
setIsPressed(false);
};

return (
<Label onMouseDown={handleMouseDown} onMouseUp={handleMouseUp}>
<Input
isPressed={isPressed}
{...inputProps}
disabled={disabled}
ref={ref}
/>
<IconContainer>
<CheckIcon
isChecked={isSelected}
strokeDashoffset={isSelected ? '' : '21'}
/>
</IconContainer>
{label}
</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',
};
19 changes: 19 additions & 0 deletions src/primitives/animation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Easing
export const easeInQuad = 'cubic-bezier(0.55, 0.085, 0.68, 0.53)';
export const easeInCubic = 'cubic-bezier(0.55, 0.055, 0.675, 0.19)';
export const easeInQuart = 'cubic-bezier(0.895, 0.03, 0.685, 0.22)';
export const easeInQuint = 'cubic-bezier(0.755, 0.05, 0.855, 0.06)';
export const easeInExpo = 'cubic-bezier(0.95, 0.05, 0.795, 0.035)';
export const easeInCirc = 'cubic-bezier(0.6, 0.04, 0.98, 0.335)';
export const easeOutQuad = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)';
export const easeOutCubic = 'cubic-bezier(0.215, 0.61, 0.355, 1)';
export const easeOutQuart = 'cubic-bezier(0.165, 0.84, 0.44, 1)';
export const easeOutQuint = 'cubic-bezier(0.23, 1, 0.32, 1)';
export const easeOutExpo = 'cubic-bezier(0.19, 1, 0.22, 1)';
export const easeOutCirc = 'cubic-bezier(0.075, 0.82, 0.165, 1)';
export const easeInOutQuad = 'cubic-bezier(0.455, 0.03, 0.515, 0.955)';
export const easeInOutCubic = 'cubic-bezier(0.645, 0.045, 0.355, 1)';
export const easeInOutQuart = 'cubic-bezier(0.77, 0, 0.175, 1)';
export const easeInOutQuint = 'cubic-bezier(0.86, 0, 0.07, 1)';
export const easeInOutExpo = 'cubic-bezier(1, 0, 0, 1)';
export const easeInOutCirc = 'cubic-bezier(0.785, 0.135, 0.15, 0.86)';
Comment on lines +1 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it be better for us to export this an an object so you can import one item and have it strongly typed and autosuggested.

ie

export const animation = Object.freeze({
  easeInQuad: 'cubic-bezier(0.55, 0.085, 0.68, 0.53),
  // ...
}) as const;

Then consumption simply becomes

import { animation } from '@contra/ui-kit';

console.log(animation.easeInQuad);

Additionally, let's make sure we export this via ./src/index.ts.

Consider naming animationTimingFunction (long winded but accurate).

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