Skip to content

Commit ee576b6

Browse files
committed
chore(React19): Enable React19
Fix lint errors
1 parent 79a8b98 commit ee576b6

File tree

165 files changed

+1514
-1178
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

165 files changed

+1514
-1178
lines changed

.eslintrc.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"plugin:react/recommended",
1010
"plugin:react-hooks/recommended",
1111
"plugin:@typescript-eslint/recommended",
12+
"plugin:react/jsx-runtime",
1213
"prettier"
1314
],
1415
"overrides": [
@@ -95,6 +96,7 @@
9596
"react-hooks/exhaustive-deps": "warn",
9697
"react/no-unescaped-entities": ["error", { "forbid": [">", "}"] }],
9798
"spaced-comment": "error",
98-
"use-isnan": "error"
99+
"use-isnan": "error",
100+
"react/react-in-jsx-scope": "off"
99101
}
100102
}

README.md

+34-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
This repo contains a set of opinionated react component groups used to standardize functionality and look and feel across products. The components are based on PatternFly with some additional functionality.
44

55
### Branches
6+
67
`main` - PatternFly 6 implementation
78

89
`v5` - PatternFly 5 implementation
@@ -14,22 +15,27 @@ This repo contains a set of opinionated react component groups used to standardi
1415
---
1516

1617
### Migration from [RedHatInsights/frontend-components](https://github.com/RedHatInsights/frontend-components) to [patternfly/react-component-groups](https://github.com/patternfly/react-component-groups)
18+
1719
Please see the [migration guide](./migration.md)
1820

1921
---
22+
2023
## Contribution guide
2124

2225
### Before adding a new component:
26+
2327
- make sure your use case is new/complex enough to be added to this extension
2428
- the component should bring a value value above and beyond existing PatternFly components
2529

2630
### To add a new component:
31+
2732
1. create a folder in `src/` matching its name (for example `src/MyComponent`)
2833
2. to the new folder add a new `.tsx` file named after the component (for example `src/MyComponent/MyComponent.tsx`)
2934
3. to the same folder include an `index.ts` which will export the component as a default and then all necessary interfaces
3035
4. if this file structure is not met, your component won't be exposed correctly
3136

3237
#### Example component:
38+
3339
```
3440
import * as React from 'react';
3541
import { Content } from '@patternfly/react-core';
@@ -49,7 +55,8 @@ const useStyles = createUseStyles({
4955
})
5056
5157
// do not use the named export of your component, just a default one
52-
const MyComponent: React.FunctionComponent<MyComponentProps> = () => {
58+
import { FunctionComponent } from 'react';
59+
const MyComponent: FunctionComponent<MyComponentProps> = () => {
5360
const classes = useStyles();
5461
5562
return (
@@ -60,42 +67,49 @@ const MyComponent: React.FunctionComponent<MyComponentProps> = () => {
6067
};
6168
6269
export default MyComponent;
63-
```
70+
```
6471

6572
#### Index file example:
73+
6674
```
6775
export { default } from './MyComponent';
6876
export * from './MyComponent';
69-
```
77+
```
7078

7179
#### Component directory structure example:
80+
7281
```
7382
src
7483
|- MyComponent
7584
|- index.ts
7685
|- MyComponent.tsx
77-
```
86+
```
7887

7988
### Component's API rules:
89+
8090
- prop names comply with PatternFly components naming standards (`variant`, `onClick`, `position`, etc.)
8191
- the API is maximally simplified and all props are provided with a description
8292
- it is built on top of existing PatternFly types without prop omitting
8393
- it is well documented using the PatternFly documentation (`/packages/module/patternfly-docs/content/extensions/component-groups/examples/MyComponent/MyComponent.md`) with examples of all possible use cases (`packages/module/patternfly-docs/content/extensions/component-groups/examples/MyComponent/MyComponent[...]Example.tsx`)
8494
- do not unnecessarily use external libraries in your component - rather, delegate the necessary logic to the component's user using the component's API
8595

8696
#### Component API definition example:
97+
8798
```
99+
100+
import { FunctionComponent } from 'react';
101+
88102
// when possible, extend available PatternFly types
89103
export interface MyComponentProps extends ButtonProps {
90104
customLabel: Boolean
91105
};
92106
93-
export const MyComponent: React.FunctionComponent<MyComponentProps> = ({ customLabel, ...props }) => ( ... );
107+
export const MyComponent: FunctionComponent<MyComponentProps> = ({ customLabel, ...props }) => ( ... );
94108
```
95109

96-
97110
#### Markdown file example:
98-
```
111+
112+
````
99113
---
100114
section: Component groups
101115
subsection: My component's category
@@ -113,36 +127,42 @@ MyComponent has been created to demo contributing to this repository.
113127
114128
```js file="./MyComponentExample.tsx"```
115129
116-
```
130+
````
117131

118132
#### Component usage file example: (`MyComponentExample.tsx`)
133+
119134
```
120-
import React from 'react';
135+
import { FunctionComponent } from 'react';
121136
122-
const MyComponentExample: React.FunctionComponent = () => (
137+
const MyComponentExample: FunctionComponent = () => (
123138
<MyComponent customLabel="My label">
124139
);
125140
126141
export default MyComponentExample;
127142
```
128143

129144
### Sub-components:
130-
When adding a component for which it is advantageous to divide it into several sub-components make sure:
145+
146+
When adding a component for which it is advantageous to divide it into several sub-components make sure:
147+
131148
- component and all its sub-components are located in separate files and directories straight under the `src/` folder
132149
- sub-components are exported and documented separately from their parent
133150
- parent component should provide a way to pass props to all its sub-components
134151

135152
The aim is to enable the user of our "complex" component to use either complete or take advantage of its sub-components and manage their composition independently.
136153

137154
### Testing:
155+
138156
When adding/making changes to a component, always make sure your code is tested:
139-
- use React Testing Library for unit testing
157+
158+
- use React Testing Library for unit testing
140159
- add unit tests to a `[ComponentName].test.tsx` file to your component's directory
141160
- make sure all the core functionality is covered using Cypress component or E2E tests
142161
- add component tests to `cypress/component/[ComponentName].cy.tsx` file and E2E tests to `cypress/e2e/[ComponentName].spec.cy.ts`
143162
- add `ouiaId` to the component props definition with a default value of the component name (for subcomponents, let's use `ComponentName-element-specification` naming convention e.g. `ouiaId="WarningModal-confirm-button"`)
144163

145164
### Styling:
165+
146166
- for styling always use JSS
147167
- new classNames should be named in camelCase starting with the name of a given component and following with more details clarifying its purpose/component's subsection to which the class is applied (`actionMenu`, `actionMenuDropdown`, `actionMenuDropdownToggle`, etc.)
148168
- do not use `pf-v6-u-XXX` classes, use CSS variables in a custom class instead (styles for the utility classes are not bundled with the standard patternfly.css - it would require the consumer to import also addons.css)
@@ -153,10 +173,12 @@ When adding/making changes to a component, always make sure your code is tested:
153173
- run `npm run build`
154174

155175
## Development
176+
156177
- run `npm install`
157178
- run `npm run start` to build and start the development server
158179

159180
## Testing and Linting
181+
160182
- run `npm run test` to run the unit tests
161183
- run `npm run cypress:run:cp` to run Cypress component tests
162184
- run `npm run cypress:run:e2e` to run Cypress E2E tests
@@ -165,4 +187,3 @@ When adding/making changes to a component, always make sure your code is tested:
165187
## A11y testing
166188

167189
- run `npm run build:docs` followed by `npm run serve:docs`, then run `npm run test:a11y` in a new terminal window to run our accessibility tests for the components. Once the accessibility tests have finished running you can run `npm run serve:a11y` to locally view the generated report.
168-

cypress/component/Ansible.cy.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import React from 'react';
21
import { Ansible } from '@patternfly/react-component-groups/dist/dynamic/Ansible';
32

43
describe('Ansible', () => {
54
it('renders supported Ansible', () => {
6-
cy.mount(<Ansible />)
5+
cy.mount(<Ansible />);
76
cy.get('i').should('have.class', 'ansibleSupported-0-2-2');
87
});
98
it('renders unsupported Ansible', () => {
10-
cy.mount(<Ansible isSupported={false}/>)
9+
cy.mount(<Ansible isSupported={false} />);
1110
cy.get('i').should('have.class', 'ansibleUnsupported-0-2-3');
1211
});
13-
});
12+
});

cypress/component/BulkSelect.cy.tsx

+29-27
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
1-
import React, { useState } from 'react';
1+
import { useState } from 'react';
22
import BulkSelect, { BulkSelectProps, BulkSelectValue } from '../../packages/module/dist/dynamic/BulkSelect';
33

44
interface DataItem {
5-
name: string
6-
};
5+
name: string;
6+
}
77

8-
const BulkSelectTestComponent = ({ canSelectAll, isDataPaginated }: Omit<BulkSelectProps, 'onSelect' | 'selectedCount' >) => {
8+
const BulkSelectTestComponent = ({
9+
canSelectAll,
10+
isDataPaginated
11+
}: Omit<BulkSelectProps, 'onSelect' | 'selectedCount'>) => {
912
const [ selected, setSelected ] = useState<DataItem[]>([]);
1013

1114
const allData = [ { name: '1' }, { name: '2' }, { name: '3' }, { name: '4' }, { name: '5' }, { name: '6' } ];
1215
const pageData = [ { name: '1' }, { name: '2' }, { name: '3' }, { name: '4' }, { name: '5' } ];
1316
const pageDataNames = pageData.map((item) => item.name);
14-
const pageSelected = pageDataNames.every(item => selected.find(selectedItem => selectedItem.name === item));
17+
const pageSelected = pageDataNames.every((item) => selected.find((selectedItem) => selectedItem.name === item));
1518

1619
const handleBulkSelect = (value: BulkSelectValue) => {
1720
if (value === BulkSelectValue.page) {
1821
const updatedSelection = [ ...selected ];
19-
pageData.forEach(item => !updatedSelection.some(selectedItem => selectedItem.name === item.name) && updatedSelection.push(item));
22+
pageData.forEach(
23+
(item) =>
24+
!updatedSelection.some((selectedItem) => selectedItem.name === item.name) && updatedSelection.push(item)
25+
);
2026
setSelected(updatedSelection);
2127
}
22-
value === BulkSelectValue.nonePage && setSelected(selected.filter(item => !pageDataNames.includes(item.name)))
28+
value === BulkSelectValue.nonePage && setSelected(selected.filter((item) => !pageDataNames.includes(item.name)));
2329
value === BulkSelectValue.none && setSelected([]);
2430
value === BulkSelectValue.all && setSelected(allData);
2531
};
@@ -32,71 +38,67 @@ const BulkSelectTestComponent = ({ canSelectAll, isDataPaginated }: Omit<BulkSel
3238
totalCount={allData.length}
3339
selectedCount={selected.length}
3440
pageSelected={pageSelected}
35-
pagePartiallySelected={pageDataNames.some(item => selected.find(selectedItem => selectedItem.name === item)) && !pageSelected}
41+
pagePartiallySelected={
42+
pageDataNames.some((item) => selected.find((selectedItem) => selectedItem.name === item)) && !pageSelected
43+
}
3644
onSelect={handleBulkSelect}
3745
/>
3846
);
3947
};
4048

4149
describe('BulkSelect', () => {
4250
it('renders the bulk select without all', () => {
43-
cy.mount(
44-
<BulkSelectTestComponent />
45-
);
51+
cy.mount(<BulkSelectTestComponent />);
4652
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').should('exist');
4753
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').click();
4854
cy.get('[data-ouia-component-id="BulkSelect-select-all"]').should('not.exist');
4955
cy.get('[data-ouia-component-id="BulkSelect-select-page"]').should('exist');
5056
cy.get('[data-ouia-component-id="BulkSelect-select-none"]').should('exist');
51-
57+
5258
cy.contains('0 selected').should('not.exist');
5359
});
5460

5561
it('renders the bulk select with all and without page', () => {
56-
cy.mount(
57-
<BulkSelectTestComponent canSelectAll isDataPaginated={false} />
58-
);
62+
cy.mount(<BulkSelectTestComponent canSelectAll isDataPaginated={false} />);
5963
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').should('exist');
6064
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').click();
6165
cy.get('[data-ouia-component-id="BulkSelect-select-all"]').should('exist');
6266
cy.get('[data-ouia-component-id="BulkSelect-select-page"]').should('not.exist');
6367
cy.get('[data-ouia-component-id="BulkSelect-select-none"]').should('exist');
64-
68+
6569
cy.contains('0 selected').should('not.exist');
6670
});
67-
71+
6872
it('renders the bulk select with data', () => {
69-
cy.mount(
70-
<BulkSelectTestComponent canSelectAll />
71-
);
72-
73+
cy.mount(<BulkSelectTestComponent canSelectAll />);
74+
7375
// Initial state
7476
cy.get('input[type="checkbox"]').each(($checkbox) => {
7577
cy.wrap($checkbox).should('not.be.checked');
7678
});
77-
79+
7880
// Checkbox select
7981
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').first().click();
8082
cy.get('input[type="checkbox"]').should('be.checked');
8183
cy.contains('5 selected').should('exist');
82-
84+
8385
// Select none
8486
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').first().click({ force: true });
8587
cy.get('[data-ouia-component-id="BulkSelect-select-none"]').first().click();
8688
cy.get('input[type="checkbox"]').should('not.be.checked');
87-
89+
8890
// Select all
8991
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').first().click({ force: true });
9092
cy.get('[data-ouia-component-id="BulkSelect-select-all"]').first().click();
9193
cy.contains('6 selected').should('exist');
92-
94+
9395
// Checkbox deselect
9496
cy.get('[data-ouia-component-id="BulkSelect-checkbox"]').first().click({ force: true });
9597
cy.contains('1 selected').should('exist');
96-
98+
9799
// Select page
98100
cy.get('[data-ouia-component-id="BulkSelect-toggle"]').first().click({ force: true });
99101
cy.get('[data-ouia-component-id="BulkSelect-select-page"]').first().click();
100102
cy.contains('6 selected').should('exist');
101103
});
102-
});
104+
});

cypress/component/CloseButton.cy.tsx

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import React from 'react';
21
import CloseButton from '../../packages/module/dist/dynamic/CloseButton';
32

43
describe('CloseButton', () => {
54
/* eslint-disable no-console */
65
it('renders the Close button', () => {
7-
cy.mount(<CloseButton dataTestID="close-button-example" onClick={()=>{console.log('Close button clicked')}} style={{ float: 'none' }}/>)
6+
cy.mount(
7+
<CloseButton
8+
dataTestID="close-button-example"
9+
onClick={() => {
10+
console.log('Close button clicked');
11+
}}
12+
style={{ float: 'none' }}
13+
/>
14+
);
815
cy.get('[data-test-id="close-button-example"]').should('exist');
916
});
1017
it('should call callback on click', () => {
1118
const onClickSpy = cy.spy().as('onClickSpy');
12-
cy.mount(<CloseButton dataTestID="close-button-example" onClick={onClickSpy}/>);
19+
cy.mount(<CloseButton dataTestID="close-button-example" onClick={onClickSpy} />);
1320
cy.get('[data-test-id="close-button-example"]').click();
1421
cy.get('@onClickSpy').should('have.been.called');
1522
});
16-
})
23+
});
+12-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
import React from 'react';
21
import ErrorBoundary from '../../packages/module/dist/dynamic/ErrorBoundary';
32

43
describe('ErrorBoundary', () => {
54
it('renders the ErrorBoundary ', () => {
6-
cy.mount(<ErrorBoundary headerTitle="My app header" errorTitle="Something wrong happened"><div data-ouia-component-id="test">Test</div></ErrorBoundary>)
5+
cy.mount(
6+
<ErrorBoundary headerTitle="My app header" errorTitle="Something wrong happened">
7+
<div data-ouia-component-id="test">Test</div>
8+
</ErrorBoundary>
9+
);
710
cy.get('[data-ouia-component-id="test"]').should('have.text', 'Test');
811
});
912

1013
it('should expand the details section', () => {
1114
const Surprise = () => {
1215
throw new Error('but a welcome one');
1316
};
14-
cy.mount(<ErrorBoundary headerTitle="My app header" errorTitle="Something wrong happened">
15-
<Surprise />
16-
</ErrorBoundary>)
17+
cy.mount(
18+
<ErrorBoundary headerTitle="My app header" errorTitle="Something wrong happened">
19+
<Surprise />
20+
</ErrorBoundary>
21+
);
1722

1823
cy.get('[data-ouia-component-id="ErrorBoundary-toggle"').click();
1924
cy.get('[class="pf-v5-c-expandable-section__content"]').should('contain.text', 'Error: but a welcome one');
20-
})
21-
})
25+
});
26+
});

0 commit comments

Comments
 (0)