Skip to content

Commit 08111e7

Browse files
authored
chore(ui-react): lint primitives (P-S) (#3265)
1 parent 4b051db commit 08111e7

File tree

27 files changed

+229
-105
lines changed

27 files changed

+229
-105
lines changed

Diff for: .changeset/witty-carrots-retire.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@aws-amplify/ui-react-core": patch
3+
"@aws-amplify/ui-react": patch
4+
---
5+
6+
chore(ui-react): lint primitives (P-S)

Diff for: docs/src/data/test/__snapshots__/props-table.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -8792,7 +8792,7 @@ exports[`Props Table 1`] = `
87928792
\\"onStepChange\\": {
87938793
\\"name\\": \\"onStepChange\\",
87948794
\\"type\\": \\"(value: number) => void\\",
8795-
\\"description\\": \\"TODO:\\\\nExtends StepperField props from Omit<TextFieldProps, 'onChange'>, after removing [key: string]: any from the base type\\\\nand rename onStepChange to onChange\\",
8795+
\\"description\\": \\"Event handler called with the current step value when it is updated\\",
87968796
\\"category\\": \\"StepperFieldProps\\",
87978797
\\"isOptional\\": true
87988798
},

Diff for: packages/react-core/src/__tests__/__snapshots__/index.spec.ts.snap

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Array [
77
"handleMessageAction",
88
"isAuthenticatorComponentRouteKey",
99
"resolveAuthenticatorComponents",
10+
"templateJoin",
1011
"useAuthenticator",
1112
"useAuthenticatorInitMachine",
1213
"useAuthenticatorRoute",

Diff for: packages/react-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ export {
4747

4848
// components/hooks/utils
4949
export { useHasValueUpdated, usePreviousValue } from './hooks';
50+
export { templateJoin } from './utils';

Diff for: packages/react-core/src/utils/__tests__/index.spec.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { areEmptyArrays, areEmptyObjects } from '..';
1+
import { areEmptyArrays, areEmptyObjects, templateJoin } from '..';
22

33
const validArrays = [[], [], [], []];
44
const invalidArrays = [[7]];
@@ -27,3 +27,10 @@ describe('areEmptyObjects', () => {
2727
expect(output).toBe(expected);
2828
});
2929
});
30+
31+
describe('templateJoin', () => {
32+
it('returns the expected value', () => {
33+
const output = templateJoin(['one', 'two'], (value) => `^${value}^`);
34+
expect(output).toBe('^one^^two^');
35+
});
36+
});

Diff for: packages/react-core/src/utils/index.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import isEmpty from 'lodash/isEmpty';
22
import isObject from 'lodash/isObject';
3+
import isString from 'lodash/isString';
34

45
function isEmptyArray<T>(value: T): boolean {
56
return Array.isArray(value) && isEmpty(value);
@@ -16,3 +17,13 @@ function isEmptyObject<T>(value: T): boolean {
1617
export function areEmptyObjects<T>(...values: T[]): boolean {
1718
return values.every(isEmptyObject);
1819
}
20+
21+
export function templateJoin(
22+
values: string[],
23+
template: (value: string) => string
24+
): string {
25+
return values.reduce(
26+
(acc, curr) => `${acc}${isString(curr) ? template(curr) : ''}`,
27+
''
28+
);
29+
}

Diff for: packages/react/.eslintrc.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const { templateJoin } = require('@aws-amplify/ui-react-core');
2+
13
// TODO remove these once the full set of rules can be turned on for the repo
24
// extensions that should be ran against the entire repo
35
const sharedExtensions = [
@@ -7,6 +9,38 @@ const sharedExtensions = [
79
'prettier',
810
];
911

12+
const primitivePatterns = templateJoin(
13+
[
14+
'shared',
15+
'utils',
16+
'A',
17+
'B',
18+
'C',
19+
'D',
20+
'E',
21+
'F',
22+
'G',
23+
'H',
24+
'I',
25+
'J',
26+
'K',
27+
'L',
28+
'M',
29+
'P',
30+
'Q',
31+
'R',
32+
'S',
33+
// 'T',
34+
// 'U',
35+
// 'V',
36+
// 'W',
37+
// 'X',
38+
// 'Y',
39+
// 'Z'
40+
],
41+
(value) => `|${value}*`
42+
);
43+
1044
// rules that should be ran against the entire repo
1145
const sharedRules = {
1246
// react hook rules
@@ -69,7 +103,7 @@ module.exports = {
69103
'src/components/**/*',
70104
'src/helpers/**/*',
71105
'src/hooks/**/*',
72-
'src/primitives/+(shared|utils|A*|B*|C*|D*|E*|F*|G*|H*|I*|J*|K*|L*|M*)/**/*',
106+
`src/primitives/+(${primitivePatterns})/**/*`,
73107
'src/studio',
74108
// 'src/primitives/**/*',
75109
],

Diff for: packages/react/src/primitives/Pagination/usePagination.ts

+15-13
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,49 @@ import { UsePaginationProps, UsePaginationResult } from '../types/pagination';
55
export const usePagination = (
66
props: UsePaginationProps
77
): UsePaginationResult => {
8-
let {
8+
const {
99
currentPage: initialPage = 1,
1010
totalPages,
1111
hasMorePages = false,
1212
siblingCount = 1,
1313
} = props;
1414

1515
// The current page should not be less than 1
16-
initialPage = Math.max(initialPage, 1);
17-
// The sibling count should not be less than 1
18-
siblingCount = Math.max(siblingCount, 1);
16+
const sanitizedInitialPage = Math.max(initialPage, 1);
17+
1918
// The total pages should be always greater than current page
20-
totalPages = Math.max(initialPage, totalPages);
21-
const [currentPage, setCurrentPage] = React.useState(initialPage);
19+
const sanitizedTotalPages = Math.max(sanitizedInitialPage, totalPages);
20+
const [currentPage, setCurrentPage] = React.useState(sanitizedInitialPage);
2221

2322
// Reset current page if initialPage or totalPages changes
24-
React.useEffect(() => setCurrentPage(initialPage), [initialPage, totalPages]);
23+
React.useEffect(() => {
24+
setCurrentPage(sanitizedInitialPage);
25+
}, [sanitizedInitialPage, sanitizedTotalPages]);
2526

2627
const onNext = React.useCallback(() => {
27-
if (currentPage < totalPages) {
28+
if (currentPage < sanitizedTotalPages) {
2829
setCurrentPage(currentPage + 1);
2930
}
30-
}, [currentPage, totalPages]);
31+
}, [currentPage, sanitizedTotalPages]);
3132

3233
const onPrevious = React.useCallback(() => {
3334
if (currentPage > 1) {
3435
setCurrentPage(currentPage - 1);
3536
}
3637
}, [currentPage]);
3738

38-
const onChange = React.useCallback((newPage: number, prevPage: number) => {
39+
const onChange = React.useCallback((newPage: number, _: number) => {
3940
setCurrentPage(newPage);
4041
}, []);
4142

4243
return {
4344
currentPage,
44-
totalPages,
4545
hasMorePages,
46-
siblingCount,
46+
onChange,
4747
onNext,
4848
onPrevious,
49-
onChange,
49+
// The sibling count should not be less than 1
50+
siblingCount: Math.max(siblingCount, 1),
51+
totalPages: sanitizedTotalPages,
5052
};
5153
};

Diff for: packages/react/src/primitives/Pagination/usePaginationItems.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const usePaginationItems = ({
4040
onNext,
4141
onPrevious,
4242
onChange,
43-
}: UsePaginationItemsProps) => {
43+
}: UsePaginationItemsProps): JSX.Element[] => {
4444
const previousItem = (
4545
<PaginationItem
4646
type="previous"

Diff for: packages/react/src/primitives/PhoneNumberField/CountryCodeSelect.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const DialCodeSelectPrimitive: Primitive<DialCodeSelectProps, 'select'> = (
3535
ComponentClassNames.DialCodeSelect,
3636
className
3737
)}
38-
labelHidden={true}
38+
labelHidden
3939
ref={ref}
4040
{...props}
4141
>

Diff for: packages/react/src/primitives/PhoneNumberField/PhoneNumberField.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const PhoneNumberFieldPrimitive: Primitive<PhoneNumberFieldProps, 'input'> = (
2727
onDialCodeChange,
2828
onInput,
2929
size,
30-
type,
3130
variation,
3231
...rest
3332
},

Diff for: packages/react/src/primitives/PhoneNumberField/__tests__/PhoneNumberField.test.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import * as React from 'react';
22
import { render, screen } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44

5-
import { PhoneNumberField } from '../PhoneNumberField';
65
import { Flex } from '../../Flex';
76
import { Button } from '../../Button';
87
import { ComponentClassNames } from '../../shared/constants';
98

9+
import { PhoneNumberField } from '../PhoneNumberField';
10+
1011
const originalLog = console.log;
1112
console.log = jest.fn();
1213

@@ -15,7 +16,7 @@ describe('PhoneNumberField primitive', () => {
1516
defaultCountryCode = '+1',
1617
label = 'Phone Number',
1718
...rest
18-
}: Partial<typeof PhoneNumberField['defaultProps']>) => {
19+
}) => {
1920
render(
2021
<PhoneNumberField
2122
defaultCountryCode={defaultCountryCode}
@@ -195,7 +196,7 @@ describe('PhoneNumberField primitive', () => {
195196
label = 'Phone Number',
196197
dialCodeLabel = 'dial code',
197198
...rest
198-
}: Partial<typeof PhoneNumberField['defaultProps']>) => {
199+
}) => {
199200
render(
200201
<PhoneNumberField
201202
defaultDialCode={defaultDialCode}

Diff for: packages/react/src/primitives/Radio/Radio.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const RadioPrimitive: Primitive<RadioProps, 'input'> = (
7171
)}
7272
data-disabled={shouldBeDisabled}
7373
data-label-position={labelPosition}
74+
height={height}
7475
width={width}
7576
bottom={bottom}
7677
top={top}

Diff for: packages/react/src/primitives/RadioGroupField/context.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ const defaultValue: RadioGroupContextType = { name: 'default' };
1919
export const RadioGroupContext =
2020
React.createContext<RadioGroupContextType>(defaultValue);
2121

22-
export const useRadioGroupContext = () => {
22+
export const useRadioGroupContext = (): RadioGroupContextType => {
2323
return useContext(RadioGroupContext);
2424
};

Diff for: packages/react/src/primitives/Rating/Rating.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const RatingPrimitive: Primitive<RatingProps, typeof Flex> = (
2828
},
2929
ref
3030
) => {
31-
const items = new Array(Math.ceil(maxValue)).fill(1).map((val, index) => {
31+
const items = new Array(Math.ceil(maxValue)).fill(1).map((_, index) => {
3232
const currentIconIndex = index + 1;
3333
if (isIconFilled(currentIconIndex, value))
3434
return (

Diff for: packages/react/src/primitives/Rating/__test__/Rating.test.tsx

+29-25
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import * as React from 'react';
2+
import { render, screen } from '@testing-library/react';
23

34
import { ComponentClassNames } from '../../shared';
45
import { Rating } from '../Rating';
5-
import { render, screen } from '@testing-library/react';
66

7-
describe('Rating: ', () => {
8-
let customIcon;
9-
beforeEach(() => {
10-
customIcon = (className) => {
11-
return <svg className={className}></svg>;
12-
};
13-
});
7+
const CustomIcon = ({ className }: { className: string }) => (
8+
<svg className={className}></svg>
9+
);
1410

11+
describe('Rating', () => {
1512
it('should render a classname for Rating', async () => {
1613
render(<Rating testId="testId" className="my-rating-component" />);
1714

@@ -53,27 +50,27 @@ describe('Rating: ', () => {
5350
expect(rating.dataset['size']).toBe('small');
5451
});
5552

56-
it('should render the empty icon color', async () => {
53+
it('should render the empty icon color', () => {
5754
const { container } = render(<Rating testId="testId" emptyColor="red" />);
5855

5956
const emptyIcon = container.getElementsByClassName(
6057
'amplify-rating-icon-empty'
6158
)[0];
62-
expect(emptyIcon['style'].color).toBe('red');
59+
expect((emptyIcon['style'] as React.CSSProperties).color).toBe('red');
6360
});
6461

65-
it('should render the filled icon color', async () => {
62+
it('should render the filled icon color', () => {
6663
const { container } = render(
6764
<Rating testId="testId" value={2} fillColor="blue" />
6865
);
6966

7067
const filledIcon = container.getElementsByClassName(
7168
'amplify-rating-icon-filled'
7269
)[0];
73-
expect(filledIcon['style'].color).toBe('blue');
70+
expect((filledIcon['style'] as React.CSSProperties).color).toBe('blue');
7471
});
7572

76-
it('should render filled icons when the value prop is used', async () => {
73+
it('should render filled icons when the value prop is used', () => {
7774
const { container } = render(<Rating testId="testId" value={2} />);
7875

7976
const filledIcons = container.getElementsByClassName(
@@ -82,7 +79,7 @@ describe('Rating: ', () => {
8279
expect(filledIcons.length).toBe(2);
8380
});
8481

85-
it('should render 2 filled and 3 empty icons', async () => {
82+
it('should render 2 filled and 3 empty icons', () => {
8683
const { container } = render(<Rating value={2} />);
8784
const filledIcons = container.getElementsByClassName(
8885
'amplify-rating-icon-filled'
@@ -94,33 +91,40 @@ describe('Rating: ', () => {
9491
expect(emptyIcons.length).toBe(3);
9592
});
9693

97-
it('should render 7 icons when the maxValue is set to 7', async () => {
94+
it('should render 7 icons when the maxValue is set to 7', () => {
9895
const { container } = render(<Rating maxValue={7} />);
9996
const emptyIcons = container.getElementsByClassName(
10097
'amplify-rating-icon-empty'
10198
);
10299
expect(emptyIcons.length).toBe(7);
103100
});
104101

105-
it('should render the passed in icon component', async () => {
106-
const icon = customIcon('my-custom-component');
107-
const { container } = render(<Rating icon={icon} />);
102+
it('should render the passed in icon component', () => {
103+
const { container } = render(
104+
<Rating icon={<CustomIcon className="my-custom-component" />} />
105+
);
108106
const emptyIcons = container.getElementsByClassName('my-custom-component');
109107
expect(emptyIcons.length).toBe(5);
110108
});
111109

112-
it('should render the passed in empty icon', async () => {
113-
const emptyIcon = customIcon('my-custom-empty-icon');
114-
const { container } = render(<Rating emptyIcon={emptyIcon} value={3} />);
110+
it('should render the passed in empty icon', () => {
111+
const { container } = render(
112+
<Rating
113+
emptyIcon={<CustomIcon className="my-custom-empty-icon" />}
114+
value={3}
115+
/>
116+
);
115117
const emptyIcons = container.getElementsByClassName('my-custom-empty-icon');
116118
expect(emptyIcons.length).toBe(2);
117119
});
118120

119-
it('should render both the passed in icon and empty icons', async () => {
120-
const filledIcon = customIcon('my-custom-filled-icon');
121-
const emptyIcon = customIcon('my-custom-empty-icon');
121+
it('should render both the passed in icon and empty icons', () => {
122122
const { container } = render(
123-
<Rating icon={filledIcon} emptyIcon={emptyIcon} value={3} />
123+
<Rating
124+
icon={<CustomIcon className="my-custom-filled-icon" />}
125+
emptyIcon={<CustomIcon className="my-custom-empty-icon" />}
126+
value={3}
127+
/>
124128
);
125129
const emptyIcons = container.getElementsByClassName('my-custom-empty-icon');
126130
const filledIcons = container.getElementsByClassName(

Diff for: packages/react/src/primitives/SearchField/SearchField.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const SearchFieldPrimitive: Primitive<SearchFieldProps, 'input'> = (
7272
innerEndComponent={
7373
<FieldClearButton
7474
ariaLabel={clearButtonLabel}
75-
excludeFromTabOrder={true}
75+
excludeFromTabOrder
7676
isVisible={strHasLength(composedValue)}
7777
onClick={onClearHandler}
7878
size={size}

0 commit comments

Comments
 (0)