@sproutsocial/racine 11.3.1-beta-deps.2 → 11.4.0-input-beta.0

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 (48) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/__flow__/Button/__snapshots__/index.test.js.snap +511 -0
  3. package/__flow__/Button/index.js +0 -2
  4. package/__flow__/Button/index.stories.js +67 -51
  5. package/__flow__/Button/index.test.js +113 -0
  6. package/__flow__/Button/styles.js +1 -1
  7. package/__flow__/EmptyState/index.test.js +1 -1
  8. package/__flow__/Input/index.js +185 -66
  9. package/__flow__/Input/index.stories.js +65 -0
  10. package/__flow__/Input/index.test.js +227 -1
  11. package/__flow__/Input/styles.js +1 -1
  12. package/__flow__/Link/index.js +2 -1
  13. package/__flow__/Menu/__snapshots__/index.test.js.snap +2 -2
  14. package/__flow__/TokenInput/index.js +1 -1
  15. package/__flow__/setupTests.js +1 -1
  16. package/__flow__/systemProps/tests/__snapshots__/layout.test.js.snap +14 -0
  17. package/__flow__/systemProps/tests/layout.test.js +9 -0
  18. package/__flow__/themes/dark/theme.js +3 -0
  19. package/__flow__/themes/light/theme.js +3 -0
  20. package/__flow__/types/theme.colors.flow.js +3 -0
  21. package/commonjs/Button/index.js +0 -1
  22. package/commonjs/Button/styles.js +0 -1
  23. package/commonjs/DatePicker/styles.js +1 -5
  24. package/commonjs/Input/index.js +125 -31
  25. package/commonjs/Input/styles.js +1 -1
  26. package/commonjs/Menu/index.js +10 -10
  27. package/commonjs/Modal/styles.js +1 -5
  28. package/commonjs/Toast/index.js +14 -14
  29. package/commonjs/Toast/styles.js +2 -5
  30. package/commonjs/TokenInput/index.js +1 -1
  31. package/commonjs/themes/dark/theme.js +4 -1
  32. package/commonjs/themes/light/theme.js +4 -1
  33. package/dist/themes/dark/dark.scss +4 -1
  34. package/dist/themes/light/light.scss +4 -1
  35. package/lib/Button/index.js +0 -1
  36. package/lib/Button/styles.js +0 -1
  37. package/lib/DatePicker/styles.js +1 -5
  38. package/lib/Input/index.js +118 -31
  39. package/lib/Input/styles.js +1 -1
  40. package/lib/Menu/index.js +10 -11
  41. package/lib/Modal/styles.js +1 -5
  42. package/lib/Toast/index.js +14 -14
  43. package/lib/Toast/styles.js +1 -5
  44. package/lib/TokenInput/index.js +1 -1
  45. package/lib/themes/dark/theme.js +4 -1
  46. package/lib/themes/light/theme.js +4 -1
  47. package/package.json +21 -25
  48. package/bin/buildNpm.js +0 -58
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { boolean, text, number } from "@storybook/addon-knobs";
2
3
  import Button from "./index";
3
4
  import Icon from "../Icon";
4
5
  import Box from "../Box";
@@ -7,140 +8,155 @@ export default {
7
8
  title: "Button",
8
9
  };
9
10
 
10
- export const defaultStory = (args) => (
11
- <Button {...args} onClick={() => alert("Testing...")}>
11
+ export const defaultStory = () => (
12
+ <Button
13
+ appearance={text("appearance", "default")}
14
+ onClick={() => alert("Testing...")}
15
+ >
12
16
  Default
13
17
  </Button>
14
18
  );
15
19
 
16
- defaultStory.args = { appearance: "default" };
17
-
18
20
  defaultStory.story = {
19
21
  name: "Default",
20
22
  };
21
23
 
22
- export const primary = (args) => (
23
- <Button {...args} onClick={() => alert("Testing...")}>
24
+ export const primary = () => (
25
+ <Button
26
+ appearance={text("appearance", "primary")}
27
+ onClick={() => alert("Testing...")}
28
+ >
24
29
  Primary Button
25
30
  </Button>
26
31
  );
27
32
 
28
- primary.args = { appearance: "primary" };
29
-
30
33
  primary.story = {
31
34
  name: "Primary",
32
35
  };
33
36
 
34
- export const secondary = (args) => <Button {...args}>Secondary Button</Button>;
35
-
36
- secondary.args = { appearance: "secondary" };
37
+ export const secondary = () => (
38
+ <Button appearance={text("appearance", "secondary")}>Secondary Button</Button>
39
+ );
37
40
 
38
41
  secondary.story = {
39
42
  name: "Secondary",
40
43
  };
41
44
 
42
- export const destructive = (args) => (
43
- <Button {...args}>Destructive Button</Button>
45
+ export const destructive = () => (
46
+ <Button appearance={text("appearance", "destructive")}>
47
+ Destructive Button
48
+ </Button>
44
49
  );
45
50
 
46
- destructive.args = { appearance: "destructive" };
47
51
  destructive.story = {
48
52
  name: "Destructive",
49
53
  };
50
54
 
51
- export const placeholder = (args) => (
52
- <Button {...args}>Placeholder Button</Button>
55
+ export const placeholder = () => (
56
+ <Button appearance={text("appearance", "placeholder")}>
57
+ Placeholder Button
58
+ </Button>
53
59
  );
54
60
 
55
- placeholder.args = { appearance: "placeholder" };
56
61
  placeholder.story = {
57
62
  name: "Placeholder",
58
63
  };
59
64
 
60
- export const largeButton = (args) => <Button {...args}>Large Button</Button>;
61
- largeButton.args = { size: "large", appearance: "primary" };
65
+ export const largeButton = () => (
66
+ <Button
67
+ appearance={text("appearance", "primary")}
68
+ size={text("size", "large")}
69
+ >
70
+ Large Button
71
+ </Button>
72
+ );
62
73
 
63
74
  largeButton.story = {
64
75
  name: "Large button",
65
76
  };
66
77
 
67
- export const pillButton = (args) => (
78
+ export const pillButton = () => (
68
79
  <Box bg="container.background.base" p={400}>
69
- <Button {...args}>
80
+ <Button appearance={text("shape", "pill")}>
70
81
  <Icon name="reply" mr={350} />
71
82
  Pill Button
72
83
  </Button>
73
84
  </Box>
74
85
  );
75
- pillButton.args = { appearance: "pill" };
86
+
76
87
  pillButton.story = {
77
88
  name: "Pill button",
78
89
  };
79
90
 
80
- export const pillIconOnlyButton = (args) => (
91
+ export const pillIconOnlyButton = () => (
81
92
  <Box bg="container.background.base" p={400}>
82
- <Button {...args} ariaLabel="This is a label">
93
+ <Button appearance={text("shape", "pill")} ariaLabel="This is a label">
83
94
  <Icon name="circle-check-outline" />
84
95
  </Button>
85
96
  </Box>
86
97
  );
87
98
 
88
- pillIconOnlyButton.args = { appearance: "pill" };
89
99
  pillIconOnlyButton.story = {
90
100
  name: "Pill icon only button",
91
101
  };
92
102
 
93
- export const activeButton = (args) => <Button {...args}>Active Button</Button>;
103
+ export const activeButton = () => (
104
+ <Button
105
+ appearance={text("appearance", "secondary")}
106
+ active={boolean("active", true)}
107
+ >
108
+ Active Button
109
+ </Button>
110
+ );
94
111
 
95
- activeButton.args = { appearance: "secondary", active: true };
96
112
  activeButton.story = {
97
113
  name: "Active button",
98
114
  };
99
115
 
100
- export const buttonAsALink = (args) => (
101
- <Button {...args}>Button using anchor element</Button>
116
+ export const buttonAsALink = () => (
117
+ <Button
118
+ href={text("href", "http://sproutsocial.style")}
119
+ external={boolean("external", true)}
120
+ appearance={text("appearance", "primary")}
121
+ >
122
+ Button using anchor element
123
+ </Button>
102
124
  );
103
- buttonAsALink.args = {
104
- appearance: "primary",
105
- external: true,
106
- href: "http://sproutsocial.style",
107
- };
125
+
108
126
  buttonAsALink.story = {
109
127
  name: "Button as a link",
110
128
  };
111
129
 
112
- export const disabledButton = (args) => (
113
- <Button {...args}>This Button is disabled</Button>
130
+ export const disabledButton = () => (
131
+ <Button
132
+ appearance={text("appearance", "primary")}
133
+ disabled={text("disabled", "true")}
134
+ >
135
+ This Button is disabled
136
+ </Button>
114
137
  );
115
- disabledButton.args = {
116
- appearance: "primary",
117
- disabled: true,
118
- };
138
+
119
139
  disabledButton.story = {
120
140
  name: "Disabled button",
121
141
  };
122
142
 
123
- export const fullWidthButton = (args) => (
124
- <Button {...args}>Full-Width Button</Button>
143
+ export const fullWidthButton = () => (
144
+ <Button appearance={text("appearance", "primary")} width={number("width", 1)}>
145
+ Full-Width Button
146
+ </Button>
125
147
  );
126
- fullWidthButton.args = {
127
- appearance: "primary",
128
- width: 1,
129
- };
148
+
130
149
  fullWidthButton.story = {
131
150
  name: "Full width button",
132
151
  };
133
152
 
134
- export const withIcon = (args) => (
135
- <Button {...args}>
153
+ export const withIcon = () => (
154
+ <Button appearance={text("appearance", "secondary")}>
136
155
  <Icon name="twitter" mr={350} />
137
156
  Secondary Button
138
157
  </Button>
139
158
  );
140
159
 
141
- withIcon.args = {
142
- appearance: "secondary",
143
- };
144
160
  withIcon.story = {
145
161
  name: "With icon",
146
162
  };
@@ -0,0 +1,113 @@
1
+ import React from "react";
2
+ import { fireEvent, render } from "../utils/react-testing-library";
3
+ import "jest-styled-components";
4
+ import Button from "./";
5
+
6
+ describe("Racine Button", () => {
7
+ it("should render in default style", () => {
8
+ const { container } = render(<Button>Button</Button>);
9
+ expect(container).toMatchSnapshot();
10
+ });
11
+ it("should render in primary style", () => {
12
+ const { container } = render(<Button appearance="primary">Button</Button>);
13
+ expect(container).toMatchSnapshot();
14
+ });
15
+ it("should render in secondary style", () => {
16
+ const { container } = render(
17
+ <Button appearance="secondary">Button</Button>
18
+ );
19
+ expect(container).toMatchSnapshot();
20
+ });
21
+
22
+ it("should render in pill style", () => {
23
+ const { container } = render(<Button shape="pill">Button</Button>);
24
+ expect(container).toMatchSnapshot();
25
+ });
26
+
27
+ it("should render in an anchor tag with external target", () => {
28
+ const { container } = render(
29
+ <Button href="http://sproutsocial.style" external>
30
+ Button
31
+ </Button>
32
+ );
33
+ expect(container).toMatchSnapshot();
34
+ });
35
+
36
+ it("should render in large size", () => {
37
+ const { container } = render(
38
+ <Button appearance="secondary" size="large">
39
+ Button
40
+ </Button>
41
+ );
42
+ expect(container).toMatchSnapshot();
43
+ });
44
+
45
+ it("should render in active state", () => {
46
+ const { container } = render(
47
+ <Button appearance="secondary" active>
48
+ Button
49
+ </Button>
50
+ );
51
+ expect(container).toMatchSnapshot();
52
+ });
53
+
54
+ it("setting external to prop true should add target blank attribute to anchor", () => {
55
+ const { getByText } = render(
56
+ <Button href="http://sproutsocial.com" external>
57
+ Link
58
+ </Button>
59
+ );
60
+ expect(getByText("Link").target).toEqual("_blank");
61
+ });
62
+
63
+ it("setting external to prop true should add rel='noopener noreferrer' attribute", () => {
64
+ const { getByText } = render(
65
+ <Button href="http://sproutsocial.com" external>
66
+ Link
67
+ </Button>
68
+ );
69
+ expect(getByText("Link").rel).toEqual("noopener noreferrer");
70
+ });
71
+
72
+ it("setting a URL renders component as an Anchor", () => {
73
+ const { getByText } = render(
74
+ <Button href="http://sproutsocial.com">Am I an anchor?</Button>
75
+ );
76
+ // expect(wrapper.find("a").length).toEqual(1);
77
+ expect(getByText("Am I an anchor?").tagName).toEqual("A");
78
+ });
79
+
80
+ it("should only submit a form when given type=submit", () => {
81
+ const onSubmit = jest.fn();
82
+ const { queryByText } = render(
83
+ <form onSubmit={onSubmit}>
84
+ <Button>Not Submit</Button>
85
+ <Button type="submit">Submit</Button>
86
+ </form>
87
+ );
88
+ fireEvent.click(queryByText("Not Submit"));
89
+ expect(onSubmit).not.toHaveBeenCalled();
90
+ fireEvent.click(queryByText("Submit"));
91
+ expect(onSubmit).toHaveBeenCalled();
92
+ });
93
+
94
+ it("Has type attribute as button by default", () => {
95
+ const { getByText } = render(<Button>My Button</Button>);
96
+ expect(getByText("My Button").type).toEqual("button");
97
+ });
98
+
99
+ it("Does not have type attribute when given href attribute", () => {
100
+ const { getByText } = render(<Button href="google.com">My Button</Button>);
101
+ expect(getByText("My Button").type).toBeFalsy();
102
+ expect(getByText("My Button").type).not.toEqual("button");
103
+ });
104
+
105
+ it("Had overriden type attribute", () => {
106
+ const { getByText } = render(
107
+ <Button href="google.com" type="button">
108
+ My Button
109
+ </Button>
110
+ );
111
+ expect(getByText("My Button").type).toEqual("button");
112
+ });
113
+ });
@@ -99,7 +99,7 @@ const Container: StyledComponent<any, TypeTheme, *> = styled.button`
99
99
  ${LAYOUT}
100
100
  ${COMMON}
101
101
  `;
102
- Container.displayName = "Button-Container";
102
+
103
103
  export default Container;
104
104
 
105
105
  //${props.theme.mode === "dark" ? "screen" : "multiply"}
@@ -105,7 +105,7 @@ describe("EmptyState", () => {
105
105
  <EmptyState
106
106
  media={
107
107
  <Image
108
- alt="No assets matching your search or filters"
108
+ alt="No assets matching yoursearch or filters"
109
109
  src="https://cl.ly/db498c7682df/download/analytics.svg"
110
110
  m={0}
111
111
  />
@@ -1,6 +1,11 @@
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";
6
+ import styled from "styled-components";
7
+ import type { StyledComponent } from "styled-components";
8
+ import type { TypeTheme } from "../types/theme.flow";
4
9
 
5
10
  type TypeProps = {
6
11
  /** ID of the form element, should match the "for" value of the associated label */
@@ -10,6 +15,8 @@ type TypeProps = {
10
15
  ariaLabel?: string,
11
16
  /** Attribute used to associate other elements that describe the Input, like an error */
12
17
  ariaDescribedby?: string,
18
+ /** Label for Input.ClearButton. Required when using <Input type="search"/> or <Input.ClearButton/>. */
19
+ clearButtonLabel?: string,
13
20
  /** Placeholder text for when value is undefined or empty */
14
21
  placeholder?: string,
15
22
  /** Current value of the input */
@@ -39,9 +46,14 @@ type TypeProps = {
39
46
  /** Props to spread onto the underlying input element */
40
47
  inputProps?: any,
41
48
  /** Used to get a reference to the underlying element */
42
- innerRef?: React.Ref<"input">,
49
+ innerRef?:
50
+ | {| current: null | HTMLInputElement |}
51
+ | ((React.ElementRef<any> | HTMLElement) => void),
43
52
  onBlur?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
44
53
  onChange?: (e: SyntheticInputEvent<HTMLInputElement>, value: string) => void,
54
+ /** Input.ClearButton onClick callback. Required when using <Input type="search"/> or <Input.ClearButton/>.
55
+ The component handles returning focus to Input after onClear is called only. You must reset "value" yourself.*/
56
+ onClear?: (e: SyntheticEvent<HTMLButtonElement>) => void,
45
57
  onFocus?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
46
58
  onKeyDown?: (
47
59
  e: SyntheticKeyboardEvent<HTMLInputElement>,
@@ -54,57 +66,130 @@ type TypeProps = {
54
66
  onPaste?: (e: SyntheticInputEvent<HTMLInputElement>, value: string) => void,
55
67
  size?: "large" | "small" | "default",
56
68
  qa?: Object,
57
- /**
58
- Controls the styles of the input. Primary is our standard input styles and secondary is a borderless input.
69
+ /**
70
+ Controls the styles of the input. Primary is our standard input styles and secondary is a borderless input.
59
71
  Note that this prop should only be used to alter styles and not functionality.
60
72
  */
61
73
  appearance?: "primary" | "secondary",
62
74
  };
63
75
 
64
- export default class Input extends React.Component<TypeProps> {
76
+ // Using Context so that Input's Input.ClearButton-specific props can be passed to Input.ClearButton,
77
+ // regardless of whether it is manually included as elemAfter or automatically included for type="search" Inputs.
78
+ type TypeInputContext = $Shape<{
79
+ onClear?: (e: SyntheticEvent<HTMLButtonElement>) => void,
80
+ handleClear: (e: SyntheticEvent<HTMLButtonElement>) => void,
81
+ clearButtonLabel: string,
82
+ hasValue: boolean,
83
+ size: "large" | "small" | "default",
84
+ }>;
85
+
86
+ const InputContext = React.createContext<TypeInputContext>({});
87
+
88
+ const StyledButton: StyledComponent<any, TypeTheme, *> = styled(Button)`
89
+ &:hover,
90
+ &:active {
91
+ color: ${(props) =>
92
+ props.theme.utils.interact(props.theme.colors.icon.base)};
93
+ }
94
+ `;
95
+
96
+ const ClearButton = () => {
97
+ const {
98
+ onClear,
99
+ handleClear,
100
+ clearButtonLabel,
101
+ hasValue,
102
+ size: inputSize,
103
+ } = React.useContext(InputContext);
104
+
105
+ // Hide the button when there is no text to clear.
106
+ if (!hasValue) {
107
+ return null;
108
+ }
109
+
110
+ // Log a warning and hide the button when no onClear callback is provided.
111
+ // If we called handleClear with no onClear prop, all the button would do is focus the Input.
112
+ if (!onClear) {
113
+ console.warn(
114
+ "Warning: No onClear prop provided to Input when using Input.ClearButton. Omitting Input.ClearButton."
115
+ );
116
+ return null;
117
+ }
118
+
119
+ // Warn if clearButtonLabel is not included, so that the unlocalized fallback will not be mistaken for a proper label.
120
+ if (!clearButtonLabel) {
121
+ console.warn(
122
+ "Warning: clearButtonLabel prop is required when using Input.ClearButton. Please pass a localized clearButtonLabel to Input."
123
+ );
124
+ }
125
+
126
+ // Reduce Button padding for size small Inputs so that the Button won't go outside the bounds of the Input.
127
+ // This adjustment is handled automatically for default and large Inputs via Button's size. There is no "small" Button.
128
+ const py = inputSize === "small" ? 100 : undefined;
129
+ const px = inputSize === "small" ? 200 : undefined;
130
+ const buttonSize = inputSize === "small" ? "default" : inputSize;
131
+
132
+ return (
133
+ <StyledButton
134
+ onClick={handleClear}
135
+ size={buttonSize}
136
+ py={py}
137
+ px={px}
138
+ title={clearButtonLabel || "Clear"}
139
+ ariaLabel={clearButtonLabel || "Clear"}
140
+ color="icon.base"
141
+ >
142
+ <Icon name="circlex" />
143
+ </StyledButton>
144
+ );
145
+ };
146
+
147
+ // Used for positioning elementAfter. This logic will detect if the element is a ClearButton,
148
+ // regardless of whether it was manually passed as elemAfter or automatically added to a search Input.
149
+ const isClearButton = (elem: any) => {
150
+ if (elem?.type) {
151
+ return elem.type.displayName === "Input.ClearButton";
152
+ }
153
+ return false;
154
+ };
155
+
156
+ class Input extends React.Component<TypeProps> {
65
157
  static defaultProps = {
66
158
  autoFocus: false,
67
159
  disabled: false,
68
160
  type: "text",
69
161
  size: "default",
70
162
  appearance: "primary",
163
+ innerRef: React.createRef<HTMLInputElement>(),
71
164
  };
72
165
 
73
- handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) => {
74
- if (this.props.onBlur) {
75
- this.props.onBlur(e);
76
- }
77
- };
166
+ static ClearButton = ClearButton;
78
167
 
79
- handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
80
- if (this.props.onChange) {
81
- this.props.onChange(e, e.currentTarget.value);
82
- }
83
- };
168
+ handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) =>
169
+ this.props.onBlur?.(e);
84
170
 
85
- handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) => {
86
- if (this.props.onFocus) {
87
- this.props.onFocus(e);
171
+ handleClear = (e: SyntheticEvent<HTMLButtonElement>) => {
172
+ // Only attempt to focus the input if the ref is an object. It won't work for refs that are functions.
173
+ if (typeof this.props.innerRef === "object") {
174
+ this.props.innerRef.current?.focus();
88
175
  }
176
+ this.props.onClear?.(e);
89
177
  };
90
178
 
91
- handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
92
- if (this.props.onKeyDown) {
93
- this.props.onKeyDown(e, e.currentTarget.value);
94
- }
95
- };
179
+ handleChange = (e: SyntheticInputEvent<HTMLInputElement>) =>
180
+ this.props.onChange?.(e, e.currentTarget.value);
96
181
 
97
- handleKeyUp = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
98
- if (this.props.onKeyUp) {
99
- this.props.onKeyUp(e, e.currentTarget.value);
100
- }
101
- };
182
+ handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) =>
183
+ this.props.onFocus?.(e);
102
184
 
103
- handlePaste = (e: SyntheticInputEvent<HTMLInputElement>) => {
104
- if (this.props.onPaste) {
105
- this.props.onPaste(e, e.currentTarget.value);
106
- }
107
- };
185
+ handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) =>
186
+ this.props.onKeyDown?.(e, e.currentTarget.value);
187
+
188
+ handleKeyUp = (e: SyntheticKeyboardEvent<HTMLInputElement>) =>
189
+ this.props.onKeyUp?.(e, e.currentTarget.value);
190
+
191
+ handlePaste = (e: SyntheticInputEvent<HTMLInputElement>) =>
192
+ this.props.onPaste?.(e, e.currentTarget.value);
108
193
 
109
194
  render() {
110
195
  const {
@@ -125,9 +210,11 @@ export default class Input extends React.Component<TypeProps> {
125
210
  maxLength,
126
211
  ariaLabel,
127
212
  ariaDescribedby,
213
+ clearButtonLabel,
128
214
  innerRef,
129
215
  onBlur,
130
216
  onChange,
217
+ onClear,
131
218
  onFocus,
132
219
  onKeyDown,
133
220
  onKeyUp,
@@ -135,58 +222,90 @@ export default class Input extends React.Component<TypeProps> {
135
222
  inputProps = {},
136
223
  qa = {},
137
224
  appearance,
225
+ size,
138
226
  ...rest
139
227
  } = this.props;
140
228
 
229
+ // Convert autoComplete from a boolean prop to a string value.
141
230
  let autoCompleteValue = undefined;
142
231
  if (autoComplete !== undefined) {
143
232
  autoCompleteValue = autoComplete ? "on" : "off";
144
233
  }
145
234
 
235
+ // Add default elemBefore and elemAfter elements if type is search.
236
+ const elementBefore =
237
+ type === "search" && !elemBefore ? (
238
+ <Icon name="search" ariaHidden color="icon.base" />
239
+ ) : (
240
+ elemBefore
241
+ );
242
+ // Do not add a ClearButton if no onClear callback is provided or if an elemAfter prop was passed.
243
+ const elementAfter =
244
+ type === "search" && onClear && !elemAfter ? <ClearButton /> : elemAfter;
245
+
146
246
  return (
147
247
  <Container
148
- hasBeforeElement={!!elemBefore}
149
- hasAfterElement={!!elemAfter}
248
+ hasBeforeElement={!!elementBefore}
249
+ hasAfterElement={!!elementAfter}
150
250
  disabled={disabled}
151
251
  invalid={!!isInvalid}
152
252
  warning={hasWarning}
153
253
  appearance={appearance}
254
+ size={size}
154
255
  // $FlowIssue - upgrade v0.112.0
155
256
  {...rest}
156
257
  >
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>}
258
+ <InputContext.Provider
259
+ value={{
260
+ handleClear: this.handleClear,
261
+ hasValue: !!value,
262
+ clearButtonLabel,
263
+ onClear,
264
+ size,
265
+ }}
266
+ >
267
+ {elementBefore && <Accessory before>{elementBefore}</Accessory>}
268
+
269
+ <input
270
+ aria-invalid={!!isInvalid}
271
+ aria-label={ariaLabel}
272
+ aria-describedby={ariaDescribedby}
273
+ autoComplete={autoCompleteValue}
274
+ autoFocus={autoFocus}
275
+ disabled={disabled}
276
+ readOnly={readOnly}
277
+ id={id}
278
+ name={name}
279
+ placeholder={placeholder}
280
+ type={type}
281
+ required={required}
282
+ value={value}
283
+ maxLength={maxLength}
284
+ onBlur={this.handleBlur}
285
+ onChange={this.handleChange}
286
+ onFocus={this.handleFocus}
287
+ onKeyDown={this.handleKeyDown}
288
+ onKeyUp={this.handleKeyUp}
289
+ onPaste={this.handlePaste}
290
+ ref={innerRef}
291
+ data-qa-input={name || ""}
292
+ data-qa-input-isdisabled={disabled === true}
293
+ data-qa-input-isrequired={required === true}
294
+ {...qa}
295
+ {...inputProps}
296
+ />
297
+
298
+ {elementAfter && (
299
+ <Accessory after isClearButton={isClearButton(elementAfter)}>
300
+ {elementAfter}
301
+ </Accessory>
302
+ )}
303
+ </InputContext.Provider>
189
304
  </Container>
190
305
  );
191
306
  }
192
307
  }
308
+
309
+ Input.ClearButton.displayName = "Input.ClearButton";
310
+
311
+ export default Input;