Skip to content
Merged
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
6 changes: 4 additions & 2 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'react-app-polyfill/ie11';
import reportWebVitals from './reportWebVitals';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import {
FormInputMaskedDemo,
Expand Down Expand Up @@ -73,7 +73,9 @@ const App = () => (
</div>
);

ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@capgeminiuk/dcx-react-library",
"author": "Capgemini UK",
"license": "MIT",
"version": "0.7.0",
"version": "0.8.0-rc3",
"source": "src/index.ts",
"main": "dist/dcx-react-library.js",
"module": "dist/dcx-react-library.module.js",
Expand Down
49 changes: 27 additions & 22 deletions src/tabGroup/TabGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, {
useRef,
useState,
} from 'react';
import { classNames, Roles } from '../common';
import { classNames, Roles, useHydrated } from '../common';

export type TabGroupProps = {
/**
Expand Down Expand Up @@ -101,15 +101,10 @@ export const TabGroup = forwardRef(
throw new Error('Tab event keys must be unique');
}

const defaultActiveTab: string = children.find(
(child: JSX.Element) => activeKey === child.props.eventKey
)?.props.eventKey;

const initialMount = useRef(true);

const [activeTab, setActiveTab] = useState<string>(
defaultActiveTab || children[0].props.eventKey
);
const defaultActiveTabKey = activeKey || children[0].props.eventKey;
const [activeTab, setActiveTab] = useState<string>(defaultActiveTabKey);

const onClickHandler: (id: string) => void = (id: string) =>
setActiveTab(id);
Expand All @@ -132,6 +127,14 @@ export const TabGroup = forwardRef(
else initialMount.current = false;
}, [activeTab]);

const activeTabElement = children.find(
(child: JSX.Element) => activeTab === child.props.eventKey
);

const hydrated = useHydrated();

const tabPanels = hydrated ? [activeTabElement] : children;

return (
<div className={containerClassName}>
<ol
Expand Down Expand Up @@ -163,20 +166,22 @@ export const TabGroup = forwardRef(
})}
</TabContext.Provider>
</ol>
{children.map((tab: JSX.Element, index: number) =>
tab.props.eventKey === activeTab ? (
<div
id={tab.props.eventKey}
key={index}
role={Roles.tabpanel}
className={contentClassName}
tabIndex={0}
aria-labelledby={tab.props.eventKey}
>
{tab.props.children}
</div>
) : undefined
)}
{tabPanels.map((tabPanel, index) => (
<>
{tabPanel && (
<div
id={tabPanel.props.eventKey}
key={index}
role={Roles.tabpanel}
className={contentClassName}
tabIndex={0}
aria-labelledby={tabPanel.props.eventKey}
>
{tabPanel.props.children}
</div>
)}
</>
))}
</div>
);
}
Expand Down
47 changes: 41 additions & 6 deletions src/tabGroup/__test__/TabGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '@testing-library/jest-dom';
import { Tab } from '../components/Tab';
import { TabGroup } from '../TabGroup';
import { Button } from '../../button';
import * as hooks from '../../common/utils/clientOnly';

describe('TabGroup', () => {
it('should not render a tab group if event keys are not unique', () => {
Expand Down Expand Up @@ -117,7 +118,7 @@ describe('TabGroup', () => {
);
});

it("should render tabs with tab id's", () => {
it('should render tabs with data attributes containing tab ids', () => {
render(
<TabGroup activeTabClassName="tab-class-active">
<Tab label="tab 1 label" eventKey="tab-1-id">
Expand All @@ -132,10 +133,10 @@ describe('TabGroup', () => {
const tabs: HTMLElement[] = screen.getAllByRole('tab');

expect(tabs[0]).toBeInTheDocument();
expect(tabs[0].getAttribute('id')).toBe('tab-1-id');
expect(tabs[0].getAttribute('data-tab-id')).toBe('tab-1-id');

expect(tabs[1]).toBeInTheDocument();
expect(tabs[1].getAttribute('id')).toBe('tab-2-id');
expect(tabs[1].getAttribute('data-tab-id')).toBe('tab-2-id');
});

it('should render tabs with ariaControls that which tab panel they control', () => {
Expand Down Expand Up @@ -170,7 +171,7 @@ describe('TabGroup', () => {

const tabs: HTMLElement[] = screen.getAllByRole('tab');

expect(tabs[0].getAttribute('tabIndex')).toBeNull();
expect(tabs[0].getAttribute('tabIndex')).toBe('0');
expect(tabs[1].getAttribute('tabIndex')).toBe('-1');
});

Expand Down Expand Up @@ -350,7 +351,7 @@ describe('TabGroup', () => {
const tabs: HTMLElement[] = screen.getAllByRole('tab');
const tabItems: HTMLElement[] = screen.getAllByRole('presentation');

expect(tabs[0].getAttribute('tabIndex')).toBeNull();
expect(tabs[0].getAttribute('tabIndex')).toBe('0');
expect(tabs[1].getAttribute('tabIndex')).toBe('-1');

expect(tabItems[0].getAttribute('class')).toBe(
Expand All @@ -361,7 +362,7 @@ describe('TabGroup', () => {
fireEvent.click(tabs[1]);

expect(tabs[0].getAttribute('tabIndex')).toBe('-1');
expect(tabs[1].getAttribute('tabIndex')).toBeNull();
expect(tabs[1].getAttribute('tabIndex')).toBe('0');

expect(tabGroupClickHandler).not.toHaveBeenCalled();

Expand Down Expand Up @@ -710,4 +711,38 @@ describe('TabGroup', () => {

expect(updated).toBeTruthy();
});

it('should render all tabpanels when JS is disabled', () => {
jest.spyOn(hooks, 'useHydrated').mockImplementation(() => false);

const { container } = render(
<TabGroup containerClassName="container-class">
<Tab eventKey="tab-1-id" label="tab 1 label">
This is the content for tab 1
</Tab>
<Tab eventKey="tab-2-id" label="tab 2 label">
This is the content for tab 2
</Tab>
</TabGroup>
);

expect(container.querySelectorAll('[role=tabpanel]').length).toBe(2);
});

it('should render a single tabpanel even when there are multiple tabs when JS is enabled', () => {
jest.spyOn(hooks, 'useHydrated').mockImplementation(() => true);

const { container } = render(
<TabGroup containerClassName="container-class">
<Tab eventKey="tab-1-id" label="tab 1 label">
This is the content for tab 1
</Tab>
<Tab eventKey="tab-2-id" label="tab 2 label">
This is the content for tab 2
</Tab>
</TabGroup>
);

expect(container.querySelectorAll('[role=tabpanel]').length).toBe(1);
});
});
6 changes: 3 additions & 3 deletions src/tabGroup/components/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ export const Tab = ({
event:
| React.MouseEvent<HTMLAnchorElement>
| React.TouchEvent<HTMLAnchorElement>
) => changeActiveTab(event.currentTarget.id);
) => changeActiveTab(event.currentTarget.dataset.tabId as string);

return (
<li role={Roles.presentation} className={classes}>
<a
role={Roles.tab}
id={eventKey}
data-tab-id={eventKey}
className={linkClassName}
aria-controls={ariaControls}
aria-selected={selected}
tabIndex={!selected ? -1 : undefined}
tabIndex={!selected ? -1 : 0}
onClick={!disabled ? onClickHandler : undefined}
href={`#${eventKey}`}
>
Expand Down
6 changes: 3 additions & 3 deletions src/tabGroup/components/__test__/Tab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('Tab', () => {

expect(screen.getByRole('tab')).toBeInTheDocument();
expect(screen.getByRole('tab').getAttribute('href')).toBe('#tab 2');
expect(screen.getByRole('tab').getAttribute('id')).toBe('tab 2');
expect(screen.getByRole('tab').getAttribute('data-tab-id')).toBe('tab 2');
});

it('should render a tab with optional properties', () => {
Expand All @@ -70,7 +70,7 @@ describe('Tab', () => {
</TabContext.Provider>
);

expect(screen.getByRole('tab').getAttribute('id')).toBe('tab 2');
expect(screen.getByRole('tab').getAttribute('data-tab-id')).toBe('tab 2');
expect(screen.getByRole('presentation').getAttribute('class')).toBe(
'myClassName'
);
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('Tab', () => {
expect(screen.getByRole('presentation').getAttribute('class')).toBe(
'myClassName tabActive'
);
expect(screen.getByRole('tab').getAttribute('tabIndex')).toBeNull();
expect(screen.getByRole('tab').getAttribute('tabIndex')).toBe('0');
});

it('should render a tab with a label', () => {
Expand Down