@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,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