@sproutsocial/seeds-react-token-input 1.0.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.
@@ -0,0 +1,137 @@
1
+ import * as React from "react";
2
+ import { render } from "@sproutsocial/seeds-react-testing-library";
3
+ import { Icon } from "@sproutsocial/seeds-react-icon";
4
+ import TokenInput from "../TokenInput";
5
+ import type { TypeTokenSpec } from "../TokenInputTypes";
6
+
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ function TokenInputTypes() {
9
+ const setTokens = () => {};
10
+ const handleAddToken = () => {};
11
+ const handleRemoveToken = () => {};
12
+ const handleChange = () => {};
13
+ const handleClickToken = () => {};
14
+ const defaultProps = {
15
+ id: "example",
16
+ name: "example",
17
+ placeholder: "Enter a value...",
18
+ ariaLabel: "Descriptive label goes here",
19
+ value: "",
20
+ disabled: undefined,
21
+ isInvalid: undefined,
22
+ };
23
+ const testTokens: TypeTokenSpec[] = [
24
+ {
25
+ id: "0",
26
+ value: "Cherries",
27
+ },
28
+ {
29
+ id: "1",
30
+ value: "Mangos",
31
+ },
32
+ ];
33
+ const testTokensWithIcons: TypeTokenSpec[] = [
34
+ {
35
+ id: "0",
36
+ iconName: "face-smile-solid",
37
+ value: "Cherries",
38
+ },
39
+ {
40
+ id: "1",
41
+ value: "Mangos",
42
+ },
43
+ ];
44
+ return (
45
+ <>
46
+ <TokenInput
47
+ {...defaultProps}
48
+ tokens={[]}
49
+ onChangeTokens={setTokens}
50
+ autocomplete="off"
51
+ tokenMaxLength={4}
52
+ />
53
+ <TokenInput
54
+ {...defaultProps}
55
+ tokens={[]}
56
+ onChangeTokens={setTokens}
57
+ autocomplete="off"
58
+ iconName="lock-outline"
59
+ />
60
+ <TokenInput
61
+ {...defaultProps}
62
+ tokens={[]}
63
+ onChangeTokens={setTokens}
64
+ autocomplete="off"
65
+ elemBefore={<Icon fixedWidth name="lock-outline" />}
66
+ placeholder="Password"
67
+ />
68
+ <TokenInput
69
+ {...defaultProps}
70
+ tokens={[]}
71
+ onChangeTokens={setTokens}
72
+ autocomplete="off"
73
+ elemAfter={<Icon fixedWidth name="lock-outline" />}
74
+ placeholder="Password"
75
+ />
76
+ <TokenInput
77
+ {...defaultProps}
78
+ tokens={[]}
79
+ onChangeTokens={setTokens}
80
+ autocomplete="off"
81
+ elemBefore={<Icon fixedWidth name="magnifying-glass-outline" />}
82
+ elemAfter={<Icon fixedWidth name="lock-outline" />}
83
+ />
84
+ <TokenInput
85
+ {...defaultProps}
86
+ tokens={testTokens}
87
+ onChangeTokens={setTokens}
88
+ autocomplete="off"
89
+ />
90
+ <TokenInput
91
+ {...defaultProps}
92
+ tokens={testTokens}
93
+ onChangeTokens={setTokens}
94
+ autocomplete="off"
95
+ disabled
96
+ />
97
+ <TokenInput
98
+ {...defaultProps}
99
+ tokens={testTokens}
100
+ onChangeTokens={setTokens}
101
+ autocomplete="off"
102
+ autoFocus
103
+ />
104
+ <TokenInput
105
+ {...defaultProps}
106
+ tokens={testTokens}
107
+ onChangeTokens={setTokens}
108
+ autocomplete="off"
109
+ isInvalid={true}
110
+ placeholder="blah@something"
111
+ />
112
+ <TokenInput
113
+ {...defaultProps}
114
+ tokens={testTokensWithIcons}
115
+ onChangeTokens={setTokens}
116
+ autocomplete="off"
117
+ iconName="triangle-outline"
118
+ />
119
+ <TokenInput
120
+ {...defaultProps}
121
+ delimiters={[" ", ","]}
122
+ onChange={handleChange}
123
+ onAddToken={handleAddToken}
124
+ onRemoveToken={handleRemoveToken}
125
+ onClickToken={handleClickToken}
126
+ />
127
+ <TokenInput {...defaultProps} inputProps={{ defaultValue: "Foo" }} />
128
+ <TokenInput
129
+ {...defaultProps}
130
+ // @ts-expect-error - inputProps should not allow styled-system props
131
+ inputProps={{ ml: 500 }}
132
+ />
133
+ {/* @ts-expect-error - test missing required props is rejected */}
134
+ <TokenInput />
135
+ </>
136
+ );
137
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import TokenInput from "./TokenInput";
2
+
3
+ export default TokenInput;
4
+ export { TokenInput };
5
+ export * from "./TokenInputTypes";
@@ -0,0 +1,7 @@
1
+ import "styled-components";
2
+ import { TypeTheme } from "@sproutsocial/seeds-react-theme";
3
+
4
+ declare module "styled-components" {
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
6
+ export interface DefaultTheme extends TypeTheme {}
7
+ }
package/src/styles.ts ADDED
@@ -0,0 +1,136 @@
1
+ import styled, { css } from "styled-components";
2
+ import { COMMON } from "@sproutsocial/seeds-react-system-props";
3
+ import { focusRing } from "@sproutsocial/seeds-react-mixins";
4
+ import type { TypeInputContainerProps } from "@sproutsocial/seeds-react-input";
5
+
6
+ interface TypeTokenInputContainerProps
7
+ extends Pick<
8
+ TypeInputContainerProps,
9
+ "hasBeforeElement" | "hasAfterElement" | "disabled" | "invalid" | "warning"
10
+ > {
11
+ focused?: boolean;
12
+ }
13
+
14
+ const Container = styled.div<TypeTokenInputContainerProps>`
15
+ box-sizing: border-box;
16
+ position: relative;
17
+ display: flex;
18
+ flex-wrap: wrap;
19
+ align-items: center;
20
+ align-content: center;
21
+ cursor: text;
22
+ width: 100%;
23
+ border: 1px solid ${(props) => props.theme.colors.form.border.base};
24
+ border-radius: ${(props) => props.theme.radii[500]};
25
+ margin: 0;
26
+ padding: ${(props) => props.theme.space[300]};
27
+ padding-top: ${(props) => props.theme.space[200]};
28
+ background-color: ${(props) => props.theme.colors.form.background.base};
29
+ color: ${(props) => props.theme.colors.text.body};
30
+ transition: border-color ${(props) => props.theme.duration.fast}
31
+ ${(props) => props.theme.easing.ease_in},
32
+ box-shadow ${(props) => props.theme.duration.fast}
33
+ ${(props) => props.theme.easing.ease_in};
34
+ ${(props) => props.theme.typography[200]};
35
+ font-family: ${(props) => props.theme.fontFamily};
36
+ font-weight: ${(props) => props.theme.fontWeights.normal};
37
+ appearance: none;
38
+
39
+ button {
40
+ margin: ${(props) => props.theme.space[200]}
41
+ ${(props) => props.theme.space[200]} 0 0;
42
+ }
43
+
44
+ input {
45
+ ${(props) => props.theme.typography[200]};
46
+ outline: none;
47
+ border: none;
48
+ flex: 1;
49
+ padding: 0;
50
+ padding-top: ${(props) => props.theme.space[100]};
51
+ margin: ${(props) => props.theme.space[200]}
52
+ ${(props) => props.theme.space[300]} ${(props) => props.theme.space[100]}
53
+ 0;
54
+ color: ${(props) => props.theme.colors.text.body};
55
+ background-color: ${(props) => props.theme.colors.form.background.base};
56
+ /** This matches the height of the token so size does not change as tokens are added */
57
+ min-height: 20px;
58
+
59
+ &::-webkit-search-cancel-button {
60
+ appearance: none;
61
+ }
62
+
63
+ /* Explicitly removes double focus ring in environments where box-shadow focus styles have been specified (Seeds). Focus is passed up from the input to the parent container. */
64
+ &:focus {
65
+ box-shadow: none;
66
+ }
67
+
68
+ /* https://stackoverflow.com/questions/14007655/remove-ie10s-clear-field-x-button-on-certain-inputs */
69
+ &::-ms-clear {
70
+ display: none;
71
+ }
72
+
73
+ /* Fix for red ring when input is marked required in Firefox */
74
+ &:not(output):not(:focus):-moz-ui-invalid {
75
+ box-shadow: none;
76
+ }
77
+
78
+ &::placeholder {
79
+ color: ${(props) => props.theme.colors.form.placeholder.base};
80
+ font-style: italic;
81
+ }
82
+
83
+ ${(props) =>
84
+ props.disabled &&
85
+ css`
86
+ opacity: 0.4;
87
+
88
+ cursor: not-allowed;
89
+ `}
90
+ }
91
+
92
+ ${(props) =>
93
+ props.hasBeforeElement &&
94
+ css`
95
+ padding-left: 40px;
96
+ `}
97
+
98
+ ${(props) =>
99
+ props.hasAfterElement &&
100
+ css`
101
+ padding-right: 40px;
102
+ `}
103
+
104
+
105
+ ${(props) =>
106
+ props.disabled &&
107
+ css`
108
+ opacity: 0.4;
109
+
110
+ cursor: not-allowed;
111
+ `}
112
+
113
+ ${(props) =>
114
+ props.focused &&
115
+ css`
116
+ ${focusRing}
117
+ `}
118
+
119
+ ${(props) =>
120
+ props.invalid &&
121
+ css`
122
+ border-color: ${(props) => props.theme.colors.form.border.error};
123
+ `}
124
+
125
+ ${(props) =>
126
+ props.warning &&
127
+ css`
128
+ border-color: ${(props) => props.theme.colors.form.border.warning};
129
+ `}
130
+
131
+ ${COMMON}
132
+ `;
133
+
134
+ Container.displayName = "TokenInputContainer";
135
+
136
+ export default Container;
package/src/util.ts ADDED
@@ -0,0 +1,22 @@
1
+ import uniqueId from "lodash.uniqueid";
2
+ import type { TypeTokenSpec } from "./";
3
+
4
+ export const asTokenSpec = (text: string): TypeTokenSpec => ({
5
+ id: uniqueId(`${text}-`),
6
+ value: text.trim(),
7
+ });
8
+
9
+ const KeyNameToRegExpChar: { [key: string]: string } = {
10
+ Enter: "\\n",
11
+ };
12
+
13
+ export const delimitersAsRegExp = (delimiters: string[] | null | undefined) => {
14
+ if (!delimiters) return /[,\n]/;
15
+ const chars = delimiters
16
+ .map(
17
+ (key) =>
18
+ KeyNameToRegExpChar[key as keyof typeof KeyNameToRegExpChar] || key
19
+ )
20
+ .join("");
21
+ return RegExp(`[${chars}]`);
22
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@sproutsocial/seeds-tsconfig/bundler/dom/library-monorepo",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "module": "esnext"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "coverage", "src/TokenInput.stories.tsx"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig((options) => ({
4
+ entry: ["src/index.ts"],
5
+ format: ["cjs", "esm"],
6
+ clean: true,
7
+ legacyOutput: true,
8
+ dts: options.dts,
9
+ external: ["react"],
10
+ sourcemap: true,
11
+ metafile: options.metafile,
12
+ }));