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

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ import React, { InputHTMLAttributes } from 'react';
2
+ import { SxProp } from './sx';
3
+ export declare type CheckboxProps = {
4
+ /**
5
+ * Apply indeterminate visual appearance to the checkbox
6
+ */
7
+ indeterminate?: boolean;
8
+ /**
9
+ * Apply inactive visual appearance to the checkbox
10
+ */
11
+ disabled?: boolean;
12
+ /**
13
+ * Forward a ref to the underlying input element
14
+ */
15
+ ref?: React.RefObject<HTMLInputElement>;
16
+ /**
17
+ * Indicates whether the checkbox must be checked
18
+ */
19
+ required?: boolean;
20
+ /**
21
+ * Indicates whether the checkbox validation state
22
+ */
23
+ validationStatus?: 'error' | 'success';
24
+ } & InputHTMLAttributes<HTMLInputElement> & SxProp;
25
+ /**
26
+ * An accessible, native checkbox component
27
+ */
28
+ declare const Checkbox: React.ForwardRefExoticComponent<Pick<CheckboxProps, "sx" | keyof React.InputHTMLAttributes<HTMLInputElement> | "indeterminate" | "validationStatus"> & React.RefAttributes<HTMLInputElement>>;
29
+ export default Checkbox;
@@ -0,0 +1,44 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
3
+ import styled from 'styled-components';
4
+ import { useProvidedRefOrCreate } from './hooks';
5
+ import React, { useLayoutEffect } from 'react';
6
+ import sx from './sx';
7
+ const StyledCheckbox = styled.input.withConfig({
8
+ displayName: "Checkbox__StyledCheckbox",
9
+ componentId: "i51804-0"
10
+ })(["cursor:pointer;", " ", ""], props => props.disabled && `cursor: not-allowed;`, sx);
11
+ /**
12
+ * An accessible, native checkbox component
13
+ */
14
+
15
+ const Checkbox = /*#__PURE__*/React.forwardRef(({
16
+ checked,
17
+ indeterminate,
18
+ disabled,
19
+ sx: sxProp,
20
+ required,
21
+ validationStatus,
22
+ ...rest
23
+ }, ref) => {
24
+ const checkboxRef = useProvidedRefOrCreate(ref);
25
+ useLayoutEffect(() => {
26
+ if (checkboxRef.current) {
27
+ checkboxRef.current.indeterminate = indeterminate || false;
28
+ }
29
+ }, [indeterminate, checked, checkboxRef]);
30
+ return /*#__PURE__*/React.createElement(StyledCheckbox, _extends({
31
+ type: "checkbox",
32
+ disabled: disabled,
33
+ "aria-disabled": disabled ? 'true' : 'false',
34
+ ref: ref || checkboxRef,
35
+ checked: indeterminate ? false : checked,
36
+ "aria-checked": indeterminate ? 'mixed' : checked ? 'true' : 'false',
37
+ sx: sxProp,
38
+ required: required,
39
+ "aria-required": required ? 'true' : 'false',
40
+ "aria-invalid": validationStatus === 'error' ? 'true' : 'false'
41
+ }, rest));
42
+ });
43
+ Checkbox.displayName = 'Checkbox';
44
+ export default Checkbox;
@@ -0,0 +1,2 @@
1
+ import 'babel-polyfill';
2
+ import '@testing-library/jest-dom';
@@ -0,0 +1,169 @@
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
+ expect.extend(toHaveNoViolations);
10
+ describe('Checkbox', () => {
11
+ beforeEach(() => {
12
+ jest.resetAllMocks();
13
+ cleanup();
14
+ });
15
+ behavesAsComponent({
16
+ Component: Checkbox
17
+ });
18
+ checkExports('Checkbox', {
19
+ default: Checkbox
20
+ });
21
+ it('renders a valid checkbox input', () => {
22
+ const {
23
+ getByRole
24
+ } = render( /*#__PURE__*/React.createElement(Checkbox, null));
25
+ const checkbox = getByRole('checkbox');
26
+ expect(checkbox).toBeDefined();
27
+ });
28
+ it('renders an unchecked checkbox by default', () => {
29
+ const {
30
+ getByRole
31
+ } = render( /*#__PURE__*/React.createElement(Checkbox, null));
32
+ const checkbox = getByRole('checkbox');
33
+ expect(checkbox.checked).toEqual(false);
34
+ });
35
+ it('renders an active checkbox when checked attribute is passed', () => {
36
+ const handleChange = jest.fn();
37
+ const {
38
+ getByRole
39
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
40
+ checked: true,
41
+ onChange: handleChange
42
+ }));
43
+ const checkbox = getByRole('checkbox');
44
+ expect(checkbox.checked).toEqual(true);
45
+ });
46
+ it('accepts a change handler that can alter the checkbox state', () => {
47
+ const handleChange = jest.fn();
48
+ const {
49
+ getByRole
50
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
51
+ onChange: handleChange
52
+ }));
53
+ const checkbox = getByRole('checkbox');
54
+ expect(checkbox.checked).toEqual(false);
55
+ userEvent.click(checkbox);
56
+ expect(handleChange).toHaveBeenCalled();
57
+ expect(checkbox.checked).toEqual(true);
58
+ userEvent.click(checkbox);
59
+ expect(handleChange).toHaveBeenCalled();
60
+ expect(checkbox.checked).toEqual(false);
61
+ });
62
+ it('renders an indeterminate prop correctly', () => {
63
+ const handleChange = jest.fn();
64
+ const {
65
+ getByRole
66
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
67
+ indeterminate: true,
68
+ checked: true,
69
+ onChange: handleChange
70
+ }));
71
+ const checkbox = getByRole('checkbox');
72
+ expect(checkbox.indeterminate).toEqual(true);
73
+ expect(checkbox.checked).toEqual(false);
74
+ });
75
+ it('renders an inactive checkbox state correctly', () => {
76
+ const handleChange = jest.fn();
77
+ const {
78
+ getByRole,
79
+ rerender
80
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
81
+ disabled: true,
82
+ onChange: handleChange
83
+ }));
84
+ const checkbox = getByRole('checkbox');
85
+ expect(checkbox.disabled).toEqual(true);
86
+ expect(checkbox.checked).toEqual(false);
87
+ expect(checkbox).toHaveAttribute('aria-disabled', 'true');
88
+ userEvent.click(checkbox);
89
+ expect(checkbox.disabled).toEqual(true);
90
+ expect(checkbox.checked).toEqual(false);
91
+ expect(checkbox).toHaveAttribute('aria-disabled', 'true'); // remove disabled attribute and retest
92
+
93
+ rerender( /*#__PURE__*/React.createElement(Checkbox, {
94
+ onChange: handleChange
95
+ }));
96
+ expect(checkbox).toHaveAttribute('aria-disabled', 'false');
97
+ });
98
+ it('renders an uncontrolled component correctly', () => {
99
+ const {
100
+ getByRole
101
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
102
+ defaultChecked: true
103
+ }));
104
+ const checkbox = getByRole('checkbox');
105
+ expect(checkbox.checked).toEqual(true);
106
+ userEvent.click(checkbox);
107
+ expect(checkbox.checked).toEqual(false);
108
+ });
109
+ it('renders an aria-checked attribute correctly', () => {
110
+ const handleChange = jest.fn();
111
+ const {
112
+ getByRole,
113
+ rerender
114
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
115
+ checked: false,
116
+ onChange: handleChange
117
+ }));
118
+ const checkbox = getByRole('checkbox');
119
+ expect(checkbox).toHaveAttribute('aria-checked', 'false');
120
+ rerender( /*#__PURE__*/React.createElement(Checkbox, {
121
+ checked: true,
122
+ onChange: handleChange
123
+ }));
124
+ expect(checkbox).toHaveAttribute('aria-checked', 'true');
125
+ rerender( /*#__PURE__*/React.createElement(Checkbox, {
126
+ indeterminate: true,
127
+ checked: true,
128
+ onChange: handleChange
129
+ }));
130
+ expect(checkbox).toHaveAttribute('aria-checked', 'mixed');
131
+ });
132
+ it('renders an invalid aria state when validation prop indicates an error', () => {
133
+ const handleChange = jest.fn();
134
+ const {
135
+ getByRole,
136
+ rerender
137
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
138
+ onChange: handleChange
139
+ }));
140
+ const checkbox = getByRole('checkbox');
141
+ expect(checkbox).toHaveAttribute('aria-invalid', 'false');
142
+ rerender( /*#__PURE__*/React.createElement(Checkbox, {
143
+ onChange: handleChange,
144
+ validationStatus: "success"
145
+ }));
146
+ expect(checkbox).toHaveAttribute('aria-invalid', 'false');
147
+ rerender( /*#__PURE__*/React.createElement(Checkbox, {
148
+ onChange: handleChange,
149
+ validationStatus: "error"
150
+ }));
151
+ expect(checkbox).toHaveAttribute('aria-invalid', 'true');
152
+ });
153
+ it('renders an aria state indicating the field is required', () => {
154
+ const handleChange = jest.fn();
155
+ const {
156
+ getByRole,
157
+ rerender
158
+ } = render( /*#__PURE__*/React.createElement(Checkbox, {
159
+ onChange: handleChange
160
+ }));
161
+ const checkbox = getByRole('checkbox');
162
+ expect(checkbox).toHaveAttribute('aria-required', 'false');
163
+ rerender( /*#__PURE__*/React.createElement(Checkbox, {
164
+ onChange: handleChange,
165
+ required: true
166
+ }));
167
+ expect(checkbox).toHaveAttribute('aria-required', 'true');
168
+ });
169
+ });
@@ -115,4 +115,6 @@ export { default as Truncate } from './Truncate';
115
115
  export type { TruncateProps } from './Truncate';
116
116
  export { default as UnderlineNav } from './UnderlineNav';
117
117
  export type { UnderlineNavProps, UnderlineNavLinkProps } from './UnderlineNav';
118
+ export { default as Checkbox } from './Checkbox';
119
+ export type { CheckboxProps } from './Checkbox';
118
120
  export { SSRProvider, useSSRSafeId } from './utils/ssr';
package/lib-esm/index.js CHANGED
@@ -70,4 +70,5 @@ export { default as Token, IssueLabelToken, AvatarToken } from './Token';
70
70
  export { default as Tooltip } from './Tooltip';
71
71
  export { default as Truncate } from './Truncate';
72
72
  export { default as UnderlineNav } from './UnderlineNav';
73
+ export { default as Checkbox } from './Checkbox';
73
74
  export { SSRProvider, useSSRSafeId } from './utils/ssr';
@@ -0,0 +1,197 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
3
+ import React, { useLayoutEffect, useRef, useState } from 'react';
4
+ import styled from 'styled-components';
5
+ import { BaseStyles, Box, Checkbox, Text, ThemeProvider } from '..';
6
+ import { action } from '@storybook/addon-actions';
7
+ import { COMMON, get } from '../constants';
8
+ export default {
9
+ title: 'Forms/Checkbox',
10
+ component: Checkbox,
11
+ decorators: [Story => {
12
+ return /*#__PURE__*/React.createElement(ThemeProvider, null, /*#__PURE__*/React.createElement(BaseStyles, null, /*#__PURE__*/React.createElement(Box, {
13
+ paddingTop: 5
14
+ }, Story())));
15
+ }],
16
+ argTypes: {
17
+ sx: {
18
+ table: {
19
+ disable: true
20
+ }
21
+ },
22
+ disabled: {
23
+ name: 'Disabled',
24
+ defaultValue: false,
25
+ control: {
26
+ type: 'boolean'
27
+ }
28
+ }
29
+ }
30
+ };
31
+ const StyledLabel = styled.label.withConfig({
32
+ displayName: "Checkboxstories__StyledLabel",
33
+ componentId: "sdupvr-0"
34
+ })(["user-select:none;font-weight:600;font-size:14px;line-height:18px;margin-left:16px;", ""], COMMON);
35
+ const StyledSubLabel = styled(Text).withConfig({
36
+ displayName: "Checkboxstories__StyledSubLabel",
37
+ componentId: "sdupvr-1"
38
+ })(["color:", ";font-size:13px;", ""], get('colors.fg.muted'), COMMON);
39
+ export const Default = args => {
40
+ const [isChecked, setChecked] = useState(false);
41
+
42
+ const handleChange = event => {
43
+ setChecked(event.target.checked);
44
+ action('Change event triggered');
45
+ };
46
+
47
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Box, {
48
+ as: "form",
49
+ sx: {
50
+ p: 3,
51
+ display: 'flex',
52
+ alignItems: 'flex-start'
53
+ }
54
+ }, /*#__PURE__*/React.createElement(Checkbox, _extends({
55
+ id: "controlled-checkbox",
56
+ onChange: handleChange,
57
+ checked: isChecked
58
+ }, args)), /*#__PURE__*/React.createElement(StyledLabel, {
59
+ htmlFor: "controlled-checkbox"
60
+ }, /*#__PURE__*/React.createElement(Text, {
61
+ sx: {
62
+ display: 'block'
63
+ }
64
+ }, "Default checkbox"), /*#__PURE__*/React.createElement(StyledSubLabel, null, "controlled"))), /*#__PURE__*/React.createElement(Box, {
65
+ as: "form",
66
+ sx: {
67
+ p: 3,
68
+ display: 'flex',
69
+ alignItems: 'flex-start'
70
+ }
71
+ }, /*#__PURE__*/React.createElement(Checkbox, _extends({
72
+ id: "always-checked-checkbox",
73
+ checked: true
74
+ }, args)), /*#__PURE__*/React.createElement(StyledLabel, {
75
+ htmlFor: "always-checked-checkbox"
76
+ }, /*#__PURE__*/React.createElement(Text, {
77
+ sx: {
78
+ display: 'block'
79
+ }
80
+ }, "Always checked"), /*#__PURE__*/React.createElement(StyledSubLabel, null, "checked=\"true\""))), /*#__PURE__*/React.createElement(Box, {
81
+ as: "form",
82
+ sx: {
83
+ p: 3,
84
+ display: 'flex',
85
+ alignItems: 'flex-start'
86
+ }
87
+ }, /*#__PURE__*/React.createElement(Checkbox, _extends({
88
+ id: "always-unchecked-checkbox",
89
+ checked: false
90
+ }, args)), /*#__PURE__*/React.createElement(StyledLabel, {
91
+ htmlFor: "always-unchecked-checkbox"
92
+ }, /*#__PURE__*/React.createElement(Text, {
93
+ sx: {
94
+ display: 'block'
95
+ }
96
+ }, "Always unchecked"), /*#__PURE__*/React.createElement(StyledSubLabel, null, "checked=\"false\""))), /*#__PURE__*/React.createElement(Box, {
97
+ as: "form",
98
+ sx: {
99
+ p: 3,
100
+ display: 'flex',
101
+ alignItems: 'flex-start'
102
+ }
103
+ }, /*#__PURE__*/React.createElement(Checkbox, {
104
+ id: "disabled-checkbox",
105
+ disabled: true,
106
+ checked: false
107
+ }), /*#__PURE__*/React.createElement(StyledLabel, {
108
+ htmlFor: "disabled-checkbox"
109
+ }, /*#__PURE__*/React.createElement(Text, {
110
+ sx: {
111
+ display: 'block'
112
+ }
113
+ }, "Inactive"), /*#__PURE__*/React.createElement(StyledSubLabel, null, "disabled=\"true\""))));
114
+ };
115
+ export const Uncontrolled = args => {
116
+ const checkboxRef = useRef(null);
117
+ useLayoutEffect(() => {
118
+ if (checkboxRef.current) {
119
+ checkboxRef.current.checked = true;
120
+ }
121
+ }, []);
122
+ return /*#__PURE__*/React.createElement(Box, {
123
+ as: "form",
124
+ sx: {
125
+ p: 3,
126
+ display: 'flex',
127
+ alignItems: 'flex-start'
128
+ }
129
+ }, /*#__PURE__*/React.createElement(Checkbox, _extends({
130
+ id: "uncontrolled-checkbox",
131
+ ref: checkboxRef
132
+ }, args)), /*#__PURE__*/React.createElement(StyledLabel, {
133
+ htmlFor: "uncontrolled-checkbox"
134
+ }, /*#__PURE__*/React.createElement(Text, {
135
+ sx: {
136
+ display: 'block'
137
+ }
138
+ }, "Uncontrolled checkbox"), /*#__PURE__*/React.createElement(StyledSubLabel, null, "Checked by default")));
139
+ };
140
+ Uncontrolled.displayName = "Uncontrolled";
141
+ export const Indeterminate = args => {
142
+ const [checkboxes, setCheckboxes] = useState([false, false, false, false]);
143
+
144
+ const handleChange = (_, index) => {
145
+ const newCheckboxes = [...checkboxes];
146
+ newCheckboxes[index] = !checkboxes[index];
147
+ setCheckboxes(newCheckboxes);
148
+ };
149
+
150
+ const handleIndeterminateChange = () => {
151
+ if (checkboxes.every(checkbox => checkbox)) {
152
+ return setCheckboxes(checkboxes.map(() => false));
153
+ }
154
+
155
+ const newCheckboxes = checkboxes.map(() => true);
156
+ setCheckboxes(newCheckboxes);
157
+ };
158
+
159
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Box, {
160
+ as: "form",
161
+ sx: {
162
+ p: 3,
163
+ display: 'flex',
164
+ alignItems: 'flex-start'
165
+ }
166
+ }, /*#__PURE__*/React.createElement(Checkbox, {
167
+ id: "indeterminate-checkbox",
168
+ checked: checkboxes.every(Boolean),
169
+ onChange: handleIndeterminateChange,
170
+ indeterminate: !checkboxes.every(Boolean)
171
+ }), /*#__PURE__*/React.createElement(StyledLabel, {
172
+ htmlFor: "controlled-checkbox"
173
+ }, /*#__PURE__*/React.createElement(Text, {
174
+ sx: {
175
+ display: 'block'
176
+ }
177
+ }, "Default checkbox"), /*#__PURE__*/React.createElement(StyledSubLabel, null, "controlled"))), checkboxes.map((field, index) => /*#__PURE__*/React.createElement(Box, {
178
+ key: `sub-checkbox-${index}`,
179
+ as: "form",
180
+ sx: {
181
+ p: 1,
182
+ pl: 7,
183
+ display: 'flex',
184
+ alignItems: 'flex-start'
185
+ }
186
+ }, /*#__PURE__*/React.createElement(Checkbox, _extends({
187
+ id: `sub-checkbox-${index}`,
188
+ checked: checkboxes[index],
189
+ onChange: event => handleChange(event, index)
190
+ }, args)), /*#__PURE__*/React.createElement(StyledLabel, {
191
+ htmlFor: `sub-checkbox-${index}`
192
+ }, /*#__PURE__*/React.createElement(Text, {
193
+ sx: {
194
+ display: 'block'
195
+ }
196
+ }, "Checkbox ", index + 1)))));
197
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/components",
3
- "version": "32.0.2-rc.859381a1",
3
+ "version": "32.1.0-rc.6f5d2b00",
4
4
  "description": "Primer react components",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-esm/index.js",
@@ -0,0 +1,75 @@
1
+ import styled from 'styled-components'
2
+ import {useProvidedRefOrCreate} from './hooks'
3
+ import React, {InputHTMLAttributes, ReactElement, useLayoutEffect} from 'react'
4
+ import sx, {SxProp} from './sx'
5
+
6
+ export type CheckboxProps = {
7
+ /**
8
+ * Apply indeterminate visual appearance to the checkbox
9
+ */
10
+ indeterminate?: boolean
11
+ /**
12
+ * Apply inactive visual appearance to the checkbox
13
+ */
14
+ disabled?: boolean
15
+ /**
16
+ * Forward a ref to the underlying input element
17
+ */
18
+ ref?: React.RefObject<HTMLInputElement>
19
+ /**
20
+ * Indicates whether the checkbox must be checked
21
+ */
22
+ required?: boolean
23
+
24
+ /**
25
+ * Indicates whether the checkbox validation state
26
+ */
27
+ validationStatus?: 'error' | 'success' // TODO: hoist to Validation typings
28
+ } & InputHTMLAttributes<HTMLInputElement> &
29
+ SxProp
30
+
31
+ const StyledCheckbox = styled.input`
32
+ cursor: pointer;
33
+
34
+ ${props => props.disabled && `cursor: not-allowed;`}
35
+
36
+ ${sx}
37
+ `
38
+
39
+ /**
40
+ * An accessible, native checkbox component
41
+ */
42
+ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
43
+ (
44
+ {checked, indeterminate, disabled, sx: sxProp, required, validationStatus, ...rest}: CheckboxProps,
45
+ ref
46
+ ): ReactElement => {
47
+ const checkboxRef = useProvidedRefOrCreate(ref as React.RefObject<HTMLInputElement>)
48
+
49
+ useLayoutEffect(() => {
50
+ if (checkboxRef.current) {
51
+ checkboxRef.current.indeterminate = indeterminate || false
52
+ }
53
+ }, [indeterminate, checked, checkboxRef])
54
+
55
+ return (
56
+ <StyledCheckbox
57
+ type="checkbox"
58
+ disabled={disabled}
59
+ aria-disabled={disabled ? 'true' : 'false'}
60
+ ref={ref || checkboxRef}
61
+ checked={indeterminate ? false : checked}
62
+ aria-checked={indeterminate ? 'mixed' : checked ? 'true' : 'false'}
63
+ sx={sxProp}
64
+ required={required}
65
+ aria-required={required ? 'true' : 'false'}
66
+ aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
67
+ {...rest}
68
+ />
69
+ )
70
+ }
71
+ )
72
+
73
+ Checkbox.displayName = 'Checkbox'
74
+
75
+ export default Checkbox