Skip to content

Commit e8d5aa3

Browse files
committed
changed separator node type from div to span in Indicator IndicatorIcons component; hoisted the memo wrapper from the MultiValue component to it's parent Value component (in addition, modified the Value component props to now take a boolean "hasInput" instead of a string "inputValue" to reduce the amount of renders triggered during search input mutations).; fix React Hooks linter rule warnings (although triggered erroneously it looks like)
1 parent cd8a1cf commit e8d5aa3

File tree

12 files changed

+76
-94
lines changed

12 files changed

+76
-94
lines changed

.storybook/manager-head.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
}
9797

9898
#storybook-panel-root .os-content > pre > div:last-of-type .token.function {
99-
color: #82AAFF;
99+
color: #85ADFF;
100100
}
101101

102102
#storybook-panel-root .os-content > pre > div:last-of-type .token.string,

__stories__/helpers/components/CodeMarkup.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,15 @@ const PreContainer = styled.div`
6464
color: #FF9CAC;
6565
}
6666
67-
.number {
68-
color: #F78C6C;
69-
}
70-
7167
.function {
72-
color: #82AAFF;
68+
color: #85ADFF;
7369
}
7470
7571
.tag {
7672
color: #F07178;
7773
}
7874
75+
.number,
7976
.attr-name {
8077
color: #c792ea;
8178
}

__tests__/Value.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const createValueProps = () => {
3737

3838
const props: ValueProps = {
3939
isMulti: false,
40-
inputValue: '',
40+
hasInput: false,
4141
focusedMultiValue: null,
4242
selectedOption: EMPTY_ARRAY,
4343
renderMultiOptions: undefined,
@@ -63,13 +63,13 @@ test('"placeholder" text displays when no option is selected', async () => {
6363
expect(getByText(PLACEHOLDER_DEFAULT)).toBeInTheDocument();
6464
});
6565

66-
test('component renders NULL if "inputValue" is truthy AND ("isMulti" != true OR ("isMulti" = true AND selectedOptions is empty))', async () => {
66+
test('component renders NULL if "hasInput" is true AND ("isMulti" !== true OR ("isMulti" === true AND "selectedOptions" is empty))', async () => {
6767
const { props } = createValueProps();
6868

6969
// Render with truthy "inputValue" and "isMulti" = false
7070
const singleProps = {
7171
...props,
72-
inputValue: 'test search',
72+
hasInput: true,
7373
};
7474

7575
const { container, rerender } = renderValue(singleProps);

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@
6565
"@testing-library/jest-dom": "^5.16.5",
6666
"@testing-library/react": "^13.4.0",
6767
"@testing-library/user-event": "^14.4.3",
68-
"@types/jest": "^29.2.3",
69-
"@types/node": "^18.11.10",
68+
"@types/jest": "^29.2.4",
69+
"@types/node": "^18.11.11",
7070
"@types/react": "^18.0.26",
7171
"@types/react-dom": "^18.0.9",
7272
"@types/react-window": "^1.8.5",
7373
"@types/styled-components": "^5.1.26",
74-
"@typescript-eslint/eslint-plugin": "^5.45.0",
75-
"@typescript-eslint/parser": "^5.45.0",
74+
"@typescript-eslint/eslint-plugin": "^5.45.1",
75+
"@typescript-eslint/parser": "^5.45.1",
7676
"babel-jest": "^29.3.1",
7777
"babel-loader": "^9.1.0",
7878
"babel-plugin-styled-components": "^2.0.7",
@@ -95,7 +95,7 @@
9595
"react-toastify": "^9.1.1",
9696
"react-window": "^1.8.8",
9797
"rimraf": "^3.0.2",
98-
"rollup": "^3.5.1",
98+
"rollup": "^3.6.0",
9999
"rollup-plugin-terser": "^7.0.2",
100100
"styled-components": "^5.3.6",
101101
"typescript": "^4.9.3",

src/Select.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -676,22 +676,24 @@ const Select = forwardRef<SelectRef, SelectProps>((
676676
setMenuOpen(true);
677677
};
678678

679-
const handleOnMouseDown = (e: SyntheticEvent<Element>): void => {
679+
// React Hooks linter rules require that this function be memoized in
680+
// ...order to be referenced in deps array of callback functions below
681+
const handleOnMouseDown = useCallback((e: SyntheticEvent<HTMLElement>): void => {
680682
suppressEvent(e);
681683
focusInput();
682-
};
684+
}, []);
683685

684686
const handleOnClearMouseDown = useCallback((e: MouseOrTouchEvent<HTMLElement>): void => {
685687
handleOnMouseDown(e);
686688
setSelectedOption(EMPTY_ARRAY);
687-
}, []);
689+
}, [handleOnMouseDown]);
688690

689691
const handleOnCaretMouseDown = useCallback((e: MouseOrTouchEvent<HTMLElement>): void => {
690692
if (!isDisabled && !openMenuOnClick) {
691693
handleOnMouseDown(e);
692694
menuOpenRef.current ? setMenuOpen(false) : openMenuAndFocusOption(OptionIndexEnum.FIRST);
693695
}
694-
}, [isDisabled, openMenuOnClick, openMenuAndFocusOption]);
696+
}, [isDisabled, menuOpenRef, openMenuOnClick, handleOnMouseDown, openMenuAndFocusOption]);
695697

696698
const flexValueWrapper = !!isMulti && hasSelectedOptions;
697699
const showClear = !!isClearable && !isDisabled && hasSelectedOptions;
@@ -717,7 +719,7 @@ const Select = forwardRef<SelectRef, SelectProps>((
717719
<ValueWrapper flex={flexValueWrapper}>
718720
<Value
719721
isMulti={isMulti}
720-
inputValue={inputValue}
722+
hasInput={!!inputValue}
721723
placeholder={placeholder}
722724
selectedOption={selectedOption}
723725
focusedMultiValue={focusedMultiValue}

src/components/AutosizeInput/index.tsx

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@ import styled, { css } from 'styled-components';
22
import { AUTOSIZE_INPUT_ATTRS } from '../../constants';
33
import React, { forwardRef, type Ref, type FormEventHandler, type FocusEventHandler } from 'react';
44

5-
type InputProps = Readonly<{
6-
invalid?: boolean;
7-
}>;
8-
95
type AutosizeInputProps = Readonly<{
106
id?: string;
117
menuId?: string;
@@ -22,6 +18,8 @@ type AutosizeInputProps = Readonly<{
2218
onChange: FormEventHandler<HTMLInputElement>;
2319
}>;
2420

21+
type InputProps = Pick<AutosizeInputProps, 'isInvalid'>;
22+
2523
const INPUT_BASE_STYLE = css`
2624
border: 0;
2725
margin: 0;
@@ -59,7 +57,7 @@ const Input = styled.input.attrs(AUTOSIZE_INPUT_ATTRS)<InputProps>`
5957
}
6058
6159
${({ theme }) => theme.input.css}
62-
${({ theme, invalid }) => invalid && theme.input.cssRequired}
60+
${({ theme, isInvalid }) => isInvalid && theme.input.cssRequired}
6361
`;
6462

6563
const AutosizeInput = forwardRef<HTMLInputElement, AutosizeInputProps>((
@@ -75,34 +73,29 @@ const AutosizeInput = forwardRef<HTMLInputElement, AutosizeInputProps>((
7573
ariaLabel,
7674
isInvalid,
7775
inputValue,
78-
ariaLabelledBy,
79-
hasSelectedOptions
76+
ariaLabelledBy
8077
},
8178
ref: Ref<HTMLInputElement>
82-
) => {
83-
const invalid = isInvalid || (required && !hasSelectedOptions);
84-
85-
return (
86-
<InputWrapper data-value={inputValue}>
87-
<Input
88-
invalid
89-
id={id}
90-
ref={ref}
91-
onBlur={onBlur}
92-
onFocus={onFocus}
93-
value={inputValue}
94-
readOnly={readOnly}
95-
aria-owns={menuId}
96-
aria-controls={menuId}
97-
aria-label={ariaLabel}
98-
aria-required={invalid}
99-
aria-expanded={menuOpen}
100-
aria-labelledby={ariaLabelledBy}
101-
onChange={!readOnly ? onChange : undefined}
102-
/>
103-
</InputWrapper>
104-
);
105-
});
79+
) => (
80+
<InputWrapper data-value={inputValue}>
81+
<Input
82+
id={id}
83+
ref={ref}
84+
onBlur={onBlur}
85+
onFocus={onFocus}
86+
value={inputValue}
87+
aria-owns={menuId}
88+
readOnly={readOnly}
89+
isInvalid={isInvalid}
90+
aria-controls={menuId}
91+
aria-label={ariaLabel}
92+
aria-expanded={menuOpen}
93+
aria-required={required}
94+
aria-labelledby={ariaLabelledBy}
95+
onChange={!readOnly ? onChange : undefined}
96+
/>
97+
</InputWrapper>
98+
));
10699

107100
AutosizeInput.displayName = 'AutosizeInput';
108101

src/components/Value/MultiValue.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { memo } from 'react';
1+
import React, { type FunctionComponent } from 'react';
22
import { suppressEvent } from '../../utils';
33
import styled, { css } from 'styled-components';
44
import { CLEAR_ICON_MV_TESTID } from '../../constants';
@@ -55,19 +55,20 @@ const Clear = styled.i<ClearProps>`
5555
${({ isFocused }) => isFocused && CLEAR_ICON_FOCUS_STYLE}
5656
`;
5757

58-
const MultiValue = memo<MultiValueProps>(({
58+
const MultiValue: FunctionComponent<MultiValueProps> = ({
5959
data,
6060
value,
6161
isFocused,
6262
renderOptionLabel,
6363
removeSelectedOption
6464
}) => {
65-
const labelNode = renderOptionLabel(data);
6665
const onClear = () => removeSelectedOption(value);
6766

6867
return (
6968
<MultiValueWrapper>
70-
<Label>{labelNode}</Label>
69+
<Label>
70+
{renderOptionLabel(data)}
71+
</Label>
7172
<Clear
7273
onClick={onClear}
7374
onTouchEnd={onClear}
@@ -79,8 +80,6 @@ const MultiValue = memo<MultiValueProps>(({
7980
</Clear>
8081
</MultiValueWrapper>
8182
);
82-
});
83-
84-
MultiValue.displayName = 'MultiValue';
83+
};
8584

8685
export default MultiValue;

src/components/Value/index.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import React, { Fragment, type ReactNode, type FunctionComponent } from 'react';
1+
import React, { memo, Fragment, type ReactNode } from 'react';
22
import MultiValue from './MultiValue';
33
import styled from 'styled-components';
44
import { isArrayWithLength } from '../../utils';
55
import type { MultiParams, SelectedOption, RenderLabelCallback } from '../../types';
66

77
type ValueProps = Readonly<{
88
isMulti?: boolean;
9-
inputValue: string;
9+
hasInput: boolean;
1010
placeholder: string;
1111
selectedOption: SelectedOption[];
1212
focusedMultiValue: string | number | null;
@@ -29,9 +29,9 @@ const Placeholder = styled(SingleValue)`
2929
color: ${({ theme }) => theme.color.placeholder};
3030
`;
3131

32-
const Value: FunctionComponent<ValueProps> = ({
32+
const Value = memo<ValueProps>(({
3333
isMulti,
34-
inputValue,
34+
hasInput,
3535
placeholder,
3636
selectedOption,
3737
focusedMultiValue,
@@ -40,7 +40,7 @@ const Value: FunctionComponent<ValueProps> = ({
4040
removeSelectedOption
4141
}) => {
4242
const noSelectedOpts = !isArrayWithLength(selectedOption);
43-
if (inputValue && (!isMulti || (isMulti && (noSelectedOpts || renderMultiOptions)))) {
43+
if (hasInput && (!isMulti || (isMulti && (noSelectedOpts || renderMultiOptions)))) {
4444
return null;
4545
}
4646

@@ -49,8 +49,8 @@ const Value: FunctionComponent<ValueProps> = ({
4949
}
5050

5151
if (!isMulti) {
52-
const labelNode = renderOptionLabel(selectedOption[0].data);
53-
return <SingleValue>{labelNode}</SingleValue>;
52+
const label = renderOptionLabel(selectedOption[0].data);
53+
return <SingleValue>{label}</SingleValue>;
5454
}
5555

5656
return (
@@ -69,6 +69,8 @@ const Value: FunctionComponent<ValueProps> = ({
6969
))}
7070
</Fragment>
7171
);
72-
};
72+
});
73+
74+
Value.displayName = 'Value';
7375

7476
export default Value;

src/constants/dom.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@ export const CONTROL_CONTAINER_CLS = 'rfs-control-container';
2222

2323
// data-testid attributes used for DOM element querying in unit test cases
2424
// ...this attribute gets rendered in development and test environments (removed in production)
25-
export const CLEAR_ICON_TESTID = process.env.NODE_ENV === 'test' ? CLEAR_ICON_CLS : undefined;
26-
export const CARET_ICON_TESTID = process.env.NODE_ENV === 'test' ? CARET_ICON_CLS : undefined;
27-
export const AUTOSIZE_INPUT_TESTID = process.env.NODE_ENV === 'test' ? AUTOSIZE_INPUT_CLS : undefined;
28-
export const MENU_CONTAINER_TESTID = process.env.NODE_ENV === 'test' ? MENU_CONTAINER_CLS : undefined;
29-
export const CLEAR_ICON_MV_TESTID = process.env.NODE_ENV === 'test' ? `${CLEAR_ICON_CLS}-mv` : undefined;
30-
export const SELECT_CONTAINER_TESTID = process.env.NODE_ENV === 'test' ? SELECT_CONTAINER_CLS : undefined;
31-
export const CONTROL_CONTAINER_TESTID = process.env.NODE_ENV === 'test' ? CONTROL_CONTAINER_CLS : undefined;
25+
const isTest = process.env.NODE_ENV === 'test';
26+
export const CLEAR_ICON_TESTID = isTest ? CLEAR_ICON_CLS : undefined;
27+
export const CARET_ICON_TESTID = isTest ? CARET_ICON_CLS : undefined;
28+
export const AUTOSIZE_INPUT_TESTID = isTest ? AUTOSIZE_INPUT_CLS : undefined;
29+
export const MENU_CONTAINER_TESTID = isTest ? MENU_CONTAINER_CLS : undefined;
30+
export const CLEAR_ICON_MV_TESTID = isTest ? `${CLEAR_ICON_CLS}-mv` : undefined;
31+
export const SELECT_CONTAINER_TESTID = isTest ? SELECT_CONTAINER_CLS : undefined;
32+
export const CONTROL_CONTAINER_TESTID = isTest ? CONTROL_CONTAINER_CLS : undefined;
3233

3334
/**
34-
* Static attributes for 'AutosizeInput' input element.
35+
* Static attributes for 'AutosizeInput' input element
3536
*/
3637
export const AUTOSIZE_INPUT_ATTRS: InputHTMLAttributes<HTMLInputElement> & TestableElement = {
3738
tabIndex: 0,

src/utils/common.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const isPlainObject = (val: unknown): boolean => val !== null && typeof v
1919
/**
2020
* Prevent default behavior and propagation of an event
2121
*/
22-
export const suppressEvent = (e: SyntheticEvent<Element>): void => {
22+
export const suppressEvent = (e: SyntheticEvent): void => {
2323
e.preventDefault();
2424
e.stopPropagation();
2525
};
@@ -95,7 +95,7 @@ export const mergeDeep = <T>(target: any, source: any): T => {
9595
? target[key]
9696
? mergeDeep(target[key], sourceProp)
9797
: sourceProp
98-
: sourceProp ?? '';
98+
: sourceProp;
9999
});
100100

101101
return output;

src/utils/device.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ let _isTouchDevice: boolean | undefined;
77
* Global, lazy evaluation.
88
*/
99
export const isTouchDevice = (): boolean => {
10-
if (isBoolean(_isTouchDevice)) return _isTouchDevice;
10+
if (isBoolean(_isTouchDevice)) {
11+
return _isTouchDevice;
12+
}
1113

12-
return (_isTouchDevice = (() => {
14+
return _isTouchDevice = (() => {
1315
try {
1416
document.createEvent('TouchEvent');
1517
return true;
1618
} catch (e) {
1719
return false;
1820
}
19-
})());
21+
})();
2022
};

0 commit comments

Comments
 (0)