Skip to content

Commit

Permalink
fix(Table): use css conventions (#2393)
Browse files Browse the repository at this point in the history
Part of #2295
Sortable button does not fill table header cell, but this is no
requirement - the TH can have the onclick listener, while the button is
mostly there to give screen readers a focusable element with button
role. Clicking the button will bubble up and trigger TH event listener
anyway :)

---------

Co-authored-by: Tobias Barsnes <tobias.barsnes@digdir.no>
  • Loading branch information
eirikbacker and Barsnes authored Sep 11, 2024
1 parent 48f5713 commit dda5b21
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 262 deletions.
6 changes: 6 additions & 0 deletions .changeset/eleven-bags-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@digdir/designsystemet-css": patch
"@digdir/designsystemet-react": patch
---

TableHeaderCell: Remove `sortable` prop, `sort` now handles this
4 changes: 2 additions & 2 deletions apps/theme/components/Previews/Components/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ export const Components = () => {
<Table size='sm' border className={classes.table}>
<Table.Head>
<Table.Row>
<Table.HeaderCell onClick={function Ya() {}} sortable>
<Table.HeaderCell onClick={function Ya() {}} sort='none'>
Navn
</Table.HeaderCell>
<Table.HeaderCell>Epost</Table.HeaderCell>
<Table.HeaderCell onClick={function Ya() {}} sortable>
<Table.HeaderCell onClick={function Ya() {}} sort='none'>
Telefon
</Table.HeaderCell>
</Table.Row>
Expand Down
251 changes: 134 additions & 117 deletions packages/css/table.css
Original file line number Diff line number Diff line change
@@ -1,143 +1,160 @@
.ds-table {
--dsc-table-padding: 0;
--dsc-table-border-radius: min(1rem, var(--ds-border-radius-md));
--dsc-table-border-color: var(--ds-color-neutral-border-subtle);
--dsc-table-background--hover: var(--ds-color-neutral-surface-default);
--dsc-table-background--zebra: var(--ds-color-neutral-background-subtle);
--dsc-table-background: var(--ds-color-neutral-background-default);
--dsc-table-border-radius: 0;
--dsc-table-border: 1px solid var(--ds-color-neutral-border-subtle);
--dsc-table-color: var(--ds-color-neutral-text-default);
--dsc-table-header-cell-background: var(--ds-color-neutral-background-default);
--dsc-table-header-sorted-background: var(--ds-color-neutral-background-subtle);
--dsc-table-header-sorted-hover: var(--ds-color-neutral-surface-default);
--dsc-table-cell-backround: var(--ds-color-neutral-background-default);
--dsc-table-cell-zebra-background: var(--ds-color-neutral-background-subtle);
--dsc-table-cell-hover-backround: var(--ds-color-neutral-surface-default);

position: relative;
border-collapse: separate;
--dsc-table-header-background--hover: var(--ds-color-neutral-surface-default);
--dsc-table-header-background--sorted: var(--ds-color-neutral-background-subtle);
--dsc-table-header-background: var(--ds-color-neutral-background-default);
--dsc-table-header-divider: 2px solid var(--ds-color-neutral-border-subtle);
--dsc-table-padding: var(--ds-spacing-2) var(--ds-spacing-3);
--dsc-table-sort-size: var(--ds-spacing-6);

border-collapse: separate; /* Using separate mode to enable border-radius */
border-radius: var(--dsc-table-border-radius);
border-spacing: 0;
text-align: left;
color: var(--dsc-table-color);
width: 100%;
}

.ds-table--sticky-header {
overflow: auto;
}

.ds-table--border .ds-table__row:last-of-type td {
border-bottom: 0;
}

.ds-table--sm {
--dsc-table-padding: var(--ds-spacing-1) var(--ds-spacing-3);
}

.ds-table--md {
--dsc-table-padding: var(--ds-spacing-2) var(--ds-spacing-3);
}
& > :is(tbody, thead) > tr > :is(th, td) {
background: var(--dsc-table-background);
border-bottom: var(--dsc-table-border);
padding: var(--dsc-table-padding);
text-align: inherit;

.ds-table--lg {
--dsc-table-padding: var(--ds-spacing-3) var(--ds-spacing-3);
}

.ds-table__head {
z-index: 0;
box-sizing: border-box;
font-family: inherit;
border-spacing: 0;
border-bottom: 2px solid var(--dsc-table-border-color);
}

.ds-table__header__cell {
padding: var(--dsc-table-padding);
font-family: inherit;
background-color: var(--dsc-table-header-cell-background);
border-spacing: 0;
border-bottom: 2px solid var(--dsc-table-border-color);
}
&:is(th) {
font-weight: 500;
}
}

.ds-table--sticky-header .ds-table__head .ds-table__header__cell {
position: sticky;
top: 0;
z-index: 1;
}
& > thead > tr > :is(th, td) {
background: var(--dsc-table-header-background);
}

.ds-table__header__cell--sortable {
padding: 0;
}
& > thead > tr:last-child > :is(th, td) {
border-bottom: var(--dsc-table-header-divider);
}

.ds-table__header__cell--sortable button {
position: relative;
width: 100%;
border: none;
font-family: inherit;
display: flex;
cursor: pointer;
gap: var(--ds-spacing-1);
align-items: center;
padding: var(--dsc-table-padding);
background-color: transparent;
color: var(--dsc-table-color);
z-index: 2;
}
/* Add rounded border to first and last row */
& > :is(thead, tbody):first-child > tr:first-child > :is(th, td) {
&:first-child {
border-top-left-radius: var(--dsc-table-border-radius);
}

.ds-table__header__cell--sorted button {
background-color: var(--dsc-table-header-sorted-background);
}
&:last-child {
border-top-right-radius: var(--dsc-table-border-radius);
}
}

.ds-table__header__cell--sortable button:focus-visible {
z-index: 3;
outline-offset: -3px;
box-shadow: unset;
}
/* Add rounded border to last row */
& > :is(thead, tbody):last-child > tr:last-child > :is(th, td) {
&:first-child {
border-bottom-left-radius: var(--dsc-table-border-radius);
}

.ds-table__header__cell--sortable button svg {
font-size: 1.2em;
}
&:last-child {
border-bottom-right-radius: var(--dsc-table-border-radius);
}
}

.ds-table__cell {
padding: var(--dsc-table-padding);
border-bottom: 1px solid var(--dsc-table-border-color);
background-color: var(--dsc-table-cell-backround);
}
/**
* Sorting
*/
& > thead > tr > [aria-sort] {
cursor: pointer;
padding: 0;

& > button {
background: none;
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: block;
font: inherit;
padding: var(--dsc-table-padding);
text-align: inherit;
width: 100%;

&:focus-visible {
position: relative; /* Place on top when painting focus border */
}

&::after {
background: currentcolor;
content: '';
display: inline-block;
height: var(--dsc-table-sort-size);
mask: center/contain no-repeat
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.53 4.47a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06L12 6.06l2.97 2.97a.75.75 0 1 0 1.06-1.06zm-3.5 10.5a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 1 0-1.06-1.06L12 17.94z'/%3E%3C/svg%3E");
vertical-align: middle;
width: var(--dsc-table-sort-size);
}
}

&[aria-sort='ascending'] > button::after {
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M11.47 7.97a.75.75 0 0 1 1.06 0l5.5 5.5a.75.75 0 1 1-1.06 1.06L12 9.56l-4.97 4.97a.75.75 0 0 1-1.06-1.06z'/%3E%3C/svg%3E");
}

&[aria-sort='descending'] > button::after {
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5.97 9.47a.75.75 0 0 1 1.06 0L12 14.44l4.97-4.97a.75.75 0 1 1 1.06 1.06l-5.5 5.5a.75.75 0 0 1-1.06 0l-5.5-5.5a.75.75 0 0 1 0-1.06'/%3E%3C/svg%3E");
}

&:not([aria-sort='none']) > button {
background: var(--dsc-table-header-background--sorted);
}
}

.ds-table--zebra .ds-table__row {
border-bottom: 0;
}
/**
* Sizes
*/
&[data-size='sm'] {
--dsc-table-padding: var(--ds-spacing-1) var(--ds-spacing-3);
}

.ds-table--zebra tr:nth-child(even) .ds-table__cell {
background-color: var(--dsc-table-cell-zebra-background);
}
&[data-size='lg'] {
--dsc-table-padding: var(--ds-spacing-3) var(--ds-spacing-3);
}

.ds-table--border {
border-radius: var(--dsc-table-border-radius);
border: 1px solid var(--dsc-table-border-color);
}
/**
* Configurations
*/
&[data-border] {
--dsc-table-border-radius: min(1rem, var(--ds-border-radius-md));

.ds-table--border .ds-table__head .ds-table__header__cell:first-of-type {
border-top-left-radius: var(--dsc-table-border-radius);
overflow: hidden;
}
border: var(--dsc-table-border);

.ds-table--border .ds-table__head .ds-table__header__cell:last-of-type {
border-top-right-radius: var(--dsc-table-border-radius);
overflow: hidden;
}
& > :is(thead, tbody):last-child > tr:last-child > :is(th, td) {
border-bottom: 0;
}
}

.ds-table--border .ds-table__row:last-of-type .ds-table__cell:first-of-type {
border-bottom-left-radius: var(--dsc-table-border-radius);
overflow: hidden;
}
&[data-sticky-header] {
position: relative;
overflow: auto;

.ds-table--border .ds-table__row:last-of-type .ds-table__cell:last-of-type {
border-bottom-right-radius: var(--dsc-table-border-radius);
overflow: hidden;
}
& > thead > tr > :is(th, td) {
position: sticky;
top: 0;
}
}

@media (hover: hover) and (pointer: fine) {
.ds-table--hover .ds-table__row:hover .ds-table__cell {
background-color: var(--dsc-table-cell-hover-backround);
&[data-zebra] > :is(thead, tbody) > tr:nth-child(even) > :is(th, td) {
background: var(--dsc-table-background--zebra);
}

.ds-table__header__cell--sortable button:hover {
background-color: var(--dsc-table-header-sorted-hover);
/**
* States
*/
@media (hover: hover) and (pointer: fine) {
&[data-hover] > tbody > tr:hover > :is(th, td) {
background: var(--dsc-table-background--hover);
}

& > thead > tr > [aria-sort]:hover {
background: var(--dsc-table-header-background--hover);
}
}
}
34 changes: 30 additions & 4 deletions packages/react/src/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,14 @@ export const Sortable: Story = (args) => {
<TableHead>
<TableRow>
<TableHeaderCell
sortable
sort={sortField === 'navn' ? sortDirection : undefined}
sort={sortField === 'navn' ? sortDirection : 'none'}
onClick={() => handleSort('navn')}
>
Navn
</TableHeaderCell>
<TableHeaderCell>Epost</TableHeaderCell>
<TableHeaderCell
sortable
sort={sortField === 'telefon' ? sortDirection : undefined}
sort={sortField === 'telefon' ? sortDirection : 'none'}
onClick={() => handleSort('telefon')}
>
Telefon
Expand Down Expand Up @@ -292,3 +290,31 @@ export const FixedTable: Story = (args) => {
</Table>
);
};

export const MultipleHeaderRows: Story = (args) => {
const rows = Array.from({ length: 50 }, (_, i) => i + 1);
return (
<Table {...args}>
<TableHead>
<TableRow>
<TableHeaderCell>Header 1</TableHeaderCell>
<TableHeaderCell colSpan={2}>Header 2</TableHeaderCell>
</TableRow>
<TableRow>
<TableHeaderCell>Header 3</TableHeaderCell>
<TableHeaderCell>Header 4</TableHeaderCell>
<TableHeaderCell>Header 5</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row}>
<TableCell>{`Cell ${row}1`}</TableCell>
<TableCell>{`Cell ${row}2`}</TableCell>
<TableCell>{`Cell ${row}3`}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
};
22 changes: 8 additions & 14 deletions packages/react/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type TableProps = {
} & Omit<React.TableHTMLAttributes<HTMLTableElement>, 'border'>;

export const Table = React.forwardRef<HTMLTableElement, TableProps>(
(
function Table(
{
zebra = false,
stickyHeader = false,
Expand All @@ -44,21 +44,17 @@ export const Table = React.forwardRef<HTMLTableElement, TableProps>(
...rest
},
ref,
) => {
) {
return (
<Paragraph asChild size={size}>
<table
className={cl('ds-table', className)}
data-border={border || undefined}
data-hover={hover || undefined}
data-size={size}
data-sticky-header={stickyHeader || undefined}
data-zebra={zebra || undefined}
ref={ref}
className={cl(
'ds-table',
`ds-table--${size}`,
zebra && 'ds-table--zebra',
stickyHeader && 'ds-table--sticky-header',
border && 'ds-table--border',
hover && 'ds-table--hover',
`ds-paragraph--${size}`,
className,
)}
{...rest}
>
{children}
Expand All @@ -67,5 +63,3 @@ export const Table = React.forwardRef<HTMLTableElement, TableProps>(
);
},
);

Table.displayName = 'Table';
Loading

0 comments on commit dda5b21

Please sign in to comment.