Skip to content

Commit 3013156

Browse files
authored
Patch fixes (#6755)
1 parent 139374a commit 3013156

File tree

10 files changed

+100
-25
lines changed

10 files changed

+100
-25
lines changed

packages/@react-aria/collections/src/CollectionBuilder.tsx

+11-6
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,16 @@ export interface CollectionBuilderProps<C extends BaseCollection<object>> {
3333
/**
3434
* Builds a `Collection` from the children provided to the `content` prop, and passes it to the child render prop function.
3535
*/
36-
export function CollectionBuilder<C extends BaseCollection<object>>(props: CollectionBuilderProps<C>) {
36+
export function CollectionBuilder<C extends BaseCollection<object>>(props: CollectionBuilderProps<C>): ReactElement {
3737
// If a document was provided above us, we're already in a hidden tree. Just render the content.
3838
let doc = useContext(CollectionDocumentContext);
3939
if (doc) {
40-
return props.content;
40+
// The React types prior to 18 did not allow returning ReactNode from components
41+
// even though the actual implementation since React 16 did.
42+
// We must return ReactElement so that TS does not complain that <CollectionBuilder>
43+
// is not a valid JSX element with React 16 and 17 types.
44+
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544
45+
return props.content as ReactElement;
4146
}
4247

4348
// Otherwise, render a hidden copy of the children so that we can build the collection before constructing the state.
@@ -151,9 +156,9 @@ function useSSRCollectionNode<T extends Element>(Type: string, props: object, re
151156
return <Type ref={itemRef}>{children}</Type>;
152157
}
153158

154-
export function createLeafComponent<T extends object, P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>) => ReactNode): (props: P & React.RefAttributes<T>) => ReactNode | null;
155-
export function createLeafComponent<T extends object, P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactNode): (props: P & React.RefAttributes<T>) => ReactNode | null;
156-
export function createLeafComponent<P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactNode) {
159+
export function createLeafComponent<T extends object, P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>) => ReactElement): (props: P & React.RefAttributes<T>) => ReactElement | null;
160+
export function createLeafComponent<T extends object, P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement): (props: P & React.RefAttributes<T>) => ReactElement | null;
161+
export function createLeafComponent<P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactElement) {
157162
let Component = ({node}) => render(node.props, node.props.ref, node);
158163
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
159164
let isShallow = useContext(ShallowRenderContext);
@@ -171,7 +176,7 @@ export function createLeafComponent<P extends object, E extends Element>(type: s
171176
return Result;
172177
}
173178

174-
export function createBranchComponent<T extends object, P extends {children?: any}, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactNode, useChildren: (props: P) => ReactNode = useCollectionChildren) {
179+
export function createBranchComponent<T extends object, P extends {children?: any}, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement, useChildren: (props: P) => ReactNode = useCollectionChildren) {
175180
let Component = ({node}) => render(node.props, node.props.ref, node);
176181
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
177182
let children = useChildren(props);

packages/@react-aria/collections/src/Hidden.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {createPortal} from 'react-dom';
1414
import {forwardRefType} from '@react-types/shared';
15-
import React, {createContext, forwardRef, ReactNode, useContext} from 'react';
15+
import React, {createContext, forwardRef, ReactElement, ReactNode, useContext} from 'react';
1616
import {useIsSSR} from '@react-aria/ssr';
1717

1818
// React doesn't understand the <template> element, which doesn't have children like a normal element.
@@ -64,7 +64,7 @@ export function Hidden(props: {children: ReactNode}) {
6464

6565
/** Creates a component that forwards its ref and returns null if it is in a hidden subtree. */
6666
// Note: this function is handled specially in the documentation generator. If you change it, you'll need to update DocsTransformer as well.
67-
export function createHideableComponent<T, P = {}>(fn: (props: P, ref: React.Ref<T>) => ReactNode | null): (props: P & React.RefAttributes<T>) => ReactNode | null {
67+
export function createHideableComponent<T, P = {}>(fn: (props: P, ref: React.Ref<T>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null {
6868
let Wrapper = (props: P, ref: React.Ref<T>) => {
6969
let isHidden = useContext(HiddenContext);
7070
if (isHidden) {

packages/@react-aria/utils/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export {mergeRefs} from './mergeRefs';
1717
export {filterDOMProps} from './filterDOMProps';
1818
export {focusWithoutScrolling} from './focusWithoutScrolling';
1919
export {getOffset} from './getOffset';
20-
export {openLink, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps} from './openLink';
20+
export {openLink, getSyntheticLinkProps, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps} from './openLink';
2121
export {runAfterTransition} from './runAfterTransition';
2222
export {useDrag1D} from './useDrag1D';
2323
export {useGlobalListeners} from './useGlobalListeners';

packages/@react-aria/utils/src/openLink.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ export function useSyntheticLinkProps(props: LinkDOMProps) {
158158
};
159159
}
160160

161+
/** @deprecated - For backward compatibility. */
162+
export function getSyntheticLinkProps(props: LinkDOMProps) {
163+
return {
164+
'data-href': props.href,
165+
'data-target': props.target,
166+
'data-rel': props.rel,
167+
'data-download': props.download,
168+
'data-ping': props.ping,
169+
'data-referrer-policy': props.referrerPolicy
170+
};
171+
}
172+
161173
export function useLinkProps(props: LinkDOMProps) {
162174
let router = useRouter();
163175
return {

packages/@react-types/shared/src/refs.d.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {ReactNode, Ref, RefAttributes} from 'react';
13+
import {ReactElement, Ref, RefAttributes} from 'react';
1414

1515
export interface DOMRefValue<T extends HTMLElement = HTMLElement> {
1616
UNSAFE_getDOMNode(): T
@@ -29,7 +29,7 @@ export interface RefObject<T> {
2929

3030
// Override forwardRef types so generics work.
3131
declare function forwardRef<T, P = {}>(
32-
render: (props: P, ref: Ref<T>) => ReactNode | null
33-
): (props: P & RefAttributes<T>) => ReactNode | null;
32+
render: (props: P, ref: Ref<T>) => ReactElement | null
33+
): (props: P & RefAttributes<T>) => ReactElement | null;
3434

3535
export type forwardRefType = typeof forwardRef;

packages/@react-types/sidenav/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
"url": "https://github.com/adobe/react-spectrum"
1111
},
1212
"dependencies": {
13-
"@react-types/shared": "^3.1.0",
14-
"@react-stately/virtualizer": "^3.1.7"
13+
"@react-types/shared": "^3.1.0"
1514
},
1615
"peerDependencies": {
1716
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"

packages/@react-types/sidenav/src/index.d.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {AriaLabelingProps, CollectionBase, DOMProps, Expandable, Key, MultipleSelection, Node, StyleProps} from '@react-types/shared';
13+
import {AriaLabelingProps, CollectionBase, DOMProps, Expandable, MultipleSelection, Node, StyleProps} from '@react-types/shared';
1414
import {HTMLAttributes, ReactNode} from 'react';
15-
import {LayoutInfo, Size} from '@react-stately/virtualizer';
1615

1716
export interface SideNavProps<T> extends CollectionBase<T>, Expandable, MultipleSelection {
1817
shouldFocusWrap?: boolean
@@ -27,14 +26,10 @@ export interface SpectrumSideNavItemProps<T> extends HTMLAttributes<HTMLElement>
2726
item: Node<T>
2827
}
2928

30-
interface IVirtualizer {
31-
updateItemSize(key: Key, size: Size): void
32-
}
33-
3429
export interface SideNavSectionProps<T> {
35-
layoutInfo: LayoutInfo,
36-
headerLayoutInfo: LayoutInfo,
37-
virtualizer: IVirtualizer,
30+
layoutInfo: any,
31+
headerLayoutInfo: any,
32+
virtualizer: any,
3833
item: Node<T>,
3934
children?: ReactNode
4035
}

packages/react-aria-components/src/Tabs.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,15 @@ function TabPanel(props: TabPanelProps, forwardedRef: ForwardedRef<HTMLDivElemen
321321
data-focus-visible={isFocusVisible || undefined}
322322
// @ts-ignore
323323
inert={!isSelected ? 'true' : undefined}
324-
data-inert={!isSelected ? 'true' : undefined} />
324+
data-inert={!isSelected ? 'true' : undefined}>
325+
<Provider
326+
values={[
327+
[TabsContext, null],
328+
[TabListStateContext, null]
329+
]}>
330+
{renderProps.children}
331+
</Provider>
332+
</div>
325333
);
326334
}
327335

packages/react-aria-components/stories/Tabs.stories.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,23 @@ const CustomTab = (props: TabProps) => {
8989
})} />
9090
);
9191
};
92+
93+
export const NestedTabs = () => (
94+
<Tabs>
95+
<TabList style={{display: 'flex', gap: 8}}>
96+
<CustomTab id="foo">Foo</CustomTab>
97+
<CustomTab id="bar">Bar</CustomTab>
98+
</TabList>
99+
<TabPanel id="foo">
100+
<Tabs>
101+
<TabList style={{display: 'flex', gap: 8}}>
102+
<CustomTab id="one">One</CustomTab>
103+
<CustomTab id="two">Two</CustomTab>
104+
</TabList>
105+
<TabPanel id="one">One</TabPanel>
106+
<TabPanel id="two">Two</TabPanel>
107+
</Tabs>
108+
</TabPanel>
109+
<TabPanel id="bar">Bar</TabPanel>
110+
</Tabs>
111+
);

packages/react-aria-components/test/Tabs.test.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {act, fireEvent, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
13+
import {act, fireEvent, pointerMap, render, waitFor, within} from '@react-spectrum/test-utils-internal';
1414
import React from 'react';
1515
import {Tab, TabList, TabPanel, Tabs} from '../';
1616
import {TabsExample} from '../stories/Tabs.stories';
@@ -410,4 +410,40 @@ describe('Tabs', () => {
410410
expect(tabs[1]).toHaveAttribute('aria-label', 'Tab B');
411411
expect(tabs[2]).toHaveAttribute('aria-label', 'Tab C');
412412
});
413+
414+
it('supports nested tabs', async () => {
415+
let {getAllByRole} = render(
416+
<Tabs>
417+
<TabList>
418+
<Tab id="foo">Foo</Tab>
419+
<Tab id="bar">Bar</Tab>
420+
</TabList>
421+
<TabPanel id="foo">
422+
<Tabs>
423+
<TabList>
424+
<Tab id="one">One</Tab>
425+
<Tab id="two">Two</Tab>
426+
</TabList>
427+
<TabPanel id="one">One</TabPanel>
428+
<TabPanel id="two">Two</TabPanel>
429+
</Tabs>
430+
</TabPanel>
431+
<TabPanel id="bar">Bar</TabPanel>
432+
</Tabs>
433+
);
434+
435+
// Wait a tick for MutationObserver in useHasTabbableChild to fire.
436+
// This avoids React's "update not wrapped in act" warning.
437+
await waitFor(() => Promise.resolve());
438+
439+
let rootTabs = within(getAllByRole('tablist')[0]).getAllByRole('tab');
440+
expect(rootTabs).toHaveLength(2);
441+
expect(rootTabs[0]).toHaveTextContent('Foo');
442+
expect(rootTabs[1]).toHaveTextContent('Bar');
443+
444+
let innerTabs = within(getAllByRole('tabpanel')[0]).getAllByRole('tab');
445+
expect(innerTabs).toHaveLength(2);
446+
expect(innerTabs[0]).toHaveTextContent('One');
447+
expect(innerTabs[1]).toHaveTextContent('Two');
448+
});
413449
});

0 commit comments

Comments
 (0)