Skip to content

Commit 47b5d89

Browse files
author
Matthew Holloway
committed
React 18 useId
1 parent c9aded3 commit 47b5d89

14 files changed

+106
-95
lines changed

.eslintrc.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
module.exports = {
2-
parser: "@typescript-eslint/parser",
2+
parser: '@typescript-eslint/parser',
33
parserOptions: {
44
jsx: true,
55
},
6-
plugins: ["react", "@typescript-eslint", "jsx-a11y"],
6+
plugins: ['react', '@typescript-eslint', 'jsx-a11y'],
77
extends: [
8-
"eslint:recommended",
9-
"plugin:react/recommended",
10-
"plugin:@typescript-eslint/recommended",
11-
"plugin:jsx-a11y/recommended",
8+
'eslint:recommended',
9+
'plugin:react/recommended',
10+
'plugin:@typescript-eslint/recommended',
11+
'plugin:jsx-a11y/recommended',
1212
],
1313
settings: {
1414
react: {
15-
pragma: "React",
16-
version: "detect",
15+
pragma: 'React',
16+
version: 'detect',
1717
},
1818
},
1919
rules: {
20-
"@typescript-eslint/no-non-null-assertion": "off",
21-
"react/prop-types": "off",
20+
'@typescript-eslint/ban-ts-comment': 'off',
21+
'@typescript-eslint/no-non-null-assertion': 'off',
22+
'react/prop-types': 'off',
2223
},
2324
};

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-accessible-accordion",
3-
"version": "4.0.0",
3+
"version": "5.0.0",
44
"description": "Accessible Accordion component for React",
55
"main": "dist/umd/index.js",
66
"module": "dist/es/index.js",
@@ -133,8 +133,8 @@
133133
"webpack-dev-server": "^3.2.1"
134134
},
135135
"peerDependencies": {
136-
"react": "^16.3.2 || ^17.0.0",
137-
"react-dom": "^16.3.3 || ^17.0.0"
136+
"react": "^16.3.2 || ^17.0.0 || ^18.0.0",
137+
"react-dom": "^16.3.3 || ^17.0.0 || ^18.0.0"
138138
},
139139
"husky": {
140140
"hooks": {

src/components/Accordion.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import * as React from 'react';
22
import { DivAttributes } from '../helpers/types';
33
import { Provider } from './AccordionContext';
4-
import { UUID } from './ItemContext';
4+
import { ID } from './ItemContext';
55

66
type AccordionProps = Pick<
77
DivAttributes,
88
Exclude<keyof DivAttributes, 'onChange'>
99
> & {
1010
className?: string;
11-
preExpanded?: UUID[];
11+
preExpanded?: ID[];
1212
allowMultipleExpanded?: boolean;
1313
allowZeroExpanded?: boolean;
14-
onChange?(args: UUID[]): void;
14+
onChange?(args: ID[]): void;
1515
};
1616

1717
const Accordion = ({

src/components/AccordionContext.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,31 @@ import AccordionStore, {
66
InjectedHeadingAttributes,
77
InjectedPanelAttributes,
88
} from '../helpers/AccordionStore';
9-
import { UUID } from './ItemContext';
9+
import { ID } from './ItemContext';
1010

1111
export interface ProviderProps {
12-
preExpanded?: UUID[];
12+
preExpanded?: ID[];
1313
allowMultipleExpanded?: boolean;
1414
allowZeroExpanded?: boolean;
1515
children?: React.ReactNode;
16-
onChange?(args: UUID[]): void;
16+
onChange?(args: ID[]): void;
1717
}
1818

1919
type ProviderState = AccordionStore;
2020

2121
export interface AccordionContext {
2222
allowMultipleExpanded: boolean;
2323
allowZeroExpanded: boolean;
24-
toggleExpanded(uuid: UUID): void;
25-
isItemDisabled(uuid: UUID): boolean;
26-
isItemExpanded(uuid: UUID): boolean;
24+
toggleExpanded(uuid: ID): void;
25+
isItemDisabled(uuid: ID): boolean;
26+
isItemExpanded(uuid: ID): boolean;
2727
getPanelAttributes(
28-
uuid: UUID,
28+
uuid: ID,
2929
dangerouslySetExpanded?: boolean,
3030
): InjectedPanelAttributes;
31-
getHeadingAttributes(uuid: UUID): InjectedHeadingAttributes;
31+
getHeadingAttributes(uuid: ID): InjectedHeadingAttributes;
3232
getButtonAttributes(
33-
uuid: UUID,
33+
uuid: ID,
3434
dangerouslySetExpanded?: boolean,
3535
): InjectedButtonAttributes;
3636
}
@@ -52,7 +52,7 @@ export class Provider extends React.PureComponent<
5252
allowZeroExpanded: this.props.allowZeroExpanded,
5353
});
5454

55-
toggleExpanded = (key: UUID): void => {
55+
toggleExpanded = (key: ID): void => {
5656
this.setState(
5757
(state: Readonly<ProviderState>) => state.toggleExpanded(key),
5858
() => {
@@ -63,16 +63,16 @@ export class Provider extends React.PureComponent<
6363
);
6464
};
6565

66-
isItemDisabled = (key: UUID): boolean => {
66+
isItemDisabled = (key: ID): boolean => {
6767
return this.state.isItemDisabled(key);
6868
};
6969

70-
isItemExpanded = (key: UUID): boolean => {
70+
isItemExpanded = (key: ID): boolean => {
7171
return this.state.isItemExpanded(key);
7272
};
7373

7474
getPanelAttributes = (
75-
key: UUID,
75+
key: ID,
7676
dangerouslySetExpanded?: boolean,
7777
): InjectedPanelAttributes => {
7878
return this.state.getPanelAttributes(key, dangerouslySetExpanded);
@@ -84,7 +84,7 @@ export class Provider extends React.PureComponent<
8484
};
8585

8686
getButtonAttributes = (
87-
key: UUID,
87+
key: ID,
8888
dangerouslySetExpanded?: boolean,
8989
): InjectedButtonAttributes => {
9090
return this.state.getButtonAttributes(key, dangerouslySetExpanded);

src/components/AccordionItem.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import * as React from 'react';
22
import { useState } from 'react';
33
import DisplayName from '../helpers/DisplayName';
44
import { DivAttributes } from '../helpers/types';
5-
import { assertValidHtmlId, nextUuid } from '../helpers/uuid';
5+
import { assertValidHtmlId, useNextId } from '../helpers/id';
66
import {
77
Consumer as ItemConsumer,
88
ItemContext,
99
Provider as ItemProvider,
10-
UUID,
10+
ID,
1111
} from './ItemContext';
1212

1313
type Props = DivAttributes & {
14-
uuid?: UUID;
14+
uuid?: ID;
1515
activeClassName?: string;
1616
dangerouslySetExpanded?: boolean;
1717
};
@@ -23,7 +23,7 @@ const AccordionItem = ({
2323
activeClassName,
2424
...rest
2525
}: Props): JSX.Element => {
26-
const [instanceUuid] = useState<UUID>(nextUuid());
26+
const [instanceUuid] = useState<ID>(useNextId());
2727
const uuid = customUuid ?? instanceUuid;
2828

2929
const renderChildren = (itemContext: ItemContext): JSX.Element => {

src/components/AccordionItemButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '../helpers/focus';
99
import keycodes from '../helpers/keycodes';
1010
import { DivAttributes } from '../helpers/types';
11-
import { assertValidHtmlId } from '../helpers/uuid';
11+
import { assertValidHtmlId } from '../helpers/id';
1212

1313
import { Consumer as ItemConsumer, ItemContext } from './ItemContext';
1414

src/components/AccordionItemHeading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from 'react';
22
import { InjectedHeadingAttributes } from '../helpers/AccordionStore';
33
import DisplayName from '../helpers/DisplayName';
44
import { DivAttributes } from '../helpers/types';
5-
import { assertValidHtmlId } from '../helpers/uuid';
5+
import { assertValidHtmlId } from '../helpers/id';
66

77
import { Consumer as ItemConsumer, ItemContext } from './ItemContext';
88

src/components/AccordionItemPanel.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { DivAttributes } from '../helpers/types';
3-
import { assertValidHtmlId } from '../helpers/uuid';
3+
import { assertValidHtmlId } from '../helpers/id';
44
import { Consumer as ItemConsumer, ItemContext } from './ItemContext';
55

66
type Props = DivAttributes & { region?: boolean; className?: string };
@@ -18,7 +18,9 @@ const AccordionItemPanel = ({
1818

1919
const attrs = {
2020
...panelAttributes,
21-
'aria-labelledby': region ? panelAttributes['aria-labelledby'] : undefined,
21+
'aria-labelledby': region
22+
? panelAttributes['aria-labelledby']
23+
: undefined,
2224
};
2325

2426
return (
@@ -27,7 +29,7 @@ const AccordionItemPanel = ({
2729
className={className}
2830
{...rest}
2931
{...attrs}
30-
role={region ? 'region': undefined}
32+
role={region ? 'region' : undefined}
3133
/>
3234
);
3335
};

src/components/ItemContext.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import {
1111
Consumer as AccordionContextConsumer,
1212
} from './AccordionContext';
1313

14-
export type UUID = string | number;
14+
export type ID = string | number;
1515

1616
type ProviderProps = {
1717
children?: React.ReactNode;
18-
uuid: UUID;
18+
uuid: ID;
1919
accordionContext: AccordionContext;
2020
dangerouslySetExpanded?: boolean;
2121
};
@@ -26,7 +26,7 @@ export type ProviderWrapperProps = Pick<
2626
>;
2727

2828
export type ItemContext = {
29-
uuid: UUID;
29+
uuid: ID;
3030
expanded: boolean;
3131
disabled: boolean;
3232
panelAttributes: InjectedPanelAttributes;
@@ -85,7 +85,7 @@ const Provider = ({
8585
);
8686
};
8787

88-
const ProviderWrapper: React.SFC<ProviderWrapperProps> = (
88+
const ProviderWrapper: React.FunctionComponent<ProviderWrapperProps> = (
8989
props: ProviderWrapperProps,
9090
): JSX.Element => (
9191
<AccordionContextConsumer>

src/helpers/AccordionStore.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { UUID } from '../components/ItemContext';
1+
import { ID } from '../components/ItemContext';
22

33
export interface InjectedPanelAttributes {
44
role: string | undefined;
@@ -22,7 +22,7 @@ export interface InjectedButtonAttributes {
2222
}
2323

2424
export default class AccordionStore {
25-
public readonly expanded: UUID[];
25+
public readonly expanded: ID[];
2626
public readonly allowMultipleExpanded: boolean;
2727
public readonly allowZeroExpanded: boolean;
2828

@@ -31,7 +31,7 @@ export default class AccordionStore {
3131
allowMultipleExpanded = false,
3232
allowZeroExpanded = false,
3333
}: {
34-
expanded?: UUID[];
34+
expanded?: ID[];
3535
allowMultipleExpanded?: boolean;
3636
allowZeroExpanded?: boolean;
3737
}) {
@@ -40,7 +40,7 @@ export default class AccordionStore {
4040
this.allowZeroExpanded = allowZeroExpanded;
4141
}
4242

43-
public readonly toggleExpanded = (uuid: UUID): AccordionStore => {
43+
public readonly toggleExpanded = (uuid: ID): AccordionStore => {
4444
if (this.isItemDisabled(uuid)) {
4545
return this;
4646
}
@@ -55,7 +55,7 @@ export default class AccordionStore {
5555
} else {
5656
return this.augment({
5757
expanded: this.expanded.filter(
58-
(expandedUuid: UUID): boolean => expandedUuid !== uuid,
58+
(expandedUuid: ID): boolean => expandedUuid !== uuid,
5959
),
6060
});
6161
}
@@ -68,7 +68,7 @@ export default class AccordionStore {
6868
* and if the accordion does not permit the panel to be collapsed, the
6969
* header button element has aria-disabled set to true.”
7070
*/
71-
public readonly isItemDisabled = (uuid: UUID): boolean => {
71+
public readonly isItemDisabled = (uuid: ID): boolean => {
7272
const isExpanded = this.isItemExpanded(uuid);
7373
const isOnlyOneExpanded = this.expanded.length === 1;
7474

@@ -77,12 +77,12 @@ export default class AccordionStore {
7777
);
7878
};
7979

80-
public readonly isItemExpanded = (uuid: UUID): boolean => {
80+
public readonly isItemExpanded = (uuid: ID): boolean => {
8181
return this.expanded.indexOf(uuid) !== -1;
8282
};
8383

8484
public readonly getPanelAttributes = (
85-
uuid: UUID,
85+
uuid: ID,
8686
dangerouslySetExpanded?: boolean,
8787
): InjectedPanelAttributes => {
8888
const expanded = dangerouslySetExpanded ?? this.isItemExpanded(uuid);
@@ -104,7 +104,7 @@ export default class AccordionStore {
104104
};
105105

106106
public readonly getButtonAttributes = (
107-
uuid: UUID,
107+
uuid: ID,
108108
dangerouslySetExpanded?: boolean,
109109
): InjectedButtonAttributes => {
110110
const expanded = dangerouslySetExpanded ?? this.isItemExpanded(uuid);
@@ -120,14 +120,13 @@ export default class AccordionStore {
120120
};
121121
};
122122

123-
private readonly getPanelId = (uuid: UUID): string =>
124-
`accordion__panel-${uuid}`;
123+
private readonly getPanelId = (id: ID): string => `accordion__panel-${id}`;
125124

126-
private readonly getButtonId = (uuid: UUID): string =>
127-
`accordion__heading-${uuid}`;
125+
private readonly getButtonId = (id: ID): string =>
126+
`accordion__heading-${id}`;
128127

129128
private readonly augment = (args: {
130-
expanded?: UUID[];
129+
expanded?: ID[];
131130
allowMultipleExpanded?: boolean;
132131
allowZeroExpanded?: boolean;
133132
}): AccordionStore => {

src/helpers/id.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
assertValidHtmlId,
3+
useGlobalCounterId,
4+
resetGlobalCounterId,
5+
} from './id';
6+
7+
describe('UUID helper', () => {
8+
describe('useGlobalCounterId', () => {
9+
it('generates incremental ids', () => {
10+
expect(useGlobalCounterId()).toBe('raa-0');
11+
expect(useGlobalCounterId()).toBe('raa-1');
12+
});
13+
});
14+
15+
describe('resetGlobalCounterId', () => {
16+
it('resets the id', () => {
17+
resetGlobalCounterId();
18+
expect(useGlobalCounterId()).toBe('raa-0');
19+
resetGlobalCounterId();
20+
expect(useGlobalCounterId()).toBe('raa-0');
21+
});
22+
});
23+
24+
describe('assertValidHtmlId', () => {
25+
it("returns false in case there's a whitespace or an empty string", () => {
26+
expect(assertValidHtmlId('a a')).toBe(false);
27+
expect(assertValidHtmlId('')).toBe(false);
28+
});
29+
30+
it('returns true on a valid id', () => {
31+
expect(assertValidHtmlId('💜')).toBe(true);
32+
expect(assertValidHtmlId('✅')).toBe(true);
33+
});
34+
});
35+
});

0 commit comments

Comments
 (0)