feat(ui): add column width configuration to Table component
Added support for configuring column widths in the Table component using React Aria's ResizableTableContainer. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props. - `width`/`defaultWidth` accept ColumnSize (number, percentage, or fr units) - `minWidth`/`maxWidth` accept ColumnStaticSize (number or percentage) - Wrapped Table in ResizableTableContainer to enable width system - Updated stories to demonstrate proportional column widths Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control.
|
||||
|
||||
Affected components: Table, Column
|
||||
@@ -7,6 +7,8 @@ import { ButtonProps as ButtonProps_2 } from 'react-aria-components';
|
||||
import { CellProps as CellProps_2 } from 'react-aria-components';
|
||||
import { CheckboxProps as CheckboxProps_2 } from 'react-aria-components';
|
||||
import { ColumnProps as ColumnProps_2 } from 'react-aria-components';
|
||||
import type { ColumnSize } from '@react-types/table';
|
||||
import type { ColumnStaticSize } from '@react-types/table';
|
||||
import { ComponentProps } from 'react';
|
||||
import type { ComponentPropsWithRef } from 'react';
|
||||
import { DetailedHTMLProps } from 'react';
|
||||
@@ -478,6 +480,8 @@ export interface ColumnConfig<T extends TableItem> {
|
||||
// (undocumented)
|
||||
cell: (item: T) => ReactNode;
|
||||
// (undocumented)
|
||||
defaultWidth?: ColumnSize | null;
|
||||
// (undocumented)
|
||||
header?: () => ReactNode;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
@@ -490,7 +494,11 @@ export interface ColumnConfig<T extends TableItem> {
|
||||
// (undocumented)
|
||||
label: string;
|
||||
// (undocumented)
|
||||
width?: number | string;
|
||||
maxWidth?: ColumnStaticSize | null;
|
||||
// (undocumented)
|
||||
minWidth?: ColumnStaticSize | null;
|
||||
// (undocumented)
|
||||
width?: ColumnSize | null;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Key } from 'react-aria-components';
|
||||
import { type Key, ResizableTableContainer } from 'react-aria-components';
|
||||
import { TableRoot } from './TableRoot';
|
||||
import { TableHeader } from './TableHeader';
|
||||
import { TableBody } from './TableBody';
|
||||
@@ -132,67 +132,73 @@ export function Table<T extends TableItem>({
|
||||
{liveRegionLabel}
|
||||
</VisuallyHidden>
|
||||
|
||||
<TableRoot
|
||||
selectionMode={selectionMode}
|
||||
selectionBehavior={selectionBehavior}
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={onSelectionChange}
|
||||
sortDescriptor={sort?.descriptor ?? undefined}
|
||||
onSortChange={sort?.onSortChange}
|
||||
disabledKeys={disabledRows}
|
||||
stale={isStale}
|
||||
aria-describedby={liveRegionId}
|
||||
>
|
||||
<TableHeader columns={visibleColumns}>
|
||||
{column =>
|
||||
column.header ? (
|
||||
<>{column.header()}</>
|
||||
) : (
|
||||
<Column
|
||||
id={column.id}
|
||||
isRowHeader={column.isRowHeader}
|
||||
allowsSorting={column.isSortable}
|
||||
>
|
||||
{column.label}
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
items={data}
|
||||
renderEmptyState={
|
||||
emptyState ? () => <Flex p="3">{emptyState}</Flex> : undefined
|
||||
}
|
||||
<ResizableTableContainer>
|
||||
<TableRoot
|
||||
selectionMode={selectionMode}
|
||||
selectionBehavior={selectionBehavior}
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={onSelectionChange}
|
||||
sortDescriptor={sort?.descriptor ?? undefined}
|
||||
onSortChange={sort?.onSortChange}
|
||||
disabledKeys={disabledRows}
|
||||
stale={isStale}
|
||||
aria-describedby={liveRegionId}
|
||||
>
|
||||
{item => {
|
||||
const itemIndex = data?.indexOf(item) ?? -1;
|
||||
|
||||
if (isRowRenderFn(rowConfig)) {
|
||||
return rowConfig({
|
||||
item,
|
||||
index: itemIndex,
|
||||
});
|
||||
<TableHeader columns={visibleColumns}>
|
||||
{column =>
|
||||
column.header ? (
|
||||
<>{column.header()}</>
|
||||
) : (
|
||||
<Column
|
||||
id={column.id}
|
||||
isRowHeader={column.isRowHeader}
|
||||
allowsSorting={column.isSortable}
|
||||
width={column.width}
|
||||
defaultWidth={column.defaultWidth}
|
||||
minWidth={column.minWidth}
|
||||
maxWidth={column.maxWidth}
|
||||
>
|
||||
{column.label}
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
items={data}
|
||||
renderEmptyState={
|
||||
emptyState ? () => <Flex p="3">{emptyState}</Flex> : undefined
|
||||
}
|
||||
>
|
||||
{item => {
|
||||
const itemIndex = data?.indexOf(item) ?? -1;
|
||||
|
||||
return (
|
||||
<Row
|
||||
id={String(item.id)}
|
||||
columns={visibleColumns}
|
||||
href={rowConfig?.getHref?.(item)}
|
||||
onAction={
|
||||
rowConfig?.onClick
|
||||
? () => rowConfig?.onClick?.(item)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{column => (
|
||||
<Fragment key={column.id}>{column.cell(item)}</Fragment>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}}
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
if (isRowRenderFn(rowConfig)) {
|
||||
return rowConfig({
|
||||
item,
|
||||
index: itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Row
|
||||
id={String(item.id)}
|
||||
columns={visibleColumns}
|
||||
href={rowConfig?.getHref?.(item)}
|
||||
onAction={
|
||||
rowConfig?.onClick
|
||||
? () => rowConfig?.onClick?.(item)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{column => (
|
||||
<Fragment key={column.id}>{column.cell(item)}</Fragment>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}}
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
</ResizableTableContainer>
|
||||
{pagination.type === 'page' && (
|
||||
<TablePagination
|
||||
pageSize={pagination.pageSize}
|
||||
|
||||
@@ -54,6 +54,7 @@ export const BasicLocalData: Story = {
|
||||
id: 'name',
|
||||
label: 'Name',
|
||||
isRowHeader: true,
|
||||
defaultWidth: '4fr',
|
||||
cell: item => (
|
||||
<CellText title={item.name} description={item.description} />
|
||||
),
|
||||
@@ -61,16 +62,19 @@ export const BasicLocalData: Story = {
|
||||
{
|
||||
id: 'owner',
|
||||
label: 'Owner',
|
||||
defaultWidth: '1fr',
|
||||
cell: item => <CellText title={item.owner.name} />,
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
label: 'Type',
|
||||
defaultWidth: '1fr',
|
||||
cell: item => <CellText title={item.type} />,
|
||||
},
|
||||
{
|
||||
id: 'lifecycle',
|
||||
label: 'Lifecycle',
|
||||
defaultWidth: '1fr',
|
||||
cell: item => <CellText title={item.lifecycle} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -40,6 +40,7 @@ export const TableRockBand: Story = {
|
||||
id: 'name',
|
||||
label: 'Band name',
|
||||
isRowHeader: true,
|
||||
defaultWidth: '4fr',
|
||||
cell: item => (
|
||||
<CellProfile name={item.name} src={item.image} href={item.website} />
|
||||
),
|
||||
@@ -47,16 +48,19 @@ export const TableRockBand: Story = {
|
||||
{
|
||||
id: 'genre',
|
||||
label: 'Genre',
|
||||
defaultWidth: '4fr',
|
||||
cell: item => <CellText title={item.genre} />,
|
||||
},
|
||||
{
|
||||
id: 'yearFormed',
|
||||
label: 'Year formed',
|
||||
defaultWidth: '1fr',
|
||||
cell: item => <CellText title={item.yearFormed.toString()} />,
|
||||
},
|
||||
{
|
||||
id: 'albums',
|
||||
label: 'Albums',
|
||||
defaultWidth: '1fr',
|
||||
cell: item => <CellText title={item.albums.toString()} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from 'react-aria-components';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { SortDescriptor as ReactStatelySortDescriptor } from 'react-stately';
|
||||
import type { ColumnSize, ColumnStaticSize } from '@react-types/table';
|
||||
import type { TextColors } from '../../types';
|
||||
import { TablePaginationProps } from '../TablePagination';
|
||||
|
||||
@@ -92,7 +93,10 @@ export interface ColumnConfig<T extends TableItem> {
|
||||
header?: () => ReactNode;
|
||||
isSortable?: boolean;
|
||||
isHidden?: boolean;
|
||||
width?: number | string;
|
||||
width?: ColumnSize | null;
|
||||
defaultWidth?: ColumnSize | null;
|
||||
minWidth?: ColumnStaticSize | null;
|
||||
maxWidth?: ColumnStaticSize | null;
|
||||
isRowHeader?: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user