@sproutsocial/racine 11.1.2 → 11.3.0-beta.1

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.
Files changed (177) hide show
  1. package/__flow__/Input/index.js +116 -67
  2. package/__flow__/Input/index.stories.js +33 -0
  3. package/__flow__/Input/index.test.js +199 -1
  4. package/__flow__/Input/styles.js +1 -1
  5. package/commonjs/Avatar/index.js +7 -4
  6. package/commonjs/Badge/index.js +3 -1
  7. package/commonjs/Badge/styles.js +1 -1
  8. package/commonjs/Banner/index.js +3 -1
  9. package/commonjs/Banner/styles.js +1 -1
  10. package/commonjs/Box/styles.js +1 -1
  11. package/commonjs/Breadcrumb/index.js +5 -2
  12. package/commonjs/Button/index.js +3 -1
  13. package/commonjs/Card/index.js +3 -1
  14. package/commonjs/Card/styles.js +3 -3
  15. package/commonjs/CharacterCounter/index.js +3 -1
  16. package/commonjs/CharacterCounter/styles.js +1 -1
  17. package/commonjs/ChartLegend/index.js +3 -1
  18. package/commonjs/ChartLegend/styles.js +3 -3
  19. package/commonjs/Checkbox/index.js +3 -1
  20. package/commonjs/Checkbox/styles.js +1 -1
  21. package/commonjs/Collapsible/index.js +5 -2
  22. package/commonjs/DatePicker/DateRangePicker.js +3 -1
  23. package/commonjs/DatePicker/SingleDatePicker.js +3 -1
  24. package/commonjs/DatePicker/StatefulDateRangePicker.js +3 -1
  25. package/commonjs/DatePicker/StatefulSingleDatePicker.js +3 -1
  26. package/commonjs/DatePicker/common.js +1 -1
  27. package/commonjs/DatePicker/styles.js +2 -6
  28. package/commonjs/Drawer/SlideTransition.js +3 -1
  29. package/commonjs/Drawer/index.js +9 -4
  30. package/commonjs/Drawer/styles.js +2 -2
  31. package/commonjs/EmptyState/index.js +3 -1
  32. package/commonjs/Fieldset/index.js +7 -3
  33. package/commonjs/FormField/index.js +3 -1
  34. package/commonjs/Icon/index.js +5 -2
  35. package/commonjs/Icon/styles.js +1 -1
  36. package/commonjs/Image/index.js +3 -1
  37. package/commonjs/Image/styles.js +1 -1
  38. package/commonjs/Indicator/index.js +3 -1
  39. package/commonjs/Input/index.js +67 -29
  40. package/commonjs/Input/styles.js +3 -3
  41. package/commonjs/KeyboardKey/index.js +3 -1
  42. package/commonjs/Label/index.js +4 -2
  43. package/commonjs/Link/index.js +3 -1
  44. package/commonjs/Link/styles.js +1 -1
  45. package/commonjs/Listbox/index.js +7 -4
  46. package/commonjs/Loader/index.js +3 -1
  47. package/commonjs/Loader/styles.js +2 -2
  48. package/commonjs/LoaderButton/index.js +3 -1
  49. package/commonjs/Menu/constants.js +1 -1
  50. package/commonjs/Menu/descendants.js +10 -7
  51. package/commonjs/Menu/hooks.js +1 -1
  52. package/commonjs/Menu/index.js +22 -16
  53. package/commonjs/Menu/styles.js +2 -2
  54. package/commonjs/Message/index.js +3 -1
  55. package/commonjs/Message/styles.js +1 -1
  56. package/commonjs/Modal/index.js +7 -3
  57. package/commonjs/Modal/styles.js +4 -6
  58. package/commonjs/Numeral/constants.js +1 -1
  59. package/commonjs/Numeral/index.js +3 -1
  60. package/commonjs/Numeral/styles.js +3 -3
  61. package/commonjs/OverflowList/styles.js +1 -1
  62. package/commonjs/Popout/index.js +7 -3
  63. package/commonjs/Popout/styles.js +1 -1
  64. package/commonjs/Radio/index.js +3 -1
  65. package/commonjs/Radio/styles.js +4 -4
  66. package/commonjs/SegmentedControl/index.js +5 -2
  67. package/commonjs/Select/index.js +3 -1
  68. package/commonjs/Stack/index.js +3 -1
  69. package/commonjs/Switch/index.js +3 -1
  70. package/commonjs/Switch/styles.js +1 -1
  71. package/commonjs/Table/index.js +10 -5
  72. package/commonjs/TableCell/index.js +3 -1
  73. package/commonjs/TableHeaderCell/index.js +3 -1
  74. package/commonjs/TableRowAccordion/index.js +3 -1
  75. package/commonjs/Tabs/index.js +5 -2
  76. package/commonjs/Tabs/styles.js +4 -4
  77. package/commonjs/Text/index.js +3 -1
  78. package/commonjs/Text/styles.js +1 -1
  79. package/commonjs/Textarea/index.js +3 -1
  80. package/commonjs/Toast/index.js +15 -15
  81. package/commonjs/Toast/styles.js +4 -7
  82. package/commonjs/ToggleHint/index.js +3 -1
  83. package/commonjs/Token/index.js +3 -1
  84. package/commonjs/Token/styles.js +1 -1
  85. package/commonjs/TokenInput/index.js +3 -1
  86. package/commonjs/Tooltip/index.js +5 -2
  87. package/commonjs/Tooltip/styles.js +1 -1
  88. package/commonjs/VisuallyHidden/index.js +1 -1
  89. package/commonjs/index.js +1 -1
  90. package/commonjs/themes/dark/decorative-palettes.js +1 -1
  91. package/commonjs/themes/dark/theme.js +1 -1
  92. package/commonjs/themes/light/decorative-palettes.js +1 -1
  93. package/commonjs/themes/light/theme.js +1 -1
  94. package/commonjs/utils/hooks.js +3 -2
  95. package/commonjs/utils/mixins.js +1 -1
  96. package/commonjs/utils/system-props.js +1 -1
  97. package/dist/themes/dark/dark.scss +5 -5
  98. package/dist/themes/light/light.scss +95 -95
  99. package/lib/Avatar/index.js +7 -4
  100. package/lib/Badge/index.js +3 -1
  101. package/lib/Badge/styles.js +1 -1
  102. package/lib/Banner/index.js +3 -1
  103. package/lib/Banner/styles.js +1 -1
  104. package/lib/Box/styles.js +1 -1
  105. package/lib/Breadcrumb/index.js +5 -2
  106. package/lib/Button/index.js +3 -1
  107. package/lib/Card/index.js +3 -1
  108. package/lib/Card/styles.js +2 -2
  109. package/lib/CharacterCounter/index.js +3 -1
  110. package/lib/CharacterCounter/styles.js +1 -1
  111. package/lib/ChartLegend/index.js +3 -1
  112. package/lib/ChartLegend/styles.js +3 -3
  113. package/lib/Checkbox/index.js +3 -1
  114. package/lib/Collapsible/index.js +5 -2
  115. package/lib/DatePicker/DateRangePicker.js +3 -1
  116. package/lib/DatePicker/SingleDatePicker.js +3 -1
  117. package/lib/DatePicker/StatefulDateRangePicker.js +3 -1
  118. package/lib/DatePicker/StatefulSingleDatePicker.js +3 -1
  119. package/lib/DatePicker/styles.js +2 -6
  120. package/lib/Drawer/SlideTransition.js +3 -1
  121. package/lib/Drawer/index.js +9 -4
  122. package/lib/Drawer/styles.js +2 -2
  123. package/lib/EmptyState/index.js +3 -1
  124. package/lib/Fieldset/index.js +7 -3
  125. package/lib/FormField/index.js +3 -1
  126. package/lib/Icon/index.js +5 -2
  127. package/lib/Icon/styles.js +1 -1
  128. package/lib/Image/index.js +3 -1
  129. package/lib/Image/styles.js +1 -1
  130. package/lib/Indicator/index.js +3 -1
  131. package/lib/Input/index.js +60 -28
  132. package/lib/Input/styles.js +3 -3
  133. package/lib/KeyboardKey/index.js +3 -1
  134. package/lib/Label/index.js +4 -2
  135. package/lib/Link/index.js +3 -1
  136. package/lib/Link/styles.js +1 -1
  137. package/lib/Listbox/index.js +6 -3
  138. package/lib/Loader/index.js +3 -1
  139. package/lib/Loader/styles.js +2 -2
  140. package/lib/LoaderButton/index.js +3 -1
  141. package/lib/Menu/descendants.js +5 -2
  142. package/lib/Menu/index.js +20 -16
  143. package/lib/Menu/styles.js +2 -2
  144. package/lib/Message/index.js +3 -1
  145. package/lib/Modal/index.js +7 -3
  146. package/lib/Modal/styles.js +3 -5
  147. package/lib/Numeral/index.js +3 -1
  148. package/lib/Numeral/styles.js +2 -2
  149. package/lib/OverflowList/styles.js +1 -1
  150. package/lib/Popout/index.js +7 -3
  151. package/lib/Popout/styles.js +1 -1
  152. package/lib/Radio/index.js +3 -1
  153. package/lib/Radio/styles.js +4 -4
  154. package/lib/SegmentedControl/index.js +5 -2
  155. package/lib/Select/index.js +3 -1
  156. package/lib/Stack/index.js +3 -1
  157. package/lib/Switch/index.js +3 -1
  158. package/lib/Switch/styles.js +1 -1
  159. package/lib/Table/index.js +9 -4
  160. package/lib/TableCell/index.js +3 -1
  161. package/lib/TableHeaderCell/index.js +3 -1
  162. package/lib/TableRowAccordion/index.js +3 -1
  163. package/lib/Tabs/index.js +5 -2
  164. package/lib/Tabs/styles.js +3 -3
  165. package/lib/Text/index.js +3 -1
  166. package/lib/Text/styles.js +1 -1
  167. package/lib/Textarea/index.js +3 -1
  168. package/lib/Toast/index.js +14 -14
  169. package/lib/Toast/styles.js +3 -7
  170. package/lib/ToggleHint/index.js +3 -1
  171. package/lib/Token/index.js +3 -1
  172. package/lib/Token/styles.js +1 -1
  173. package/lib/TokenInput/index.js +3 -1
  174. package/lib/Tooltip/index.js +5 -2
  175. package/lib/Tooltip/styles.js +1 -1
  176. package/lib/VisuallyHidden/index.js +1 -1
  177. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import Container, { Accessory } from "./styles";
4
+ import Button from "../Button";
5
+ import Icon from "../Icon";
4
6
 
5
7
  type TypeProps = {
6
8
  /** ID of the form element, should match the "for" value of the associated label */
@@ -10,6 +12,8 @@ type TypeProps = {
10
12
  ariaLabel?: string,
11
13
  /** Attribute used to associate other elements that describe the Input, like an error */
12
14
  ariaDescribedby?: string,
15
+ /** Label for Input.ClearButton. Required when using <Input type="search"/> or <Input.ClearButton/>. */
16
+ clearButtonLabel?: string,
13
17
  /** Placeholder text for when value is undefined or empty */
14
18
  placeholder?: string,
15
19
  /** Current value of the input */
@@ -42,6 +46,8 @@ type TypeProps = {
42
46
  innerRef?: React.Ref<"input">,
43
47
  onBlur?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
44
48
  onChange?: (e: SyntheticInputEvent<HTMLInputElement>, value: string) => void,
49
+ /** Called on Input.ClearButton trigger. */
50
+ onClear?: (e: SyntheticEvent<HTMLButtonElement>) => void,
45
51
  onFocus?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
46
52
  onKeyDown?: (
47
53
  e: SyntheticKeyboardEvent<HTMLInputElement>,
@@ -54,14 +60,34 @@ type TypeProps = {
54
60
  onPaste?: (e: SyntheticInputEvent<HTMLInputElement>, value: string) => void,
55
61
  size?: "large" | "small" | "default",
56
62
  qa?: Object,
57
- /**
58
- Controls the styles of the input. Primary is our standard input styles and secondary is a borderless input.
63
+ /**
64
+ Controls the styles of the input. Primary is our standard input styles and secondary is a borderless input.
59
65
  Note that this prop should only be used to alter styles and not functionality.
60
66
  */
61
67
  appearance?: "primary" | "secondary",
62
68
  };
63
69
 
64
- export default class Input extends React.Component<TypeProps> {
70
+ // Using Context so that Input's Input.ClearButton-specific props can be passed to Input.ClearButton,
71
+ // regardless of whether it is manually included as elemAfter or automatically included for type="search" Inputs.
72
+ type TypeInputContext = $Shape<{
73
+ handleClear: (e: SyntheticEvent<HTMLButtonElement>) => void,
74
+ clearButtonLabel: string,
75
+ }>;
76
+
77
+ const InputContext = React.createContext<TypeInputContext>({});
78
+
79
+ const ClearButton = () => {
80
+ const { handleClear, clearButtonLabel } = React.useContext(InputContext);
81
+ return (
82
+ <Button onClick={handleClear}>
83
+ {/*Unlocalized fallback should not be used. Always include a localized
84
+ clearButtonLabel when using <Input.ClearButton/> or <Input type="search"/>.*/}
85
+ <Icon name="circlex" title={clearButtonLabel || "Clear"} />
86
+ </Button>
87
+ );
88
+ };
89
+
90
+ class Input extends React.Component<TypeProps> {
65
91
  static defaultProps = {
66
92
  autoFocus: false,
67
93
  disabled: false,
@@ -70,41 +96,28 @@ export default class Input extends React.Component<TypeProps> {
70
96
  appearance: "primary",
71
97
  };
72
98
 
73
- handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) => {
74
- if (this.props.onBlur) {
75
- this.props.onBlur(e);
76
- }
77
- };
99
+ static ClearButton = ClearButton;
78
100
 
79
- handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
80
- if (this.props.onChange) {
81
- this.props.onChange(e, e.currentTarget.value);
82
- }
83
- };
101
+ handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) =>
102
+ this.props.onBlur?.(e);
84
103
 
85
- handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) => {
86
- if (this.props.onFocus) {
87
- this.props.onFocus(e);
88
- }
89
- };
104
+ handleClear = (e: SyntheticEvent<HTMLButtonElement>) =>
105
+ this.props.onClear?.(e);
90
106
 
91
- handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
92
- if (this.props.onKeyDown) {
93
- this.props.onKeyDown(e, e.currentTarget.value);
94
- }
95
- };
107
+ handleChange = (e: SyntheticInputEvent<HTMLInputElement>) =>
108
+ this.props.onChange?.(e, e.currentTarget.value);
96
109
 
97
- handleKeyUp = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
98
- if (this.props.onKeyUp) {
99
- this.props.onKeyUp(e, e.currentTarget.value);
100
- }
101
- };
110
+ handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) =>
111
+ this.props.onFocus?.(e);
102
112
 
103
- handlePaste = (e: SyntheticInputEvent<HTMLInputElement>) => {
104
- if (this.props.onPaste) {
105
- this.props.onPaste(e, e.currentTarget.value);
106
- }
107
- };
113
+ handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) =>
114
+ this.props.onKeyDown?.(e, e.currentTarget.value);
115
+
116
+ handleKeyUp = (e: SyntheticKeyboardEvent<HTMLInputElement>) =>
117
+ this.props.onKeyUp?.(e, e.currentTarget.value);
118
+
119
+ handlePaste = (e: SyntheticInputEvent<HTMLInputElement>) =>
120
+ this.props.onPaste?.(e, e.currentTarget.value);
108
121
 
109
122
  render() {
110
123
  const {
@@ -125,9 +138,11 @@ export default class Input extends React.Component<TypeProps> {
125
138
  maxLength,
126
139
  ariaLabel,
127
140
  ariaDescribedby,
141
+ clearButtonLabel,
128
142
  innerRef,
129
143
  onBlur,
130
144
  onChange,
145
+ onClear,
131
146
  onFocus,
132
147
  onKeyDown,
133
148
  onKeyUp,
@@ -138,15 +153,27 @@ export default class Input extends React.Component<TypeProps> {
138
153
  ...rest
139
154
  } = this.props;
140
155
 
156
+ // Convert autoComplete from a boolean prop to a string value.
141
157
  let autoCompleteValue = undefined;
142
158
  if (autoComplete !== undefined) {
143
159
  autoCompleteValue = autoComplete ? "on" : "off";
144
160
  }
145
161
 
162
+ // Add default elemBefore and elemAfter elements if type is search.
163
+ const elementBefore =
164
+ type === "search" && !elemBefore ? (
165
+ <Icon name="search" ariaHidden />
166
+ ) : (
167
+ elemBefore
168
+ );
169
+ // Do not add a ClearButton if no onClear callback is provided or if an elemAfter prop was passed.
170
+ const elementAfter =
171
+ type === "search" && onClear && !elemAfter ? <ClearButton /> : elemAfter;
172
+
146
173
  return (
147
174
  <Container
148
- hasBeforeElement={!!elemBefore}
149
- hasAfterElement={!!elemAfter}
175
+ hasBeforeElement={!!elementBefore}
176
+ hasAfterElement={!!elementAfter}
150
177
  disabled={disabled}
151
178
  invalid={!!isInvalid}
152
179
  warning={hasWarning}
@@ -154,39 +181,61 @@ export default class Input extends React.Component<TypeProps> {
154
181
  // $FlowIssue - upgrade v0.112.0
155
182
  {...rest}
156
183
  >
157
- {elemBefore && <Accessory before>{elemBefore}</Accessory>}
158
-
159
- <input
160
- aria-invalid={!!isInvalid}
161
- aria-label={ariaLabel}
162
- aria-describedby={ariaDescribedby}
163
- autoComplete={autoCompleteValue}
164
- autoFocus={autoFocus}
165
- disabled={disabled}
166
- readOnly={readOnly}
167
- id={id}
168
- name={name}
169
- placeholder={placeholder}
170
- type={type}
171
- required={required}
172
- value={value}
173
- maxLength={maxLength}
174
- onBlur={this.handleBlur}
175
- onChange={this.handleChange}
176
- onFocus={this.handleFocus}
177
- onKeyDown={this.handleKeyDown}
178
- onKeyUp={this.handleKeyUp}
179
- onPaste={this.handlePaste}
180
- ref={innerRef}
181
- data-qa-input={name || ""}
182
- data-qa-input-isdisabled={disabled === true}
183
- data-qa-input-isrequired={required === true}
184
- {...qa}
185
- {...inputProps}
186
- />
187
-
188
- {elemAfter && <Accessory after>{elemAfter}</Accessory>}
184
+ <InputContext.Provider
185
+ value={{
186
+ handleClear: this.handleClear,
187
+ clearButtonLabel,
188
+ }}
189
+ >
190
+ {elementBefore && <Accessory before>{elementBefore}</Accessory>}
191
+
192
+ <input
193
+ aria-invalid={!!isInvalid}
194
+ aria-label={ariaLabel}
195
+ aria-describedby={ariaDescribedby}
196
+ autoComplete={autoCompleteValue}
197
+ autoFocus={autoFocus}
198
+ disabled={disabled}
199
+ readOnly={readOnly}
200
+ id={id}
201
+ name={name}
202
+ placeholder={placeholder}
203
+ type={type}
204
+ required={required}
205
+ value={value}
206
+ maxLength={maxLength}
207
+ onBlur={this.handleBlur}
208
+ onChange={this.handleChange}
209
+ onFocus={this.handleFocus}
210
+ onKeyDown={this.handleKeyDown}
211
+ onKeyUp={this.handleKeyUp}
212
+ onPaste={this.handlePaste}
213
+ ref={innerRef}
214
+ data-qa-input={name || ""}
215
+ data-qa-input-isdisabled={disabled === true}
216
+ data-qa-input-isrequired={required === true}
217
+ {...qa}
218
+ {...inputProps}
219
+ />
220
+
221
+ {elementAfter && (
222
+ <Accessory
223
+ after
224
+ // Used for positioning. This logic will detect if the element is a ClearButton,
225
+ // regardless of whether it was manually passed as elemAfter or automatically added to a search Input.
226
+ isClearButton={
227
+ elementAfter?.type?.prototype === Input.ClearButton.prototype
228
+ }
229
+ >
230
+ {elementAfter}
231
+ </Accessory>
232
+ )}
233
+ </InputContext.Provider>
189
234
  </Container>
190
235
  );
191
236
  }
192
237
  }
238
+
239
+ Input.ClearButton.displayName = "Input.ClearButton";
240
+
241
+ export default Input;
@@ -141,6 +141,39 @@ leftAndRightIcons.story = {
141
141
  name: "Left and right icons",
142
142
  };
143
143
 
144
+ export const searchInput = () => (
145
+ <Input
146
+ type="search"
147
+ placeholder={text("placeholder", "Please enter a value...")}
148
+ value={text("value", "val")}
149
+ onClear={() => window.alert("Cleared!")}
150
+ clearButtonLabel={text("clearButtonLabel", "Clear search")}
151
+ ariaLabel={text("ariaLabel", "Descriptive label goes here")}
152
+ />
153
+ );
154
+
155
+ searchInput.story = {
156
+ name: "Search Input",
157
+ };
158
+
159
+ export const nonSearchClearButtonInput = () => {
160
+ return (
161
+ <Input
162
+ type="text"
163
+ placeholder={text("placeholder", "Please enter a value...")}
164
+ value={text("value", "val")}
165
+ onClear={() => window.alert("Cleared!")}
166
+ ariaLabel={text("ariaLabel", "Descriptive label goes here")}
167
+ clearButtonLabel={text("clearButtonLabel", "Clear text")}
168
+ elemAfter={<Input.ClearButton />}
169
+ />
170
+ );
171
+ };
172
+
173
+ nonSearchClearButtonInput.story = {
174
+ name: "Input.ClearButton usage",
175
+ };
176
+
144
177
  export const autofocus = () => (
145
178
  <Input
146
179
  autoFocus
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import "jest-styled-components";
3
- import { render, fireEvent } from "../utils/react-testing-library";
3
+ import { render, fireEvent, userEvent } from "../utils/react-testing-library";
4
4
  import Input from "./";
5
5
  import Text from "../Text";
6
6
 
@@ -69,6 +69,204 @@ describe("Input", () => {
69
69
  expect(getByText("After")).toBeTruthy();
70
70
  });
71
71
 
72
+ describe("Input.ClearButton", () => {
73
+ describe("Input type=search", () => {
74
+ it("should render a clear button for search Inputs", () => {
75
+ const { getByRole } = render(
76
+ <Input
77
+ id="name"
78
+ name="name"
79
+ value="mic"
80
+ type="search"
81
+ onClear={jest.fn()}
82
+ clearButtonLabel="Clear search"
83
+ />
84
+ );
85
+ expect(getByRole("button")).toBeTruthy();
86
+ });
87
+
88
+ it("should not override an elemAfter prop if passed", () => {
89
+ const { getByText, queryByTitle } = render(
90
+ <Input
91
+ id="name"
92
+ name="name"
93
+ value="mic"
94
+ type="search"
95
+ onClear={jest.fn()}
96
+ elemAfter={<Text>After</Text>}
97
+ />
98
+ );
99
+ expect(getByText("After")).toBeTruthy();
100
+ expect(queryByTitle("Clear")).not.toBeInTheDocument();
101
+ });
102
+
103
+ it("should use the fallback title if clearButtonLabel is not provided", () => {
104
+ const { getByTitle } = render(
105
+ <Input
106
+ id="name"
107
+ name="name"
108
+ value="mic"
109
+ type="search"
110
+ onClear={jest.fn()}
111
+ />
112
+ );
113
+ expect(getByTitle("Clear")).toBeTruthy();
114
+ });
115
+
116
+ it("should call onClear when clicked", () => {
117
+ const mockOnClear = jest.fn();
118
+ const { getByRole } = render(
119
+ <Input
120
+ id="name"
121
+ name="name"
122
+ value="mic"
123
+ type="search"
124
+ onClear={mockOnClear}
125
+ clearButtonLabel="Clear search"
126
+ />
127
+ );
128
+ fireEvent.click(getByRole("button"));
129
+ expect(mockOnClear).toHaveBeenCalled();
130
+ });
131
+
132
+ it("should allow keyboard access to and Space key triggering of the clear button", () => {
133
+ const mockOnClear = jest.fn();
134
+ const { getByRole } = render(
135
+ <Input
136
+ id="name"
137
+ name="name"
138
+ value="mic"
139
+ type="search"
140
+ onClear={mockOnClear}
141
+ clearButtonLabel="Clear search"
142
+ />
143
+ );
144
+ const input = getByRole("searchbox");
145
+ const button = getByRole("button");
146
+ userEvent.tab();
147
+ expect(input).toHaveFocus();
148
+ userEvent.tab();
149
+ expect(button).toHaveFocus();
150
+ userEvent.keyboard("{space}");
151
+ expect(mockOnClear).toHaveBeenCalled();
152
+ });
153
+
154
+ it("should allow keyboard access to and Enter key triggering of the clear button", () => {
155
+ const mockOnClear = jest.fn();
156
+ const { getByRole } = render(
157
+ <Input
158
+ id="name"
159
+ name="name"
160
+ value="mic"
161
+ type="search"
162
+ onClear={mockOnClear}
163
+ clearButtonLabel="Clear search"
164
+ />
165
+ );
166
+ const input = getByRole("searchbox");
167
+ const button = getByRole("button");
168
+ userEvent.tab();
169
+ expect(input).toHaveFocus();
170
+ userEvent.tab();
171
+ expect(button).toHaveFocus();
172
+ userEvent.keyboard("{enter}");
173
+ expect(mockOnClear).toHaveBeenCalled();
174
+ });
175
+ });
176
+
177
+ describe("Manual Input.ClearButton usage", () => {
178
+ it("should render a clear button when passed with elemAfter", () => {
179
+ const { getByRole } = render(
180
+ <Input
181
+ id="name"
182
+ name="name"
183
+ value="mic"
184
+ type="text"
185
+ elemAfter={<Input.ClearButton />}
186
+ clearButtonLabel="Clear search"
187
+ />
188
+ );
189
+ expect(getByRole("button")).toBeTruthy();
190
+ });
191
+
192
+ it("should use the fallback title if clearButtonLabel is not provided", () => {
193
+ const { getByTitle } = render(
194
+ <Input
195
+ id="name"
196
+ name="name"
197
+ value="mic"
198
+ type="text"
199
+ elemAfter={<Input.ClearButton />}
200
+ />
201
+ );
202
+ expect(getByTitle("Clear")).toBeTruthy();
203
+ });
204
+
205
+ it("should call onClear when Input.ClearButton is clicked", () => {
206
+ const mockOnClear = jest.fn();
207
+ const { getByRole } = render(
208
+ <Input
209
+ id="name"
210
+ name="name"
211
+ value="mic"
212
+ type="text"
213
+ elemAfter={<Input.ClearButton />}
214
+ onClear={mockOnClear}
215
+ clearButtonLabel="Clear search"
216
+ />
217
+ );
218
+ fireEvent.click(getByRole("button"));
219
+ expect(mockOnClear).toHaveBeenCalled();
220
+ });
221
+
222
+ it("should allow keyboard access to and Space key triggering of the clear button", () => {
223
+ const mockOnClear = jest.fn();
224
+ const { getByRole } = render(
225
+ <Input
226
+ id="name"
227
+ name="name"
228
+ value="mic"
229
+ type="text"
230
+ elemAfter={<Input.ClearButton />}
231
+ onClear={mockOnClear}
232
+ clearButtonLabel="Clear search"
233
+ />
234
+ );
235
+ const input = getByRole("textbox");
236
+ const button = getByRole("button");
237
+ userEvent.tab();
238
+ expect(input).toHaveFocus();
239
+ userEvent.tab();
240
+ expect(button).toHaveFocus();
241
+ userEvent.keyboard("{space}");
242
+ expect(mockOnClear).toHaveBeenCalled();
243
+ });
244
+
245
+ it("should allow keyboard access to and Enter key triggering of the clear button", () => {
246
+ const mockOnClear = jest.fn();
247
+ const { getByRole } = render(
248
+ <Input
249
+ id="name"
250
+ name="name"
251
+ value="mic"
252
+ type="text"
253
+ elemAfter={<Input.ClearButton />}
254
+ onClear={mockOnClear}
255
+ clearButtonLabel="Clear search"
256
+ />
257
+ );
258
+ const input = getByRole("textbox");
259
+ const button = getByRole("button");
260
+ userEvent.tab();
261
+ expect(input).toHaveFocus();
262
+ userEvent.tab();
263
+ expect(button).toHaveFocus();
264
+ userEvent.keyboard("{enter}");
265
+ expect(mockOnClear).toHaveBeenCalled();
266
+ });
267
+ });
268
+ });
269
+
72
270
  describe("autoComplete prop", () => {
73
271
  it("should not have autoComplete attribute when passed prop is not passed", () => {
74
272
  const { getByDataQaLabel } = render(<Input id="name" name="name" />);
@@ -140,7 +140,7 @@ export const Accessory: StyledComponent<any, TypeTheme, *> = styled.div`
140
140
  ${(props) =>
141
141
  props.after &&
142
142
  css`
143
- right: ${props.theme.space[350]};
143
+ right: ${props.isClearButton ? 0 : props.theme.space[350]};
144
144
  `};
145
145
  `;
146
146
 
@@ -15,6 +15,9 @@ var _Image = _interopRequireDefault(require("../Image"));
15
15
 
16
16
  var _Text = _interopRequireDefault(require("../Text"));
17
17
 
18
+ var _excluded = ["fontSize"],
19
+ _excluded2 = ["appearance", "name", "src", "variant", "type", "size", "bg", "color"];
20
+
18
21
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
22
 
20
23
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -27,12 +30,12 @@ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) r
27
30
 
28
31
  var AvatarText = (0, _styledComponents.default)(function (_ref) {
29
32
  var fontSize = _ref.fontSize,
30
- rest = _objectWithoutPropertiesLoose(_ref, ["fontSize"]);
33
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded);
31
34
 
32
35
  return /*#__PURE__*/React.createElement(_Text.default, rest);
33
36
  }).withConfig({
34
37
  displayName: "Avatar__AvatarText",
35
- componentId: "yx873f-0"
38
+ componentId: "sc-yx873f-0"
36
39
  })(["font-size:", "px;color:", "px;"], function (props) {
37
40
  return props.fontSize;
38
41
  }, function (props) {
@@ -40,7 +43,7 @@ var AvatarText = (0, _styledComponents.default)(function (_ref) {
40
43
  });
41
44
  var Container = (0, _styledComponents.default)(_Box.default).withConfig({
42
45
  displayName: "Avatar__Container",
43
- componentId: "yx873f-1"
46
+ componentId: "sc-yx873f-1"
44
47
  })(["", ""], function (_ref2) {
45
48
  var theme = _ref2.theme,
46
49
  type = _ref2.type,
@@ -75,7 +78,7 @@ var Avatar = function Avatar(_ref3) {
75
78
  size = _ref3$size === void 0 ? "40px" : _ref3$size,
76
79
  bg = _ref3.bg,
77
80
  color = _ref3.color,
78
- rest = _objectWithoutPropertiesLoose(_ref3, ["appearance", "name", "src", "variant", "type", "size", "bg", "color"]);
81
+ rest = _objectWithoutPropertiesLoose(_ref3, _excluded2);
79
82
 
80
83
  var _useState = (0, React.useState)(false),
81
84
  imageFailedLoading = _useState[0],
@@ -7,6 +7,8 @@ var React = _interopRequireWildcard(require("react"));
7
7
 
8
8
  var _styles = _interopRequireDefault(require("./styles"));
9
9
 
10
+ var _excluded = ["size", "type", "tip", "text"];
11
+
10
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
13
 
12
14
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -36,7 +38,7 @@ var Badge = /*#__PURE__*/function (_React$Component) {
36
38
  type = _this$props.type,
37
39
  tip = _this$props.tip,
38
40
  text = _this$props.text,
39
- rest = _objectWithoutPropertiesLoose(_this$props, ["size", "type", "tip", "text"]);
41
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded);
40
42
 
41
43
  return /*#__PURE__*/React.createElement(_styles.default, _extends({
42
44
  type: type,
@@ -34,7 +34,7 @@ var backgroundColors = {
34
34
 
35
35
  var Container = _styledComponents.default.span.withConfig({
36
36
  displayName: "styles__Container",
37
- componentId: "g1g76b-0"
37
+ componentId: "sc-g1g76b-0"
38
38
  })(["display:inline-block;text-align:center;border-radius:50px;font-family:", ";padding:", ";", " ", " ", ""], function (props) {
39
39
  return props.theme.fontFamily;
40
40
  }, function (props) {
@@ -11,6 +11,8 @@ var _styles = _interopRequireDefault(require("./styles"));
11
11
 
12
12
  var _Box = _interopRequireDefault(require("../Box"));
13
13
 
14
+ var _excluded = ["type", "text"];
15
+
14
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
17
 
16
18
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -81,7 +83,7 @@ var Banner = /*#__PURE__*/function (_React$Component) {
81
83
  var _this$props = this.props,
82
84
  type = _this$props.type,
83
85
  text = _this$props.text,
84
- rest = _objectWithoutPropertiesLoose(_this$props, ["type", "text"]);
86
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded);
85
87
 
86
88
  return /*#__PURE__*/React.createElement(_styles.default // danger needs to be properly deprecated and removed DS-1094
87
89
  , _extends({
@@ -13,7 +13,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
13
13
 
14
14
  var Container = _styledComponents.default.div.withConfig({
15
15
  displayName: "styles__Container",
16
- componentId: "q43dr4-0"
16
+ componentId: "sc-q43dr4-0"
17
17
  })(function (props) {
18
18
  return (0, _styledComponents.css)(["display:flex;overflow:hidden;align-items:center;justify-content:space-between;color:", ";border-radius:", ";font-family:", ";", " padding:", ";border:1px solid ", ";background-color:", ";.Icon{align-self:flex-start;margin-top:3px;margin-right:", ";min-width:16px;color:", ";}a,button{font-weight:", ";color:", ";font-size:inherit;&:hover{color:", ";text-decoration:underline;}}> span:not(.Icon){display:block;}", " ", ""], props.theme.colors.text.body, props.theme.radii.outer, props.theme.fontFamily, props.theme.typography[200], props.theme.space[300], props.theme.colors.container.border[props.type], props.theme.colors.container.background[props.type], props.theme.space[400], props.theme.colors.icon[props.type], props.theme.fontWeights.semibold, props.theme.colors.text.body, props.theme.colors.text.body, _systemProps.COMMON, _systemProps.LAYOUT);
19
19
  });
@@ -11,7 +11,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
11
11
 
12
12
  var Container = _styledComponents.default.div.withConfig({
13
13
  displayName: "styles__Container",
14
- componentId: "ma8yo6-0"
14
+ componentId: "sc-ma8yo6-0"
15
15
  })(["box-sizing:border-box;font-family:", ";", " ", " ", " ", " ", " ", ""], function (_ref) {
16
16
  var theme = _ref.theme;
17
17
  return theme.fontFamily;
@@ -13,6 +13,9 @@ var _Menu = require("../Menu");
13
13
 
14
14
  var _styles = _interopRequireDefault(require("./styles"));
15
15
 
16
+ var _excluded = ["children", "href"],
17
+ _excluded2 = ["ariaLabel", "overflow", "children"];
18
+
16
19
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
20
 
18
21
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -26,7 +29,7 @@ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) r
26
29
  var BreadcrumbItem = function BreadcrumbItem(_ref) {
27
30
  var children = _ref.children,
28
31
  href = _ref.href,
29
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "href"]);
32
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded);
30
33
 
31
34
  return /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement(_Link.default, _extends({
32
35
  href: href // $FlowIssue - upgrade v0.112.0
@@ -38,7 +41,7 @@ var Breadcrumb = function Breadcrumb(_ref2) {
38
41
  var ariaLabel = _ref2.ariaLabel,
39
42
  overflow = _ref2.overflow,
40
43
  children = _ref2.children,
41
- rest = _objectWithoutPropertiesLoose(_ref2, ["ariaLabel", "overflow", "children"]);
44
+ rest = _objectWithoutPropertiesLoose(_ref2, _excluded2);
42
45
 
43
46
  var listItems = React.Children.toArray(children);
44
47
  var lastItem = listItems[listItems.length - 1];