@sproutsocial/racine 11.3.0-beta.1 → 11.3.0-beta.4

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # Change Log
2
2
 
3
+ ## 11.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 206bd32: copy updates to the TypeProps comments that power Seeds prop tables
8
+
9
+ ## 11.2.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 514d738: Patch badge component flow issue
14
+
15
+ ## 11.2.2
16
+
17
+ ### Patch Changes
18
+
19
+ - 9cc377e: Created extended theme directory
20
+
21
+ ## 11.2.1
22
+
23
+ ### Patch Changes
24
+
25
+ - 6968733: adds deprecation messaging to typeProps to be displayed on seeds page
26
+
27
+ ## 11.2.0
28
+
29
+ ### Minor Changes
30
+
31
+ - a71a431: backwards compatible style and api changes to the badge component
32
+
3
33
  ## 11.1.2
4
34
 
5
35
  ### Patch Changes
@@ -0,0 +1,48 @@
1
+ //@flow
2
+
3
+ const defaultPurple = {
4
+ color: "colors.text.body",
5
+ background: "colors.container.background.decorative.purple",
6
+ };
7
+
8
+ const suggestion = {
9
+ color: "colors.text.body",
10
+ background: "colors.container.background.decorative.blue",
11
+ };
12
+
13
+ const passive = {
14
+ color: "colors.text.body",
15
+ background: "colors.container.background.decorative.neutral",
16
+ };
17
+
18
+ const primary = {
19
+ color: "colors.text.body",
20
+ background: "colors.container.background.decorative.blue",
21
+ };
22
+
23
+ const secondary = {
24
+ color: "colors.text.body",
25
+ background: "colors.container.background.decorative.yellow",
26
+ };
27
+
28
+ const common = {
29
+ color: "colors.text.inverse",
30
+ background: "colors.aqua.600",
31
+ };
32
+
33
+ const approval = {
34
+ color: "colors.text.body",
35
+ background: "colors.container.background.decorative.orange",
36
+ };
37
+
38
+ //Deprecated former "types"
39
+
40
+ export const legacyBadgeColors = {
41
+ primary,
42
+ secondary,
43
+ passive,
44
+ common,
45
+ approval,
46
+ default: defaultPurple,
47
+ suggestion,
48
+ };
@@ -1,43 +1,70 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
+ import Icon from "../Icon";
3
4
  import Container from "./styles";
5
+ import Box from "../Box";
4
6
 
5
7
  type TypeProps = {
6
- text: React.Node,
7
- type:
8
- | "primary"
9
- | "secondary"
10
- | "passive"
11
- | "common"
12
- | "approval"
13
- | "default"
14
- | "suggestion",
15
- size: "default" | "small",
8
+ children?: React.Node,
9
+ /** DEPRECATED: Use children instead of text */
10
+ text?: React.Node,
11
+ /** Size default is deprecated in favor of small and large */
12
+ size?: "small" | "large" | "default",
13
+ badgeColor?:
14
+ | "green"
15
+ | "blue"
16
+ | "purple"
17
+ | "yellow"
18
+ | "orange"
19
+ | "red"
20
+ | "neutral",
21
+ iconName?: string,
22
+ /** DEPRECATED: Possibly only used for testing. Refrain from using at all if possible. (optional) */
16
23
  tip?: React.Node,
24
+ /** DEPRECATED: The legacy method of choosing a theme. Use badgeColor instead. (optional) */
25
+ type?: string,
17
26
  };
18
27
 
19
- export default class Badge extends React.Component<TypeProps> {
20
- static defaultProps = {
21
- type: "primary",
22
- size: "default",
23
- };
24
-
25
- render() {
26
- const { size, type, tip, text, ...rest } = this.props;
27
-
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>
28
+ const Badge = ({
29
+ children,
30
+ text,
31
+ iconName,
32
+ type,
33
+ tip,
34
+ size = "small",
35
+ badgeColor = "blue",
36
+ ...rest
37
+ }: TypeProps) => {
38
+ if (children && text) {
39
+ throw new Error(
40
+ "can't use both `children` and `text` props. Text is deprecated, consider using children."
41
41
  );
42
42
  }
43
- }
43
+
44
+ return (
45
+ <Container
46
+ {...rest}
47
+ // size previously included default, which currently maps to small. Once consumers have updated this can be simplified.
48
+ size={size === "default" ? "large" : size}
49
+ badgeColor={badgeColor}
50
+ data-tip={tip}
51
+ data-qa-badge={text || ""}
52
+ data-qa-badge-type={type}
53
+ data-qa-badge-tip={tip || ""}
54
+ type={type && type}
55
+ >
56
+ <Box display="flex" alignItems="center" JustifyContent="center">
57
+ {iconName ? (
58
+ <Icon
59
+ mr={200}
60
+ name={iconName}
61
+ size={size === "small" ? "mini" : "default"}
62
+ />
63
+ ) : null}
64
+ {children || text}
65
+ </Box>
66
+ </Container>
67
+ );
68
+ };
69
+
70
+ export default Badge;
@@ -1,53 +1,46 @@
1
1
  import React from "react";
2
- import { text } from "@storybook/addon-knobs";
3
2
  import Badge from "./index";
3
+ import Box from "../Box";
4
+ import Numeral from "../Numeral";
5
+ import Text from "../Text";
6
+ import Stack from "../Stack";
4
7
 
5
8
  export default {
6
9
  title: "Badge",
7
10
  };
8
11
 
9
12
  export const permutations = () => (
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
- </>
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>
51
44
  );
52
45
 
53
46
  permutations.story = {
@@ -3,48 +3,50 @@ import Badge from "./";
3
3
  import { render } from "../utils/react-testing-library";
4
4
  import "jest-styled-components";
5
5
 
6
- describe("Racine Badge", () => {
7
- it("should render with default props", () => {
8
- const { getByText, getByDataQaLabel } = render(<Badge text="Test" />);
6
+ describe("Badge...", () => {
7
+ it("...should render with default props", () => {
8
+ const { getByText } = render(<Badge>Test</Badge>);
9
9
  expect(getByText("Test")).toBeTruthy();
10
- expect(getByDataQaLabel({ "badge-type": "primary" })).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
+ );
11
18
  });
12
19
 
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");
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");
16
23
  });
17
24
 
18
- it("should render with secondary type", () => {
19
- const { getByText, getByDataQaLabel } = render(
20
- <Badge text="Test" type="secondary" />
21
- );
25
+ it("...should render with legacy type", () => {
26
+ const { getByText } = render(<Badge type="secondary">Test</Badge>);
22
27
  expect(getByText("Test")).toBeTruthy();
23
- expect(getByDataQaLabel({ "badge-type": "secondary" })).toBeTruthy();
24
- });
25
-
26
- it("should render with passive type", () => {
27
- const { getByText, getByDataQaLabel } = render(
28
- <Badge text="Test" type="passive" />
28
+ expect(getByText("Test").closest("span")).toHaveStyleRule(
29
+ "color",
30
+ "#364141"
31
+ );
32
+ expect(getByText("Test").closest("span")).toHaveStyleRule(
33
+ "background",
34
+ "#fdefcd"
29
35
  );
30
- expect(getByText("Test")).toBeTruthy();
31
- expect(getByDataQaLabel({ "badge-type": "passive" })).toBeTruthy();
32
36
  });
33
37
 
34
- it("should render with common type", () => {
35
- const { getByText, getByDataQaLabel } = render(
36
- <Badge text="Test" type="common" />
37
- );
38
+ it("...should render with a badge color", () => {
39
+ const { getByText } = render(<Badge badgeColor="purple">Test</Badge>);
38
40
  expect(getByText("Test")).toBeTruthy();
39
- expect(getByDataQaLabel({ "badge-type": "common" })).toBeTruthy();
41
+ expect(getByText("Test").closest("span")).toHaveStyleRule(
42
+ "background",
43
+ "#eaeaf9"
44
+ );
40
45
  });
41
46
 
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();
47
+ it("...should render with an icon", () => {
48
+ const { container } = render(<Badge iconName="sparkles">Test</Badge>);
49
+ expect(container.querySelector("svg")).toBeTruthy();
48
50
  });
49
51
  it("should render with suggestion type", () => {
50
52
  const { getByText, getByDataQaLabel } = render(
@@ -54,9 +56,9 @@ describe("Racine Badge", () => {
54
56
  expect(getByDataQaLabel({ "badge-type": "suggestion" })).toBeTruthy();
55
57
  });
56
58
 
57
- it("should render with tooltip class", () => {
59
+ it("...should render with tooltip class", () => {
58
60
  const { getByText, getByDataQaLabel } = render(
59
- <Badge text="Test" tip="test tip" />
61
+ <Badge tip="test tip">Test</Badge>
60
62
  );
61
63
  expect(getByText("Test")).toBeTruthy();
62
64
  expect(getByDataQaLabel({ "badge-tip": "test tip" })).toBeTruthy();
@@ -1,51 +1,31 @@
1
1
  //@flow
2
- import styled, { css, type StyledComponent } from "styled-components";
2
+ import styled, { type StyledComponent } from "styled-components";
3
3
  import { COMMON } from "../utils/system-props";
4
- import { themeGet } from "@styled-system/theme-get";
5
-
6
4
  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
- };
5
+ import { themeGet } from "@styled-system/theme-get";
6
+ import { legacyBadgeColors } from "./constants";
27
7
 
28
8
  // eslint-disable-next-line prettier/prettier
29
- const Container: StyledComponent<{type: "primary" | "secondary" | "passive" | "common" | "approval" | "default" | "suggestion", small: boolean, ...}, TypeTheme, *> = styled.span`
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;
30
15
  display: inline-block;
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}
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};
49
29
  `;
50
30
 
51
31
  export default Container;
@@ -72,21 +72,48 @@ type TypeProps = {
72
72
  type TypeInputContext = $Shape<{
73
73
  handleClear: (e: SyntheticEvent<HTMLButtonElement>) => void,
74
74
  clearButtonLabel: string,
75
+ hasValue: boolean,
75
76
  }>;
76
77
 
77
78
  const InputContext = React.createContext<TypeInputContext>({});
78
79
 
79
80
  const ClearButton = () => {
80
- const { handleClear, clearButtonLabel } = React.useContext(InputContext);
81
+ const { handleClear, clearButtonLabel, hasValue, size } =
82
+ React.useContext(InputContext);
83
+
84
+ // Hide the button when there is no text to clear.
85
+ if (!hasValue) {
86
+ return null;
87
+ }
88
+
89
+ // Cut down the padding for size small Inputs so that the focus ring won't go outside the bounds of the Input.
90
+ // This adjustment is handled automatically for default and large Inputs via Button's size. There is no "small" Button.
91
+ const py = size === "small" ? 100 : undefined;
92
+ const px = size === "small" ? 200 : undefined;
93
+ const buttonSize = size === "small" ? "default" : size;
94
+
95
+ // Warn if clearButtonLabel is not included, so that the unlocalized fallback will not be mistaken for a proper label.
96
+ if (!clearButtonLabel) {
97
+ console.warn(
98
+ "Warning: clearButtonLabel prop is required when using Input.ClearButton. Please pass a localized label to Input."
99
+ );
100
+ }
81
101
  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"/>.*/}
102
+ <Button onClick={handleClear} size={buttonSize} py={py} px={px}>
85
103
  <Icon name="circlex" title={clearButtonLabel || "Clear"} />
86
104
  </Button>
87
105
  );
88
106
  };
89
107
 
108
+ // Used for positioning elementAfter. This logic will detect if the element is a ClearButton,
109
+ // regardless of whether it was manually passed as elemAfter or automatically added to a search Input.
110
+ const isClearButton = (elem: any) => {
111
+ if (elem?.type) {
112
+ return elem.type.displayName === "Input.ClearButton";
113
+ }
114
+ return false;
115
+ };
116
+
90
117
  class Input extends React.Component<TypeProps> {
91
118
  static defaultProps = {
92
119
  autoFocus: false,
@@ -101,8 +128,10 @@ class Input extends React.Component<TypeProps> {
101
128
  handleBlur = (e: SyntheticFocusEvent<HTMLInputElement>) =>
102
129
  this.props.onBlur?.(e);
103
130
 
104
- handleClear = (e: SyntheticEvent<HTMLButtonElement>) =>
131
+ handleClear = (e: SyntheticEvent<HTMLButtonElement>) => {
105
132
  this.props.onClear?.(e);
133
+ this.props.innerRef?.current?.focus();
134
+ };
106
135
 
107
136
  handleChange = (e: SyntheticInputEvent<HTMLInputElement>) =>
108
137
  this.props.onChange?.(e, e.currentTarget.value);
@@ -150,6 +179,7 @@ class Input extends React.Component<TypeProps> {
150
179
  inputProps = {},
151
180
  qa = {},
152
181
  appearance,
182
+ size,
153
183
  ...rest
154
184
  } = this.props;
155
185
 
@@ -162,7 +192,7 @@ class Input extends React.Component<TypeProps> {
162
192
  // Add default elemBefore and elemAfter elements if type is search.
163
193
  const elementBefore =
164
194
  type === "search" && !elemBefore ? (
165
- <Icon name="search" ariaHidden />
195
+ <Icon name="search" ariaHidden color="icon.base" />
166
196
  ) : (
167
197
  elemBefore
168
198
  );
@@ -178,13 +208,16 @@ class Input extends React.Component<TypeProps> {
178
208
  invalid={!!isInvalid}
179
209
  warning={hasWarning}
180
210
  appearance={appearance}
211
+ size={size}
181
212
  // $FlowIssue - upgrade v0.112.0
182
213
  {...rest}
183
214
  >
184
215
  <InputContext.Provider
185
216
  value={{
186
217
  handleClear: this.handleClear,
218
+ hasValue: !!value,
187
219
  clearButtonLabel,
220
+ size,
188
221
  }}
189
222
  >
190
223
  {elementBefore && <Accessory before>{elementBefore}</Accessory>}
@@ -219,14 +252,7 @@ class Input extends React.Component<TypeProps> {
219
252
  />
220
253
 
221
254
  {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
- >
255
+ <Accessory after isClearButton={isClearButton(elementAfter)}>
230
256
  {elementAfter}
231
257
  </Accessory>
232
258
  )}
@@ -156,6 +156,38 @@ searchInput.story = {
156
156
  name: "Search Input",
157
157
  };
158
158
 
159
+ export const smallSearchInput = () => (
160
+ <Input
161
+ type="search"
162
+ size="small"
163
+ placeholder={text("placeholder", "Please enter a value...")}
164
+ value={text("value", "val")}
165
+ onClear={() => window.alert("Cleared!")}
166
+ clearButtonLabel={text("clearButtonLabel", "Clear search")}
167
+ ariaLabel={text("ariaLabel", "Descriptive label goes here")}
168
+ />
169
+ );
170
+
171
+ smallSearchInput.story = {
172
+ name: "Small Search Input",
173
+ };
174
+
175
+ export const largeSearchInput = () => (
176
+ <Input
177
+ type="search"
178
+ size="large"
179
+ placeholder={text("placeholder", "Please enter a value...")}
180
+ value={text("value", "val")}
181
+ onClear={() => window.alert("Cleared!")}
182
+ clearButtonLabel={text("clearButtonLabel", "Clear search")}
183
+ ariaLabel={text("ariaLabel", "Descriptive label goes here")}
184
+ />
185
+ );
186
+
187
+ largeSearchInput.story = {
188
+ name: "Large Search Input",
189
+ };
190
+
159
191
  export const nonSearchClearButtonInput = () => {
160
192
  return (
161
193
  <Input
@@ -171,7 +203,7 @@ export const nonSearchClearButtonInput = () => {
171
203
  };
172
204
 
173
205
  nonSearchClearButtonInput.story = {
174
- name: "Input.ClearButton usage",
206
+ name: "Manual Input.ClearButton usage",
175
207
  };
176
208
 
177
209
  export const autofocus = () => (
@@ -85,6 +85,20 @@ describe("Input", () => {
85
85
  expect(getByRole("button")).toBeTruthy();
86
86
  });
87
87
 
88
+ it("should not render a clear button for search Inputs if there is no text", () => {
89
+ const { queryByRole } = render(
90
+ <Input
91
+ id="name"
92
+ name="name"
93
+ value=""
94
+ type="search"
95
+ onClear={jest.fn()}
96
+ clearButtonLabel="Clear search"
97
+ />
98
+ );
99
+ expect(queryByRole("button")).toBeFalsy();
100
+ });
101
+
88
102
  it("should not override an elemAfter prop if passed", () => {
89
103
  const { getByText, queryByTitle } = render(
90
104
  <Input
@@ -189,6 +203,20 @@ describe("Input", () => {
189
203
  expect(getByRole("button")).toBeTruthy();
190
204
  });
191
205
 
206
+ it("should not render a clear button if there is no text", () => {
207
+ const { queryByRole } = render(
208
+ <Input
209
+ id="name"
210
+ name="name"
211
+ value=""
212
+ type="text"
213
+ elemAfter={<Input.ClearButton />}
214
+ clearButtonLabel="Clear search"
215
+ />
216
+ );
217
+ expect(queryByRole("button")).toBeFalsy();
218
+ });
219
+
192
220
  it("should use the fallback title if clearButtonLabel is not provided", () => {
193
221
  const { getByTitle } = render(
194
222
  <Input
@@ -8,10 +8,11 @@ type TypeProps = {
8
8
  /** Optional prop to make the URL open in a new tab */
9
9
  external?: boolean,
10
10
  children: React.Node,
11
+ /** Setting this prop will cause the component to be rendered as a link */
11
12
  href?: string,
12
13
  /** Disables user action and applies a disabled style to the component */
13
14
  disabled?: boolean,
14
- /** Setting this prop will cause the component to be rendered as a button instead of an anchor) */
15
+ /** Can be used in addition to an href but still renders as a link. Omitting href will render as button */
15
16
  onClick?: (e: SyntheticEvent<HTMLButtonElement>) => void,
16
17
  as?: $PropertyType<TypeStyledComponentsCommonProps, "as">,
17
18
  underline?: boolean,
@@ -0,0 +1,6 @@
1
+ ## Extended Theme Directory
2
+
3
+ This directory serves as a shared environment for all extended themes. Each unique theme should have its own folder and theme.js file.
4
+ `src/themes/extendedThemes/customTheme/theme.js`
5
+
6
+ Check out our documentation for [How to extend the theme](https://seeds.sproutsocial.com/components/theme#extending-the-theme) on Seeds.