ui: document table cell wrapper requirement

Added TSDoc comments to ColumnConfig.cell, RowRenderFn, CellProps,
CellTextProps, and CellProfileProps making it explicit that cell render
functions must return a cell component (Cell, CellText, or CellProfile)
as the top-level element. Also added a Table Cell Requirement section
to the package README with correct and incorrect usage examples.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-16 20:16:25 +01:00
parent 3f2788e1ef
commit 872dc1025e
4 changed files with 117 additions and 17 deletions
+28
View File
@@ -17,6 +17,34 @@ yarn add @backstage/ui
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
- [Backstage Documentation](https://backstage.io/docs)
## Table Cell Requirement
When using the `Table` component, every cell rendered via `ColumnConfig.cell` (or
inside a custom `RowRenderFn`) **must** return a cell component as the top-level
element. The available cell components are:
- **`CellText`** — displays a title with optional description and icon.
- **`CellProfile`** — displays an avatar with a name and optional description.
- **`Cell`** — a generic wrapper for fully custom cell content.
Returning bare text, React fragments, or other elements without wrapping them in
one of these cell components will break the table layout.
```tsx
// ✅ Correct — CellText is the top-level element
cell: item => <CellText title={item.name} />;
// ✅ Correct — Cell wraps custom content
cell: item => (
<Cell>
<MyCustomContent value={item.name} />
</Cell>
);
// ❌ Wrong — bare text without a cell wrapper
cell: item => <span>{item.name}</span>;
```
## Writing Changesets for Components
When creating changesets for component-specific changes, add component metadata to help maintain documentation:
+8 -9
View File
@@ -766,7 +766,7 @@ export const Cell: {
displayName: string;
};
// @public (undocumented)
// @public
export type CellOwnProps = {
className?: string;
};
@@ -774,7 +774,7 @@ export type CellOwnProps = {
// @public (undocumented)
export const CellProfile: (props: CellProfileProps) => JSX_2.Element;
// @public (undocumented)
// @public
export type CellProfileOwnProps = {
src?: string;
name?: string;
@@ -784,12 +784,12 @@ export type CellProfileOwnProps = {
className?: string;
};
// @public (undocumented)
// @public
export interface CellProfileProps
extends CellProfileOwnProps,
Omit<CellProps_2, keyof CellProfileOwnProps> {}
// @public (undocumented)
// @public
export interface CellProps
extends CellOwnProps,
Omit<CellProps_2, keyof CellOwnProps> {}
@@ -800,7 +800,7 @@ export const CellText: {
displayName: string;
};
// @public (undocumented)
// @public
export type CellTextOwnProps = {
title: string;
description?: string;
@@ -810,7 +810,7 @@ export type CellTextOwnProps = {
className?: string;
};
// @public (undocumented)
// @public
export interface CellTextProps
extends CellTextOwnProps,
Omit<CellProps_2, keyof CellTextOwnProps> {}
@@ -849,9 +849,8 @@ export interface CheckboxProps
// @public (undocumented)
export const Column: (props: ColumnProps) => JSX_2.Element;
// @public (undocumented)
// @public
export interface ColumnConfig<T extends TableItem> {
// (undocumented)
cell: (item: T) => ReactElement;
// (undocumented)
defaultWidth?: ColumnSize | null;
@@ -2068,7 +2067,7 @@ export interface RowProps<T>
extends RowOwnProps<T>,
Omit<RowProps_2<T>, keyof RowOwnProps> {}
// @public (undocumented)
// @public
export type RowRenderFn<T extends TableItem> = (params: {
item: T;
index: number;
+74 -8
View File
@@ -93,17 +93,35 @@ export interface ColumnProps
extends ColumnOwnProps,
Omit<ReactAriaColumnProps, keyof ColumnOwnProps> {}
/** @public */
/**
* Own props for the {@link Cell} component.
*
* @public
*/
export type CellOwnProps = {
className?: string;
};
/** @public */
/**
* Props for the {@link Cell} component.
*
* `Cell` is a generic cell wrapper for custom cell content. When rendering
* cells via {@link ColumnConfig.cell} or a custom {@link RowRenderFn}, the
* returned element **must** be a cell component (`Cell`, `CellText`, or
* `CellProfile`) at the top level. Returning bare text or other elements
* without a cell wrapper will break the table layout.
*
* @public
*/
export interface CellProps
extends CellOwnProps,
Omit<ReactAriaCellProps, keyof CellOwnProps> {}
/** @public */
/**
* Own props for the {@link CellText} component.
*
* @public
*/
export type CellTextOwnProps = {
title: string;
description?: string;
@@ -113,12 +131,25 @@ export type CellTextOwnProps = {
className?: string;
};
/** @public */
/**
* Props for the {@link CellText} component.
*
* `CellText` renders a table cell with a title and optional description. It
* is one of the cell components (`Cell`, `CellText`, `CellProfile`) that
* **must** be used as the top-level element returned from
* {@link ColumnConfig.cell} or a custom {@link RowRenderFn}.
*
* @public
*/
export interface CellTextProps
extends CellTextOwnProps,
Omit<ReactAriaCellProps, keyof CellTextOwnProps> {}
/** @public */
/**
* Own props for the {@link CellProfile} component.
*
* @public
*/
export type CellProfileOwnProps = {
src?: string;
name?: string;
@@ -128,7 +159,16 @@ export type CellProfileOwnProps = {
className?: string;
};
/** @public */
/**
* Props for the {@link CellProfile} component.
*
* `CellProfile` renders a table cell with an avatar, name, and optional
* description. It is one of the cell components (`Cell`, `CellText`,
* `CellProfile`) that **must** be used as the top-level element returned
* from {@link ColumnConfig.cell} or a custom {@link RowRenderFn}.
*
* @public
*/
export interface CellProfileProps
extends CellProfileOwnProps,
Omit<ReactAriaCellProps, keyof CellProfileOwnProps> {}
@@ -151,10 +191,27 @@ export interface PagePagination extends TablePaginationProps {
/** @public */
export type TablePaginationType = NoPagination | PagePagination;
/** @public */
/**
* Configuration for a single table column.
*
* @public
*/
export interface ColumnConfig<T extends TableItem> {
id: string;
label: string;
/**
* Renders the cell content for this column.
*
* **Important:** The returned element **must** be a cell component at the
* top level either `Cell`, `CellText`, or `CellProfile`. Returning bare
* text, fragments, or other elements without a cell wrapper will break the
* table layout.
*
* @example
* ```tsx
* cell: item => <CellText title={item.name} />
* ```
*/
cell: (item: T) => ReactElement;
header?: () => ReactElement;
isSortable?: boolean;
@@ -173,7 +230,16 @@ export interface RowConfig<T extends TableItem> {
getIsDisabled?: (item: T) => boolean;
}
/** @public */
/**
* Custom render function for table rows.
*
* When using a custom row render function, each cell rendered inside the row
* **must** use a cell component (`Cell`, `CellText`, or `CellProfile`) as
* the top-level element. Returning bare text or other elements without a
* cell wrapper will break the table layout.
*
* @public
*/
export type RowRenderFn<T extends TableItem> = (params: {
item: T;
index: number;