@primer/components 31.0.2-rc.c7dafefb → 31.1.0-rc.71d00800
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 +5 -1
- package/dist/browser.esm.js +11 -10
- package/dist/browser.esm.js.map +1 -1
- package/dist/browser.umd.js +11 -10
- package/dist/browser.umd.js.map +1 -1
- package/docs/content/TextInputWithTokens.mdx +114 -0
- package/lib/TextInputWithTokens.d.ts +4 -0
- package/lib/TextInputWithTokens.js +61 -8
- package/lib/_TextInputWrapper.js +1 -1
- package/lib/__tests__/TextInputWithTokens.test.js +149 -9
- package/lib/stories/TextInputWithTokens.stories.js +18 -1
- package/lib-esm/TextInputWithTokens.d.ts +4 -0
- package/lib-esm/TextInputWithTokens.js +60 -8
- package/lib-esm/_TextInputWrapper.js +1 -1
- package/lib-esm/__tests__/TextInputWithTokens.test.js +142 -9
- package/lib-esm/stories/TextInputWithTokens.stories.js +14 -0
- package/package.json +1 -1
- package/src/TextInputWithTokens.tsx +64 -8
- package/src/_TextInputWrapper.tsx +1 -0
- package/src/__tests__/TextInputWithTokens.test.tsx +133 -1
- package/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap +7 -0
- package/src/__tests__/__snapshots__/TextInput.test.tsx.snap +6 -0
- package/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap +463 -0
- package/src/stories/TextInputWithTokens.stories.tsx +9 -0
- package/stats.html +1 -1
@@ -48,6 +48,7 @@ render(BasicExample)
|
|
48
48
|
| preventTokenWrapping | `boolean` | `false` | Optional. Whether tokens should render inline horizontally. By default, tokens wrap to new lines. |
|
49
49
|
| size | `TokenSizeKeys` | `extralarge` | Optional. The size of the tokens |
|
50
50
|
| hideTokenRemoveButtons | `boolean` | `false` | Optional. Whether the remove buttons should be rendered in the tokens |
|
51
|
+
| visibleTokenCount | `number` | `undefined` | Optional. The number of tokens to display before truncating |
|
51
52
|
|
52
53
|
## Adding and removing tokens
|
53
54
|
|
@@ -95,3 +96,116 @@ const UsingIssueLabelTokens = () => {
|
|
95
96
|
|
96
97
|
render(<UsingIssueLabelTokens />)
|
97
98
|
```
|
99
|
+
|
100
|
+
## Dealing with long lists of tokens
|
101
|
+
|
102
|
+
By default, all tokens will be visible when the component is rendered.
|
103
|
+
|
104
|
+
If the component is being used in an area where it's height needs to be constrained, there are options to limit the height of the input.
|
105
|
+
|
106
|
+
### Hide and show tokens
|
107
|
+
|
108
|
+
```javascript live noinline
|
109
|
+
const VisibleTokenCountExample = () => {
|
110
|
+
const [tokens, setTokens] = React.useState([
|
111
|
+
{text: 'zero', id: 0},
|
112
|
+
{text: 'one', id: 1},
|
113
|
+
{text: 'two', id: 2},
|
114
|
+
{text: 'three', id: 3}
|
115
|
+
])
|
116
|
+
const onTokenRemove = tokenId => {
|
117
|
+
setTokens(tokens.filter(token => token.id !== tokenId))
|
118
|
+
}
|
119
|
+
|
120
|
+
return (
|
121
|
+
<Box maxWidth="500px">
|
122
|
+
<Box as="label" display="block" htmlFor="inputWithTokens-basic">
|
123
|
+
Tokens truncated after 2
|
124
|
+
</Box>
|
125
|
+
<TextInputWithTokens
|
126
|
+
visibleTokenCount={2}
|
127
|
+
block
|
128
|
+
tokens={tokens}
|
129
|
+
onTokenRemove={onTokenRemove}
|
130
|
+
id="inputWithTokens-basic"
|
131
|
+
/>
|
132
|
+
</Box>
|
133
|
+
)
|
134
|
+
}
|
135
|
+
|
136
|
+
render(VisibleTokenCountExample)
|
137
|
+
```
|
138
|
+
|
139
|
+
### Render tokens on a single line
|
140
|
+
|
141
|
+
```javascript live noinline
|
142
|
+
const PreventTokenWrappingExample = () => {
|
143
|
+
const [tokens, setTokens] = React.useState([
|
144
|
+
{text: 'zero', id: 0},
|
145
|
+
{text: 'one', id: 1},
|
146
|
+
{text: 'two', id: 2},
|
147
|
+
{text: 'three', id: 3},
|
148
|
+
{text: 'four', id: 4},
|
149
|
+
{text: 'five', id: 5},
|
150
|
+
{text: 'six', id: 6},
|
151
|
+
{text: 'seven', id: 7}
|
152
|
+
])
|
153
|
+
const onTokenRemove = tokenId => {
|
154
|
+
setTokens(tokens.filter(token => token.id !== tokenId))
|
155
|
+
}
|
156
|
+
|
157
|
+
return (
|
158
|
+
<Box maxWidth="500px">
|
159
|
+
<Box as="label" display="block" htmlFor="inputWithTokens-basic">
|
160
|
+
Tokens on one line
|
161
|
+
</Box>
|
162
|
+
<TextInputWithTokens
|
163
|
+
preventTokenWrapping
|
164
|
+
block
|
165
|
+
tokens={tokens}
|
166
|
+
onTokenRemove={onTokenRemove}
|
167
|
+
id="inputWithTokens-basic"
|
168
|
+
/>
|
169
|
+
</Box>
|
170
|
+
)
|
171
|
+
}
|
172
|
+
|
173
|
+
render(PreventTokenWrappingExample)
|
174
|
+
```
|
175
|
+
|
176
|
+
### Set a maximum height for the input
|
177
|
+
|
178
|
+
```javascript live noinline
|
179
|
+
const MaxHeightExample = () => {
|
180
|
+
const [tokens, setTokens] = React.useState([
|
181
|
+
{text: 'zero', id: 0},
|
182
|
+
{text: 'one', id: 1},
|
183
|
+
{text: 'two', id: 2},
|
184
|
+
{text: 'three', id: 3},
|
185
|
+
{text: 'four', id: 4},
|
186
|
+
{text: 'five', id: 5},
|
187
|
+
{text: 'six', id: 6},
|
188
|
+
{text: 'seven', id: 7}
|
189
|
+
])
|
190
|
+
const onTokenRemove = tokenId => {
|
191
|
+
setTokens(tokens.filter(token => token.id !== tokenId))
|
192
|
+
}
|
193
|
+
|
194
|
+
return (
|
195
|
+
<Box maxWidth="500px">
|
196
|
+
<Box as="label" display="block" htmlFor="inputWithTokens-basic">
|
197
|
+
Tokens restricted to a max height
|
198
|
+
</Box>
|
199
|
+
<TextInputWithTokens
|
200
|
+
maxHeight="50px"
|
201
|
+
block
|
202
|
+
tokens={tokens}
|
203
|
+
onTokenRemove={onTokenRemove}
|
204
|
+
id="inputWithTokens-basic"
|
205
|
+
/>
|
206
|
+
</Box>
|
207
|
+
)
|
208
|
+
}
|
209
|
+
|
210
|
+
render(MaxHeightExample)
|
211
|
+
```
|
@@ -32,6 +32,10 @@ declare const TextInputWithTokens: React.ForwardRefExoticComponent<Pick<{
|
|
32
32
|
* Whether the remove buttons should be rendered in the tokens
|
33
33
|
*/
|
34
34
|
hideTokenRemoveButtons?: boolean | undefined;
|
35
|
+
/**
|
36
|
+
* The number of tokens to display before truncating
|
37
|
+
*/
|
38
|
+
visibleTokenCount?: number | undefined;
|
35
39
|
} & Pick<Omit<Pick<{
|
36
40
|
[x: string]: any;
|
37
41
|
[x: number]: any;
|
@@ -25,6 +25,8 @@ var _TextInputWrapper = _interopRequireDefault(require("./_TextInputWrapper"));
|
|
25
25
|
|
26
26
|
var _Box = _interopRequireDefault(require("./Box"));
|
27
27
|
|
28
|
+
var _Text = _interopRequireDefault(require("./Text"));
|
29
|
+
|
28
30
|
var _iterateFocusableElements = require("./utils/iterateFocusableElements");
|
29
31
|
|
30
32
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
@@ -35,7 +37,13 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
|
|
35
37
|
|
36
38
|
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
37
39
|
|
38
|
-
|
40
|
+
const overflowCountFontSizeMap = {
|
41
|
+
small: 0,
|
42
|
+
medium: 1,
|
43
|
+
large: 1,
|
44
|
+
extralarge: 2
|
45
|
+
}; // using forwardRef is important so that other components (ex. Autocomplete) can use the ref
|
46
|
+
|
39
47
|
function TextInputWithTokensInnerComponent({
|
40
48
|
icon: IconComponent,
|
41
49
|
contrast,
|
@@ -55,9 +63,11 @@ function TextInputWithTokensInnerComponent({
|
|
55
63
|
minWidth: minWidthProp,
|
56
64
|
maxWidth: maxWidthProp,
|
57
65
|
variant: variantProp,
|
66
|
+
visibleTokenCount,
|
58
67
|
...rest
|
59
68
|
}, externalRef) {
|
60
69
|
const {
|
70
|
+
onBlur,
|
61
71
|
onFocus,
|
62
72
|
onKeyDown,
|
63
73
|
...inputPropsRest
|
@@ -66,6 +76,7 @@ function TextInputWithTokensInnerComponent({
|
|
66
76
|
const localInputRef = (0, _react.useRef)(null);
|
67
77
|
const combinedInputRef = (0, _useCombinedRefs.useCombinedRefs)(localInputRef, ref);
|
68
78
|
const [selectedTokenIndex, setSelectedTokenIndex] = (0, _react.useState)();
|
79
|
+
const [tokensAreTruncated, setTokensAreTruncated] = (0, _react.useState)(Boolean(visibleTokenCount));
|
69
80
|
const {
|
70
81
|
containerRef
|
71
82
|
} = (0, _useFocusZone.useFocusZone)({
|
@@ -126,20 +137,45 @@ function TextInputWithTokensInnerComponent({
|
|
126
137
|
};
|
127
138
|
|
128
139
|
const handleTokenBlur = () => {
|
129
|
-
setSelectedTokenIndex(undefined);
|
140
|
+
setSelectedTokenIndex(undefined); // HACK: wait a tick and check the focused element before hiding truncated tokens
|
141
|
+
// this prevents the tokens from hiding when the user is moving focus between tokens,
|
142
|
+
// but still hides the tokens when the user blurs the token by tabbing out or clicking somewhere else on the page
|
143
|
+
|
144
|
+
setTimeout(() => {
|
145
|
+
var _containerRef$current4;
|
146
|
+
|
147
|
+
if (!((_containerRef$current4 = containerRef.current) !== null && _containerRef$current4 !== void 0 && _containerRef$current4.contains(document.activeElement)) && visibleTokenCount) {
|
148
|
+
setTokensAreTruncated(true);
|
149
|
+
}
|
150
|
+
}, 0);
|
130
151
|
};
|
131
152
|
|
132
|
-
const handleTokenKeyUp =
|
133
|
-
if (
|
153
|
+
const handleTokenKeyUp = event => {
|
154
|
+
if (event.key === 'Escape') {
|
134
155
|
var _ref$current2;
|
135
156
|
|
136
157
|
(_ref$current2 = ref.current) === null || _ref$current2 === void 0 ? void 0 : _ref$current2.focus();
|
137
158
|
}
|
138
159
|
};
|
139
160
|
|
140
|
-
const handleInputFocus =
|
141
|
-
onFocus && onFocus(
|
161
|
+
const handleInputFocus = event => {
|
162
|
+
onFocus && onFocus(event);
|
142
163
|
setSelectedTokenIndex(undefined);
|
164
|
+
visibleTokenCount && setTokensAreTruncated(false);
|
165
|
+
};
|
166
|
+
|
167
|
+
const handleInputBlur = event => {
|
168
|
+
onBlur && onBlur(event); // HACK: wait a tick and check the focused element before hiding truncated tokens
|
169
|
+
// this prevents the tokens from hiding when the user is moving focus from the input to a token,
|
170
|
+
// but still hides the tokens when the user blurs the input by tabbing out or clicking somewhere else on the page
|
171
|
+
|
172
|
+
setTimeout(() => {
|
173
|
+
var _containerRef$current5;
|
174
|
+
|
175
|
+
if (!((_containerRef$current5 = containerRef.current) !== null && _containerRef$current5 !== void 0 && _containerRef$current5.contains(document.activeElement)) && visibleTokenCount) {
|
176
|
+
setTokensAreTruncated(true);
|
177
|
+
}
|
178
|
+
}, 0);
|
143
179
|
};
|
144
180
|
|
145
181
|
const handleInputKeyDown = e => {
|
@@ -177,6 +213,17 @@ function TextInputWithTokensInnerComponent({
|
|
177
213
|
}
|
178
214
|
};
|
179
215
|
|
216
|
+
const focusInput = () => {
|
217
|
+
var _combinedInputRef$cur;
|
218
|
+
|
219
|
+
(_combinedInputRef$cur = combinedInputRef.current) === null || _combinedInputRef$cur === void 0 ? void 0 : _combinedInputRef$cur.focus();
|
220
|
+
};
|
221
|
+
|
222
|
+
const preventTokenClickPropagation = event => {
|
223
|
+
event.stopPropagation();
|
224
|
+
};
|
225
|
+
|
226
|
+
const visibleTokens = tokensAreTruncated ? tokens.slice(0, visibleTokenCount) : tokens;
|
180
227
|
return /*#__PURE__*/_react.default.createElement(_TextInputWrapper.default, {
|
181
228
|
block: block,
|
182
229
|
className: className,
|
@@ -188,6 +235,7 @@ function TextInputWithTokensInnerComponent({
|
|
188
235
|
minWidth: minWidthProp,
|
189
236
|
maxWidth: maxWidthProp,
|
190
237
|
variant: variantProp,
|
238
|
+
onClick: focusInput,
|
191
239
|
sx: { ...(block ? {
|
192
240
|
display: 'flex',
|
193
241
|
width: '100%'
|
@@ -227,12 +275,13 @@ function TextInputWithTokensInnerComponent({
|
|
227
275
|
ref: combinedInputRef,
|
228
276
|
disabled: disabled,
|
229
277
|
onFocus: handleInputFocus,
|
278
|
+
onBlur: handleInputBlur,
|
230
279
|
onKeyDown: handleInputKeyDown,
|
231
280
|
type: "text",
|
232
281
|
sx: {
|
233
282
|
height: '100%'
|
234
283
|
}
|
235
|
-
}, inputPropsRest))),
|
284
|
+
}, inputPropsRest))), TokenComponent ? visibleTokens.map(({
|
236
285
|
id,
|
237
286
|
...tokenRest
|
238
287
|
}, i) => /*#__PURE__*/_react.default.createElement(TokenComponent, _extends({
|
@@ -240,6 +289,7 @@ function TextInputWithTokensInnerComponent({
|
|
240
289
|
onFocus: handleTokenFocus(i),
|
241
290
|
onBlur: handleTokenBlur,
|
242
291
|
onKeyUp: handleTokenKeyUp,
|
292
|
+
onClick: preventTokenClickPropagation,
|
243
293
|
isSelected: selectedTokenIndex === i,
|
244
294
|
onRemove: () => {
|
245
295
|
handleTokenRemove(id);
|
@@ -247,7 +297,10 @@ function TextInputWithTokensInnerComponent({
|
|
247
297
|
hideRemoveButton: hideTokenRemoveButtons,
|
248
298
|
size: size,
|
249
299
|
tabIndex: 0
|
250
|
-
}, tokenRest))) : null
|
300
|
+
}, tokenRest))) : null, tokensAreTruncated ? /*#__PURE__*/_react.default.createElement(_Text.default, {
|
301
|
+
color: "fg.muted",
|
302
|
+
fontSize: size && overflowCountFontSizeMap[size]
|
303
|
+
}, "+", tokens.length - visibleTokens.length) : null));
|
251
304
|
}
|
252
305
|
|
253
306
|
TextInputWithTokensInnerComponent.displayName = "TextInputWithTokensInnerComponent";
|
package/lib/_TextInputWrapper.js
CHANGED
@@ -39,7 +39,7 @@ const sizeVariants = (0, _styledSystem.variant)({
|
|
39
39
|
const TextInputWrapper = _styledComponents.default.span.withConfig({
|
40
40
|
displayName: "_TextInputWrapper__TextInputWrapper",
|
41
41
|
componentId: "sc-5vfcis-0"
|
42
|
-
})(["display:inline-flex;align-items:stretch;min-height:34px;font-size:", ";line-height:20px;color:", ";vertical-align:middle;background-repeat:no-repeat;background-position:right 8px center;border:1px solid ", ";border-radius:", ";outline:none;box-shadow:", ";", " .TextInput-icon{align-self:center;color:", ";margin:0 ", ";flex-shrink:0;}&:focus-within{border-color:", ";box-shadow:", ";}", " ", " ", " @media (min-width:", "){font-size:", ";}", " ", " ", " ", " ", ";"], (0, _constants.get)('fontSizes.1'), (0, _constants.get)('colors.fg.default'), (0, _constants.get)('colors.border.default'), (0, _constants.get)('radii.2'), (0, _constants.get)('shadows.primer.shadow.inset'), props => {
|
42
|
+
})(["display:inline-flex;align-items:stretch;min-height:34px;font-size:", ";line-height:20px;color:", ";vertical-align:middle;background-repeat:no-repeat;background-position:right 8px center;border:1px solid ", ";border-radius:", ";outline:none;box-shadow:", ";cursor:text;", " .TextInput-icon{align-self:center;color:", ";margin:0 ", ";flex-shrink:0;}&:focus-within{border-color:", ";box-shadow:", ";}", " ", " ", " @media (min-width:", "){font-size:", ";}", " ", " ", " ", " ", ";"], (0, _constants.get)('fontSizes.1'), (0, _constants.get)('colors.fg.default'), (0, _constants.get)('colors.border.default'), (0, _constants.get)('radii.2'), (0, _constants.get)('shadows.primer.shadow.inset'), props => {
|
43
43
|
if (props.hasIcon) {
|
44
44
|
return (0, _styledComponents.css)(["padding:0;"]);
|
45
45
|
} else {
|
@@ -58,8 +58,18 @@ const LabelledTextInputWithTokens = ({
|
|
58
58
|
tokens: tokens,
|
59
59
|
onTokenRemove: onTokenRemove,
|
60
60
|
id: "tokenInput"
|
61
|
-
}, rest)));
|
62
|
-
|
61
|
+
}, rest))); // describe('axe test', () => {
|
62
|
+
// it('should have no axe violations', async () => {
|
63
|
+
// const onRemoveMock = jest.fn()
|
64
|
+
// const {container} = HTMLRender(<LabelledTextInputWithTokens tokens={mockTokens} onTokenRemove={onRemoveMock} />)
|
65
|
+
// const results = await axe(container)
|
66
|
+
// expect(results).toHaveNoViolations()
|
67
|
+
// cleanup()
|
68
|
+
// })
|
69
|
+
// })
|
70
|
+
|
71
|
+
|
72
|
+
jest.useFakeTimers();
|
63
73
|
describe('TextInputWithTokens', () => {
|
64
74
|
it('renders without tokens', () => {
|
65
75
|
const onRemoveMock = jest.fn();
|
@@ -127,6 +137,14 @@ describe('TextInputWithTokens', () => {
|
|
127
137
|
onTokenRemove: onRemoveMock
|
128
138
|
}))).toMatchSnapshot();
|
129
139
|
});
|
140
|
+
it('renders a truncated set of tokens', () => {
|
141
|
+
const onRemoveMock = jest.fn();
|
142
|
+
expect((0, _testing.render)( /*#__PURE__*/_react.default.createElement(_TextInputWithTokens.default, {
|
143
|
+
tokens: mockTokens,
|
144
|
+
onTokenRemove: onRemoveMock,
|
145
|
+
visibleTokenCount: 2
|
146
|
+
}))).toMatchSnapshot();
|
147
|
+
});
|
130
148
|
it('focuses the previous token when keying ArrowLeft', () => {
|
131
149
|
var _document$activeEleme, _document$activeEleme2;
|
132
150
|
|
@@ -250,8 +268,130 @@ describe('TextInputWithTokens', () => {
|
|
250
268
|
expect((_document$activeEleme8 = document.activeElement) === null || _document$activeEleme8 === void 0 ? void 0 : _document$activeEleme8.id).not.toEqual(lastTokenNode.id);
|
251
269
|
expect((_document$activeEleme9 = document.activeElement) === null || _document$activeEleme9 === void 0 ? void 0 : _document$activeEleme9.id).toEqual(inputNode.id);
|
252
270
|
});
|
271
|
+
it('does not focus the input when clicking a token', () => {
|
272
|
+
var _document$activeEleme10;
|
273
|
+
|
274
|
+
const onRemoveMock = jest.fn();
|
275
|
+
const {
|
276
|
+
getByLabelText,
|
277
|
+
getByText
|
278
|
+
} = (0, _react2.render)( /*#__PURE__*/_react.default.createElement(LabelledTextInputWithTokens, {
|
279
|
+
tokens: mockTokens,
|
280
|
+
onTokenRemove: onRemoveMock,
|
281
|
+
visibleTokenCount: 2
|
282
|
+
}));
|
283
|
+
const inputNode = getByLabelText('Tokens');
|
284
|
+
const tokenNode = getByText(mockTokens[0].text);
|
285
|
+
expect(document.activeElement).not.toEqual(inputNode.id);
|
286
|
+
|
287
|
+
_react2.fireEvent.click(tokenNode);
|
288
|
+
|
289
|
+
expect((_document$activeEleme10 = document.activeElement) === null || _document$activeEleme10 === void 0 ? void 0 : _document$activeEleme10.id).not.toEqual(inputNode.id);
|
290
|
+
});
|
291
|
+
it('focuses the input when clicking somewhere in the component besides the tokens', () => {
|
292
|
+
var _document$activeEleme11;
|
293
|
+
|
294
|
+
const onRemoveMock = jest.fn();
|
295
|
+
const {
|
296
|
+
getByLabelText,
|
297
|
+
getByText
|
298
|
+
} = (0, _react2.render)( /*#__PURE__*/_react.default.createElement(LabelledTextInputWithTokens, {
|
299
|
+
tokens: mockTokens,
|
300
|
+
onTokenRemove: onRemoveMock,
|
301
|
+
visibleTokenCount: 2
|
302
|
+
}));
|
303
|
+
const inputNode = getByLabelText('Tokens');
|
304
|
+
const truncatedTokenCount = getByText('+6');
|
305
|
+
expect(document.activeElement).not.toEqual(inputNode.id);
|
306
|
+
|
307
|
+
_react2.fireEvent.click(truncatedTokenCount);
|
308
|
+
|
309
|
+
expect((_document$activeEleme11 = document.activeElement) === null || _document$activeEleme11 === void 0 ? void 0 : _document$activeEleme11.id).toEqual(inputNode.id);
|
310
|
+
});
|
311
|
+
it('shows all tokens when the input is focused and hides them when it is blurred (when visibleTokenCount is set)', () => {
|
312
|
+
const onRemoveMock = jest.fn();
|
313
|
+
const visibleTokenCount = 2;
|
314
|
+
const {
|
315
|
+
getByLabelText,
|
316
|
+
getByText
|
317
|
+
} = (0, _react2.render)( /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(LabelledTextInputWithTokens, {
|
318
|
+
tokens: mockTokens,
|
319
|
+
onTokenRemove: onRemoveMock,
|
320
|
+
visibleTokenCount: visibleTokenCount
|
321
|
+
}), /*#__PURE__*/_react.default.createElement("button", {
|
322
|
+
id: "focusableOutsideComponent"
|
323
|
+
}, "Focus me")));
|
324
|
+
const inputNode = getByLabelText('Tokens');
|
325
|
+
const focusableOutsideComponentNode = getByText('Focus me');
|
326
|
+
const allTokenLabels = mockTokens.map(token => token.text);
|
327
|
+
const truncatedTokenCountNode = getByText('+6');
|
328
|
+
(0, _react2.act)(() => {
|
329
|
+
jest.runAllTimers();
|
330
|
+
|
331
|
+
_react2.fireEvent.focus(inputNode);
|
332
|
+
});
|
333
|
+
setTimeout(() => {
|
334
|
+
for (const tokenLabel of allTokenLabels) {
|
335
|
+
const tokenNode = getByText(tokenLabel);
|
336
|
+
expect(tokenNode).toBeDefined();
|
337
|
+
}
|
338
|
+
}, 0);
|
339
|
+
(0, _react2.act)(() => {
|
340
|
+
jest.runAllTimers(); // onBlur isn't called on input unless we specifically fire the "blur" event
|
341
|
+
// eslint-disable-next-line github/no-blur
|
342
|
+
|
343
|
+
_react2.fireEvent.blur(inputNode);
|
344
|
+
|
345
|
+
_react2.fireEvent.focus(focusableOutsideComponentNode);
|
346
|
+
});
|
347
|
+
setTimeout(() => {
|
348
|
+
expect(truncatedTokenCountNode).toBeDefined();
|
349
|
+
|
350
|
+
for (const tokenLabel of allTokenLabels) {
|
351
|
+
const tokenNode = getByText(tokenLabel);
|
352
|
+
|
353
|
+
if (allTokenLabels.indexOf(tokenLabel) > visibleTokenCount) {
|
354
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
355
|
+
expect(tokenNode).toBeDefined();
|
356
|
+
} else {
|
357
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
358
|
+
expect(tokenNode).not.toBeDefined();
|
359
|
+
}
|
360
|
+
}
|
361
|
+
}, 0);
|
362
|
+
jest.useRealTimers();
|
363
|
+
});
|
364
|
+
it('does not hide tokens when blurring the input to focus within the component (when visibleTokenCount is set)', () => {
|
365
|
+
const onRemoveMock = jest.fn();
|
366
|
+
const visibleTokenCount = 2;
|
367
|
+
const {
|
368
|
+
getByLabelText,
|
369
|
+
getByText
|
370
|
+
} = (0, _react2.render)( /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(LabelledTextInputWithTokens, {
|
371
|
+
tokens: mockTokens,
|
372
|
+
onTokenRemove: onRemoveMock,
|
373
|
+
visibleTokenCount: visibleTokenCount
|
374
|
+
}), /*#__PURE__*/_react.default.createElement("button", {
|
375
|
+
id: "focusableOutsideComponent"
|
376
|
+
}, "Focus me")));
|
377
|
+
const inputNode = getByLabelText('Tokens');
|
378
|
+
const firstTokenNode = getByText(mockTokens[visibleTokenCount - 1].text);
|
379
|
+
const allTokenLabels = mockTokens.map(token => token.text);
|
380
|
+
const truncatedTokenCountNode = getByText('+6');
|
381
|
+
(0, _react2.act)(() => {
|
382
|
+
_react2.fireEvent.focus(inputNode);
|
383
|
+
|
384
|
+
_react2.fireEvent.focus(firstTokenNode);
|
385
|
+
});
|
386
|
+
expect(truncatedTokenCountNode).toBeDefined();
|
387
|
+
|
388
|
+
for (const tokenLabel of allTokenLabels) {
|
389
|
+
const tokenNode = getByText(tokenLabel);
|
390
|
+
expect(tokenNode).toBeDefined();
|
391
|
+
}
|
392
|
+
});
|
253
393
|
it('focuses the first token when keying ArrowRight in the input', () => {
|
254
|
-
var _document$
|
394
|
+
var _document$activeEleme12, _document$activeEleme13;
|
255
395
|
|
256
396
|
const onRemoveMock = jest.fn();
|
257
397
|
const {
|
@@ -271,8 +411,8 @@ describe('TextInputWithTokens', () => {
|
|
271
411
|
key: 'ArrowRight'
|
272
412
|
});
|
273
413
|
|
274
|
-
expect((_document$
|
275
|
-
expect((_document$
|
414
|
+
expect((_document$activeEleme12 = document.activeElement) === null || _document$activeEleme12 === void 0 ? void 0 : _document$activeEleme12.id).not.toEqual(inputNode.id);
|
415
|
+
expect((_document$activeEleme13 = document.activeElement) === null || _document$activeEleme13 === void 0 ? void 0 : _document$activeEleme13.id).toEqual(firstTokenNode.id);
|
276
416
|
});
|
277
417
|
it('calls onTokenRemove on the last token when keying Backspace in an empty input', () => {
|
278
418
|
const onRemoveMock = jest.fn();
|
@@ -372,9 +512,9 @@ describe('TextInputWithTokens', () => {
|
|
372
512
|
|
373
513
|
jest.runAllTimers();
|
374
514
|
setTimeout(() => {
|
375
|
-
var _document$
|
515
|
+
var _document$activeEleme14;
|
376
516
|
|
377
|
-
expect((_document$
|
517
|
+
expect((_document$activeEleme14 = document.activeElement) === null || _document$activeEleme14 === void 0 ? void 0 : _document$activeEleme14.textContent).toBe(mockTokens[1].text);
|
378
518
|
}, 0);
|
379
519
|
jest.useRealTimers();
|
380
520
|
});
|
@@ -399,9 +539,9 @@ describe('TextInputWithTokens', () => {
|
|
399
539
|
|
400
540
|
jest.runAllTimers();
|
401
541
|
setTimeout(() => {
|
402
|
-
var _document$
|
542
|
+
var _document$activeEleme15;
|
403
543
|
|
404
|
-
expect((_document$
|
544
|
+
expect((_document$activeEleme15 = document.activeElement) === null || _document$activeEleme15 === void 0 ? void 0 : _document$activeEleme15.id).toBe(inputNode.id);
|
405
545
|
}, 0);
|
406
546
|
jest.useRealTimers();
|
407
547
|
});
|
@@ -3,7 +3,7 @@
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
4
4
|
value: true
|
5
5
|
});
|
6
|
-
exports.Unstyled = exports.TokenRemoveButtonsHidden = exports.TokenWrappingPrevented = exports.MaxHeight = exports.TokenSizeVariants = exports.UsingIssueLabelTokens = exports.Default = exports.default = void 0;
|
6
|
+
exports.Unstyled = exports.WithVisibleTokenCount = exports.TokenRemoveButtonsHidden = exports.TokenWrappingPrevented = exports.MaxHeight = exports.TokenSizeVariants = exports.UsingIssueLabelTokens = exports.Default = exports.default = void 0;
|
7
7
|
|
8
8
|
var _react = _interopRequireWildcard(require("react"));
|
9
9
|
|
@@ -208,6 +208,23 @@ const TokenRemoveButtonsHidden = () => {
|
|
208
208
|
exports.TokenRemoveButtonsHidden = TokenRemoveButtonsHidden;
|
209
209
|
TokenRemoveButtonsHidden.displayName = "TokenRemoveButtonsHidden";
|
210
210
|
|
211
|
+
const WithVisibleTokenCount = () => {
|
212
|
+
const [tokens, setTokens] = (0, _react.useState)([...mockTokens].slice(0, 5));
|
213
|
+
|
214
|
+
const onTokenRemove = tokenId => {
|
215
|
+
setTokens(tokens.filter(token => token.id !== tokenId));
|
216
|
+
};
|
217
|
+
|
218
|
+
return /*#__PURE__*/_react.default.createElement(_TextInputWithTokens.default, {
|
219
|
+
tokens: tokens,
|
220
|
+
onTokenRemove: onTokenRemove,
|
221
|
+
visibleTokenCount: 2
|
222
|
+
});
|
223
|
+
};
|
224
|
+
|
225
|
+
exports.WithVisibleTokenCount = WithVisibleTokenCount;
|
226
|
+
WithVisibleTokenCount.displayName = "WithVisibleTokenCount";
|
227
|
+
|
211
228
|
const Unstyled = () => {
|
212
229
|
const [tokens, setTokens] = (0, _react.useState)([...mockTokens].slice(0, 2));
|
213
230
|
|
@@ -32,6 +32,10 @@ declare const TextInputWithTokens: React.ForwardRefExoticComponent<Pick<{
|
|
32
32
|
* Whether the remove buttons should be rendered in the tokens
|
33
33
|
*/
|
34
34
|
hideTokenRemoveButtons?: boolean | undefined;
|
35
|
+
/**
|
36
|
+
* The number of tokens to display before truncating
|
37
|
+
*/
|
38
|
+
visibleTokenCount?: number | undefined;
|
35
39
|
} & Pick<Omit<Pick<{
|
36
40
|
[x: string]: any;
|
37
41
|
[x: number]: any;
|