@sproutsocial/racine 11.2.0 → 11.3.0-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 (185) hide show
  1. package/CHANGELOG.md +0 -6
  2. package/__flow__/Badge/index.js +32 -58
  3. package/__flow__/Badge/index.stories.js +42 -35
  4. package/__flow__/Badge/index.test.js +32 -34
  5. package/__flow__/Badge/styles.js +42 -22
  6. package/__flow__/Input/index.js +124 -66
  7. package/__flow__/Input/index.stories.js +33 -0
  8. package/__flow__/Input/index.test.js +224 -1
  9. package/__flow__/Input/styles.js +1 -1
  10. package/commonjs/Avatar/index.js +7 -4
  11. package/commonjs/Badge/index.js +39 -39
  12. package/commonjs/Badge/styles.js +32 -16
  13. package/commonjs/Banner/index.js +3 -1
  14. package/commonjs/Banner/styles.js +1 -1
  15. package/commonjs/Box/styles.js +1 -1
  16. package/commonjs/Breadcrumb/index.js +5 -2
  17. package/commonjs/Button/index.js +3 -1
  18. package/commonjs/Card/index.js +3 -1
  19. package/commonjs/Card/styles.js +3 -3
  20. package/commonjs/CharacterCounter/index.js +3 -1
  21. package/commonjs/CharacterCounter/styles.js +1 -1
  22. package/commonjs/ChartLegend/index.js +3 -1
  23. package/commonjs/ChartLegend/styles.js +3 -3
  24. package/commonjs/Checkbox/index.js +3 -1
  25. package/commonjs/Checkbox/styles.js +1 -1
  26. package/commonjs/Collapsible/index.js +5 -2
  27. package/commonjs/DatePicker/DateRangePicker.js +3 -1
  28. package/commonjs/DatePicker/SingleDatePicker.js +3 -1
  29. package/commonjs/DatePicker/StatefulDateRangePicker.js +3 -1
  30. package/commonjs/DatePicker/StatefulSingleDatePicker.js +3 -1
  31. package/commonjs/DatePicker/common.js +1 -1
  32. package/commonjs/DatePicker/styles.js +2 -6
  33. package/commonjs/Drawer/SlideTransition.js +3 -1
  34. package/commonjs/Drawer/index.js +9 -4
  35. package/commonjs/Drawer/styles.js +2 -2
  36. package/commonjs/EmptyState/index.js +3 -1
  37. package/commonjs/Fieldset/index.js +7 -3
  38. package/commonjs/FormField/index.js +3 -1
  39. package/commonjs/Icon/index.js +5 -2
  40. package/commonjs/Icon/styles.js +1 -1
  41. package/commonjs/Image/index.js +3 -1
  42. package/commonjs/Image/styles.js +1 -1
  43. package/commonjs/Indicator/index.js +3 -1
  44. package/commonjs/Input/index.js +69 -28
  45. package/commonjs/Input/styles.js +3 -3
  46. package/commonjs/KeyboardKey/index.js +3 -1
  47. package/commonjs/Label/index.js +4 -2
  48. package/commonjs/Link/index.js +3 -1
  49. package/commonjs/Link/styles.js +1 -1
  50. package/commonjs/Listbox/index.js +7 -4
  51. package/commonjs/Loader/index.js +3 -1
  52. package/commonjs/Loader/styles.js +2 -2
  53. package/commonjs/LoaderButton/index.js +3 -1
  54. package/commonjs/Menu/constants.js +1 -1
  55. package/commonjs/Menu/descendants.js +10 -7
  56. package/commonjs/Menu/hooks.js +1 -1
  57. package/commonjs/Menu/index.js +22 -16
  58. package/commonjs/Menu/styles.js +2 -2
  59. package/commonjs/Message/index.js +3 -1
  60. package/commonjs/Message/styles.js +1 -1
  61. package/commonjs/Modal/index.js +7 -3
  62. package/commonjs/Modal/styles.js +4 -6
  63. package/commonjs/Numeral/constants.js +1 -1
  64. package/commonjs/Numeral/index.js +3 -1
  65. package/commonjs/Numeral/styles.js +3 -3
  66. package/commonjs/OverflowList/styles.js +1 -1
  67. package/commonjs/Popout/index.js +7 -3
  68. package/commonjs/Popout/styles.js +1 -1
  69. package/commonjs/Radio/index.js +3 -1
  70. package/commonjs/Radio/styles.js +4 -4
  71. package/commonjs/SegmentedControl/index.js +5 -2
  72. package/commonjs/Select/index.js +3 -1
  73. package/commonjs/Stack/index.js +3 -1
  74. package/commonjs/Switch/index.js +3 -1
  75. package/commonjs/Switch/styles.js +1 -1
  76. package/commonjs/Table/index.js +10 -5
  77. package/commonjs/TableCell/index.js +3 -1
  78. package/commonjs/TableHeaderCell/index.js +3 -1
  79. package/commonjs/TableRowAccordion/index.js +3 -1
  80. package/commonjs/Tabs/index.js +5 -2
  81. package/commonjs/Tabs/styles.js +4 -4
  82. package/commonjs/Text/index.js +3 -1
  83. package/commonjs/Text/styles.js +1 -1
  84. package/commonjs/Textarea/index.js +3 -1
  85. package/commonjs/Toast/index.js +15 -15
  86. package/commonjs/Toast/styles.js +4 -7
  87. package/commonjs/ToggleHint/index.js +3 -1
  88. package/commonjs/Token/index.js +3 -1
  89. package/commonjs/Token/styles.js +1 -1
  90. package/commonjs/TokenInput/index.js +3 -1
  91. package/commonjs/Tooltip/index.js +5 -2
  92. package/commonjs/Tooltip/styles.js +1 -1
  93. package/commonjs/VisuallyHidden/index.js +1 -1
  94. package/commonjs/index.js +1 -1
  95. package/commonjs/themes/dark/decorative-palettes.js +1 -1
  96. package/commonjs/themes/dark/theme.js +1 -1
  97. package/commonjs/themes/light/decorative-palettes.js +1 -1
  98. package/commonjs/themes/light/theme.js +1 -1
  99. package/commonjs/utils/hooks.js +3 -2
  100. package/commonjs/utils/mixins.js +1 -1
  101. package/commonjs/utils/system-props.js +1 -1
  102. package/dist/themes/dark/dark.scss +5 -5
  103. package/dist/themes/light/light.scss +95 -95
  104. package/lib/Avatar/index.js +7 -4
  105. package/lib/Badge/index.js +40 -37
  106. package/lib/Badge/styles.js +28 -13
  107. package/lib/Banner/index.js +3 -1
  108. package/lib/Banner/styles.js +1 -1
  109. package/lib/Box/styles.js +1 -1
  110. package/lib/Breadcrumb/index.js +5 -2
  111. package/lib/Button/index.js +3 -1
  112. package/lib/Card/index.js +3 -1
  113. package/lib/Card/styles.js +2 -2
  114. package/lib/CharacterCounter/index.js +3 -1
  115. package/lib/CharacterCounter/styles.js +1 -1
  116. package/lib/ChartLegend/index.js +3 -1
  117. package/lib/ChartLegend/styles.js +3 -3
  118. package/lib/Checkbox/index.js +3 -1
  119. package/lib/Collapsible/index.js +5 -2
  120. package/lib/DatePicker/DateRangePicker.js +3 -1
  121. package/lib/DatePicker/SingleDatePicker.js +3 -1
  122. package/lib/DatePicker/StatefulDateRangePicker.js +3 -1
  123. package/lib/DatePicker/StatefulSingleDatePicker.js +3 -1
  124. package/lib/DatePicker/styles.js +2 -6
  125. package/lib/Drawer/SlideTransition.js +3 -1
  126. package/lib/Drawer/index.js +9 -4
  127. package/lib/Drawer/styles.js +2 -2
  128. package/lib/EmptyState/index.js +3 -1
  129. package/lib/Fieldset/index.js +7 -3
  130. package/lib/FormField/index.js +3 -1
  131. package/lib/Icon/index.js +5 -2
  132. package/lib/Icon/styles.js +1 -1
  133. package/lib/Image/index.js +3 -1
  134. package/lib/Image/styles.js +1 -1
  135. package/lib/Indicator/index.js +3 -1
  136. package/lib/Input/index.js +62 -27
  137. package/lib/Input/styles.js +3 -3
  138. package/lib/KeyboardKey/index.js +3 -1
  139. package/lib/Label/index.js +4 -2
  140. package/lib/Link/index.js +3 -1
  141. package/lib/Link/styles.js +1 -1
  142. package/lib/Listbox/index.js +6 -3
  143. package/lib/Loader/index.js +3 -1
  144. package/lib/Loader/styles.js +2 -2
  145. package/lib/LoaderButton/index.js +3 -1
  146. package/lib/Menu/descendants.js +5 -2
  147. package/lib/Menu/index.js +20 -16
  148. package/lib/Menu/styles.js +2 -2
  149. package/lib/Message/index.js +3 -1
  150. package/lib/Modal/index.js +7 -3
  151. package/lib/Modal/styles.js +3 -5
  152. package/lib/Numeral/index.js +3 -1
  153. package/lib/Numeral/styles.js +2 -2
  154. package/lib/OverflowList/styles.js +1 -1
  155. package/lib/Popout/index.js +7 -3
  156. package/lib/Popout/styles.js +1 -1
  157. package/lib/Radio/index.js +3 -1
  158. package/lib/Radio/styles.js +4 -4
  159. package/lib/SegmentedControl/index.js +5 -2
  160. package/lib/Select/index.js +3 -1
  161. package/lib/Stack/index.js +3 -1
  162. package/lib/Switch/index.js +3 -1
  163. package/lib/Switch/styles.js +1 -1
  164. package/lib/Table/index.js +9 -4
  165. package/lib/TableCell/index.js +3 -1
  166. package/lib/TableHeaderCell/index.js +3 -1
  167. package/lib/TableRowAccordion/index.js +3 -1
  168. package/lib/Tabs/index.js +5 -2
  169. package/lib/Tabs/styles.js +3 -3
  170. package/lib/Text/index.js +3 -1
  171. package/lib/Text/styles.js +1 -1
  172. package/lib/Textarea/index.js +3 -1
  173. package/lib/Toast/index.js +14 -14
  174. package/lib/Toast/styles.js +3 -7
  175. package/lib/ToggleHint/index.js +3 -1
  176. package/lib/Token/index.js +3 -1
  177. package/lib/Token/styles.js +1 -1
  178. package/lib/TokenInput/index.js +3 -1
  179. package/lib/Tooltip/index.js +5 -2
  180. package/lib/Tooltip/styles.js +1 -1
  181. package/lib/VisuallyHidden/index.js +1 -1
  182. package/package.json +1 -1
  183. package/__flow__/Badge/constants.js +0 -48
  184. package/commonjs/Badge/constants.js +0 -43
  185. package/lib/Badge/constants.js +0 -38
package/CHANGELOG.md CHANGED
@@ -1,11 +1,5 @@
1
1
  # Change Log
2
2
 
3
- ## 11.2.0
4
-
5
- ### Minor Changes
6
-
7
- - a71a431: backwards compatible style and api changes to the badge component
8
-
9
3
  ## 11.1.2
10
4
 
11
5
  ### Patch Changes
@@ -1,69 +1,43 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
- import Icon from "../Icon";
4
3
  import Container from "./styles";
5
- import Box from "../Box";
6
4
 
7
5
  type TypeProps = {
8
- children?: React.Node,
9
- /** DEPRECATED: Use children instead of text */
10
6
  text: React.Node,
11
- size?: "small" | "large" | "default",
12
- badgeColor?:
13
- | "green"
14
- | "blue"
15
- | "purple"
16
- | "yellow"
17
- | "orange"
18
- | "red"
19
- | "neutral",
20
- iconName?: string,
21
- /** DEPRECATED: Possibly only used for testing. Refrain from using at all if possible. (optional) */
7
+ type:
8
+ | "primary"
9
+ | "secondary"
10
+ | "passive"
11
+ | "common"
12
+ | "approval"
13
+ | "default"
14
+ | "suggestion",
15
+ size: "default" | "small",
22
16
  tip?: React.Node,
23
- /** DEPRECATED: The legacy method of choosing a theme. Use badgeColor instead. (optional) */
24
- type?: string,
25
17
  };
26
18
 
27
- const Badge = ({
28
- children,
29
- text,
30
- iconName,
31
- type,
32
- tip,
33
- size = "small",
34
- badgeColor = "blue",
35
- ...rest
36
- }: TypeProps) => {
37
- if (children && text) {
38
- throw new Error(
39
- "can't use both `children` and `text` props. Text is deprecated, consider using children."
40
- );
41
- }
19
+ export default class Badge extends React.Component<TypeProps> {
20
+ static defaultProps = {
21
+ type: "primary",
22
+ size: "default",
23
+ };
42
24
 
43
- return (
44
- <Container
45
- {...rest}
46
- // size previously included default, which currently maps to small. Once consumers have updated this can be simplified.
47
- size={size === "default" ? "large" : size}
48
- badgeColor={badgeColor}
49
- data-tip={tip}
50
- data-qa-badge={text || ""}
51
- data-qa-badge-type={type}
52
- data-qa-badge-tip={tip || ""}
53
- type={type && type}
54
- >
55
- <Box display="flex" alignItems="center" JustifyContent="center">
56
- {iconName ? (
57
- <Icon
58
- mr={200}
59
- name={iconName}
60
- size={size === "small" ? "mini" : "default"}
61
- />
62
- ) : null}
63
- {children || text}
64
- </Box>
65
- </Container>
66
- );
67
- };
25
+ render() {
26
+ const { size, type, tip, text, ...rest } = this.props;
68
27
 
69
- export default Badge;
28
+ return (
29
+ <Container
30
+ type={type}
31
+ small={size === "small"}
32
+ data-tip={tip}
33
+ data-qa-badge={text || ""}
34
+ data-qa-badge-type={type}
35
+ data-qa-badge-tip={tip || ""}
36
+ // $FlowIssue - upgrade v0.112.0
37
+ {...rest}
38
+ >
39
+ {text}
40
+ </Container>
41
+ );
42
+ }
43
+ }
@@ -1,46 +1,53 @@
1
1
  import React from "react";
2
+ import { text } from "@storybook/addon-knobs";
2
3
  import Badge from "./index";
3
- import Box from "../Box";
4
- import Numeral from "../Numeral";
5
- import Text from "../Text";
6
- import Stack from "../Stack";
7
4
 
8
5
  export default {
9
6
  title: "Badge",
10
7
  };
11
8
 
12
9
  export const permutations = () => (
13
- <Stack space={450}>
14
- <Box display="flex" alignItems="center">
15
- <Badge badgeColor="green">Badge</Badge>
16
- <Badge>Badge</Badge>
17
- <Badge badgeColor="purple">Badge</Badge>
18
- <Badge badgeColor="yellow">Badge</Badge>
19
- <Badge badgeColor="orange">Badge</Badge>
20
- <Badge badgeColor="red">Badge</Badge>
21
- <Badge badgeColor="neutral" iconName="atom">
22
- Badge
23
- </Badge>
24
- <Badge
25
- size="small"
26
- type="default"
27
- bg="chartreuse"
28
- color="crimson"
29
- p={500}
30
- >
31
- Radical overrides!
32
- </Badge>
33
- </Box>
34
- <Box display="flex" alignItems="center">
35
- <Badge size="large" type="secondary" iconName="sparkles">
36
- Supports legacy types
37
- </Badge>
38
- <Badge size="large">
39
- <Numeral fontWeight="bold" number={1569241} />
40
- <Text ml={200}>and children!</Text>
41
- </Badge>
42
- </Box>
43
- </Stack>
10
+ <>
11
+ <Badge
12
+ text={text("text", "Test")}
13
+ type="default"
14
+ size={text("size", "small")}
15
+ />
16
+ <Badge text={text("text", "Test")} size={text("size", "small")} />
17
+ <Badge
18
+ text={text("text", "Test")}
19
+ type="secondary"
20
+ size={text("size", "small")}
21
+ />
22
+ <Badge
23
+ text={text("text", "Test")}
24
+ type="passive"
25
+ size={text("size", "small")}
26
+ />
27
+ <Badge
28
+ text={text("text", "Test")}
29
+ type="common"
30
+ size={text("size", "small")}
31
+ />
32
+ <Badge
33
+ text={text("text", "Test")}
34
+ type="approval"
35
+ size={text("size", "small")}
36
+ />
37
+ <Badge
38
+ text={text("text", "Test")}
39
+ type="suggestion"
40
+ size={text("size", "small")}
41
+ />
42
+
43
+ <Badge text={text("text", "Test")} type="default" />
44
+ <Badge text={text("text", "Test")} />
45
+ <Badge text={text("text", "Test")} type="secondary" />
46
+ <Badge text={text("text", "Test")} type="passive" />
47
+ <Badge text={text("text", "Test")} type="common" />
48
+ <Badge text={text("text", "Test")} type="approval" />
49
+ <Badge text={text("text", "Test")} type="suggestion" />
50
+ </>
44
51
  );
45
52
 
46
53
  permutations.story = {
@@ -3,50 +3,48 @@ import Badge from "./";
3
3
  import { render } from "../utils/react-testing-library";
4
4
  import "jest-styled-components";
5
5
 
6
- describe("Badge...", () => {
7
- it("...should render with default props", () => {
8
- const { getByText } = render(<Badge>Test</Badge>);
6
+ describe("Racine Badge", () => {
7
+ it("should render with default props", () => {
8
+ const { getByText, getByDataQaLabel } = render(<Badge text="Test" />);
9
9
  expect(getByText("Test")).toBeTruthy();
10
- expect(getByText("Test").closest("span")).toHaveStyleRule(
11
- "padding",
12
- "0px 4px"
13
- );
14
- expect(getByText("Test").closest("span")).toHaveStyleRule(
15
- "background",
16
- "#dcf2ff"
17
- );
10
+ expect(getByDataQaLabel({ "badge-type": "primary" })).toBeTruthy();
18
11
  });
19
12
 
20
- it("...should render with correct size styling", () => {
21
- const { getByText } = render(<Badge size="large">Test</Badge>);
22
- expect(getByText("Test").closest("span")).toHaveStyleRule("padding", "8px");
13
+ it("should render with correct size styling", () => {
14
+ const { getByText } = render(<Badge text="Test" size="small" />);
15
+ expect(getByText("Test")).toHaveStyleRule("padding", "4px 8px");
23
16
  });
24
17
 
25
- it("...should render with legacy type", () => {
26
- const { getByText } = render(<Badge type="secondary">Test</Badge>);
27
- expect(getByText("Test")).toBeTruthy();
28
- expect(getByText("Test").closest("span")).toHaveStyleRule(
29
- "color",
30
- "#364141"
31
- );
32
- expect(getByText("Test").closest("span")).toHaveStyleRule(
33
- "background",
34
- "#fdefcd"
18
+ it("should render with secondary type", () => {
19
+ const { getByText, getByDataQaLabel } = render(
20
+ <Badge text="Test" type="secondary" />
35
21
  );
22
+ expect(getByText("Test")).toBeTruthy();
23
+ expect(getByDataQaLabel({ "badge-type": "secondary" })).toBeTruthy();
36
24
  });
37
25
 
38
- it("...should render with a badge color", () => {
39
- const { getByText } = render(<Badge badgeColor="purple">Test</Badge>);
26
+ it("should render with passive type", () => {
27
+ const { getByText, getByDataQaLabel } = render(
28
+ <Badge text="Test" type="passive" />
29
+ );
40
30
  expect(getByText("Test")).toBeTruthy();
41
- expect(getByText("Test").closest("span")).toHaveStyleRule(
42
- "background",
43
- "#eaeaf9"
31
+ expect(getByDataQaLabel({ "badge-type": "passive" })).toBeTruthy();
32
+ });
33
+
34
+ it("should render with common type", () => {
35
+ const { getByText, getByDataQaLabel } = render(
36
+ <Badge text="Test" type="common" />
44
37
  );
38
+ expect(getByText("Test")).toBeTruthy();
39
+ expect(getByDataQaLabel({ "badge-type": "common" })).toBeTruthy();
45
40
  });
46
41
 
47
- it("...should render with an icon", () => {
48
- const { container } = render(<Badge iconName="sparkles">Test</Badge>);
49
- expect(container.querySelector("svg")).toBeTruthy();
42
+ it("should render with approval type", () => {
43
+ const { getByText, getByDataQaLabel } = render(
44
+ <Badge text="Test" type="approval" />
45
+ );
46
+ expect(getByText("Test")).toBeTruthy();
47
+ expect(getByDataQaLabel({ "badge-type": "approval" })).toBeTruthy();
50
48
  });
51
49
  it("should render with suggestion type", () => {
52
50
  const { getByText, getByDataQaLabel } = render(
@@ -56,9 +54,9 @@ describe("Badge...", () => {
56
54
  expect(getByDataQaLabel({ "badge-type": "suggestion" })).toBeTruthy();
57
55
  });
58
56
 
59
- it("...should render with tooltip class", () => {
57
+ it("should render with tooltip class", () => {
60
58
  const { getByText, getByDataQaLabel } = render(
61
- <Badge tip="test tip">Test</Badge>
59
+ <Badge text="Test" tip="test tip" />
62
60
  );
63
61
  expect(getByText("Test")).toBeTruthy();
64
62
  expect(getByDataQaLabel({ "badge-tip": "test tip" })).toBeTruthy();
@@ -1,31 +1,51 @@
1
1
  //@flow
2
- import styled, { type StyledComponent } from "styled-components";
2
+ import styled, { css, type StyledComponent } from "styled-components";
3
3
  import { COMMON } from "../utils/system-props";
4
- import type { TypeTheme } from "../types/theme.flow";
5
4
  import { themeGet } from "@styled-system/theme-get";
6
- import { legacyBadgeColors } from "./constants";
5
+
6
+ import type { TypeTheme } from "../types/theme.flow";
7
+
8
+ const colors = {
9
+ primary: "colors.neutral.0",
10
+ secondary: "colors.neutral.800",
11
+ passive: "colors.neutral.800",
12
+ common: "colors.neutral.0",
13
+ approval: "colors.neutral.800",
14
+ default: "colors.neutral.0",
15
+ suggestion: "colors.neutral.900",
16
+ };
17
+
18
+ const backgroundColors = {
19
+ primary: "colors.blue.700",
20
+ secondary: "colors.yellow.500",
21
+ passive: "colors.neutral.200",
22
+ common: "colors.aqua.600",
23
+ approval: "colors.orange.300",
24
+ default: "colors.purple.700",
25
+ suggestion: "colors.blue.300",
26
+ };
7
27
 
8
28
  // eslint-disable-next-line prettier/prettier
9
- const Container: StyledComponent<{type: ?string, badgeColor: string, size: string, ...}, TypeTheme, *> = styled.span`
10
- font-family: ${(p) => p.theme.fontFamily};
11
- ${(p) =>
12
- p.size === "small" ? p.theme.typography[100] : p.theme.typography[200]};
13
- border-radius: 9999px;
14
- line-height: 16px;
29
+ const Container: StyledComponent<{type: "primary" | "secondary" | "passive" | "common" | "approval" | "default" | "suggestion", small: boolean, ...}, TypeTheme, *> = styled.span`
15
30
  display: inline-block;
16
- color: ${(p) =>
17
- p.type
18
- ? themeGet(legacyBadgeColors[p.type].color)
19
- : p.theme.colors.text.body};
20
- background: ${(p) =>
21
- p.type
22
- ? themeGet(legacyBadgeColors[p.type].background)
23
- : p.theme.colors.container.background.decorative[p.badgeColor]};
24
- padding: ${(p) =>
25
- p.size === "small"
26
- ? `${p.theme.space[0]} ${p.theme.space[200]}`
27
- : `${p.theme.space[300]}`};
28
- ${COMMON};
31
+ text-align: center;
32
+ border-radius: 50px;
33
+ font-family: ${(props) => props.theme.fontFamily};
34
+
35
+ padding: ${(props) =>
36
+ props.small
37
+ ? `${props.theme.space[200]} ${props.theme.space[300]}`
38
+ : `${props.theme.space[300]} ${props.theme.space[350]}`};
39
+
40
+ ${(props) =>
41
+ props.small ? props.theme.typography[100] : props.theme.typography[200]}
42
+
43
+ ${(props) => css`
44
+ color: ${themeGet(colors[props.type])(props)};
45
+ background: ${themeGet(backgroundColors[props.type])(props)};
46
+ `}
47
+
48
+ ${COMMON}
29
49
  `;
30
50
 
31
51
  export default Container;
@@ -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 */
@@ -41,7 +45,14 @@ type TypeProps = {
41
45
  /** Used to get a reference to the underlying element */
42
46
  innerRef?: React.Ref<"input">,
43
47
  onBlur?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
44
- onChange?: (e: SyntheticInputEvent<HTMLInputElement>, value: string) => void,
48
+ onChange?: (
49
+ e:
50
+ | SyntheticInputEvent<HTMLInputElement>
51
+ | SyntheticEvent<HTMLButtonElement>,
52
+ value: string
53
+ ) => void,
54
+ /** Called on Input.ClearButton trigger */
55
+ onClear?: (e: SyntheticEvent<HTMLButtonElement>) => void,
45
56
  onFocus?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
46
57
  onKeyDown?: (
47
58
  e: SyntheticKeyboardEvent<HTMLInputElement>,
@@ -54,14 +65,34 @@ type TypeProps = {
54
65
  onPaste?: (e: SyntheticInputEvent<HTMLInputElement>, value: string) => void,
55
66
  size?: "large" | "small" | "default",
56
67
  qa?: Object,
57
- /**
58
- Controls the styles of the input. Primary is our standard input styles and secondary is a borderless input.
68
+ /**
69
+ Controls the styles of the input. Primary is our standard input styles and secondary is a borderless input.
59
70
  Note that this prop should only be used to alter styles and not functionality.
60
71
  */
61
72
  appearance?: "primary" | "secondary",
62
73
  };
63
74
 
64
- export default class Input extends React.Component<TypeProps> {
75
+ // Using Context so that Input's Input.ClearButton-specific props can be passed to Input.ClearButton,
76
+ // regardless of whether it is manually included as elemAfter or automatically included for type="search" Inputs.
77
+ type TypeInputContext = $Shape<{
78
+ handleClear: (e: SyntheticEvent<HTMLButtonElement>) => void,
79
+ clearButtonLabel: string,
80
+ }>;
81
+
82
+ const InputContext = React.createContext<TypeInputContext>({});
83
+
84
+ const ClearButton = () => {
85
+ const { handleClear, clearButtonLabel } = React.useContext(InputContext);
86
+ return (
87
+ <Button onClick={handleClear}>
88
+ {/*Unlocalized fallback should not be used. Always include a localized
89
+ clearButtonLabel when using <Input.ClearButton/> or <Input type="search"/>.*/}
90
+ <Icon name="circlex" title={clearButtonLabel || "Clear"} />
91
+ </Button>
92
+ );
93
+ };
94
+
95
+ class Input extends React.Component<TypeProps> {
65
96
  static defaultProps = {
66
97
  autoFocus: false,
67
98
  disabled: false,
@@ -70,41 +101,33 @@ export default class Input extends React.Component<TypeProps> {
70
101
  appearance: "primary",
71
102
  };
72
103
 
73
- handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) => {
74
- if (this.props.onBlur) {
75
- this.props.onBlur(e);
76
- }
77
- };
104
+ static ClearButton = ClearButton;
78
105
 
79
- handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
80
- if (this.props.onChange) {
81
- this.props.onChange(e, e.currentTarget.value);
82
- }
83
- };
106
+ handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) =>
107
+ this.props.onBlur?.(e);
84
108
 
85
- handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) => {
86
- if (this.props.onFocus) {
87
- this.props.onFocus(e);
109
+ handleClear = (e: SyntheticEvent<HTMLButtonElement>) => {
110
+ if (this.props.onClear) {
111
+ this.props.onClear(e);
112
+ } else if (this.props.onChange) {
113
+ this.props.onChange(e, "");
88
114
  }
89
115
  };
90
116
 
91
- handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
92
- if (this.props.onKeyDown) {
93
- this.props.onKeyDown(e, e.currentTarget.value);
94
- }
95
- };
117
+ handleChange = (e: SyntheticInputEvent<HTMLInputElement>) =>
118
+ this.props.onChange?.(e, e.currentTarget.value);
96
119
 
97
- handleKeyUp = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
98
- if (this.props.onKeyUp) {
99
- this.props.onKeyUp(e, e.currentTarget.value);
100
- }
101
- };
120
+ handleFocus = (e: SyntheticFocusEvent<HTMLInputElement>) =>
121
+ this.props.onFocus?.(e);
102
122
 
103
- handlePaste = (e: SyntheticInputEvent<HTMLInputElement>) => {
104
- if (this.props.onPaste) {
105
- this.props.onPaste(e, e.currentTarget.value);
106
- }
107
- };
123
+ handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) =>
124
+ this.props.onKeyDown?.(e, e.currentTarget.value);
125
+
126
+ handleKeyUp = (e: SyntheticKeyboardEvent<HTMLInputElement>) =>
127
+ this.props.onKeyUp?.(e, e.currentTarget.value);
128
+
129
+ handlePaste = (e: SyntheticInputEvent<HTMLInputElement>) =>
130
+ this.props.onPaste?.(e, e.currentTarget.value);
108
131
 
109
132
  render() {
110
133
  const {
@@ -125,9 +148,11 @@ export default class Input extends React.Component<TypeProps> {
125
148
  maxLength,
126
149
  ariaLabel,
127
150
  ariaDescribedby,
151
+ clearButtonLabel,
128
152
  innerRef,
129
153
  onBlur,
130
154
  onChange,
155
+ onClear,
131
156
  onFocus,
132
157
  onKeyDown,
133
158
  onKeyUp,
@@ -138,15 +163,26 @@ export default class Input extends React.Component<TypeProps> {
138
163
  ...rest
139
164
  } = this.props;
140
165
 
166
+ // Convert autoComplete from a boolean prop to a string value.
141
167
  let autoCompleteValue = undefined;
142
168
  if (autoComplete !== undefined) {
143
169
  autoCompleteValue = autoComplete ? "on" : "off";
144
170
  }
145
171
 
172
+ // Add default elemBefore and elemAfter elements if type is search.
173
+ const elementBefore =
174
+ type === "search" && !elemBefore ? (
175
+ <Icon name="search" ariaHidden />
176
+ ) : (
177
+ elemBefore
178
+ );
179
+ const elementAfter =
180
+ type === "search" && !elemAfter ? <ClearButton /> : elemAfter;
181
+
146
182
  return (
147
183
  <Container
148
- hasBeforeElement={!!elemBefore}
149
- hasAfterElement={!!elemAfter}
184
+ hasBeforeElement={!!elementBefore}
185
+ hasAfterElement={!!elementAfter}
150
186
  disabled={disabled}
151
187
  invalid={!!isInvalid}
152
188
  warning={hasWarning}
@@ -154,39 +190,61 @@ export default class Input extends React.Component<TypeProps> {
154
190
  // $FlowIssue - upgrade v0.112.0
155
191
  {...rest}
156
192
  >
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>}
193
+ <InputContext.Provider
194
+ value={{
195
+ handleClear: this.handleClear,
196
+ clearButtonLabel,
197
+ }}
198
+ >
199
+ {elementBefore && <Accessory before>{elementBefore}</Accessory>}
200
+
201
+ <input
202
+ aria-invalid={!!isInvalid}
203
+ aria-label={ariaLabel}
204
+ aria-describedby={ariaDescribedby}
205
+ autoComplete={autoCompleteValue}
206
+ autoFocus={autoFocus}
207
+ disabled={disabled}
208
+ readOnly={readOnly}
209
+ id={id}
210
+ name={name}
211
+ placeholder={placeholder}
212
+ type={type}
213
+ required={required}
214
+ value={value}
215
+ maxLength={maxLength}
216
+ onBlur={this.handleBlur}
217
+ onChange={this.handleChange}
218
+ onFocus={this.handleFocus}
219
+ onKeyDown={this.handleKeyDown}
220
+ onKeyUp={this.handleKeyUp}
221
+ onPaste={this.handlePaste}
222
+ ref={innerRef}
223
+ data-qa-input={name || ""}
224
+ data-qa-input-isdisabled={disabled === true}
225
+ data-qa-input-isrequired={required === true}
226
+ {...qa}
227
+ {...inputProps}
228
+ />
229
+
230
+ {elementAfter && (
231
+ <Accessory
232
+ after
233
+ // Used for positioning. This logic will detect if the element is a ClearButton,
234
+ // regardless of whether it was manually passed as elemAfter or automatically added to a search Input.
235
+ isClearButton={
236
+ elementAfter?.type?.prototype === Input.ClearButton.prototype
237
+ }
238
+ >
239
+ {elementAfter}
240
+ </Accessory>
241
+ )}
242
+ </InputContext.Provider>
189
243
  </Container>
190
244
  );
191
245
  }
192
246
  }
247
+
248
+ Input.ClearButton.displayName = "Input.ClearButton";
249
+
250
+ export default Input;