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

feat(APP-3954): Update Tab component to not render the TabList when there is a single child #407

Merged
merged 12 commits into from
Feb 18, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Changed

- Update `Tabs.List` component to only render when used with multiple tabs
- Bump `elliptic` to 6.6.1

## [1.0.66] - 2025-02-11
Expand Down
26 changes: 17 additions & 9 deletions src/core/components/tabs/tabsList/tabsList.test.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
// TabsRoot.test.tsx
import { render, screen } from '@testing-library/react';
import { Tabs, type ITabsListProps } from '../../tabs';
import { type ITabsListProps, Tabs } from '../../tabs';

describe('<Tabs.Root /> component', () => {
describe('<Tabs.List /> component', () => {
const createTestComponent = (props?: Partial<ITabsListProps>) => {
const completeProps: ITabsListProps = {
...props,
};

return (
<Tabs.Root>
<Tabs.List {...completeProps}>
<Tabs.Trigger label="Tab 1" value="1" />
<Tabs.Trigger label="Tab 2" value="2" />
</Tabs.List>
<Tabs.List {...completeProps} />
</Tabs.Root>
);
};

it('should render multiple tab triggers without crashing', () => {
render(createTestComponent());
it('renders multiple tab triggers', () => {
const children = [
<Tabs.Trigger key="1" label="Tab 1" value="1" />,
<Tabs.Trigger key="2" label="Tab 2" value="2" />,
];
render(createTestComponent({ children }));

expect(screen.getByText('Tab 1')).toBeInTheDocument();
expect(screen.getByText('Tab 2')).toBeInTheDocument();
});

it('renders null when only a single tab trigger is present', () => {
const children = <Tabs.Trigger label="Tab 1" value="1" />;
render(createTestComponent({ children }));

expect(screen.queryByText('Tab 1')).not.toBeInTheDocument();
});
});
7 changes: 6 additions & 1 deletion src/core/components/tabs/tabsList/tabsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TabsList as RadixTabsList } from '@radix-ui/react-tabs';
import classNames from 'classnames';
import { useContext, type ComponentProps } from 'react';
import { Children, useContext, type ComponentProps } from 'react';
import { TabsContext } from '../tabsRoot/tabsRoot';

export interface ITabsListProps extends ComponentProps<'div'> {}
Expand All @@ -11,6 +11,11 @@ export const TabsList: React.FC<ITabsListProps> = (props) => {

const tabsListClassNames = classNames('flex gap-x-6', { 'border-b border-neutral-100': isUnderlined }, className);

// If there is only a single child then the tabs are redundant and we just show the content
if (Children.count(children) === 1) {
return null;
}

return (
<RadixTabsList className={tabsListClassNames} {...otherProps}>
{children}
Expand Down
20 changes: 20 additions & 0 deletions src/core/components/tabs/tabsRoot/tabsRoot.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,24 @@ export const InsideCard: Story = {
render: (args) => <Card className="p-6">{reusableStoryComponent(args)}</Card>,
};

/**
* Usage example of a Tabs component with a single tab with the default value.
* Make sure to set the `defaultValue` or `value` property to select and show the content of the tab.
*/
export const SingleTab: Story = {
args: { defaultValue: '1' },
render: (args) => (
<Tabs.Root {...args}>
<Tabs.List>
<Tabs.Trigger label="Default Tab" value="1" />
</Tabs.List>
<Tabs.Content value="1">
<div className="flex h-24 w-96 items-center justify-center border border-dashed border-info-300 bg-info-100">
Item 1 Content
</div>
</Tabs.Content>
</Tabs.Root>
),
};

export default meta;
15 changes: 8 additions & 7 deletions src/core/components/tabs/tabsTrigger/tabsTrigger.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TabsList as RadixTabsList, Tabs as RadixTabsRoot } from '@radix-ui/react-tabs';
import { render, screen } from '@testing-library/react';
import { IconType } from '../../icon';
import { Tabs, type ITabsTriggerProps } from '../../tabs';
Expand All @@ -11,11 +12,11 @@ describe('<Tabs.Trigger /> component', () => {
};

return (
<Tabs.Root>
<Tabs.List>
<RadixTabsRoot>
<RadixTabsList>
<Tabs.Trigger {...completeProps} />
</Tabs.List>
</Tabs.Root>
</RadixTabsList>
</RadixTabsRoot>
);
};

Expand All @@ -33,8 +34,8 @@ describe('<Tabs.Trigger /> component', () => {
});

it('disables the tab when the disabled property is set to true', () => {
const disabled = true;
render(createTestComponent({ disabled }));
expect(screen.getByRole('tab').getAttribute('disabled')).toEqual('');
render(createTestComponent({ disabled: true }));
const tab = screen.getByRole('tab');
expect(tab.getAttribute('disabled')).toEqual('');
});
});