@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.
- package/CHANGELOG.md +22 -0
- package/__flow__/Button/__snapshots__/index.test.js.snap +511 -0
- package/__flow__/Button/index.js +0 -2
- package/__flow__/Button/index.stories.js +67 -51
- package/__flow__/Button/index.test.js +113 -0
- package/__flow__/Button/styles.js +1 -1
- package/__flow__/EmptyState/index.test.js +1 -1
- package/__flow__/Input/index.js +185 -66
- package/__flow__/Input/index.stories.js +65 -0
- package/__flow__/Input/index.test.js +227 -1
- package/__flow__/Input/styles.js +1 -1
- package/__flow__/Link/index.js +2 -1
- package/__flow__/Menu/__snapshots__/index.test.js.snap +2 -2
- package/__flow__/TokenInput/index.js +1 -1
- package/__flow__/setupTests.js +1 -1
- package/__flow__/systemProps/tests/__snapshots__/layout.test.js.snap +14 -0
- package/__flow__/systemProps/tests/layout.test.js +9 -0
- package/__flow__/themes/dark/theme.js +3 -0
- package/__flow__/themes/light/theme.js +3 -0
- package/__flow__/types/theme.colors.flow.js +3 -0
- package/commonjs/Button/index.js +0 -1
- package/commonjs/Button/styles.js +0 -1
- package/commonjs/DatePicker/styles.js +1 -5
- package/commonjs/Input/index.js +125 -31
- package/commonjs/Input/styles.js +1 -1
- package/commonjs/Menu/index.js +10 -10
- package/commonjs/Modal/styles.js +1 -5
- package/commonjs/Toast/index.js +14 -14
- package/commonjs/Toast/styles.js +2 -5
- package/commonjs/TokenInput/index.js +1 -1
- package/commonjs/themes/dark/theme.js +4 -1
- package/commonjs/themes/light/theme.js +4 -1
- package/dist/themes/dark/dark.scss +4 -1
- package/dist/themes/light/light.scss +4 -1
- package/lib/Button/index.js +0 -1
- package/lib/Button/styles.js +0 -1
- package/lib/DatePicker/styles.js +1 -5
- package/lib/Input/index.js +118 -31
- package/lib/Input/styles.js +1 -1
- package/lib/Menu/index.js +10 -11
- package/lib/Modal/styles.js +1 -5
- package/lib/Toast/index.js +14 -14
- package/lib/Toast/styles.js +1 -5
- package/lib/TokenInput/index.js +1 -1
- package/lib/themes/dark/theme.js +4 -1
- package/lib/themes/light/theme.js +4 -1
- package/package.json +21 -25
- 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 = (
|
|
11
|
-
<Button
|
|
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 = (
|
|
23
|
-
<Button
|
|
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 = (
|
|
35
|
-
|
|
36
|
-
|
|
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 = (
|
|
43
|
-
<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 = (
|
|
52
|
-
<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 = (
|
|
61
|
-
|
|
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 = (
|
|
78
|
+
export const pillButton = () => (
|
|
68
79
|
<Box bg="container.background.base" p={400}>
|
|
69
|
-
<Button {
|
|
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
|
-
|
|
86
|
+
|
|
76
87
|
pillButton.story = {
|
|
77
88
|
name: "Pill button",
|
|
78
89
|
};
|
|
79
90
|
|
|
80
|
-
export const pillIconOnlyButton = (
|
|
91
|
+
export const pillIconOnlyButton = () => (
|
|
81
92
|
<Box bg="container.background.base" p={400}>
|
|
82
|
-
<Button {
|
|
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 = (
|
|
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 = (
|
|
101
|
-
<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
|
-
|
|
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 = (
|
|
113
|
-
<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
|
-
|
|
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 = (
|
|
124
|
-
<Button {
|
|
143
|
+
export const fullWidthButton = () => (
|
|
144
|
+
<Button appearance={text("appearance", "primary")} width={number("width", 1)}>
|
|
145
|
+
Full-Width Button
|
|
146
|
+
</Button>
|
|
125
147
|
);
|
|
126
|
-
|
|
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 = (
|
|
135
|
-
<Button {
|
|
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
|
+
});
|
package/__flow__/Input/index.js
CHANGED
|
@@ -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?:
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
if (this.props.onBlur) {
|
|
75
|
-
this.props.onBlur(e);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
166
|
+
static ClearButton = ClearButton;
|
|
78
167
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.props.onChange(e, e.currentTarget.value);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
168
|
+
handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) =>
|
|
169
|
+
this.props.onBlur?.(e);
|
|
84
170
|
|
|
85
|
-
|
|
86
|
-
if
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
this.props.onKeyUp(e, e.currentTarget.value);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
182
|
+
handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) =>
|
|
183
|
+
this.props.onFocus?.(e);
|
|
102
184
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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={!!
|
|
149
|
-
hasAfterElement={!!
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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;
|