Merge pull request #30654 from backstage/bui-table
BUI - Move Table to React Aria
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood.
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,133 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { TableSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
tablePropDefs,
|
||||
tableHeaderPropDefs,
|
||||
tableBodyPropDefs,
|
||||
tablePaginationPropDefs,
|
||||
tableUsageSnippet,
|
||||
tableBasicSnippet,
|
||||
tableRowClickSnippet,
|
||||
tableHybridSnippet,
|
||||
tableCellInteractionsSnippet,
|
||||
tablePaginationSnippet,
|
||||
tableSelectionSnippet,
|
||||
tableSortingSnippet,
|
||||
columnPropDefs,
|
||||
rowPropDefs,
|
||||
cellPropDefs,
|
||||
} from './table.props';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
|
||||
<PageTitle
|
||||
title="Table"
|
||||
description="A table component that can be used to display data in a grid. (Coming soon)"
|
||||
description="A flexible table component built on top of TanStack Table with built-in styling, interactions, and pagination support."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<TableSnippet story="TableRockBand" />}
|
||||
code={tableBasicSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={tableUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Table
|
||||
|
||||
The main table component that renders data in a structured grid format.
|
||||
|
||||
<PropsTable data={tablePropDefs} />
|
||||
|
||||
### TableHeader
|
||||
|
||||
The header section of the table that contains the column definitions.
|
||||
|
||||
<PropsTable data={tableHeaderPropDefs} />
|
||||
|
||||
### Column
|
||||
|
||||
A column definition that describes how a column should be rendered.
|
||||
|
||||
<PropsTable data={columnPropDefs} />
|
||||
|
||||
### TableBody
|
||||
|
||||
The body section of the table that contains the rows.
|
||||
|
||||
<PropsTable data={tableBodyPropDefs} />
|
||||
|
||||
### Row
|
||||
|
||||
A row definition that describes how a row should be rendered.
|
||||
|
||||
<PropsTable data={rowPropDefs} />
|
||||
|
||||
### Cell
|
||||
|
||||
A cell definition that describes how a cell should be rendered.
|
||||
|
||||
<PropsTable data={cellPropDefs} />
|
||||
|
||||
### TablePagination
|
||||
|
||||
A pagination component designed to work with the Table component.
|
||||
|
||||
<PropsTable data={tablePaginationPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Table
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Row selection
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Row Clicks
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Pagination
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Sorting
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Asynchronous loading
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Empty state
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Column resizing
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
### Column reordering
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
### Column pinning
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
### Column visibility
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
<Theming component="Table" />
|
||||
|
||||
<ChangelogComponent component="table" />
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '../../utils/propDefs';
|
||||
|
||||
export const tablePropDefs: Record<string, PropDef> = {
|
||||
selectionBehavior: {
|
||||
type: 'enum',
|
||||
values: ['toggle', 'replace'],
|
||||
default: 'toggle',
|
||||
description: 'How multiple selection should behave in the collection.',
|
||||
},
|
||||
disabledBehavior: {
|
||||
type: 'enum',
|
||||
values: ['selection', 'all'],
|
||||
default: 'selection',
|
||||
description:
|
||||
'Whether disabledKeys applies to all interactions, or only selection.',
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
description: 'A list of row keys to disable.',
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['single', 'multiple'],
|
||||
description: 'The type of selection that is allowed in the collection.',
|
||||
},
|
||||
selectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
description: 'The currently selected keys in the collection (controlled).',
|
||||
},
|
||||
defaultSelectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
description: 'The initial selected keys in the collection (uncontrolled).',
|
||||
},
|
||||
onRowAction: {
|
||||
type: 'enum',
|
||||
values: ['(key: Key) => void'],
|
||||
description:
|
||||
'Handler that is called when a user performs an action on the row.',
|
||||
},
|
||||
onSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(keys: Selection) => void'],
|
||||
description: 'Handler that is called when the selection changes.',
|
||||
},
|
||||
onSortChange: {
|
||||
type: 'enum',
|
||||
values: ['(descriptor: SortDescriptor) => any'],
|
||||
description:
|
||||
'Handler that is called when the sorted column or direction changes.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tableHeaderPropDefs: Record<string, PropDef> = {
|
||||
onHoverStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction starts.',
|
||||
},
|
||||
onHoverEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction ends.',
|
||||
},
|
||||
onHoverChange: {
|
||||
type: 'enum',
|
||||
values: ['(isHovering: boolean) => void'],
|
||||
description: 'Handler that is called when the hover state changes.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const columnPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
description: 'The unique id of the column.',
|
||||
},
|
||||
allowsSorting: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the column allows sorting.',
|
||||
},
|
||||
isRowHeader: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether a column is a row header and should be announced by assistive technology during row navigation.',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A string representation of the column's contents, used for accessibility announcements.",
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tableBodyPropDefs: Record<string, PropDef> = {
|
||||
renderEmptyState: {
|
||||
type: 'enum',
|
||||
values: ['(props) => ReactNode'],
|
||||
description:
|
||||
'Provides content to display when there are no rows in the table.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const rowPropDefs: Record<string, PropDef> = {
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A string representation of the row's contents, used for accessibility announcements.",
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the row is disabled.',
|
||||
},
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
description: 'The unique id of the row.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
description: 'The URL to navigate to when the row is clicked.',
|
||||
},
|
||||
hrefLang: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The language of the URL to navigate to when the row is clicked.',
|
||||
},
|
||||
target: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The target of the URL to navigate to when the row is clicked.',
|
||||
},
|
||||
rel: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The relationship of the URL to navigate to when the row is clicked.',
|
||||
},
|
||||
onAction: {
|
||||
type: 'enum',
|
||||
values: ['() => void'],
|
||||
description:
|
||||
"Handler that is called when a user performs an action on the row. The exact user event depends on the collection's selectionBehavior prop and the interaction modality.",
|
||||
},
|
||||
onHoverStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction starts.',
|
||||
},
|
||||
onHoverEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction ends.',
|
||||
},
|
||||
onHoverChange: {
|
||||
type: 'enum',
|
||||
values: ['(isHovering: boolean) => void'],
|
||||
description: 'Handler that is called when the hover state changes.',
|
||||
},
|
||||
onPress: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description:
|
||||
'Handler that is called when the press is released over the target.',
|
||||
},
|
||||
onPressStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description: 'Handler that is called when a press interaction starts.',
|
||||
},
|
||||
onPressEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description:
|
||||
'Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.',
|
||||
},
|
||||
onPressChange: {
|
||||
type: 'enum',
|
||||
values: ['(isPressed: boolean) => void'],
|
||||
description: 'Handler that is called when the press state changes.',
|
||||
},
|
||||
onPressUp: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description:
|
||||
'Handler that is called when a press is released over the target, regardless of whether it started on the target or not.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const cellPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
description: 'The unique id of the cell.',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A string representation of the cell's contents, used for features like typeahead.",
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tablePaginationPropDefs: Record<string, PropDef> = {
|
||||
pageIndex: {
|
||||
type: 'number',
|
||||
description: 'The current page index.',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
description: 'The number of items per page.',
|
||||
},
|
||||
setPageIndex: {
|
||||
type: 'enum',
|
||||
values: ['(pageIndex: number) => void'],
|
||||
description: 'Handler that is called when the page index changes.',
|
||||
},
|
||||
setPageSize: {
|
||||
type: 'enum',
|
||||
values: ['(pageSize: number) => void'],
|
||||
description: 'Handler that is called when the page size changes.',
|
||||
},
|
||||
rowCount: {
|
||||
type: 'number',
|
||||
description: 'The total number of rows in the table.',
|
||||
},
|
||||
onNextPage: {
|
||||
type: 'enum',
|
||||
values: ['() => void'],
|
||||
description: 'Handler that is called when the next page is requested.',
|
||||
},
|
||||
onPreviousPage: {
|
||||
type: 'enum',
|
||||
values: ['() => void'],
|
||||
description: 'Handler that is called when the previous page is requested.',
|
||||
},
|
||||
onPageSizeChange: {
|
||||
type: 'enum',
|
||||
values: ['(pageSize: number) => void'],
|
||||
description: 'Handler that is called when the page size changes.',
|
||||
},
|
||||
showPageSizeOptions: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to show the page size options.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePagination } from '@backstage/ui';
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row>
|
||||
<Cell />
|
||||
<CellProfile />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination />`;
|
||||
|
||||
export const tableBasicSnippet = `import { Table, TablePagination } from '@backstage/ui';
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: 'The Beatles',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg',
|
||||
genre: 'Rock, Pop, Psychedelic Rock',
|
||||
yearFormed: 1960,
|
||||
albums: 13
|
||||
},
|
||||
// ... more data
|
||||
];
|
||||
|
||||
const newData = data4.slice(
|
||||
pageIndex * pageSize,
|
||||
(pageIndex + 1) * pageSize,
|
||||
);
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Band name</Column>
|
||||
<Column>Genre</Column>
|
||||
<Column>Year formed</Column>
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{newData.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
/>
|
||||
<Cell title={item.genre} />
|
||||
<Cell title={item.yearFormed.toString()} />
|
||||
<Cell title={item.albums.toString()} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
rowCount={data4.length}
|
||||
setPageIndex={setPageIndex}
|
||||
setPageSize={setPageSize}
|
||||
/>`;
|
||||
@@ -26,6 +26,7 @@ import * as SkeletonStories from '../../../packages/ui/src/components/Skeleton/S
|
||||
import * as CardStories from '../../../packages/ui/src/components/Card/Card.stories';
|
||||
import * as HeaderStories from '../../../packages/ui/src/components/Header/Header.stories';
|
||||
import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPage/HeaderPage.stories';
|
||||
import * as TableStories from '../../../packages/ui/src/components/Table/Table.stories';
|
||||
|
||||
// Helper function to create snippet components
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -67,3 +68,4 @@ export const SkeletonSnippet = createSnippetComponent(SkeletonStories);
|
||||
export const CardSnippet = createSnippetComponent(CardStories);
|
||||
export const HeaderSnippet = createSnippetComponent(HeaderStories);
|
||||
export const HeaderPageSnippet = createSnippetComponent(HeaderPageStories);
|
||||
export const TableSnippet = createSnippetComponent(TableStories);
|
||||
|
||||
@@ -28,6 +28,7 @@ export type PropDef = {
|
||||
default?: string;
|
||||
required?: boolean;
|
||||
responsive?: boolean;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export { breakpoints };
|
||||
|
||||
+77
-32
@@ -9517,36 +9517,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-DataTableRoot {
|
||||
gap: var(--bui-space-3);
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination {
|
||||
padding-top: var(--bui-space-5);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--left {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--right {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--select {
|
||||
min-width: 10.5rem;
|
||||
}
|
||||
|
||||
.bui-FieldError {
|
||||
color: var(--bui-fg-danger);
|
||||
font-size: var(--bui-font-size-2);
|
||||
@@ -10051,12 +10021,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableRoot {
|
||||
.bui-Table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bui-TableHeader {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color .2s ease-in-out;
|
||||
}
|
||||
|
||||
.bui-TableHead {
|
||||
text-align: left;
|
||||
padding: var(--bui-space-3);
|
||||
@@ -10064,6 +10039,33 @@
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableHeadSortButton {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-1);
|
||||
display: inline-flex;
|
||||
|
||||
&:hover svg {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
& svg {
|
||||
opacity: 0;
|
||||
transition: opacity .1s ease-in-out, transform .1s ease-in-out;
|
||||
}
|
||||
|
||||
&[data-sort-order="asc"] svg {
|
||||
opacity: 1;
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
&[data-sort-order="desc"] svg {
|
||||
opacity: 1;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableBody {
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
@@ -10071,6 +10073,10 @@
|
||||
.bui-TableRow {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color .2s ease-in-out;
|
||||
|
||||
&[data-react-aria-pressable="true"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableBody .bui-TableRow:hover {
|
||||
@@ -10080,9 +10086,24 @@
|
||||
.bui-TableCell {
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
|
||||
.bui-TableCellText, .bui-TableCellLink {
|
||||
.bui-TableCellContentWrapper {
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
flex-direction: row;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.bui-TableCellIcon, .bui-TableCellIcon svg {
|
||||
color: var(--bui-fg-primary);
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.bui-TableCellContent {
|
||||
gap: var(--bui-space-0_5);
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
@@ -10130,6 +10151,30 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination {
|
||||
padding-top: var(--bui-space-5);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--left {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--right {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--select {
|
||||
min-width: 10.5rem;
|
||||
}
|
||||
|
||||
.bui-Tabs {
|
||||
--active-tab-left: 0px;
|
||||
--active-tab-right: 0px;
|
||||
|
||||
+133
-128
@@ -5,13 +5,14 @@
|
||||
```ts
|
||||
import { Avatar as Avatar_2 } from '@base-ui-components/react/avatar';
|
||||
import { ButtonProps as ButtonProps_2 } from 'react-aria-components';
|
||||
import { CellProps as CellProps_2 } from 'react-aria-components';
|
||||
import { Collapsible as Collapsible_2 } from '@base-ui-components/react/collapsible';
|
||||
import { ColumnProps } from 'react-aria-components';
|
||||
import { ComponentProps } from 'react';
|
||||
import type { ComponentPropsWithRef } from 'react';
|
||||
import { Context } from 'react';
|
||||
import type { ElementType } from 'react';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { HTMLAttributes } from 'react';
|
||||
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
||||
import { LinkProps as LinkProps_2 } from 'react-aria-components';
|
||||
import { Menu as Menu_2 } from '@base-ui-components/react/menu';
|
||||
@@ -21,21 +22,21 @@ import { ReactElement } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RefAttributes } from 'react';
|
||||
import type { RemixiconComponentType } from '@remixicon/react';
|
||||
import { RowProps } from 'react-aria-components';
|
||||
import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area';
|
||||
import type { SearchFieldProps as SearchFieldProps_2 } from 'react-aria-components';
|
||||
import type { SelectProps as SelectProps_2 } from 'react-aria-components';
|
||||
import type { SwitchProps as SwitchProps_2 } from 'react-aria-components';
|
||||
import { Table as Table_2 } from '@tanstack/react-table';
|
||||
import { TableBodyProps } from 'react-aria-components';
|
||||
import { TableHeaderProps } from 'react-aria-components';
|
||||
import { TableProps } from 'react-aria-components';
|
||||
import type { TabListProps as TabListProps_2 } from 'react-aria-components';
|
||||
import type { TabPanelProps as TabPanelProps_2 } from 'react-aria-components';
|
||||
import type { TabProps as TabProps_2 } from 'react-aria-components';
|
||||
import { TabsProps as TabsProps_2 } from 'react-aria-components';
|
||||
import { TdHTMLAttributes } from 'react';
|
||||
import type { TextFieldProps as TextFieldProps_2 } from 'react-aria-components';
|
||||
import { ThHTMLAttributes } from 'react';
|
||||
import { TooltipProps as TooltipProps_2 } from 'react-aria-components';
|
||||
import { TooltipTriggerComponentProps } from 'react-aria-components';
|
||||
import type { useRender } from '@base-ui-components/react/use-render';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlignItems = 'stretch' | 'start' | 'center' | 'end';
|
||||
@@ -230,6 +231,43 @@ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Cell: {
|
||||
(props: CellProps): JSX_2.Element;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const CellProfile: (props: CellProfileProps) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CellProfileProps extends CellProps_2 {
|
||||
// (undocumented)
|
||||
color?: 'primary' | 'secondary';
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
href?: string;
|
||||
// (undocumented)
|
||||
name?: string;
|
||||
// (undocumented)
|
||||
src?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CellProps extends CellProps_2 {
|
||||
// (undocumented)
|
||||
color?: 'primary' | 'secondary';
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
href?: string;
|
||||
// (undocumented)
|
||||
leadingIcon?: React.ReactNode | null;
|
||||
// (undocumented)
|
||||
title: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Checkbox: ForwardRefExoticComponent<
|
||||
CheckboxProps & RefAttributes<HTMLButtonElement>
|
||||
@@ -281,6 +319,13 @@ export const Collapsible: {
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const Column: (
|
||||
props: Omit<ColumnProps, 'children'> & {
|
||||
children?: React.ReactNode;
|
||||
},
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto';
|
||||
|
||||
@@ -521,16 +566,17 @@ export const componentDefinitions: {
|
||||
};
|
||||
readonly Table: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-TableRoot';
|
||||
readonly table: 'bui-Table';
|
||||
readonly header: 'bui-TableHeader';
|
||||
readonly body: 'bui-TableBody';
|
||||
readonly row: 'bui-TableRow';
|
||||
readonly head: 'bui-TableHead';
|
||||
readonly headSortButton: 'bui-TableHeadSortButton';
|
||||
readonly caption: 'bui-TableCaption';
|
||||
readonly cell: 'bui-TableCell';
|
||||
readonly cellText: 'bui-TableCellText';
|
||||
readonly cellLink: 'bui-TableCellLink';
|
||||
readonly cellProfile: 'bui-TableCellProfile';
|
||||
readonly cellContentWrapper: 'bui-TableCellContentWrapper';
|
||||
readonly cellContent: 'bui-TableCellContent';
|
||||
readonly cellIcon: 'bui-TableCellIcon';
|
||||
readonly cellProfileAvatar: 'bui-TableCellProfileAvatar';
|
||||
readonly cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage';
|
||||
readonly cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback';
|
||||
@@ -619,68 +665,6 @@ export type DataAttributesMap = Record<string, DataAttributeValues>;
|
||||
// @public
|
||||
export type DataAttributeValues = readonly (string | number | boolean)[];
|
||||
|
||||
// @public
|
||||
export const DataTable: {
|
||||
Root: <TData>(
|
||||
props: {
|
||||
table: Table_2<TData>;
|
||||
} & React.HTMLAttributes<HTMLDivElement>,
|
||||
) => JSX.Element;
|
||||
Pagination: ForwardRefExoticComponent<
|
||||
DataTablePaginationProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
Table: ForwardRefExoticComponent<
|
||||
DataTableTableProps & RefAttributes<HTMLTableElement>
|
||||
>;
|
||||
TableRoot: ForwardRefExoticComponent<
|
||||
Omit<
|
||||
HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement>,
|
||||
'ref'
|
||||
> &
|
||||
RefAttributes<HTMLTableElement>
|
||||
>;
|
||||
TableHeader: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableSectionElement> &
|
||||
RefAttributes<HTMLTableSectionElement>
|
||||
>;
|
||||
TableBody: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableSectionElement> &
|
||||
RefAttributes<HTMLTableSectionElement>
|
||||
>;
|
||||
TableRow: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableRowElement> & RefAttributes<HTMLTableRowElement>
|
||||
>;
|
||||
TableCell: ForwardRefExoticComponent<
|
||||
TdHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
|
||||
>;
|
||||
TableCellText: ForwardRefExoticComponent<
|
||||
TableCellTextProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
TableCellLink: ForwardRefExoticComponent<
|
||||
TableCellLinkProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
TableCellProfile: ForwardRefExoticComponent<
|
||||
TableCellProfileProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
TableHead: ForwardRefExoticComponent<
|
||||
ThHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DataTablePaginationProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DataTableRootProps<TData>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
table: Table_2<TData>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DataTableTableProps
|
||||
extends React.HTMLAttributes<HTMLTableElement> {}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Display = 'none' | 'flex' | 'block' | 'inline';
|
||||
|
||||
@@ -1431,6 +1415,15 @@ export type ResponsivePropDef<T = any> = RegularPropDef<T> & {
|
||||
responsive: true;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function Row<T extends object>({
|
||||
id,
|
||||
columns,
|
||||
children,
|
||||
href,
|
||||
...otherProps
|
||||
}: RowProps<T>): JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ScrollArea: {
|
||||
Root: ForwardRefExoticComponent<
|
||||
@@ -1578,76 +1571,44 @@ export interface SwitchProps extends SwitchProps_2 {
|
||||
// @public
|
||||
export const Tab: (props: TabProps) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Table: (props: TableProps) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TableBody: <T extends object>(
|
||||
props: TableBodyProps<T>,
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TableHeader: <T extends object>({
|
||||
columns,
|
||||
children,
|
||||
}: TableHeaderProps<T>) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export const Table: {
|
||||
Root: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement>
|
||||
>;
|
||||
Header: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableSectionElement> &
|
||||
RefAttributes<HTMLTableSectionElement>
|
||||
>;
|
||||
Body: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableSectionElement> &
|
||||
RefAttributes<HTMLTableSectionElement>
|
||||
>;
|
||||
Head: ForwardRefExoticComponent<
|
||||
ThHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
|
||||
>;
|
||||
Row: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableRowElement> & RefAttributes<HTMLTableRowElement>
|
||||
>;
|
||||
Cell: ForwardRefExoticComponent<
|
||||
TdHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
|
||||
>;
|
||||
CellText: ForwardRefExoticComponent<
|
||||
TableCellTextProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
CellLink: ForwardRefExoticComponent<
|
||||
TableCellLinkProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
CellProfile: ForwardRefExoticComponent<
|
||||
TableCellProfileProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
Caption: ForwardRefExoticComponent<
|
||||
HTMLAttributes<HTMLTableCaptionElement> &
|
||||
RefAttributes<HTMLTableCaptionElement>
|
||||
>;
|
||||
};
|
||||
export function TablePagination(props: TablePaginationProps): JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TableCellLinkProps
|
||||
export interface TablePaginationProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
offset?: number;
|
||||
// (undocumented)
|
||||
href: string;
|
||||
onNextPage?: () => void;
|
||||
// (undocumented)
|
||||
render?: useRender.ComponentProps<'a'>['render'];
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
// (undocumented)
|
||||
title: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TableCellProfileProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
onPreviousPage?: () => void;
|
||||
// (undocumented)
|
||||
name?: string;
|
||||
pageSize?: number;
|
||||
// (undocumented)
|
||||
src?: string;
|
||||
rowCount?: number;
|
||||
// (undocumented)
|
||||
to?: string;
|
||||
setOffset?: (offset: number) => void;
|
||||
// (undocumented)
|
||||
withImage?: boolean;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TableCellTextProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
setPageSize?: (pageSize: number) => void;
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
title: string;
|
||||
showPageSizeOptions?: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -1777,6 +1738,50 @@ export const useBreakpoint: () => {
|
||||
// @public (undocumented)
|
||||
export const useIcons: () => IconContextProps;
|
||||
|
||||
// @public
|
||||
export function useTable<T = any>(
|
||||
config?: UseTableConfig<T>,
|
||||
): UseTableResult<T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseTableConfig<T = any> {
|
||||
data?: T[];
|
||||
pagination?: UseTablePaginationConfig;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseTablePagination<T = any> {
|
||||
data?: T[];
|
||||
nextPage: () => void;
|
||||
offset: number;
|
||||
pageSize: number;
|
||||
paginationProps: TablePaginationProps;
|
||||
previousPage: () => void;
|
||||
setOffset: (offset: number) => void;
|
||||
setPageSize: (pageSize: number) => void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseTablePaginationConfig {
|
||||
defaultOffset?: number;
|
||||
defaultPageSize?: number;
|
||||
offset?: number;
|
||||
onNextPage?: () => void;
|
||||
onOffsetChange?: (offset: number) => void;
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
onPreviousPage?: () => void;
|
||||
pageSize?: number;
|
||||
rowCount?: number;
|
||||
showPageSizeOptions?: boolean;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseTableResult<T = any> {
|
||||
data?: T[];
|
||||
pagination: UseTablePagination<T>;
|
||||
paginationProps: TablePaginationProps;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UtilityProps extends SpaceProps {
|
||||
// (undocumented)
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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 type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
||||
import { DataTable } from '.';
|
||||
import { data, DataProps } from './mocked-components';
|
||||
import { columns } from './mocked-columns';
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
useReactTable,
|
||||
PaginationState,
|
||||
} from '@tanstack/react-table';
|
||||
import { useState } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/DataTable',
|
||||
decorators: [
|
||||
(Story: StoryFn) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Uncontrolled: Story = {
|
||||
render: () => {
|
||||
const table = useReactTable<DataProps>({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable.Root table={table}>
|
||||
<DataTable.Table />
|
||||
<DataTable.Pagination />
|
||||
</DataTable.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Controlled: Story = {
|
||||
render: () => {
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 4,
|
||||
pageSize: 5,
|
||||
});
|
||||
|
||||
const table = useReactTable<DataProps>({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
state: {
|
||||
pagination,
|
||||
},
|
||||
onPaginationChange: setPagination,
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable.Root table={table}>
|
||||
<DataTable.Table />
|
||||
<DataTable.Pagination />
|
||||
</DataTable.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomTable: Story = {
|
||||
render: () => {
|
||||
const table = useReactTable<DataProps>({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable.Root table={table}>
|
||||
<DataTable.TableRoot>
|
||||
<DataTable.TableHeader>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<DataTable.TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map(header => {
|
||||
return (
|
||||
<DataTable.TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</DataTable.TableHead>
|
||||
);
|
||||
})}
|
||||
</DataTable.TableRow>
|
||||
))}
|
||||
</DataTable.TableHeader>
|
||||
<DataTable.TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map(row => (
|
||||
<DataTable.TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
>
|
||||
{row.getVisibleCells().map(cell => (
|
||||
<DataTable.TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</DataTable.TableCell>
|
||||
))}
|
||||
</DataTable.TableRow>
|
||||
))
|
||||
) : (
|
||||
<DataTable.TableRow>
|
||||
<DataTable.TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</DataTable.TableCell>
|
||||
</DataTable.TableRow>
|
||||
)}
|
||||
</DataTable.TableBody>
|
||||
</DataTable.TableRoot>
|
||||
<DataTable.Pagination />
|
||||
</DataTable.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import { Table } from '../Table';
|
||||
import { DataTableRoot } from './Root/DataTableRoot';
|
||||
import { DataTablePagination } from './Pagination/DataTablePagination';
|
||||
import { Table as TanstackTable } from '@tanstack/react-table';
|
||||
import { DataTableTable } from './Table/DataTableTable';
|
||||
|
||||
const TableRoot = forwardRef<
|
||||
React.ElementRef<typeof Table.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof Table.Root>
|
||||
>(({ className, ...props }, ref) => <Table.Root ref={ref} {...props} />);
|
||||
TableRoot.displayName = Table.Root.displayName;
|
||||
|
||||
/**
|
||||
* DataTable component for displaying tabular data with pagination
|
||||
* @public
|
||||
*/
|
||||
export const DataTable = {
|
||||
Root: DataTableRoot as <TData>(
|
||||
props: {
|
||||
table: TanstackTable<TData>;
|
||||
} & React.HTMLAttributes<HTMLDivElement>,
|
||||
) => JSX.Element,
|
||||
Pagination: DataTablePagination,
|
||||
Table: DataTableTable,
|
||||
TableRoot: TableRoot,
|
||||
TableHeader: Table.Header,
|
||||
TableBody: Table.Body,
|
||||
TableRow: Table.Row,
|
||||
TableCell: Table.Cell,
|
||||
TableCellText: Table.CellText,
|
||||
TableCellLink: Table.CellLink,
|
||||
TableCellProfile: Table.CellProfile,
|
||||
TableHead: Table.Head,
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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 type { Meta, StoryObj } from '@storybook/react';
|
||||
import { DataTablePagination } from './DataTablePagination';
|
||||
import {
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import { data, DataProps } from '../mocked-components';
|
||||
import { columns } from '../mocked-columns';
|
||||
import { DataTable } from '../DataTable';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/DataTable/Pagination',
|
||||
component: DataTablePagination,
|
||||
} satisfies Meta<typeof DataTablePagination>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const table = useReactTable<DataProps>({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable.Root table={table}>
|
||||
<DataTable.Pagination />
|
||||
</DataTable.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import { Text } from '../../Text';
|
||||
import { DataTablePaginationProps } from './types';
|
||||
import { ButtonIcon } from '../../ButtonIcon';
|
||||
import clsx from 'clsx';
|
||||
import { Select } from '../../Select';
|
||||
import { useDataTable } from '../Root/DataTableRoot';
|
||||
import { Icon } from '../../Icon';
|
||||
|
||||
/** @public */
|
||||
const DataTablePagination = forwardRef(
|
||||
(
|
||||
props: DataTablePaginationProps,
|
||||
ref: React.ForwardedRef<HTMLDivElement>,
|
||||
) => {
|
||||
const { className, ...rest } = props;
|
||||
const { table } = useDataTable();
|
||||
const pageIndex = table?.getState().pagination.pageIndex;
|
||||
const pageSize = table?.getState().pagination.pageSize;
|
||||
const rowCount = table?.getRowCount();
|
||||
const fromCount = (pageIndex ?? 0) * (pageSize ?? 10) + 1;
|
||||
const toCount = Math.min(
|
||||
((pageIndex ?? 0) + 1) * (pageSize ?? 10),
|
||||
rowCount,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{ minWidth: table?.getTotalSize() }}
|
||||
className={clsx('bui-DataTablePagination', className)}
|
||||
{...rest}
|
||||
>
|
||||
<div className="bui-DataTablePagination--left">
|
||||
{!table.options.manualPagination && (
|
||||
<Select
|
||||
name="pageSize"
|
||||
size="small"
|
||||
placeholder="Show 10 results"
|
||||
options={[
|
||||
{ label: 'Show 5 results', value: '5' },
|
||||
{ label: 'Show 10 results', value: '10' },
|
||||
{ label: 'Show 20 results', value: '20' },
|
||||
{ label: 'Show 30 results', value: '30' },
|
||||
{ label: 'Show 40 results', value: '40' },
|
||||
{ label: 'Show 50 results', value: '50' },
|
||||
]}
|
||||
selectedKey={pageSize?.toString()}
|
||||
onSelectionChange={value => {
|
||||
table?.setPageSize(Number(value));
|
||||
}}
|
||||
className="bui-DataTablePagination--select"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="bui-DataTablePagination--right">
|
||||
<Text variant="body-medium">{`${fromCount} - ${toCount} of ${rowCount}`}</Text>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => table?.previousPage()}
|
||||
isDisabled={!table?.getCanPreviousPage()}
|
||||
icon={<Icon name="chevron-left" />}
|
||||
/>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => table?.nextPage()}
|
||||
isDisabled={!table?.getCanNextPage()}
|
||||
icon={<Icon name="chevron-right" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DataTablePagination.displayName = 'DataTablePagination';
|
||||
|
||||
export { DataTablePagination };
|
||||
@@ -1,5 +0,0 @@
|
||||
.bui-DataTableRoot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-3);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef, createContext, useContext } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { DataTableRootProps } from './types';
|
||||
import { Table } from '@tanstack/react-table';
|
||||
|
||||
type DataTableContextType<TData> = {
|
||||
table: Table<TData>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const DataTableContext = createContext<DataTableContextType<any> | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
/** @public */
|
||||
const DataTableRoot = forwardRef(
|
||||
<TData,>(
|
||||
props: DataTableRootProps<TData>,
|
||||
ref: React.ForwardedRef<HTMLDivElement>,
|
||||
) => {
|
||||
const { className, table, ...rest } = props;
|
||||
|
||||
return (
|
||||
<DataTableContext.Provider value={{ table }}>
|
||||
<div ref={ref} className={clsx('bui-DataTable', className)} {...rest} />
|
||||
</DataTableContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DataTableRoot.displayName = 'DataTableRoot';
|
||||
|
||||
export { DataTableRoot };
|
||||
|
||||
/** @public */
|
||||
export function useDataTable<TData>() {
|
||||
const context = useContext(DataTableContext);
|
||||
if (!context) {
|
||||
throw new Error('useDataTable must be used within a DataTableRoot');
|
||||
}
|
||||
return context as DataTableContextType<TData>;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { DataTableTableProps } from './types';
|
||||
import { Table } from '../../Table';
|
||||
import { useDataTable } from '../Root/DataTableRoot';
|
||||
import { flexRender } from '@tanstack/react-table';
|
||||
|
||||
/** @public */
|
||||
const DataTableTable = forwardRef(
|
||||
(props: DataTableTableProps, ref: React.ForwardedRef<HTMLTableElement>) => {
|
||||
const { className, ...rest } = props;
|
||||
const { table } = useDataTable();
|
||||
|
||||
return (
|
||||
<Table.Root
|
||||
ref={ref}
|
||||
style={{ minWidth: table.getTotalSize() }}
|
||||
className={clsx(className)}
|
||||
{...rest}
|
||||
>
|
||||
<Table.Header>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<Table.Row key={headerGroup.id}>
|
||||
{headerGroup.headers.map(header => {
|
||||
return (
|
||||
<Table.Head
|
||||
key={header.id}
|
||||
style={{ width: header.getSize() }}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</Table.Head>
|
||||
);
|
||||
})}
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map(row => (
|
||||
<Table.Row
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
>
|
||||
{row.getVisibleCells().map(cell => (
|
||||
<Table.Cell
|
||||
key={cell.id}
|
||||
style={{ width: cell.column.getSize() }}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
))
|
||||
) : (
|
||||
<Table.Row>
|
||||
<Table.Cell
|
||||
colSpan={table.getAllColumns().length}
|
||||
className="h-24 text-center"
|
||||
style={{ width: table.getTotalSize() }}
|
||||
>
|
||||
No results.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DataTableTable.displayName = 'DataTableRoot';
|
||||
|
||||
export { DataTableTable };
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export interface DataTableTableProps
|
||||
extends React.HTMLAttributes<HTMLTableElement> {}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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 { ColumnDef } from '@tanstack/react-table';
|
||||
import { DataProps } from './mocked-components';
|
||||
import { Checkbox } from '../Checkbox';
|
||||
import { DataTable } from './DataTable';
|
||||
|
||||
export const columns: ColumnDef<DataProps>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onChange={(checked: boolean) =>
|
||||
table.toggleAllPageRowsSelected(checked)
|
||||
}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<td>
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onChange={(checked: boolean) => row.toggleSelected(checked)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
</td>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ row }) => (
|
||||
<DataTable.TableCellLink
|
||||
title={row.getValue('name')}
|
||||
description={row.original.description}
|
||||
href="/"
|
||||
/>
|
||||
),
|
||||
size: 450,
|
||||
},
|
||||
{
|
||||
accessorKey: 'owner',
|
||||
header: 'Owner',
|
||||
cell: ({ row }) => {
|
||||
const owner = row.getValue('owner') as DataProps['owner'];
|
||||
|
||||
return (
|
||||
<DataTable.TableCellProfile
|
||||
name={owner.name}
|
||||
src={owner.profilePicture}
|
||||
to={owner.link}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'type',
|
||||
header: 'Type',
|
||||
cell: ({ row }) => <DataTable.TableCellText title={row.getValue('type')} />,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
accessorKey: 'tags',
|
||||
header: 'Tags',
|
||||
cell: ({ row }) => <DataTable.TableCellText title={row.getValue('tags')} />,
|
||||
size: 150,
|
||||
},
|
||||
];
|
||||
@@ -20,28 +20,7 @@ import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import type { LinkProps } from './types';
|
||||
import { useNavigate, useHref } from 'react-router-dom';
|
||||
|
||||
// Helper function to determine if a link is external
|
||||
function isExternalLink(href?: string): boolean {
|
||||
if (!href) return false;
|
||||
|
||||
// Check if it's an absolute URL with protocol
|
||||
if (href.startsWith('http://') || href.startsWith('https://')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a protocol-relative URL
|
||||
if (href.startsWith('//')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a mailto: or tel: link
|
||||
if (href.startsWith('mailto:') || href.startsWith('tel:')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
import { isExternalLink } from '../../utils/isExternalLink';
|
||||
|
||||
/** @public */
|
||||
export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 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.
|
||||
@@ -14,93 +14,317 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentType } from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Table } from '.';
|
||||
|
||||
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',
|
||||
},
|
||||
];
|
||||
import { useState } from 'react';
|
||||
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
Column,
|
||||
TableBody,
|
||||
Row,
|
||||
Cell,
|
||||
CellProfile as CellProfileBUI,
|
||||
useTable,
|
||||
} from '.';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { data as data1 } from './mocked-data1';
|
||||
import { data as data2 } from './mocked-data2';
|
||||
import { data as data3 } from './mocked-data3';
|
||||
import { data as data4 } from './mocked-data4';
|
||||
import { RiCactusLine } from '@remixicon/react';
|
||||
import { TablePagination } from '../TablePagination';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Table',
|
||||
component: Table.Root,
|
||||
subcomponents: {
|
||||
Body: Table.Body as ComponentType<unknown>,
|
||||
Cell: Table.Cell as ComponentType<unknown>,
|
||||
Head: Table.Head as ComponentType<unknown>,
|
||||
Header: Table.Header as ComponentType<unknown>,
|
||||
Row: Table.Row as ComponentType<unknown>,
|
||||
},
|
||||
} satisfies Meta<typeof Table>;
|
||||
decorators: [
|
||||
(Story: StoryFn) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head className="w-[100px]">Invoice</Table.Head>
|
||||
<Table.Head>Status</Table.Head>
|
||||
<Table.Head>Method</Table.Head>
|
||||
<Table.Head className="text-right">Amount</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{invoices.map(invoice => (
|
||||
<Table.Row key={invoice.invoice}>
|
||||
<Table.Cell className="font-medium">{invoice.invoice}</Table.Cell>
|
||||
<Table.Cell>{invoice.paymentStatus}</Table.Cell>
|
||||
<Table.Cell>{invoice.paymentMethod}</Table.Cell>
|
||||
<Table.Cell className="text-right">
|
||||
{invoice.totalAmount}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
),
|
||||
export const TableOnly: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
<Column>Lifecycle</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data1.map(item => (
|
||||
<Row key={item.name}>
|
||||
<Cell
|
||||
title={item.name}
|
||||
leadingIcon={<RiCactusLine />}
|
||||
description={item.description}
|
||||
/>
|
||||
<CellProfileBUI
|
||||
name={item.owner.name}
|
||||
src={item.owner.profilePicture}
|
||||
href={item.owner.link}
|
||||
/>
|
||||
<Cell title={item.type} />
|
||||
<Cell title={item.lifecycle} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPaginationUncontrolled: Story = {
|
||||
render: () => {
|
||||
const { data, paginationProps } = useTable({ data: data1 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Owner</Column>
|
||||
<Column>Type</Column>
|
||||
<Column>Lifecycle</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map(item => (
|
||||
<Row key={item.name}>
|
||||
<Cell
|
||||
title={item.name}
|
||||
leadingIcon={<RiCactusLine />}
|
||||
description={item.description}
|
||||
/>
|
||||
<Cell title={item.owner.name} />
|
||||
<Cell title={item.type} />
|
||||
<Cell title={item.lifecycle} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPaginationControlled: Story = {
|
||||
render: () => {
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
|
||||
const { data, paginationProps } = useTable({
|
||||
data: data4,
|
||||
pagination: {
|
||||
offset,
|
||||
pageSize,
|
||||
onOffsetChange: setOffset,
|
||||
onPageSizeChange: setPageSize,
|
||||
onNextPage: () => console.log('Next page analytics'),
|
||||
onPreviousPage: () => console.log('Previous page analytics'),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Band name</Column>
|
||||
<Column>Genre</Column>
|
||||
<Column>Year formed</Column>
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
/>
|
||||
<Cell title={item.genre} />
|
||||
<Cell title={item.yearFormed.toString()} />
|
||||
<Cell title={item.albums.toString()} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />
|
||||
<div style={{ marginTop: '16px', fontSize: '12px', color: '#666' }}>
|
||||
Current state: offset={offset}, pageSize={pageSize}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const TableRockBand: Story = {
|
||||
render: () => {
|
||||
const { data, paginationProps } = useTable({
|
||||
data: data4,
|
||||
pagination: {
|
||||
defaultPageSize: 5,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Band name</Column>
|
||||
<Column>Genre</Column>
|
||||
<Column>Year formed</Column>
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
/>
|
||||
<Cell title={item.genre} />
|
||||
<Cell title={item.yearFormed.toString()} />
|
||||
<Cell title={item.albums.toString()} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const RowClick: Story = {
|
||||
render: () => {
|
||||
const { data, paginationProps } = useTable({
|
||||
data: data4,
|
||||
pagination: {
|
||||
defaultPageSize: 5,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Band name</Column>
|
||||
<Column>Genre</Column>
|
||||
<Column>Year formed</Column>
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map(item => (
|
||||
<Row key={item.name} onAction={() => alert('Row clicked')}>
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
/>
|
||||
<Cell title={item.genre} />
|
||||
<Cell title={item.yearFormed.toString()} />
|
||||
<Cell title={item.albums.toString()} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const RowLink: Story = {
|
||||
render: () => {
|
||||
const { data, paginationProps } = useTable({
|
||||
data: data4,
|
||||
pagination: {
|
||||
defaultPageSize: 5,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Band name</Column>
|
||||
<Column>Genre</Column>
|
||||
<Column>Year formed</Column>
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map(item => (
|
||||
<Row key={item.name} href="/band">
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
/>
|
||||
<Cell title={item.genre} />
|
||||
<Cell title={item.yearFormed.toString()} />
|
||||
<Cell title={item.albums.toString()} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CellText: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data2.map(item => (
|
||||
<Row key={item.name}>
|
||||
<Cell
|
||||
title={item.name}
|
||||
leadingIcon={item.icon}
|
||||
description={item.description}
|
||||
/>
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CellProfile: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data3.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfileBUI
|
||||
name={item.name}
|
||||
src={item.profilePicture}
|
||||
href={item.link}
|
||||
description={item.description}
|
||||
/>
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
.bui-Table {
|
||||
width: 100%;
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.bui-TableHeader {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.bui-TableHead {
|
||||
text-align: left;
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableHeadSortButton {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-1);
|
||||
|
||||
&:hover svg {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
& svg {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
&[data-sort-order='asc'] svg {
|
||||
opacity: 1;
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
&[data-sort-order='desc'] svg {
|
||||
opacity: 1;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableBody {
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableRow {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color 0.2s ease-in-out;
|
||||
|
||||
&[data-react-aria-pressable='true'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-TableBody .bui-TableRow:hover {
|
||||
background-color: var(--bui-gray-2);
|
||||
}
|
||||
|
||||
.bui-TableCell {
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
|
||||
.bui-TableCell {
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
|
||||
.bui-TableCellContentWrapper {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-TableCellIcon,
|
||||
.bui-TableCellIcon svg {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableCellContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-0_5);
|
||||
}
|
||||
|
||||
.bui-TableCellProfile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--bui-space-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatar {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
border-radius: 100%;
|
||||
user-select: none;
|
||||
font-weight: 500;
|
||||
color: var(--bui-fg-primary);
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatarImage {
|
||||
object-fit: cover;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatarFallback {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
box-shadow: inset 0 0 0 1px var(--bui-border);
|
||||
border-radius: var(--bui-radius-full);
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { TableCell } from './TableCell/TableCell';
|
||||
import { TableCellText } from './TableCellText/TableCellText';
|
||||
import { TableCellLink } from './TableCellLink/TableCellLink';
|
||||
import { TableCellProfile } from './TableCellProfile/TableCellProfile';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
|
||||
const TableRoot = forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<table ref={ref} className={clsx(classNames.root, className)} {...props} />
|
||||
);
|
||||
});
|
||||
TableRoot.displayName = 'TableRoot';
|
||||
|
||||
const TableHeader = forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<thead
|
||||
ref={ref}
|
||||
className={clsx(classNames.header, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
TableHeader.displayName = 'TableHeader';
|
||||
|
||||
const TableBody = forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<tbody ref={ref} className={clsx(classNames.body, className)} {...props} />
|
||||
);
|
||||
});
|
||||
TableBody.displayName = 'TableBody';
|
||||
|
||||
const TableRow = forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<tr ref={ref} className={clsx(classNames.row, className)} {...props}>
|
||||
{props.children}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
TableRow.displayName = 'TableRow';
|
||||
|
||||
const TableHead = forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<th ref={ref} className={clsx(classNames.head, className)} {...props} />
|
||||
);
|
||||
});
|
||||
TableHead.displayName = 'TableHead';
|
||||
|
||||
const TableCaption = forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={clsx(classNames.caption, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
TableCaption.displayName = 'TableCaption';
|
||||
|
||||
/**
|
||||
* Table component for displaying tabular data
|
||||
* @public
|
||||
*/
|
||||
export const Table = {
|
||||
Root: TableRoot,
|
||||
Header: TableHeader,
|
||||
Body: TableBody,
|
||||
Head: TableHead,
|
||||
Row: TableRow,
|
||||
Cell: TableCell,
|
||||
CellText: TableCellText,
|
||||
CellLink: TableCellLink,
|
||||
CellProfile: TableCellProfile,
|
||||
Caption: TableCaption,
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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 type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TableCell } from './TableCell';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Table/TableCell',
|
||||
component: TableCell,
|
||||
} satisfies Meta<typeof TableCell>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Hello world',
|
||||
},
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.bui-TableCell {
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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 type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
||||
import { TableCellLink } from './TableCellLink';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Table/TableCellLink',
|
||||
component: TableCellLink,
|
||||
decorators: [
|
||||
(Story: StoryFn) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof TableCellLink>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'I am a link',
|
||||
href: 'https://ui.backstage.io',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
description: 'This is a description',
|
||||
},
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.bui-TableCellLink {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-0_5);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { TableCellLinkProps } from './types';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Link } from '../../Link/Link';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
const TableCellLink = forwardRef<HTMLDivElement, TableCellLinkProps>(
|
||||
({ className, title, description, href, render, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.cellLink, className)}
|
||||
{...props}
|
||||
>
|
||||
{title && <Link href={href}>{title}</Link>}
|
||||
{description && (
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
TableCellLink.displayName = 'TableCellLink';
|
||||
|
||||
export { TableCellLink };
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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 type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
||||
import { TableCellProfile } from './TableCellProfile';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Table/TableCellProfile',
|
||||
component: TableCellProfile,
|
||||
decorators: [
|
||||
(Story: StoryFn) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof TableCellProfile>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
src: 'https://avatars.githubusercontent.com/u/1540635?v=4',
|
||||
name: 'Charles de Dreuille',
|
||||
},
|
||||
};
|
||||
|
||||
export const Fallback: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
src: 'https://avatars.githubusercontent.com/u/15406AAAAAAAAA',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLink: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
to: 'https://www.google.com',
|
||||
},
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.bui-TableCellProfile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--bui-space-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatar {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
border-radius: 100%;
|
||||
user-select: none;
|
||||
font-weight: 500;
|
||||
color: var(--bui-fg-primary);
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatarImage {
|
||||
object-fit: cover;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bui-TableCellProfileAvatarFallback {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
box-shadow: inset 0 0 0 1px var(--bui-border);
|
||||
border-radius: var(--bui-radius-full);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { TableCellProfileProps } from './types';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Link } from '../../Link/Link';
|
||||
import { Avatar } from '@base-ui-components/react/avatar';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
const TableCellProfile = forwardRef<HTMLDivElement, TableCellProfileProps>(
|
||||
({ className, src, name, to, withImage = true, ...rest }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.cellProfile, className)}
|
||||
{...rest}
|
||||
>
|
||||
{withImage && (
|
||||
<Avatar.Root className={classNames.cellProfileAvatar}>
|
||||
<Avatar.Image
|
||||
src={src}
|
||||
width="20"
|
||||
height="20"
|
||||
className={classNames.cellProfileAvatarImage}
|
||||
/>
|
||||
<Avatar.Fallback className={classNames.cellProfileAvatarFallback}>
|
||||
{(name || '')
|
||||
.split(' ')
|
||||
.map(word => word[0])
|
||||
.join('')
|
||||
.toLocaleUpperCase('en-US')
|
||||
.slice(0, 1)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
)}
|
||||
{name && to ? (
|
||||
<Link href={to}>{name}</Link>
|
||||
) : (
|
||||
<Text variant="body-medium">{name}</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
TableCellProfile.displayName = 'TableCellProfile';
|
||||
|
||||
export { TableCellProfile };
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export interface TableCellProfileProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
src?: string;
|
||||
name?: string;
|
||||
to?: string;
|
||||
withImage?: boolean;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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 type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TableCellText } from './TableCellText';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Table/TableCellText',
|
||||
component: TableCellText,
|
||||
} satisfies Meta<typeof TableCellText>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'Hello world',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
description: 'This is a description',
|
||||
},
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.bui-TableCellText {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-0_5);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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 { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { TableCellTextProps } from './types';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
const TableCellText = forwardRef<HTMLDivElement, TableCellTextProps>(
|
||||
({ className, title, description, ...props }, ref) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.cellText, className)}
|
||||
{...props}
|
||||
>
|
||||
{title && <Text variant="body-medium">{title}</Text>}
|
||||
{description && (
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
TableCellText.displayName = 'TableCellText';
|
||||
|
||||
export { TableCellText };
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export interface TableCellTextProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 clsx from 'clsx';
|
||||
import { Text } from '../../Text';
|
||||
import { Link } from '../../Link';
|
||||
import { Cell as ReactAriaCell } from 'react-aria-components';
|
||||
import type { CellProps } from '../types';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
const Cell = (props: CellProps) => {
|
||||
const {
|
||||
className,
|
||||
title,
|
||||
description,
|
||||
color = 'primary',
|
||||
leadingIcon,
|
||||
href,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaCell className={clsx(classNames.cell, className)} {...rest}>
|
||||
<div className={classNames.cellContentWrapper}>
|
||||
{leadingIcon && (
|
||||
<div className={classNames.cellIcon}>{leadingIcon}</div>
|
||||
)}
|
||||
<div className={classNames.cellContent}>
|
||||
{href ? (
|
||||
<Link href={href} variant="body-medium" color={color}>
|
||||
{title}
|
||||
</Link>
|
||||
) : (
|
||||
<Text as="p" variant="body-medium" color={color}>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ReactAriaCell>
|
||||
);
|
||||
};
|
||||
|
||||
Cell.displayName = 'Cell';
|
||||
|
||||
export { Cell };
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 clsx from 'clsx';
|
||||
import { CellProfileProps } from '../types';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Link } from '../../Link/Link';
|
||||
import { Avatar } from '@base-ui-components/react/avatar';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import { Cell as ReactAriaCell } from 'react-aria-components';
|
||||
|
||||
/** @public */
|
||||
export const CellProfile = (props: CellProfileProps) => {
|
||||
const {
|
||||
className,
|
||||
src,
|
||||
name,
|
||||
href,
|
||||
description,
|
||||
color = 'primary',
|
||||
...rest
|
||||
} = props;
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaCell className={clsx(classNames.cell, className)} {...rest}>
|
||||
<div className={classNames.cellContentWrapper}>
|
||||
<div className={classNames.cellIcon}>
|
||||
{src && (
|
||||
<Avatar.Root className={classNames.cellProfileAvatar}>
|
||||
<Avatar.Image
|
||||
src={src}
|
||||
width="20"
|
||||
height="20"
|
||||
className={classNames.cellProfileAvatarImage}
|
||||
/>
|
||||
<Avatar.Fallback className={classNames.cellProfileAvatarFallback}>
|
||||
{(name || '')
|
||||
.split(' ')
|
||||
.map(word => word[0])
|
||||
.join('')
|
||||
.toLocaleUpperCase('en-US')
|
||||
.slice(0, 1)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
)}
|
||||
</div>
|
||||
<div className={classNames.cellContent}>
|
||||
{name && href ? (
|
||||
<Link href={href}>{name}</Link>
|
||||
) : (
|
||||
<Text as="p" variant="body-medium" color={color}>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ReactAriaCell>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2025 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 {
|
||||
Column as ReactAriaColumn,
|
||||
type ColumnProps,
|
||||
} from 'react-aria-components';
|
||||
import { Icon } from '../../Icon';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
export const Column = (
|
||||
props: Omit<ColumnProps, 'children'> & { children?: React.ReactNode },
|
||||
) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaColumn className={classNames.head} {...props}>
|
||||
{({ allowsSorting, sortDirection }) => (
|
||||
<>
|
||||
{props.children}
|
||||
{allowsSorting && (
|
||||
<span aria-hidden="true" className={classNames.headSortButton}>
|
||||
{sortDirection === 'ascending' ? (
|
||||
<Icon name="arrow-up" size={16} />
|
||||
) : (
|
||||
<Icon name="arrow-down" size={16} />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ReactAriaColumn>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2025 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 {
|
||||
Row as ReactAriaRow,
|
||||
RowProps,
|
||||
useTableOptions,
|
||||
Cell,
|
||||
Collection,
|
||||
Checkbox,
|
||||
RouterProvider,
|
||||
} from 'react-aria-components';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useHref } from 'react-router-dom';
|
||||
import { isExternalLink } from '../../../utils/isExternalLink';
|
||||
|
||||
/** @public */
|
||||
export function Row<T extends object>({
|
||||
id,
|
||||
columns,
|
||||
children,
|
||||
href,
|
||||
...otherProps
|
||||
}: RowProps<T>) {
|
||||
const { classNames } = useStyles('Table');
|
||||
const navigate = useNavigate();
|
||||
const isExternal = isExternalLink(href);
|
||||
|
||||
let { selectionBehavior } = useTableOptions();
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{selectionBehavior === 'toggle' && (
|
||||
<Cell>
|
||||
<Checkbox slot="selection" />
|
||||
</Cell>
|
||||
)}
|
||||
<Collection items={columns}>{children}</Collection>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isExternal) {
|
||||
return (
|
||||
<ReactAriaRow id={id} className={classNames.row} {...otherProps}>
|
||||
{content}
|
||||
</ReactAriaRow>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
<ReactAriaRow
|
||||
id={id}
|
||||
className={classNames.row}
|
||||
data-react-aria-pressable="true"
|
||||
{...otherProps}
|
||||
>
|
||||
{content}
|
||||
</ReactAriaRow>
|
||||
</RouterProvider>
|
||||
);
|
||||
}
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 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.
|
||||
@@ -14,21 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import {
|
||||
Table as ReactAriaTable,
|
||||
type TableProps,
|
||||
} from 'react-aria-components';
|
||||
|
||||
/** @public */
|
||||
const TableCell = forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
export const Table = (props: TableProps) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<td ref={ref} className={clsx(classNames.cell, className)} {...props} />
|
||||
<ReactAriaTable
|
||||
className={classNames.table}
|
||||
aria-label="Data table"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
TableCell.displayName = 'TableCell';
|
||||
|
||||
export { TableCell };
|
||||
};
|
||||
+11
-2
@@ -14,6 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
TableBody as ReactAriaTableBody,
|
||||
type TableBodyProps,
|
||||
} from 'react-aria-components';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
export interface DataTablePaginationProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
export const TableBody = <T extends object>(props: TableBodyProps<T>) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return <ReactAriaTableBody className={classNames.body} {...props} />;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 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 {
|
||||
TableHeader as ReactAriaTableHeader,
|
||||
type TableHeaderProps,
|
||||
Checkbox,
|
||||
} from 'react-aria-components';
|
||||
import { Collection, useTableOptions } from 'react-aria-components';
|
||||
import { Column } from './Column';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
export const TableHeader = <T extends object>({
|
||||
columns,
|
||||
children,
|
||||
}: TableHeaderProps<T>) => {
|
||||
let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions();
|
||||
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaTableHeader className={classNames.header}>
|
||||
{/* Add extra columns for drag and drop and selection. */}
|
||||
{allowsDragging && <Column />}
|
||||
{selectionBehavior === 'toggle' && (
|
||||
<Column>
|
||||
{selectionMode === 'multiple' && <Checkbox slot="selection" />}
|
||||
</Column>
|
||||
)}
|
||||
<Collection items={columns}>{children}</Collection>
|
||||
</ReactAriaTableHeader>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2025 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 type { TablePaginationProps } from '../../TablePagination/types';
|
||||
|
||||
/** @public */
|
||||
export interface UseTablePaginationConfig {
|
||||
/** Total number of rows in the dataset - only needed when data is not provided at the top level */
|
||||
rowCount?: number;
|
||||
|
||||
// Controlled pagination with offset/pageSize (Backstage style)
|
||||
/** Current offset. When provided, pagination is controlled */
|
||||
offset?: number;
|
||||
/** Current page size. When provided, pagination is controlled */
|
||||
pageSize?: number;
|
||||
/** Callback when offset changes */
|
||||
onOffsetChange?: (offset: number) => void;
|
||||
/** Callback when page size changes */
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
|
||||
// Uncontrolled pagination defaults
|
||||
/** Default page size for uncontrolled mode */
|
||||
defaultPageSize?: number;
|
||||
/** Default offset for uncontrolled mode */
|
||||
defaultOffset?: number;
|
||||
|
||||
// Analytics callbacks
|
||||
/** Callback when next page is clicked */
|
||||
onNextPage?: () => void;
|
||||
/** Callback when previous page is clicked */
|
||||
onPreviousPage?: () => void;
|
||||
|
||||
// UI options
|
||||
/** Whether to show page size options */
|
||||
showPageSizeOptions?: boolean;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UseTablePagination<T = any> {
|
||||
/** Props to pass to TablePagination component */
|
||||
paginationProps: TablePaginationProps;
|
||||
/** Current offset */
|
||||
offset: number;
|
||||
/** Current page size */
|
||||
pageSize: number;
|
||||
/** Sliced data for current page - only available when data is provided to useTable */
|
||||
data?: T[];
|
||||
/** Go to next page */
|
||||
nextPage: () => void;
|
||||
/** Go to previous page */
|
||||
previousPage: () => void;
|
||||
/** Set specific offset */
|
||||
setOffset: (offset: number) => void;
|
||||
/** Set page size */
|
||||
setPageSize: (pageSize: number) => void;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UseTableConfig<T = any> {
|
||||
/** Full dataset - when provided, rowCount is calculated automatically and sliced data is returned */
|
||||
data?: T[];
|
||||
/** Pagination configuration */
|
||||
pagination?: UseTablePaginationConfig;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UseTableResult<T = any> {
|
||||
/** Sliced data for current page */
|
||||
data?: T[];
|
||||
/** Props to pass to TablePagination component */
|
||||
paginationProps: TablePaginationProps;
|
||||
/** Pagination utilities */
|
||||
pagination: UseTablePagination<T>;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2025 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 { useState, useMemo, useCallback } from 'react';
|
||||
import type { TablePaginationProps } from '../../TablePagination/types';
|
||||
import type {
|
||||
UseTableConfig,
|
||||
UseTableResult,
|
||||
UseTablePagination,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Hook for managing table state including pagination and future features like sorting.
|
||||
* Supports both controlled and uncontrolled modes using offset/pageSize pattern (Backstage style).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useTable<T = any>(
|
||||
config: UseTableConfig<T> = {},
|
||||
): UseTableResult<T> {
|
||||
const { data, pagination: paginationConfig = {} } = config;
|
||||
|
||||
const {
|
||||
rowCount: providedRowCount,
|
||||
offset: controlledOffset,
|
||||
pageSize: controlledPageSize,
|
||||
onOffsetChange,
|
||||
onPageSizeChange,
|
||||
defaultPageSize = 10,
|
||||
defaultOffset = 0,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
showPageSizeOptions = true,
|
||||
} = paginationConfig;
|
||||
|
||||
// Determine if we're in controlled mode
|
||||
const isControlled =
|
||||
controlledOffset !== undefined || controlledPageSize !== undefined;
|
||||
|
||||
// Calculate row count from data or use provided value
|
||||
const rowCount = data?.length ?? providedRowCount ?? 0;
|
||||
|
||||
// Internal state for uncontrolled mode
|
||||
const [internalOffset, setInternalOffset] = useState(defaultOffset);
|
||||
const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
|
||||
|
||||
// Calculate current values
|
||||
const currentOffset = controlledOffset ?? internalOffset;
|
||||
const currentPageSize = controlledPageSize ?? internalPageSize;
|
||||
|
||||
// Calculate sliced data if data array is provided
|
||||
const currentData = useMemo(() => {
|
||||
if (!data) return undefined;
|
||||
return data.slice(currentOffset, currentOffset + currentPageSize);
|
||||
}, [data, currentOffset, currentPageSize]);
|
||||
|
||||
// Update functions
|
||||
const setOffset = useCallback(
|
||||
(newOffset: number) => {
|
||||
if (isControlled) {
|
||||
onOffsetChange?.(newOffset);
|
||||
} else {
|
||||
setInternalOffset(newOffset);
|
||||
}
|
||||
},
|
||||
[isControlled, onOffsetChange],
|
||||
);
|
||||
|
||||
const setPageSize = useCallback(
|
||||
(newPageSize: number) => {
|
||||
// When changing page size, reset to first page to avoid showing empty results
|
||||
const newOffset = 0;
|
||||
|
||||
if (isControlled) {
|
||||
onPageSizeChange?.(newPageSize);
|
||||
onOffsetChange?.(newOffset);
|
||||
} else {
|
||||
setInternalPageSize(newPageSize);
|
||||
setInternalOffset(newOffset);
|
||||
}
|
||||
},
|
||||
[isControlled, onPageSizeChange, onOffsetChange],
|
||||
);
|
||||
|
||||
const nextPage = useCallback(() => {
|
||||
const nextOffset = currentOffset + currentPageSize;
|
||||
if (nextOffset < rowCount) {
|
||||
onNextPage?.();
|
||||
setOffset(nextOffset);
|
||||
}
|
||||
}, [currentOffset, currentPageSize, rowCount, onNextPage, setOffset]);
|
||||
|
||||
const previousPage = useCallback(() => {
|
||||
if (currentOffset > 0) {
|
||||
onPreviousPage?.();
|
||||
const prevOffset = Math.max(0, currentOffset - currentPageSize);
|
||||
setOffset(prevOffset);
|
||||
}
|
||||
}, [currentOffset, currentPageSize, onPreviousPage, setOffset]);
|
||||
|
||||
// Pagination props for TablePagination component
|
||||
const paginationProps: TablePaginationProps = useMemo(
|
||||
() => ({
|
||||
offset: currentOffset,
|
||||
pageSize: currentPageSize,
|
||||
rowCount,
|
||||
setOffset,
|
||||
setPageSize,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
showPageSizeOptions,
|
||||
}),
|
||||
[
|
||||
currentOffset,
|
||||
currentPageSize,
|
||||
rowCount,
|
||||
setOffset,
|
||||
setPageSize,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
showPageSizeOptions,
|
||||
],
|
||||
);
|
||||
|
||||
const pagination: UseTablePagination<T> = useMemo(
|
||||
() => ({
|
||||
paginationProps,
|
||||
offset: currentOffset,
|
||||
pageSize: currentPageSize,
|
||||
data: currentData,
|
||||
nextPage,
|
||||
previousPage,
|
||||
setOffset,
|
||||
setPageSize,
|
||||
}),
|
||||
[
|
||||
paginationProps,
|
||||
currentOffset,
|
||||
currentPageSize,
|
||||
currentData,
|
||||
nextPage,
|
||||
previousPage,
|
||||
setOffset,
|
||||
setPageSize,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
data: currentData,
|
||||
paginationProps,
|
||||
pagination,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 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.
|
||||
@@ -14,7 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './Table';
|
||||
export * from './TableCellText/types';
|
||||
export * from './TableCellLink/types';
|
||||
export * from './TableCellProfile/types';
|
||||
export { Table } from './components/Table';
|
||||
export { TableHeader } from './components/TableHeader';
|
||||
export { TableBody } from './components/TableBody';
|
||||
export { Column } from './components/Column';
|
||||
export { Row } from './components/Row';
|
||||
export { Cell } from './components/Cell';
|
||||
export { CellProfile } from './components/CellProfile';
|
||||
export { useTable } from './hooks/useTable';
|
||||
|
||||
export type { CellProps, CellProfileProps } from './types';
|
||||
export type {
|
||||
UseTableConfig,
|
||||
UseTableResult,
|
||||
UseTablePagination,
|
||||
UseTablePaginationConfig,
|
||||
} from './hooks/types';
|
||||
|
||||
+102
-55
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface DataProps {
|
||||
name: string;
|
||||
owner: {
|
||||
@@ -23,6 +24,7 @@ export interface DataProps {
|
||||
type: 'documentation' | 'library' | 'service' | 'website' | 'other';
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
lifecycle: 'experimental' | 'production';
|
||||
}
|
||||
|
||||
export const data: DataProps[] = [
|
||||
@@ -37,6 +39,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'A comprehensive service handling user authentication and role-based access control across all applications.',
|
||||
tags: ['security', 'authentication', 'authorization'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-interface-dashboard-and-analytics-platform',
|
||||
@@ -49,6 +52,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Interactive dashboard providing real-time analytics and data visualization for business metrics.',
|
||||
tags: ['analytics', 'visualization', 'dashboard'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'payment-gateway',
|
||||
@@ -61,6 +65,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Secure payment processing system supporting multiple payment methods and currencies.',
|
||||
tags: ['payments', 'security', 'finance'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'real-time-analytics-processing-and-visualization-engine',
|
||||
@@ -73,6 +78,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'High-performance engine for processing and visualizing streaming data analytics.',
|
||||
tags: ['analytics', 'real-time', 'data-processing'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'notification-center',
|
||||
@@ -85,6 +91,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Centralized system for managing and delivering notifications across multiple channels.',
|
||||
tags: ['notifications', 'messaging'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'administrative-control-panel-and-user-management-interface',
|
||||
@@ -97,6 +104,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Admin interface for managing users, permissions, and system configurations.',
|
||||
tags: ['admin', 'user-management', 'configuration'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'search-indexer',
|
||||
@@ -109,6 +117,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service responsible for indexing and updating searchable content across the platform.',
|
||||
tags: ['search', 'indexing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'cross-platform-mobile-application-framework',
|
||||
@@ -121,6 +130,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Framework enabling development of cross-platform mobile applications with shared codebase.',
|
||||
tags: ['mobile', 'framework', 'cross-platform'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'database-migration',
|
||||
@@ -133,6 +143,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Tools and scripts for managing database schema migrations and data transformations.',
|
||||
tags: ['database', 'migration', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'api-gateway',
|
||||
@@ -145,6 +156,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Central entry point for all API requests, handling routing, authentication, and rate limiting.',
|
||||
tags: ['api', 'gateway', 'security', 'routing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'content-management',
|
||||
@@ -157,6 +169,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'System for managing and delivering digital content across multiple channels.',
|
||||
tags: ['content', 'management', 'delivery'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'enterprise-reporting-and-analytics-dashboard',
|
||||
@@ -169,6 +182,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Comprehensive business intelligence platform for enterprise-wide reporting and analytics.',
|
||||
tags: ['analytics', 'reporting', 'business-intelligence'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'image-processing-and-optimization-service',
|
||||
@@ -181,6 +195,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for processing, optimizing, and delivering images across different devices and networks.',
|
||||
tags: ['media', 'optimization', 'processing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'customer-portal',
|
||||
@@ -193,6 +208,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Self-service portal for customers to manage their accounts and access services.',
|
||||
tags: ['customer', 'self-service'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'log-aggregator',
|
||||
@@ -205,6 +221,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Centralized logging system for collecting, processing, and analyzing application logs.',
|
||||
tags: ['logging', 'monitoring', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'identity-provider',
|
||||
@@ -217,6 +234,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service managing user identities and authentication across the organization.',
|
||||
tags: ['identity', 'security', 'authentication'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'document-storage',
|
||||
@@ -229,6 +247,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Secure and scalable document storage system with version control and access management.',
|
||||
tags: ['storage', 'documents', 'version-control'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'workflow-engine',
|
||||
@@ -241,6 +260,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Engine for defining and executing business processes and workflows.',
|
||||
tags: ['workflow', 'automation'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'mobile-backend',
|
||||
@@ -253,6 +273,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Backend services supporting mobile applications with optimized APIs and data synchronization.',
|
||||
tags: ['mobile', 'backend', 'api'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'system-monitoring-and-alerting-dashboard',
|
||||
@@ -265,6 +286,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Real-time monitoring and alerting system for infrastructure and application health.',
|
||||
tags: ['monitoring', 'alerting', 'devops', 'infrastructure'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'email-service',
|
||||
@@ -277,6 +299,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Reliable email delivery service with templates and tracking capabilities.',
|
||||
tags: ['email', 'communication'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-pipeline',
|
||||
@@ -289,6 +312,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'ETL pipeline for processing and transforming large volumes of data.',
|
||||
tags: ['data', 'etl', 'pipeline'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'configuration-manager',
|
||||
@@ -301,6 +325,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Centralized system for managing application configurations across environments.',
|
||||
tags: ['configuration', 'management'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'testing-framework',
|
||||
@@ -313,6 +338,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Comprehensive testing framework supporting various types of automated tests.',
|
||||
tags: ['testing', 'automation', 'qa'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'cache-service',
|
||||
@@ -325,6 +351,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Distributed caching service for improving application performance.',
|
||||
tags: ['caching', 'performance'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'billing-system',
|
||||
@@ -337,6 +364,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'System for managing customer billing, invoicing, and payment processing.',
|
||||
tags: ['billing', 'finance', 'payments'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'comprehensive-product-documentation-and-api-reference',
|
||||
@@ -349,6 +377,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Complete documentation covering product features, APIs, and integration guides.',
|
||||
tags: ['documentation', 'api', 'reference'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'queue-manager',
|
||||
@@ -361,6 +390,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Message queue system for asynchronous processing and event handling.',
|
||||
tags: ['queue', 'messaging', 'async'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'security-scanner',
|
||||
@@ -373,6 +403,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Automated security scanning tool for identifying vulnerabilities in code and infrastructure.',
|
||||
tags: ['security', 'scanning', 'vulnerability'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'user-profile',
|
||||
@@ -385,6 +416,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'User profile management interface with personalization features.',
|
||||
tags: ['user', 'profile', 'personalization'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-warehouse',
|
||||
@@ -397,6 +429,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Centralized data repository for business intelligence and analytics.',
|
||||
tags: ['data', 'warehouse', 'analytics'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'deployment-automation',
|
||||
@@ -409,6 +442,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Automated deployment pipeline for continuous integration and delivery.',
|
||||
tags: ['deployment', 'automation', 'ci-cd', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'chat-service',
|
||||
@@ -421,6 +455,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Real-time chat service supporting text, file sharing, and group conversations.',
|
||||
tags: ['chat', 'communication', 'real-time'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'analytics-dashboard',
|
||||
@@ -433,6 +468,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Interactive dashboard for visualizing and analyzing business metrics.',
|
||||
tags: ['analytics', 'dashboard', 'visualization'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'file-uploader',
|
||||
@@ -445,6 +481,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for handling secure file uploads with progress tracking and validation.',
|
||||
tags: ['storage', 'upload', 'files'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'search-service',
|
||||
@@ -457,6 +494,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Full-text search service with advanced filtering and ranking capabilities.',
|
||||
tags: ['search', 'full-text'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'mobile-sdk',
|
||||
@@ -469,6 +507,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Software development kit for building mobile applications with native features.',
|
||||
tags: ['mobile', 'sdk', 'development'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'performance-monitor',
|
||||
@@ -481,6 +520,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'System for monitoring and analyzing application performance metrics.',
|
||||
tags: ['performance', 'monitoring', 'metrics'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'content-delivery',
|
||||
@@ -493,6 +533,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'CDN service for optimized content delivery across global networks.',
|
||||
tags: ['cdn', 'content', 'delivery'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-authentication',
|
||||
@@ -505,6 +546,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service handling user login, session management, and authentication flows.',
|
||||
tags: ['authentication', 'security', 'user'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-export',
|
||||
@@ -517,6 +559,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for exporting data in various formats with scheduling capabilities.',
|
||||
tags: ['data', 'export', 'scheduling'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'admin-api',
|
||||
@@ -529,6 +572,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'API endpoints for administrative functions and system management.',
|
||||
tags: ['api', 'admin', 'management'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'testing-dashboard',
|
||||
@@ -540,6 +584,7 @@ export const data: DataProps[] = [
|
||||
type: 'website',
|
||||
description: 'Dashboard for monitoring test results and quality metrics.',
|
||||
tags: ['testing', 'dashboard', 'qa'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'message-broker',
|
||||
@@ -552,6 +597,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Message broker service for reliable event-driven communication between services.',
|
||||
tags: ['messaging', 'broker', 'event-driven'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'payment-processor',
|
||||
@@ -564,6 +610,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for processing financial transactions and payment methods.',
|
||||
tags: ['payments', 'finance', 'processing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'document-viewer',
|
||||
@@ -575,6 +622,7 @@ export const data: DataProps[] = [
|
||||
type: 'website',
|
||||
description: 'Web-based document viewer supporting multiple file formats.',
|
||||
tags: ['documents', 'viewer'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'load-balancer',
|
||||
@@ -587,6 +635,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for distributing network traffic across multiple servers.',
|
||||
tags: ['load-balancing', 'networking', 'infrastructure'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'security-audit',
|
||||
@@ -599,6 +648,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Tools and processes for conducting security audits and compliance checks.',
|
||||
tags: ['security', 'audit', 'compliance'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-settings',
|
||||
@@ -611,6 +661,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Interface for users to manage their preferences and account settings.',
|
||||
tags: ['user', 'settings', 'preferences'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-import',
|
||||
@@ -623,6 +674,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for importing and validating data from external sources.',
|
||||
tags: ['data', 'import', 'validation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'infrastructure-monitor',
|
||||
@@ -635,6 +687,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Monitoring system for infrastructure components and resources.',
|
||||
tags: ['monitoring', 'infrastructure', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'notification-manager',
|
||||
@@ -647,6 +700,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for managing and delivering notifications across multiple channels.',
|
||||
tags: ['notifications', 'management'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'analytics-processor',
|
||||
@@ -659,6 +713,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for processing and analyzing business data and metrics.',
|
||||
tags: ['analytics', 'processing', 'metrics'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'file-manager',
|
||||
@@ -670,6 +725,7 @@ export const data: DataProps[] = [
|
||||
type: 'website',
|
||||
description: 'Web interface for managing files and storage resources.',
|
||||
tags: ['files', 'storage', 'management'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'search-index',
|
||||
@@ -681,6 +737,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for maintaining and updating search indices.',
|
||||
tags: ['search', 'indexing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'mobile-authentication',
|
||||
@@ -693,6 +750,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Authentication service specifically designed for mobile applications.',
|
||||
tags: ['mobile', 'authentication', 'security'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'system-monitor',
|
||||
@@ -705,6 +763,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Monitoring service for system health and performance metrics.',
|
||||
tags: ['monitoring', 'system', 'metrics'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'media-processor',
|
||||
@@ -716,6 +775,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for processing and optimizing media files.',
|
||||
tags: ['media', 'processing', 'optimization'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-management',
|
||||
@@ -727,6 +787,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for managing user accounts and permissions.',
|
||||
tags: ['user', 'management', 'security'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-transformer',
|
||||
@@ -739,6 +800,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for transforming data between different formats and structures.',
|
||||
tags: ['data', 'transformation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'admin-dashboard',
|
||||
@@ -751,6 +813,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Administrative interface for system management and monitoring.',
|
||||
tags: ['admin', 'dashboard', 'management'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'test-automation',
|
||||
@@ -762,6 +825,7 @@ export const data: DataProps[] = [
|
||||
type: 'other',
|
||||
description: 'Tools and frameworks for automating testing processes.',
|
||||
tags: ['testing', 'automation', 'qa'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'event-bus',
|
||||
@@ -773,6 +837,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Event-driven communication system between services.',
|
||||
tags: ['events', 'messaging', 'communication'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'invoice-generator',
|
||||
@@ -784,6 +849,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for generating and managing invoices.',
|
||||
tags: ['invoices', 'finance'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'document-editor',
|
||||
@@ -795,6 +861,7 @@ export const data: DataProps[] = [
|
||||
type: 'website',
|
||||
description: 'Web-based document editing interface.',
|
||||
tags: ['documents', 'editor'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'service-discovery',
|
||||
@@ -806,6 +873,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for discovering and registering available services.',
|
||||
tags: ['discovery', 'services', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'security-monitor',
|
||||
@@ -817,6 +885,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for monitoring security events and threats.',
|
||||
tags: ['security', 'monitoring', 'threats'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-preferences',
|
||||
@@ -828,6 +897,7 @@ export const data: DataProps[] = [
|
||||
type: 'website',
|
||||
description: 'Interface for managing user preferences and settings.',
|
||||
tags: ['user', 'preferences'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-validator',
|
||||
@@ -839,6 +909,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for validating data integrity and format.',
|
||||
tags: ['data', 'validation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'infrastructure-automation',
|
||||
@@ -851,6 +922,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Tools for automating infrastructure provisioning and management.',
|
||||
tags: ['infrastructure', 'automation', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'notification-dispatcher',
|
||||
@@ -863,6 +935,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Service for dispatching notifications to appropriate channels.',
|
||||
tags: ['notifications', 'dispatch'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'analytics-collector',
|
||||
@@ -874,6 +947,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for collecting and aggregating analytics data.',
|
||||
tags: ['analytics', 'collection', 'aggregation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'file-processor',
|
||||
@@ -885,6 +959,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for processing and managing files.',
|
||||
tags: ['files', 'processing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'search-analyzer',
|
||||
@@ -896,6 +971,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for analyzing search queries and results.',
|
||||
tags: ['search', 'analysis'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'mobile-notifications',
|
||||
@@ -907,6 +983,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for sending notifications to mobile devices.',
|
||||
tags: ['mobile', 'notifications'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'system-alerts',
|
||||
@@ -918,6 +995,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for managing and dispatching system alerts.',
|
||||
tags: ['alerts', 'system', 'monitoring'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'media-encoder',
|
||||
@@ -929,6 +1007,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for encoding and processing media files.',
|
||||
tags: ['media', 'encoding'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-authorization',
|
||||
@@ -940,6 +1019,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for managing user permissions and access control.',
|
||||
tags: ['authorization', 'security', 'user'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-aggregator',
|
||||
@@ -951,6 +1031,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for aggregating data from multiple sources.',
|
||||
tags: ['data', 'aggregation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'admin-authentication',
|
||||
@@ -962,6 +1043,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Authentication service for administrative access.',
|
||||
tags: ['admin', 'authentication', 'security'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'test-coverage',
|
||||
@@ -973,6 +1055,7 @@ export const data: DataProps[] = [
|
||||
type: 'other',
|
||||
description: 'Tools for measuring and reporting test coverage.',
|
||||
tags: ['testing', 'coverage', 'qa'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'event-processor',
|
||||
@@ -984,6 +1067,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for processing and handling events.',
|
||||
tags: ['events', 'processing'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'payment-validator',
|
||||
@@ -995,6 +1079,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for validating payment transactions.',
|
||||
tags: ['payments', 'validation', 'finance'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'document-converter',
|
||||
@@ -1006,6 +1091,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for converting documents between different formats.',
|
||||
tags: ['documents', 'conversion'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'service-health',
|
||||
@@ -1017,6 +1103,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for monitoring and reporting service health status.',
|
||||
tags: ['health', 'monitoring', 'services'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'security-logger',
|
||||
@@ -1028,6 +1115,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for logging security-related events and activities.',
|
||||
tags: ['security', 'logging'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-analytics',
|
||||
@@ -1040,6 +1128,7 @@ export const data: DataProps[] = [
|
||||
description:
|
||||
'Analytics dashboard for user behavior and engagement metrics.',
|
||||
tags: ['analytics', 'user', 'metrics'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'data-cleaner',
|
||||
@@ -1051,6 +1140,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for cleaning and standardizing data.',
|
||||
tags: ['data', 'cleaning'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'infrastructure-deployer',
|
||||
@@ -1062,6 +1152,7 @@ export const data: DataProps[] = [
|
||||
type: 'other',
|
||||
description: 'Tools for deploying and managing infrastructure resources.',
|
||||
tags: ['infrastructure', 'deployment', 'devops'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'notification-queue',
|
||||
@@ -1073,6 +1164,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Queue system for managing notification delivery.',
|
||||
tags: ['notifications', 'queue'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'analytics-exporter',
|
||||
@@ -1084,6 +1176,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for exporting analytics data in various formats.',
|
||||
tags: ['analytics', 'export'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'file-validator',
|
||||
@@ -1095,6 +1188,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for validating file integrity and format.',
|
||||
tags: ['files', 'validation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'search-optimizer',
|
||||
@@ -1106,6 +1200,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for optimizing search performance and relevance.',
|
||||
tags: ['search', 'optimization'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'mobile-analytics',
|
||||
@@ -1117,6 +1212,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Analytics service specifically for mobile applications.',
|
||||
tags: ['mobile', 'analytics'],
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
{
|
||||
name: 'system-logger',
|
||||
@@ -1128,6 +1224,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for logging system events and activities.',
|
||||
tags: ['logging', 'system'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'media-validator',
|
||||
@@ -1139,6 +1236,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for validating media files and formats.',
|
||||
tags: ['media', 'validation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'user-audit',
|
||||
@@ -1150,6 +1248,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for auditing user activities and access.',
|
||||
tags: ['audit', 'user', 'security'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'data-normalizer',
|
||||
@@ -1161,6 +1260,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Service for normalizing data formats and structures.',
|
||||
tags: ['data', 'normalization'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'admin-authorization',
|
||||
@@ -1172,6 +1272,7 @@ export const data: DataProps[] = [
|
||||
type: 'service',
|
||||
description: 'Authorization service for administrative functions.',
|
||||
tags: ['admin', 'authorization', 'security'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
{
|
||||
name: 'test-reporting',
|
||||
@@ -1183,60 +1284,6 @@ export const data: DataProps[] = [
|
||||
type: 'other',
|
||||
description: 'Tools for generating and managing test reports.',
|
||||
tags: ['testing', 'reporting', 'qa'],
|
||||
},
|
||||
{
|
||||
name: 'event-aggregator',
|
||||
owner: {
|
||||
name: 'platform-team',
|
||||
profilePicture: 'https://github.com/platform-team.png',
|
||||
link: 'https://github.com/orgs/company/teams/platform-team',
|
||||
},
|
||||
type: 'service',
|
||||
description: 'Service for aggregating and processing events.',
|
||||
tags: ['events', 'aggregation'],
|
||||
},
|
||||
{
|
||||
name: 'payment-reconciler',
|
||||
owner: {
|
||||
name: 'finance-team',
|
||||
profilePicture: 'https://github.com/finance-team.png',
|
||||
link: 'https://github.com/orgs/company/teams/finance-team',
|
||||
},
|
||||
type: 'service',
|
||||
description: 'Service for reconciling payment transactions.',
|
||||
tags: ['payments', 'reconciliation', 'finance'],
|
||||
},
|
||||
{
|
||||
name: 'document-validator',
|
||||
owner: {
|
||||
name: 'frontend-team',
|
||||
profilePicture: 'https://github.com/frontend-team.png',
|
||||
link: 'https://github.com/orgs/company/teams/frontend-team',
|
||||
},
|
||||
type: 'service',
|
||||
description: 'Service for validating document formats and content.',
|
||||
tags: ['documents', 'validation'],
|
||||
},
|
||||
{
|
||||
name: 'service-monitor',
|
||||
owner: {
|
||||
name: 'devops-team',
|
||||
profilePicture: 'https://github.com/devops-team.png',
|
||||
link: 'https://github.com/orgs/company/teams/devops-team',
|
||||
},
|
||||
type: 'service',
|
||||
description: 'Service for monitoring service health and performance.',
|
||||
tags: ['monitoring', 'services', 'health'],
|
||||
},
|
||||
{
|
||||
name: 'security-validator',
|
||||
owner: {
|
||||
name: 'security-team',
|
||||
profilePicture: 'https://github.com/security-team.png',
|
||||
link: 'https://github.com/orgs/company/teams/security-team',
|
||||
},
|
||||
type: 'service',
|
||||
description: 'Service for validating security configurations and policies.',
|
||||
tags: ['security', 'validation'],
|
||||
lifecycle: 'production',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 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 { createElement } from 'react';
|
||||
import { RiCactusLine } from '@remixicon/react';
|
||||
|
||||
export interface DataProps {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const data: DataProps[] = [
|
||||
{
|
||||
name: 'Cell with title only',
|
||||
},
|
||||
{
|
||||
name: 'Cell with title and description',
|
||||
description:
|
||||
'A comprehensive service handling user authentication and role-based access control across all applications.',
|
||||
},
|
||||
{
|
||||
name: 'Cell with title and icon',
|
||||
icon: createElement(RiCactusLine),
|
||||
},
|
||||
{
|
||||
name: 'Cell with title, description and icon',
|
||||
|
||||
description:
|
||||
'A comprehensive service handling user authentication and role-based access control across all applications.',
|
||||
icon: createElement(RiCactusLine),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
export interface DataProps {
|
||||
name: string;
|
||||
profilePicture?: string;
|
||||
link?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export const data: DataProps[] = [
|
||||
{
|
||||
name: 'John Lennon',
|
||||
profilePicture:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/85/John_Lennon_1969_%28cropped%29.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Paul McCartney',
|
||||
profilePicture:
|
||||
'https://d2kdkfqxnvpuu9.cloudfront.net/images/giant/51895.jpg?1341834484',
|
||||
},
|
||||
{
|
||||
name: 'Paul McCartney (Broken image link - fallback instead)',
|
||||
profilePicture:
|
||||
'https://d2kdkfqxnvpuu9.clont.net/images/giant/51895.jpg?1341834484',
|
||||
},
|
||||
{
|
||||
name: 'George Harrison (with link)',
|
||||
profilePicture:
|
||||
'https://www.who2.com/wp-content/uploads/2015/10/georgeharrison-6-scaled.jpg',
|
||||
link: 'https://en.wikipedia.org/wiki/George_Harrison',
|
||||
},
|
||||
{
|
||||
name: 'Ringo Starr (with description)',
|
||||
profilePicture:
|
||||
'https://ntvb.tmsimg.com/assets/assets/1686_v9_bb.jpg?w=360&h=480',
|
||||
description: 'Ringo Starr is a drummer and singer.',
|
||||
},
|
||||
{
|
||||
name: 'Ringo Starr (with everything)',
|
||||
profilePicture:
|
||||
'https://ntvb.tmsimg.com/assets/assets/1686_v9_bb.jpg?w=360&h=480',
|
||||
description: 'Ringo Starr is a drummer and singer.',
|
||||
link: 'https://en.wikipedia.org/wiki/George_Harrison',
|
||||
},
|
||||
];
|
||||
File diff suppressed because one or more lines are too long
@@ -1,25 +0,0 @@
|
||||
.bui-TableRoot {
|
||||
width: 100%;
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.bui-TableHead {
|
||||
text-align: left;
|
||||
padding: var(--bui-space-3);
|
||||
font-size: var(--bui-font-size-3);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableBody {
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-TableRow {
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.bui-TableBody .bui-TableRow:hover {
|
||||
background-color: var(--bui-gray-2);
|
||||
}
|
||||
+14
-5
@@ -14,13 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { useRender } from '@base-ui-components/react/use-render';
|
||||
import { CellProps as ReactAriaCellProps } from 'react-aria-components';
|
||||
|
||||
/** @public */
|
||||
export interface TableCellLinkProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface CellProps extends ReactAriaCellProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
href: string;
|
||||
render?: useRender.ComponentProps<'a'>['render'];
|
||||
color?: 'primary' | 'secondary';
|
||||
leadingIcon?: React.ReactNode | null;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface CellProfileProps extends ReactAriaCellProps {
|
||||
src?: string;
|
||||
name?: string;
|
||||
href?: string;
|
||||
description?: string;
|
||||
color?: 'primary' | 'secondary';
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { useArgs } from '@storybook/preview-api';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TablePagination } from './TablePagination';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/TablePagination',
|
||||
component: TablePagination,
|
||||
argTypes: {
|
||||
offset: { control: 'number' },
|
||||
pageSize: { control: 'radio', options: [5, 10, 20, 30, 40, 50] },
|
||||
rowCount: { control: 'number' },
|
||||
showPageSizeOptions: { control: 'boolean', defaultValue: true },
|
||||
setOffset: { action: 'setOffset' },
|
||||
setPageSize: { action: 'setPageSize' },
|
||||
},
|
||||
} satisfies Meta<typeof TablePagination>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
offset: 0,
|
||||
pageSize: 10,
|
||||
rowCount: 100,
|
||||
},
|
||||
render: args => {
|
||||
const [{}, updateArgs] = useArgs();
|
||||
|
||||
return (
|
||||
<TablePagination
|
||||
{...args}
|
||||
setOffset={value => {
|
||||
updateArgs({ offset: value });
|
||||
}}
|
||||
setPageSize={value => {
|
||||
updateArgs({ pageSize: value });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2025 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 clsx from 'clsx';
|
||||
import { Text, ButtonIcon, Select, Icon } from '../..';
|
||||
import type { TablePaginationProps } from './types';
|
||||
|
||||
/**
|
||||
* Pagination controls for Table components with page navigation and size selection.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function TablePagination(props: TablePaginationProps) {
|
||||
const {
|
||||
className,
|
||||
offset,
|
||||
pageSize,
|
||||
rowCount,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onPageSizeChange,
|
||||
setOffset,
|
||||
setPageSize,
|
||||
showPageSizeOptions = true,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const currentOffset = offset ?? 0;
|
||||
const currentPageSize = pageSize ?? 10;
|
||||
|
||||
const fromCount = currentOffset + 1;
|
||||
const toCount = Math.min(currentOffset + currentPageSize, rowCount ?? 0);
|
||||
|
||||
const nextPage = () => {
|
||||
const totalRows = rowCount ?? 0;
|
||||
const nextOffset = currentOffset + currentPageSize;
|
||||
|
||||
// Check if there are more items to navigate to
|
||||
if (nextOffset < totalRows) {
|
||||
onNextPage?.(); // Analytics tracking
|
||||
setOffset?.(nextOffset); // Navigate to next page
|
||||
}
|
||||
};
|
||||
|
||||
const previousPage = () => {
|
||||
// Check if we can go to previous page
|
||||
if (currentOffset > 0) {
|
||||
onPreviousPage?.(); // Analytics tracking
|
||||
const prevOffset = Math.max(0, currentOffset - currentPageSize);
|
||||
setOffset?.(prevOffset); // Navigate to previous page
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('bui-DataTablePagination', className)} {...rest}>
|
||||
<div className="bui-DataTablePagination--left">
|
||||
{showPageSizeOptions && (
|
||||
<Select
|
||||
name="pageSize"
|
||||
size="small"
|
||||
placeholder="Show 10 results"
|
||||
options={[
|
||||
{ label: 'Show 5 results', value: '5' },
|
||||
{ label: 'Show 10 results', value: '10' },
|
||||
{ label: 'Show 20 results', value: '20' },
|
||||
{ label: 'Show 30 results', value: '30' },
|
||||
{ label: 'Show 40 results', value: '40' },
|
||||
{ label: 'Show 50 results', value: '50' },
|
||||
]}
|
||||
selectedKey={pageSize?.toString()}
|
||||
onSelectionChange={value => {
|
||||
const newPageSize = Number(value);
|
||||
setPageSize?.(newPageSize);
|
||||
onPageSizeChange?.(newPageSize);
|
||||
}}
|
||||
className="bui-DataTablePagination--select"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="bui-DataTablePagination--right">
|
||||
<Text
|
||||
as="p"
|
||||
variant="body-medium"
|
||||
>{`${fromCount} - ${toCount} of ${rowCount}`}</Text>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={previousPage}
|
||||
isDisabled={currentOffset === 0}
|
||||
icon={<Icon name="chevron-left" />}
|
||||
aria-label="Previous"
|
||||
/>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={nextPage}
|
||||
isDisabled={
|
||||
rowCount !== undefined &&
|
||||
currentOffset + currentPageSize >= rowCount
|
||||
}
|
||||
icon={<Icon name="chevron-right" />}
|
||||
aria-label="Next"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+3
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 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.
|
||||
@@ -14,7 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './DataTable';
|
||||
export * from './Root/types';
|
||||
export * from './Pagination/types';
|
||||
export * from './Table/types';
|
||||
export { TablePagination } from './TablePagination';
|
||||
export type { TablePaginationProps } from './types';
|
||||
+10
-7
@@ -14,13 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Table } from '@tanstack/react-table';
|
||||
|
||||
/** @public */
|
||||
export interface DataTableRootProps<TData>
|
||||
export interface TablePaginationProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* The table instance.
|
||||
*/
|
||||
table: Table<TData>;
|
||||
offset?: number;
|
||||
pageSize?: number;
|
||||
setPageSize?: (pageSize: number) => void;
|
||||
setOffset?: (offset: number) => void;
|
||||
rowCount?: number;
|
||||
onNextPage?: () => void;
|
||||
onPreviousPage?: () => void;
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
showPageSizeOptions?: boolean;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 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.
|
||||
@@ -22,8 +22,6 @@
|
||||
@import '../components/Checkbox/styles.css';
|
||||
@import '../components/Collapsible/Collapsible.styles.css';
|
||||
@import '../components/Container/styles.css';
|
||||
@import '../components/DataTable/Root/DataTableRoot.styles.css';
|
||||
@import '../components/DataTable/Pagination/DataTablePagination.styles.css';
|
||||
@import '../components/FieldError/FieldError.styles.css';
|
||||
@import '../components/FieldLabel/FieldLabel.styles.css';
|
||||
@import '../components/Flex/styles.css';
|
||||
@@ -35,11 +33,8 @@
|
||||
@import '../components/Menu/Menu.styles.css';
|
||||
@import '../components/Popover/Popover.styles.css';
|
||||
@import '../components/RadioGroup/RadioGroup.styles.css';
|
||||
@import '../components/Table/styles.css';
|
||||
@import '../components/Table/TableCell/TableCell.styles.css';
|
||||
@import '../components/Table/TableCellText/TableCellText.styles.css';
|
||||
@import '../components/Table/TableCellLink/TableCellLink.styles.css';
|
||||
@import '../components/Table/TableCellProfile/TableCellProfile.styles.css';
|
||||
@import '../components/Table/Table.styles.css';
|
||||
@import '../components/TablePagination/TablePagination.styles.css';
|
||||
@import '../components/Tabs/Tabs.styles.css';
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/TextField/TextField.styles.css';
|
||||
|
||||
@@ -34,7 +34,6 @@ export * from './components/Avatar';
|
||||
export * from './components/Button';
|
||||
export * from './components/Card';
|
||||
export * from './components/Collapsible';
|
||||
export * from './components/DataTable';
|
||||
export * from './components/FieldLabel';
|
||||
export * from './components/Header';
|
||||
export * from './components/HeaderPage';
|
||||
@@ -44,6 +43,7 @@ export * from './components/ButtonLink';
|
||||
export * from './components/Checkbox';
|
||||
export * from './components/RadioGroup';
|
||||
export * from './components/Table';
|
||||
export * from './components/TablePagination';
|
||||
export * from './components/Tabs';
|
||||
export * from './components/Text';
|
||||
export * from './components/TextField';
|
||||
|
||||
@@ -241,16 +241,17 @@ export const componentDefinitions = {
|
||||
},
|
||||
Table: {
|
||||
classNames: {
|
||||
root: 'bui-TableRoot',
|
||||
table: 'bui-Table',
|
||||
header: 'bui-TableHeader',
|
||||
body: 'bui-TableBody',
|
||||
row: 'bui-TableRow',
|
||||
head: 'bui-TableHead',
|
||||
headSortButton: 'bui-TableHeadSortButton',
|
||||
caption: 'bui-TableCaption',
|
||||
cell: 'bui-TableCell',
|
||||
cellText: 'bui-TableCellText',
|
||||
cellLink: 'bui-TableCellLink',
|
||||
cellProfile: 'bui-TableCellProfile',
|
||||
cellContentWrapper: 'bui-TableCellContentWrapper',
|
||||
cellContent: 'bui-TableCellContent',
|
||||
cellIcon: 'bui-TableCellIcon',
|
||||
cellProfileAvatar: 'bui-TableCellProfileAvatar',
|
||||
cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage',
|
||||
cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback',
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determines if a link is external.
|
||||
* @param href - The href of the link.
|
||||
* @returns True if the link is external, false otherwise.
|
||||
* @internal
|
||||
*/
|
||||
export function isExternalLink(href?: string): boolean {
|
||||
if (!href) return false;
|
||||
|
||||
// Check if it's an absolute URL with protocol
|
||||
if (href.startsWith('http://') || href.startsWith('https://')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a protocol-relative URL
|
||||
if (href.startsWith('//')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a mailto: or tel: link
|
||||
if (href.startsWith('mailto:') || href.startsWith('tel:')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user