Improve table component styling

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2024-12-11 18:04:02 +00:00
parent 1ccd1d9206
commit e587b22137
12 changed files with 264 additions and 43 deletions
+22 -8
View File
@@ -20,7 +20,7 @@ const Table = React.forwardRef<
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="table">
<table ref={ref} className={['', className].join(' ')} {...props} />
<table ref={ref} className={className} {...props} />
</div>
));
Table.displayName = 'Table';
@@ -29,7 +29,11 @@ const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={['', className].join(' ')} {...props} />
<thead
ref={ref}
className={['table-header', className].join(' ')}
{...props}
/>
));
TableHeader.displayName = 'TableHeader';
@@ -37,7 +41,7 @@ const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody ref={ref} className={['', className].join(' ')} {...props} />
<tbody ref={ref} className={['table-body', className].join(' ')} {...props} />
));
TableBody.displayName = 'TableBody';
@@ -45,7 +49,11 @@ const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot ref={ref} className={['', className].join(' ')} {...props} />
<tfoot
ref={ref}
className={['table-footer', className].join(' ')}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';
@@ -53,7 +61,9 @@ const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr ref={ref} className={['', className].join(' ')} {...props} />
<tr ref={ref} className={['table-row', className].join(' ')} {...props}>
{props.children}
</tr>
));
TableRow.displayName = 'TableRow';
@@ -61,7 +71,7 @@ const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th ref={ref} className={['', className].join(' ')} {...props} />
<th ref={ref} className={['table-head', className].join(' ')} {...props} />
));
TableHead.displayName = 'TableHead';
@@ -69,7 +79,7 @@ const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td ref={ref} className={['', className].join(' ')} {...props} />
<td ref={ref} className={['table-cell', className].join(' ')} {...props} />
));
TableCell.displayName = 'TableCell';
@@ -77,7 +87,11 @@ const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption ref={ref} className={['', className].join(' ')} {...props} />
<caption
ref={ref}
className={['table-caption', className].join(' ')}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';
@@ -0,0 +1,111 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import {
Table,
TableHead,
TableRow,
TableHeader,
TableCell,
TableBody,
TableFooter,
} from '../Table';
const meta = {
title: 'Components/Table/Examples/Simple',
component: Table,
} satisfies Meta<typeof Table>;
const invoices = [
{
invoice: 'INV001',
paymentStatus: 'Paid',
totalAmount: '$250.00',
paymentMethod: 'Credit Card',
},
{
invoice: 'INV002',
paymentStatus: 'Pending',
totalAmount: '$150.00',
paymentMethod: 'PayPal',
},
{
invoice: 'INV003',
paymentStatus: 'Unpaid',
totalAmount: '$350.00',
paymentMethod: 'Bank Transfer',
},
{
invoice: 'INV004',
paymentStatus: 'Paid',
totalAmount: '$450.00',
paymentMethod: 'Credit Card',
},
{
invoice: 'INV005',
paymentStatus: 'Paid',
totalAmount: '$550.00',
paymentMethod: 'PayPal',
},
{
invoice: 'INV006',
paymentStatus: 'Pending',
totalAmount: '$200.00',
paymentMethod: 'Bank Transfer',
},
{
invoice: 'INV007',
paymentStatus: 'Unpaid',
totalAmount: '$300.00',
paymentMethod: 'Credit Card',
},
];
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map(invoice => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
),
};
@@ -18,7 +18,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Table } from '../Table';
const meta = {
title: 'Components/Table/Table',
title: 'Components/Table/Components/Table',
component: Table,
} satisfies Meta<typeof Table>;
@@ -14,17 +14,27 @@
* limitations under the License.
*/
import * as React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { TableBody } from '../Table';
import { Table, TableBody } from '../Table';
const meta = {
title: 'Components/Table/TableBody',
title: 'Components/Table/Components/TableBody',
component: TableBody,
decorators: [
Story => (
<Table>
<Story />
</Table>
),
],
} satisfies Meta<typeof TableBody>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
args: {
children: "Hello World! I'm a basic table body",
},
};
@@ -18,7 +18,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { TableCaption } from '../Table';
const meta = {
title: 'Components/Table/TableCaption',
title: 'Components/Table/Components/TableCaption',
component: TableCaption,
} satisfies Meta<typeof TableCaption>;
@@ -18,7 +18,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { TableCell } from '../Table';
const meta = {
title: 'Components/Table/TableCell',
title: 'Components/Table/Components/TableCell',
component: TableCell,
} satisfies Meta<typeof TableCell>;
@@ -18,7 +18,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { TableFooter } from '../Table';
const meta = {
title: 'Components/Table/TableFooter',
title: 'Components/Table/Components/TableFooter',
component: TableFooter,
} satisfies Meta<typeof TableFooter>;
@@ -18,7 +18,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { TableHead } from '../Table';
const meta = {
title: 'Components/Table/TableHead',
title: 'Components/Table/Components/TableHead',
component: TableHead,
} satisfies Meta<typeof TableHead>;
@@ -14,17 +14,27 @@
* limitations under the License.
*/
import * as React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { TableHeader } from '../Table';
import { Table, TableHeader } from '../Table';
const meta = {
title: 'Components/Table/TableHeader',
title: 'Components/Table/Components/TableHeader',
component: TableHeader,
decorators: [
Story => (
<Table>
<Story />
</Table>
),
],
} satisfies Meta<typeof TableHeader>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
args: {
children: "Hello World! I'm a basic table header",
},
};
@@ -14,17 +14,29 @@
* limitations under the License.
*/
import * as React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { TableRow } from '../Table';
import { Table, TableRow, TableHeader } from '../Table';
const meta = {
title: 'Components/Table/TableRow',
title: 'Components/Table/Components/TableRow',
component: TableRow,
decorators: [
Story => (
<Table>
<TableHeader>
<Story />
</TableHeader>
</Table>
),
],
} satisfies Meta<typeof TableRow>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
args: {
children: "Hello World! I'm a basic table row",
},
};
@@ -2,10 +2,64 @@
position: relative;
overflow: auto;
width: 100%;
background-color: var(--canon-surface-1);
border-radius: var(--canon-border-radius-xs);
padding-bottom: var(--canon-spacing-5xs);
padding-top: var(--canon-spacing-5xs);
font-size: var(--canon-font-size-body);
table {
width: 100%;
caption-side: bottom;
font-size: var(--canon-font-size-sm);
border-collapse: collapse;
}
}
.table-header {
tr {
/* border-bottom: 1px solid var(--canon-outline); */
}
}
.table-head {
text-align: left;
padding: var(--canon-spacing-xs);
}
.table-body {
tr:last-child {
border-bottom: none;
}
}
.table-row {
transition: color 0.2s ease-in-out;
&:hover td {
background-color: var(--canon-surface-3);
}
& .table-cell:first-child {
border-top-left-radius: var(--canon-border-radius-xs);
border-bottom-left-radius: var(--canon-border-radius-xs);
box-shadow: inset 4px 2px 0 0 var(--canon-surface-1),
inset 4px -2px 0 0 var(--canon-surface-1);
padding-left: var(--canon-spacing-xs);
}
& .table-cell:last-child {
border-top-right-radius: var(--canon-border-radius-xs);
border-bottom-right-radius: var(--canon-border-radius-xs);
box-shadow: inset -4px 2px 0 0 var(--canon-surface-1),
inset -4px -2px 0 0 var(--canon-surface-1);
padding-right: var(--canon-spacing-xs);
}
}
.table-cell {
padding: var(--canon-spacing-xs);
background-color: var(--canon-surface-2);
box-shadow: inset 0px 2px 0 0 var(--canon-surface-1),
inset 0px -2px 0 0 var(--canon-surface-1);
}
+31 -21
View File
@@ -34,6 +34,7 @@ body {
--canon-background: #f8f8f8;
--canon-surface-1: #fff;
--canon-surface-2: #f4f4f4;
--canon-surface-3: #f1f1f1;
/* Outlines */
--canon-outline: rgba(0, 0, 0, 0.1);
@@ -64,29 +65,38 @@ body {
--canon-font-bold: 600;
/* Font sizes */
--canon-font-size-2xs: 0.625rem;
--canon-font-size-xs: 0.75rem;
--canon-font-size-sm: 0.875rem;
--canon-font-size-md: 1rem;
--canon-font-size-lg: 1.125rem;
--canon-font-size-xl: 1.25rem;
--canon-font-size-2xl: 1.5rem;
--canon-font-size-3xl: 1.875rem;
--canon-font-size-4xl: 2.25rem;
--canon-font-size-5xl: 3rem;
--canon-font-size-6xl: 3.75rem;
--canon-font-size-7xl: 4.5rem;
--canon-font-size-8xl: 6rem;
--canon-font-size-9xl: 8rem;
--canon-font-size-label: 0.625rem; /* 10px */
--canon-font-size-caption: 0.75rem; /* 12px */
--canon-font-size-body: 0.875rem; /* 14px */
--canon-font-size-subtitle: 1rem; /* 16px */
--canon-font-size-title1: 1.25rem; /* 20px */
--canon-font-size-title2: 1.5rem; /* 24px */
--canon-font-size-title3: 2rem; /* 32px */
--canon-font-size-title4: 3rem; /* 48px */
--canon-font-size-title5: 4rem; /* 64px */
--canon-font-size-display: 5.75rem; /* 48px */
/* Spacing */
--canon-spacing-2xs: 0.25rem;
--canon-spacing-xs: 0.5rem;
--canon-spacing-sm: 0.75rem;
--canon-spacing-md: 1.25rem;
--canon-spacing-lg: 2rem;
--canon-spacing-xl: 3.25rem;
--canon-spacing-2xl: 5.25rem;
--canon-spacing-5xs: 0.125rem; /* 2px */
--canon-spacing-4xs: 0.25rem; /* 4px */
--canon-spacing-3xs: 0.375rem; /* 6px */
--canon-spacing-2xs: 0.5rem; /* 8px */
--canon-spacing-xs: 0.75rem; /* 12px */
--canon-spacing-sm: 1rem; /* 16px */
--canon-spacing-md: 1.5rem; /* 24px */
--canon-spacing-lg: 2rem; /* 32px */
--canon-spacing-xl: 2.5rem; /* 40px */
--canon-spacing-2xl: 3rem; /* 48px */
--canon-spacing-3xl: 3.5rem; /* 56px */
/* Border radius */
--canon-border-radius-2xs: 0.125rem; /* 2px */
--canon-border-radius-xs: 0.25rem; /* 4px */
--canon-border-radius-sm: 0.5rem; /* 8px */
--canon-border-radius-md: 0.75rem; /* 12px */
--canon-border-radius-lg: 1rem; /* 16px */
--canon-border-radius-xl: 1.25rem; /* 20px */
--canon-border-radius-2xl: 1.5rem; /* 24px */
/* Container */
--canon-container-max-width: 1200px;