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

fix: error message and table #134

Merged
merged 4 commits into from
Sep 27, 2024
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
5 changes: 5 additions & 0 deletions .changeset/curvy-rockets-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nhsuk-frontend-react": patch
---

fix(nhsuk-frontend-react): fix empty heading classname
5 changes: 5 additions & 0 deletions .changeset/grumpy-spoons-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nhsuk-frontend-react": patch
---

fix(nhsuk-frontend-react): support for firstCellIsHeader in table
5 changes: 5 additions & 0 deletions .changeset/moody-ducks-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nhsuk-frontend-react": patch
---

fix(nhsuk-frontend-react): replace span with p in error message
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { ReactNode, createContext, useContext } from 'react';

export type TableContextValue = {
variant?: 'default' | 'responsive';
firstCellIsHeader: boolean;
responsiveHeadings: ReactNode[];
registerHeadings: (heading: ReactNode[]) => void;
};

const TableContext = createContext<TableContextValue>({
responsiveHeadings: [],
firstCellIsHeader: false,
registerHeadings: () => {},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { composeStory } from '@storybook/react';
import meta, {
TwoColumn as TwoColumnStory,
ThreeColumn as ThreeColumnStory,
ThreeColumnWithFirstCellAsHeader as ThreeColumnWithFirstCellAsHeaderStory,
} from './Table.stories';

const TwoColumn = composeStory(TwoColumnStory, meta);
const ThreeColumn = composeStory(ThreeColumnStory, meta);
const ThreeColumnWithFirstCellAsHeader = composeStory(
ThreeColumnWithFirstCellAsHeaderStory,
meta,
);

it('should render a two column table', () => {
const { container } = render(<TwoColumn />);
Expand All @@ -30,3 +35,13 @@ it('should render a three column table', () => {
).toBeInTheDocument();
expect(container).toMatchSnapshot();
});

it('should render a three column table with the first cell as a header', () => {
const { container } = render(<ThreeColumnWithFirstCellAsHeader />);

expect(container.querySelector('tbody > tr > th')).toBeInTheDocument();
expect(
container.querySelector('table.nhsuk-table-responsive'),
).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Table } from './Table';
import { Column, Row } from '@/components/styles/layout/grid/Grid';
import { Container } from '@/components/styles/layout/container/Container';

/**
* Use a table to make it easier for users to compare and scan information.
Expand Down Expand Up @@ -95,3 +97,47 @@ export const ThreeColumn: Story = {
</Table>
),
};

export const ThreeColumnWithFirstCellAsHeader: Story = {
args: {
firstCellIsHeader: true,
variant: 'responsive',
},
render: (args) => (
<Container>
<Row>
<Column width="two-thirds">
<Table {...args}>
<Table.Caption>
Prescription prepayment certificate (PPC) charges
</Table.Caption>
<Table.Head>
<Table.Row>
<Table.Cell>Item</Table.Cell>
<Table.Cell variant="numeric">Current charge</Table.Cell>
<Table.Cell variant="numeric">New charge</Table.Cell>
</Table.Row>
</Table.Head>
<Table.Body>
<Table.Row>
<Table.Cell>3-month</Table.Cell>
<Table.Cell variant="numeric">£31.25</Table.Cell>
<Table.Cell variant="numeric">£32.05</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>12-month</Table.Cell>
<Table.Cell variant="numeric">£111.60</Table.Cell>
<Table.Cell variant="numeric">£114.50</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>HRT</Table.Cell>
<Table.Cell variant="numeric">£19.30</Table.Cell>
<Table.Cell variant="numeric">£19.80</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</Column>
</Row>
</Container>
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export type TableProps = {
* The variant of the table. Defaults to a non-responsive table.
*/
variant?: 'default' | 'responsive';
/**
* Whether the first cell in the table is a header cell. Defaults to false.
* @default false
*/
firstCellIsHeader?: boolean;
} & ElementProps<'table'>;

type TableFactory = Factory<{
Expand All @@ -40,30 +45,37 @@ type TableFactory = Factory<{
};
}>;

const Table = factory<TableFactory>(({ variant, className, ...props }, ref) => {
const [responsiveHeadings, registerHeadings] = useState<ReactNode[]>([]);
const Table = factory<TableFactory>(
({ variant, className, firstCellIsHeader = false, ...props }, ref) => {
const [responsiveHeadings, registerHeadings] = useState<ReactNode[]>([]);

const value = useMemo(
() => ({ variant, responsiveHeadings, registerHeadings }),
[variant, responsiveHeadings, registerHeadings],
);
const value = useMemo(
() => ({
variant,
responsiveHeadings,
firstCellIsHeader,
registerHeadings,
}),
[variant, responsiveHeadings, registerHeadings],
);

return (
<TableProvider value={value}>
<table
className={clsx(
{
'nhsuk-table': !variant,
[`nhsuk-table-${variant}`]: variant,
},
className,
)}
{...props}
ref={ref}
/>
</TableProvider>
);
});
return (
<TableProvider value={value}>
<table
className={clsx(
{
'nhsuk-table': !variant,
[`nhsuk-table-${variant}`]: variant,
},
className,
)}
{...props}
ref={ref}
/>
</TableProvider>
);
},
);

export type TableCaptionProps = ElementProps<'caption'>;

Expand Down Expand Up @@ -110,6 +122,7 @@ const TableRow = ({
const {
variant: tableVariant,
responsiveHeadings,
firstCellIsHeader,
registerHeadings,
} = useTableContext();

Expand Down Expand Up @@ -147,14 +160,27 @@ const TableRow = ({
});
}

if (variant === 'default' || !head) {
_children = Children.map(_children, (child, index) => {
if (isValidElement(child) && child.type === TableCell) {
return cloneElement(child as ReactElement<TableCellProps>, {
__firstCellIsHeader: index === 0 && firstCellIsHeader,
});
}
return child;
});
}

return (
<tr
className={clsx(
{
'nhsuk-table__row': variant === 'default' || !head,
},
className,
)}
className={
clsx(
{
'nhsuk-table__row': variant === 'default' || !head,
},
className,
) || undefined
}
role={role}
{...props}
>
Expand All @@ -176,6 +202,11 @@ export type TableCellProps = {
* For internal use only
*/
__responsiveHeading?: ReactNode;

/**
* For internal use only
*/
__firstCellIsHeader?: boolean;
} & ElementProps<'td'>;

const TableCell = ({
Expand All @@ -185,34 +216,46 @@ const TableCell = ({
children,
responsiveHeading,
__responsiveHeading,
__firstCellIsHeader,
...props
}: TableCellProps) => {
const { variant: tableVariant } = useTableContext();
const { head } = useTableHeadContext();

const baseProps = head
? {
as: 'th',
scope: 'col',
role: role || 'columnheader',
className: className,
'data-responsive-heading': responsiveHeading,
}
: {
as: 'td',
role: role || 'cell',
className: clsx(
'nhsuk-table__cell',
{ 'nhsuk-table__cell--numeric': variant === 'numeric' },
className,
),
};
const headerCellClassNames = clsx(
{
'nhsuk-table__header': __firstCellIsHeader,
'nhsuk-table__header--numeric': variant === 'numeric',
},
className,
);

const cellClassNames = clsx(
'nhsuk-table__cell',
{
'nhsuk-table__cell--numeric': variant === 'numeric',
},
className,
);

const baseProps =
head || __firstCellIsHeader
? {
as: 'th',
scope: __firstCellIsHeader ? 'row' : 'col',
role: __firstCellIsHeader ? role : role || 'columnheader',
...(headerCellClassNames && { className: headerCellClassNames }),
}
: {
as: 'td',
...(cellClassNames && { className: cellClassNames }),
};

return (
<Base<any> {...baseProps} {...props}>
{tableVariant === 'responsive' && !head && (
<span className="nhsuk-table-responsive__heading" aria-hidden="true">
{__responsiveHeading}&nbsp;
{responsiveHeading || __responsiveHeading}&nbsp;
</span>
)}
{children}
Expand Down
Loading