@primer/components 32.0.2-rc.859381a1 → 32.1.0-rc.6f5d2b00

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ import React from 'react'
2
+ import {Checkbox} from '..'
3
+ import {behavesAsComponent, checkExports} from '../utils/testing'
4
+ import {render, cleanup} from '@testing-library/react'
5
+ import {toHaveNoViolations} from 'jest-axe'
6
+ import 'babel-polyfill'
7
+ import '@testing-library/jest-dom'
8
+ import userEvent from '@testing-library/user-event'
9
+
10
+ expect.extend(toHaveNoViolations)
11
+
12
+ describe('Checkbox', () => {
13
+ beforeEach(() => {
14
+ jest.resetAllMocks()
15
+ cleanup()
16
+ })
17
+ behavesAsComponent({Component: Checkbox})
18
+
19
+ checkExports('Checkbox', {
20
+ default: Checkbox
21
+ })
22
+
23
+ it('renders a valid checkbox input', () => {
24
+ const {getByRole} = render(<Checkbox />)
25
+
26
+ const checkbox = getByRole('checkbox')
27
+
28
+ expect(checkbox).toBeDefined()
29
+ })
30
+
31
+ it('renders an unchecked checkbox by default', () => {
32
+ const {getByRole} = render(<Checkbox />)
33
+
34
+ const checkbox = getByRole('checkbox') as HTMLInputElement
35
+
36
+ expect(checkbox.checked).toEqual(false)
37
+ })
38
+
39
+ it('renders an active checkbox when checked attribute is passed', () => {
40
+ const handleChange = jest.fn()
41
+ const {getByRole} = render(<Checkbox checked onChange={handleChange} />)
42
+
43
+ const checkbox = getByRole('checkbox') as HTMLInputElement
44
+
45
+ expect(checkbox.checked).toEqual(true)
46
+ })
47
+
48
+ it('accepts a change handler that can alter the checkbox state', () => {
49
+ const handleChange = jest.fn()
50
+ const {getByRole} = render(<Checkbox onChange={handleChange} />)
51
+
52
+ const checkbox = getByRole('checkbox') as HTMLInputElement
53
+
54
+ expect(checkbox.checked).toEqual(false)
55
+
56
+ userEvent.click(checkbox)
57
+ expect(handleChange).toHaveBeenCalled()
58
+ expect(checkbox.checked).toEqual(true)
59
+
60
+ userEvent.click(checkbox)
61
+ expect(handleChange).toHaveBeenCalled()
62
+ expect(checkbox.checked).toEqual(false)
63
+ })
64
+
65
+ it('renders an indeterminate prop correctly', () => {
66
+ const handleChange = jest.fn()
67
+ const {getByRole} = render(<Checkbox indeterminate checked onChange={handleChange} />)
68
+
69
+ const checkbox = getByRole('checkbox') as HTMLInputElement
70
+
71
+ expect(checkbox.indeterminate).toEqual(true)
72
+ expect(checkbox.checked).toEqual(false)
73
+ })
74
+
75
+ it('renders an inactive checkbox state correctly', () => {
76
+ const handleChange = jest.fn()
77
+ const {getByRole, rerender} = render(<Checkbox disabled onChange={handleChange} />)
78
+
79
+ const checkbox = getByRole('checkbox') as HTMLInputElement
80
+
81
+ expect(checkbox.disabled).toEqual(true)
82
+ expect(checkbox.checked).toEqual(false)
83
+ expect(checkbox).toHaveAttribute('aria-disabled', 'true')
84
+
85
+ userEvent.click(checkbox)
86
+
87
+ expect(checkbox.disabled).toEqual(true)
88
+ expect(checkbox.checked).toEqual(false)
89
+ expect(checkbox).toHaveAttribute('aria-disabled', 'true')
90
+
91
+ // remove disabled attribute and retest
92
+ rerender(<Checkbox onChange={handleChange} />)
93
+
94
+ expect(checkbox).toHaveAttribute('aria-disabled', 'false')
95
+ })
96
+
97
+ it('renders an uncontrolled component correctly', () => {
98
+ const {getByRole} = render(<Checkbox defaultChecked />)
99
+
100
+ const checkbox = getByRole('checkbox') as HTMLInputElement
101
+
102
+ expect(checkbox.checked).toEqual(true)
103
+
104
+ userEvent.click(checkbox)
105
+
106
+ expect(checkbox.checked).toEqual(false)
107
+ })
108
+
109
+ it('renders an aria-checked attribute correctly', () => {
110
+ const handleChange = jest.fn()
111
+ const {getByRole, rerender} = render(<Checkbox checked={false} onChange={handleChange} />)
112
+
113
+ const checkbox = getByRole('checkbox') as HTMLInputElement
114
+
115
+ expect(checkbox).toHaveAttribute('aria-checked', 'false')
116
+
117
+ rerender(<Checkbox checked={true} onChange={handleChange} />)
118
+
119
+ expect(checkbox).toHaveAttribute('aria-checked', 'true')
120
+
121
+ rerender(<Checkbox indeterminate checked onChange={handleChange} />)
122
+
123
+ expect(checkbox).toHaveAttribute('aria-checked', 'mixed')
124
+ })
125
+
126
+ it('renders an invalid aria state when validation prop indicates an error', () => {
127
+ const handleChange = jest.fn()
128
+ const {getByRole, rerender} = render(<Checkbox onChange={handleChange} />)
129
+
130
+ const checkbox = getByRole('checkbox') as HTMLInputElement
131
+
132
+ expect(checkbox).toHaveAttribute('aria-invalid', 'false')
133
+
134
+ rerender(<Checkbox onChange={handleChange} validationStatus="success" />)
135
+
136
+ expect(checkbox).toHaveAttribute('aria-invalid', 'false')
137
+
138
+ rerender(<Checkbox onChange={handleChange} validationStatus="error" />)
139
+
140
+ expect(checkbox).toHaveAttribute('aria-invalid', 'true')
141
+ })
142
+
143
+ it('renders an aria state indicating the field is required', () => {
144
+ const handleChange = jest.fn()
145
+ const {getByRole, rerender} = render(<Checkbox onChange={handleChange} />)
146
+
147
+ const checkbox = getByRole('checkbox') as HTMLInputElement
148
+
149
+ expect(checkbox).toHaveAttribute('aria-required', 'false')
150
+
151
+ rerender(<Checkbox onChange={handleChange} required />)
152
+
153
+ expect(checkbox).toHaveAttribute('aria-required', 'true')
154
+ })
155
+ })
@@ -0,0 +1,16 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Checkbox renders consistently 1`] = `
4
+ .c0 {
5
+ cursor: pointer;
6
+ }
7
+
8
+ <input
9
+ aria-checked="false"
10
+ aria-disabled="false"
11
+ aria-invalid="false"
12
+ aria-required="false"
13
+ className="c0"
14
+ type="checkbox"
15
+ />
16
+ `;
package/src/index.ts CHANGED
@@ -169,4 +169,7 @@ export type {TruncateProps} from './Truncate'
169
169
  export {default as UnderlineNav} from './UnderlineNav'
170
170
  export type {UnderlineNavProps, UnderlineNavLinkProps} from './UnderlineNav'
171
171
 
172
+ export {default as Checkbox} from './Checkbox'
173
+ export type {CheckboxProps} from './Checkbox'
174
+
172
175
  export {SSRProvider, useSSRSafeId} from './utils/ssr'
@@ -0,0 +1,164 @@
1
+ import React, {useLayoutEffect, useRef, useState} from 'react'
2
+ import {Meta} from '@storybook/react'
3
+ import styled from 'styled-components'
4
+
5
+ import {BaseStyles, Box, Checkbox, CheckboxProps, Text, ThemeProvider} from '..'
6
+ import {action} from '@storybook/addon-actions'
7
+ import {COMMON, get} from '../constants'
8
+
9
+ export default {
10
+ title: 'Forms/Checkbox',
11
+ component: Checkbox,
12
+ decorators: [
13
+ Story => {
14
+ return (
15
+ <ThemeProvider>
16
+ <BaseStyles>
17
+ <Box paddingTop={5}>{Story()}</Box>
18
+ </BaseStyles>
19
+ </ThemeProvider>
20
+ )
21
+ }
22
+ ],
23
+ argTypes: {
24
+ sx: {
25
+ table: {
26
+ disable: true
27
+ }
28
+ },
29
+ disabled: {
30
+ name: 'Disabled',
31
+ defaultValue: false,
32
+ control: {
33
+ type: 'boolean'
34
+ }
35
+ }
36
+ }
37
+ } as Meta
38
+
39
+ const StyledLabel = styled.label`
40
+ user-select: none;
41
+ font-weight: 600;
42
+ font-size: 14px;
43
+ line-height: 18px;
44
+ margin-left: 16px;
45
+ ${COMMON}
46
+ `
47
+
48
+ const StyledSubLabel = styled(Text)`
49
+ color: ${get('colors.fg.muted')};
50
+ font-size: 13px;
51
+ ${COMMON}
52
+ `
53
+
54
+ export const Default = (args: CheckboxProps) => {
55
+ const [isChecked, setChecked] = useState<boolean>(false)
56
+
57
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
58
+ setChecked(event.target.checked)
59
+ action('Change event triggered')
60
+ }
61
+
62
+ return (
63
+ <>
64
+ <Box as="form" sx={{p: 3, display: 'flex', alignItems: 'flex-start'}}>
65
+ <Checkbox id="controlled-checkbox" onChange={handleChange} checked={isChecked} {...args} />
66
+ <StyledLabel htmlFor="controlled-checkbox">
67
+ <Text sx={{display: 'block'}}>Default checkbox</Text>
68
+ <StyledSubLabel>controlled</StyledSubLabel>
69
+ </StyledLabel>
70
+ </Box>
71
+ <Box as="form" sx={{p: 3, display: 'flex', alignItems: 'flex-start'}}>
72
+ <Checkbox id="always-checked-checkbox" checked {...args} />
73
+ <StyledLabel htmlFor="always-checked-checkbox">
74
+ <Text sx={{display: 'block'}}>Always checked</Text>
75
+ <StyledSubLabel>checked=&quot;true&quot;</StyledSubLabel>
76
+ </StyledLabel>
77
+ </Box>
78
+ <Box as="form" sx={{p: 3, display: 'flex', alignItems: 'flex-start'}}>
79
+ <Checkbox id="always-unchecked-checkbox" checked={false} {...args} />
80
+ <StyledLabel htmlFor="always-unchecked-checkbox">
81
+ <Text sx={{display: 'block'}}>Always unchecked</Text>
82
+ <StyledSubLabel>checked=&quot;false&quot;</StyledSubLabel>
83
+ </StyledLabel>
84
+ </Box>
85
+ <Box as="form" sx={{p: 3, display: 'flex', alignItems: 'flex-start'}}>
86
+ <Checkbox id="disabled-checkbox" disabled checked={false} />
87
+ <StyledLabel htmlFor="disabled-checkbox">
88
+ <Text sx={{display: 'block'}}>Inactive</Text>
89
+ <StyledSubLabel>disabled=&quot;true&quot;</StyledSubLabel>
90
+ </StyledLabel>
91
+ </Box>
92
+ </>
93
+ )
94
+ }
95
+
96
+ export const Uncontrolled = (args: CheckboxProps) => {
97
+ const checkboxRef = useRef<HTMLInputElement | null>(null)
98
+
99
+ useLayoutEffect(() => {
100
+ if (checkboxRef.current) {
101
+ checkboxRef.current.checked = true
102
+ }
103
+ }, [])
104
+
105
+ return (
106
+ <Box as="form" sx={{p: 3, display: 'flex', alignItems: 'flex-start'}}>
107
+ <Checkbox id="uncontrolled-checkbox" ref={checkboxRef} {...args} />
108
+ <StyledLabel htmlFor="uncontrolled-checkbox">
109
+ <Text sx={{display: 'block'}}>Uncontrolled checkbox</Text>
110
+ <StyledSubLabel>Checked by default</StyledSubLabel>
111
+ </StyledLabel>
112
+ </Box>
113
+ )
114
+ }
115
+
116
+ export const Indeterminate = (args: CheckboxProps) => {
117
+ const [checkboxes, setCheckboxes] = useState<boolean[]>([false, false, false, false])
118
+
119
+ const handleChange = (_: React.ChangeEvent<HTMLInputElement>, index: number) => {
120
+ const newCheckboxes = [...checkboxes]
121
+ newCheckboxes[index] = !checkboxes[index]
122
+ setCheckboxes(newCheckboxes)
123
+ }
124
+
125
+ const handleIndeterminateChange = () => {
126
+ if (checkboxes.every(checkbox => checkbox)) {
127
+ return setCheckboxes(checkboxes.map(() => false))
128
+ }
129
+
130
+ const newCheckboxes = checkboxes.map(() => true)
131
+ setCheckboxes(newCheckboxes)
132
+ }
133
+
134
+ return (
135
+ <>
136
+ <Box as="form" sx={{p: 3, display: 'flex', alignItems: 'flex-start'}}>
137
+ <Checkbox
138
+ id="indeterminate-checkbox"
139
+ checked={checkboxes.every(Boolean)}
140
+ onChange={handleIndeterminateChange}
141
+ indeterminate={!checkboxes.every(Boolean)}
142
+ />
143
+ <StyledLabel htmlFor="controlled-checkbox">
144
+ <Text sx={{display: 'block'}}>Default checkbox</Text>
145
+ <StyledSubLabel>controlled</StyledSubLabel>
146
+ </StyledLabel>
147
+ </Box>
148
+
149
+ {checkboxes.map((field, index) => (
150
+ <Box key={`sub-checkbox-${index}`} as="form" sx={{p: 1, pl: 7, display: 'flex', alignItems: 'flex-start'}}>
151
+ <Checkbox
152
+ id={`sub-checkbox-${index}`}
153
+ checked={checkboxes[index]}
154
+ onChange={event => handleChange(event, index)}
155
+ {...args}
156
+ />
157
+ <StyledLabel htmlFor={`sub-checkbox-${index}`}>
158
+ <Text sx={{display: 'block'}}>Checkbox {index + 1}</Text>
159
+ </StyledLabel>
160
+ </Box>
161
+ ))}
162
+ </>
163
+ )
164
+ }