Skip to content

Commit

Permalink
Patch fixes (#6755)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett authored Jul 23, 2024
1 parent 139374a commit 3013156
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 25 deletions.
17 changes: 11 additions & 6 deletions packages/@react-aria/collections/src/CollectionBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ export interface CollectionBuilderProps<C extends BaseCollection<object>> {
/**
* Builds a `Collection` from the children provided to the `content` prop, and passes it to the child render prop function.
*/
export function CollectionBuilder<C extends BaseCollection<object>>(props: CollectionBuilderProps<C>) {
export function CollectionBuilder<C extends BaseCollection<object>>(props: CollectionBuilderProps<C>): ReactElement {
// If a document was provided above us, we're already in a hidden tree. Just render the content.
let doc = useContext(CollectionDocumentContext);
if (doc) {
return props.content;
// The React types prior to 18 did not allow returning ReactNode from components
// even though the actual implementation since React 16 did.
// We must return ReactElement so that TS does not complain that <CollectionBuilder>
// is not a valid JSX element with React 16 and 17 types.
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544
return props.content as ReactElement;
}

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

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;
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;
export function createLeafComponent<P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactNode) {
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;
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;
export function createLeafComponent<P extends object, E extends Element>(type: string, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactElement) {
let Component = ({node}) => render(node.props, node.props.ref, node);
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
let isShallow = useContext(ShallowRenderContext);
Expand All @@ -171,7 +176,7 @@ export function createLeafComponent<P extends object, E extends Element>(type: s
return Result;
}

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) {
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) {
let Component = ({node}) => render(node.props, node.props.ref, node);
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
let children = useChildren(props);
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-aria/collections/src/Hidden.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

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

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

/** Creates a component that forwards its ref and returns null if it is in a hidden subtree. */
// Note: this function is handled specially in the documentation generator. If you change it, you'll need to update DocsTransformer as well.
export function createHideableComponent<T, P = {}>(fn: (props: P, ref: React.Ref<T>) => ReactNode | null): (props: P & React.RefAttributes<T>) => ReactNode | null {
export function createHideableComponent<T, P = {}>(fn: (props: P, ref: React.Ref<T>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null {
let Wrapper = (props: P, ref: React.Ref<T>) => {
let isHidden = useContext(HiddenContext);
if (isHidden) {
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export {mergeRefs} from './mergeRefs';
export {filterDOMProps} from './filterDOMProps';
export {focusWithoutScrolling} from './focusWithoutScrolling';
export {getOffset} from './getOffset';
export {openLink, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps} from './openLink';
export {openLink, getSyntheticLinkProps, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps} from './openLink';
export {runAfterTransition} from './runAfterTransition';
export {useDrag1D} from './useDrag1D';
export {useGlobalListeners} from './useGlobalListeners';
Expand Down
12 changes: 12 additions & 0 deletions packages/@react-aria/utils/src/openLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ export function useSyntheticLinkProps(props: LinkDOMProps) {
};
}

/** @deprecated - For backward compatibility. */
export function getSyntheticLinkProps(props: LinkDOMProps) {
return {
'data-href': props.href,
'data-target': props.target,
'data-rel': props.rel,
'data-download': props.download,
'data-ping': props.ping,
'data-referrer-policy': props.referrerPolicy
};
}

export function useLinkProps(props: LinkDOMProps) {
let router = useRouter();
return {
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-types/shared/src/refs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {ReactNode, Ref, RefAttributes} from 'react';
import {ReactElement, Ref, RefAttributes} from 'react';

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

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

export type forwardRefType = typeof forwardRef;
3 changes: 1 addition & 2 deletions packages/@react-types/sidenav/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"url": "https://github.com/adobe/react-spectrum"
},
"dependencies": {
"@react-types/shared": "^3.1.0",
"@react-stately/virtualizer": "^3.1.7"
"@react-types/shared": "^3.1.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
Expand Down
13 changes: 4 additions & 9 deletions packages/@react-types/sidenav/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
* governing permissions and limitations under the License.
*/

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

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

interface IVirtualizer {
updateItemSize(key: Key, size: Size): void
}

export interface SideNavSectionProps<T> {
layoutInfo: LayoutInfo,
headerLayoutInfo: LayoutInfo,
virtualizer: IVirtualizer,
layoutInfo: any,
headerLayoutInfo: any,
virtualizer: any,
item: Node<T>,
children?: ReactNode
}
10 changes: 9 additions & 1 deletion packages/react-aria-components/src/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,15 @@ function TabPanel(props: TabPanelProps, forwardedRef: ForwardedRef<HTMLDivElemen
data-focus-visible={isFocusVisible || undefined}
// @ts-ignore
inert={!isSelected ? 'true' : undefined}
data-inert={!isSelected ? 'true' : undefined} />
data-inert={!isSelected ? 'true' : undefined}>
<Provider
values={[
[TabsContext, null],
[TabListStateContext, null]
]}>
{renderProps.children}
</Provider>
</div>
);
}

Expand Down
20 changes: 20 additions & 0 deletions packages/react-aria-components/stories/Tabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,23 @@ const CustomTab = (props: TabProps) => {
})} />
);
};

export const NestedTabs = () => (
<Tabs>
<TabList style={{display: 'flex', gap: 8}}>
<CustomTab id="foo">Foo</CustomTab>
<CustomTab id="bar">Bar</CustomTab>
</TabList>
<TabPanel id="foo">
<Tabs>
<TabList style={{display: 'flex', gap: 8}}>
<CustomTab id="one">One</CustomTab>
<CustomTab id="two">Two</CustomTab>
</TabList>
<TabPanel id="one">One</TabPanel>
<TabPanel id="two">Two</TabPanel>
</Tabs>
</TabPanel>
<TabPanel id="bar">Bar</TabPanel>
</Tabs>
);
38 changes: 37 additions & 1 deletion packages/react-aria-components/test/Tabs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {act, fireEvent, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
import {act, fireEvent, pointerMap, render, waitFor, within} from '@react-spectrum/test-utils-internal';
import React from 'react';
import {Tab, TabList, TabPanel, Tabs} from '../';
import {TabsExample} from '../stories/Tabs.stories';
Expand Down Expand Up @@ -410,4 +410,40 @@ describe('Tabs', () => {
expect(tabs[1]).toHaveAttribute('aria-label', 'Tab B');
expect(tabs[2]).toHaveAttribute('aria-label', 'Tab C');
});

it('supports nested tabs', async () => {
let {getAllByRole} = render(
<Tabs>
<TabList>
<Tab id="foo">Foo</Tab>
<Tab id="bar">Bar</Tab>
</TabList>
<TabPanel id="foo">
<Tabs>
<TabList>
<Tab id="one">One</Tab>
<Tab id="two">Two</Tab>
</TabList>
<TabPanel id="one">One</TabPanel>
<TabPanel id="two">Two</TabPanel>
</Tabs>
</TabPanel>
<TabPanel id="bar">Bar</TabPanel>
</Tabs>
);

// Wait a tick for MutationObserver in useHasTabbableChild to fire.
// This avoids React's "update not wrapped in act" warning.
await waitFor(() => Promise.resolve());

let rootTabs = within(getAllByRole('tablist')[0]).getAllByRole('tab');
expect(rootTabs).toHaveLength(2);
expect(rootTabs[0]).toHaveTextContent('Foo');
expect(rootTabs[1]).toHaveTextContent('Bar');

let innerTabs = within(getAllByRole('tabpanel')[0]).getAllByRole('tab');
expect(innerTabs).toHaveLength(2);
expect(innerTabs[0]).toHaveTextContent('One');
expect(innerTabs[1]).toHaveTextContent('Two');
});
});

2 comments on commit 3013156

@rspbot
Copy link

@rspbot rspbot commented on 3013156 Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rspbot
Copy link

@rspbot rspbot commented on 3013156 Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.