Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: fix: tablist auto selection #7529

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/@react-spectrum/utils/src/Slots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function cssModuleToSlots(cssModule) {

export function SlotProvider(props) {
const emptyObj = useMemo(() => ({}), []);
// eslint-disable-next-line react-hooks/exhaustive-deps
let parentSlots = useContext(SlotContext) || emptyObj;
let {slots = emptyObj, children} = props;

Expand Down
2 changes: 1 addition & 1 deletion packages/@react-stately/tabs/src/useTabListState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function useTabListState<T extends object>(props: TabListStateOptions<T>)
useEffect(() => {
// Ensure a tab is always selected (in case no selected key was specified or if selected item was deleted from collection)
let selectedKey = currentSelectedKey;
if (selectionManager.isEmpty || selectedKey == null || !collection.getItem(selectedKey)) {
if (props.selectedKey == null && (selectionManager.isEmpty || selectedKey == null || !collection.getItem(selectedKey))) {
selectedKey = findDefaultSelectedKey(collection, state.disabledKeys);
if (selectedKey != null) {
// directly set selection because replace/toggle selection won't consider disabled keys
Expand Down
92 changes: 90 additions & 2 deletions packages/react-aria-components/test/Tabs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
*/

import {act, fireEvent, pointerMap, render, waitFor, within} from '@react-spectrum/test-utils-internal';
import React from 'react';
import {Tab, TabList, TabPanel, Tabs} from '../';
import {Button, Collection, Tab, TabList, TabPanel, Tabs} from '../';
import React, {useState} from 'react';
import {TabsExample} from '../stories/Tabs.stories';
import userEvent from '@testing-library/user-event';

Expand Down Expand Up @@ -474,4 +474,92 @@ describe('Tabs', () => {
expect(innerTabs[0]).toHaveTextContent('One');
expect(innerTabs[1]).toHaveTextContent('Two');
});

it('can add tabs and keep the current selected key', async () => {
let onSelectionChange = jest.fn();
function Example(props) {
let [tabs, setTabs] = useState([
{id: 1, title: 'Tab 1', content: 'Tab body 1'},
{id: 2, title: 'Tab 2', content: 'Tab body 2'},
{id: 3, title: 'Tab 3', content: 'Tab body 3'}
]);

const [selectedTabId, setSelectedTabId] = useState(tabs[0].id);

let addTab = () => {
const tabId = tabs.length + 1;

setTabs((prevTabs) => [
...prevTabs,
{
id: tabId,
title: `Tab ${tabId}`,
content: `Tab body ${tabId}`
}
]);

// Use functional update to ensure you're working with the most recent state
setSelectedTabId(tabId);
};

let removeTab = () => {
if (tabs.length > 1) {
setTabs((prevTabs) => {
const updatedTabs = prevTabs.slice(0, -1);
// Update selectedTabId to the last remaining tab's ID if the current selected tab is removed
const newSelectedTabId = updatedTabs[updatedTabs.length - 1].id;
setSelectedTabId(newSelectedTabId);
return updatedTabs;
});
}
};

const onSelectionChange = (value) => {
setSelectedTabId(value);
props.onSelectionChange(value);
};

return (
<Tabs selectedKey={selectedTabId} onSelectionChange={onSelectionChange}>
<div style={{display: 'flex'}}>
<TabList aria-label="Dynamic tabs" items={tabs} style={{flex: 1}}>
{(item) => (
<Tab>
{({isSelected}) => (
<p
style={{
color: isSelected ? 'red' : 'black'
}}>
{item.title}
</p>
)}
</Tab>
)}
</TabList>
<div className="button-group">
<Button onPress={addTab}>Add tab</Button>
<Button onPress={removeTab}>Remove tab</Button>
</div>
</div>
<Collection items={tabs}>
{(item) => (
<TabPanel
style={{
borderTop: '2px solid black'
}}>
{item.content}
</TabPanel>
)}
</Collection>
</Tabs>
);
}
render(<Example onSelectionChange={onSelectionChange} />);
await user.tab();
await user.keyboard('{ArrowRight}');
await user.tab();
onSelectionChange.mockClear();
await user.keyboard('{Enter}');
expect(onSelectionChange).not.toHaveBeenCalled();
Copy link
Member

@ktabors ktabors Dec 17, 2024

Choose a reason for hiding this comment

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

Should we confirm which tab is selected?

});
});
Loading