azure-devops-ui
Version:
React components for building web UI in Azure DevOps
730 lines (729 loc) • 29.9 kB
TypeScript
import * as React from "react";
import { IReadonlyObservableValue } from '../../Core/Observable';
import { ScreenSize } from '../../Core/Util/Screen';
import { IFocusZoneProps } from '../../FocusZone';
import { IIconProps } from '../../Icon';
import { ILinkProps } from '../../Link';
import { IList, IListItemDetails, IListRow, IListSelection, ISimpleListCell } from '../../List';
import { ITooltipProps } from '../../TooltipEx';
import { IBehavior } from '../../Utilities/Behavior';
import { IEventDispatch } from '../../Utilities/Dispatch';
import { IItemProvider } from '../../Utilities/Provider';
/**
* ITable<T> is the interace the Table component exposes for use by callers.
* If they ref the component they should access the table using this interface.
*/
export interface ITable<T> extends IList<T> {
}
/**
* An ITableRow is used communicate details about a given row in a table.
*/
export interface ITableRow<T> extends IListRow<T> {
}
/**
* ITableRowDetails are used to describe the details of a given row in the table.
* This information is used in rendering rows.
*/
export interface ITableRowDetails<T> extends IListItemDetails<T> {
/**
* css class name to add to the rendered row element.
*/
className?: string;
/**
* id to add to the rendered row element.
*/
id?: string;
/**
* The caller MUST supply the set of items to be shown through the ItemProvider.
* The IItemProvider allows the caller to store their items in the form that
* bests suits their needs but gives the table a well defined interface for
* requesting the items. This can include async fetching of items through
* observables.
*/
itemProvider: IItemProvider<T | IReadonlyObservableValue<T | undefined>>;
/**
* renderSpacer can be supplied to override the spacer columns before and
* after the actual columns in the table. This is used to apply custom
* semantics to these areas. The standard spacers should be used unless there
* is a specific need otherwise.
*/
renderSpacer?: (rowIndex: number, left: boolean) => React.ReactNode | null;
/**
* Sets the aria role on the tr element.
*/
role?: string;
/**
* A selection object can be supplied for managing the table selection. This
* is not required since the table offers onSelect as a delegate. If the caller
* wants multi-selction they must use an IListSelection that supports multi
* select.
*/
selection?: IListSelection;
/**
* Using singleClickActivation will activate the item when the row is clicked.
* Where setting singleClickActivation to false will require a doubleclick to
* activate a given row.
*/
singleClickActivation?: boolean;
/**
* Optional tooltip props to pass to the table row.
*/
tooltipProps?: ITooltipProps;
}
/**
* TabelRowRenderer function is used to render rows within a table.
*
* @param index The zero based index of the row being rendered.
* @param item The object that represents the row being rendered.
* @param details This contains both row and table properties that should be used
* to render the row and its children appropriately.
*/
export declare type TableRowRenderer<T> = (index: number, item: T, details: ITableRowDetails<T>) => JSX.Element;
/**
* ITableProps<T> are used to render of Table where each row is represented
* by type T.
*
* If the object for a given row implements the renderRow function, this
* function is used instead of the table scoped renderRow function. If no
* renderRow function is available from the item or from the table scope
* the default TableRow component will be used.
*/
export interface ITableProps<T> {
/**
* columns is a required property of a table used to define the column layout
* for the table.
*/
columns: Array<ITableColumn<T>>;
/**
* The caller MUST supply the set of items to be shown through the ItemProvider.
* The IItemProvider allows the caller to store their items in the form that
* bests suits their needs but gives the table a well defined interface for
* requesting the items. This can include async fetching of items through
* observables.
*
* There is simple ArrayItemProvider<T> for those that just have a set of items
* they want to supply without writing a custom ItemProvider.
*/
itemProvider: IItemProvider<T | IReadonlyObservableValue<T | undefined>>;
/**
* ariaLabel allows the caller to describe the elements contents to assistive
* technology.
*/
ariaLabel?: string;
/**
* Behaviors can be added to the table and monitor events and interact with the
* component through the ITable methods.
*/
behaviors?: Array<IBehavior<ITableProps<T>, ITable<T>>>;
/**
* CSS className to add to the table root element.
*/
className?: string;
/**
* CSS className to add to the container element.
*/
containerClassName?: string;
/**
* A custom way to force single-select options in a multi-select selection.
*/
enforceSingleSelect?: boolean;
/**
* The caller can supply an EventDispatch to the table if it wishes to
* participate it extending the behaviors. If one isn't supplied the table will
* create its own dispatcher when behaviors are supplied.
*/
eventDispatch?: IEventDispatch;
/**
* Set to true to set the tabIndex of the first row to -1 instead of 0.
*/
excludeTabStop?: boolean;
/**
* focuszoneProps allows the caller to manage the how the table rows are focused.
* The default focuszone if one isn't supplied is a Vertical non-cyclic focus zone.
*/
focuszoneProps?: IFocusZoneProps | null;
/**
* The maximum height of the table when virtualized. Browsers have issues
* rendering elements that are too large and when the List contains thousands
* of elements, the list renders very large spacer elements to correctly
* position the scroll bar. The large spacer elements cause rendering issues
* across browsers. To bypass this, we need to limit how large the list can
* grow to. By default this size is 100,000px. However, if you have multiple
* items within a scrollable region, this number might need to be reduced.
* For instance, if you have 5 lists that can contain a lot of rows in the
* same scrollable region, you would likely want to set the max height for
* each list to 20,000. Keep in mind that the smaller this number, the harder
* it will be for a user to scroll with precision.
*
* @default 100000
*/
maxHeight?: number;
/**
* showHeader determines whether or not the column headers are shown at the top
* of the table. If a function is passed, this function will be called whenever
* the screen size changes.
*
* @default true
*/
showHeader?: boolean | ((screenSize: ScreenSize) => boolean);
/**
* showLines determines whether or not lines displayed between rows.
*
* @default true
*/
showLines?: boolean;
/**
* showScroll determines whether we allow overflow on the table
* if it is false (as in default), we will add scroll-hidden css class to the table
* @default false
*/
showScroll?: boolean;
/**
* Unique Id for this table.
*/
id?: string;
/**
* pageSize controls the granularity of row rendering. The table always renders
* a full page worth of rows even when they are not needed to fill the viewport.
*
* Smaller values will help reduce the number of wasted rows that are rendered
* outside the viewport, but will force the table to re-render more often as
* scrolling occurs.
*
* @default 10
*/
pageSize?: number;
/**
* Tables MAY render a header above the table contents. This is done through the
* onRenderHeader. Like the renderRow method, there are set of restrictions the
* implmentation MUST follow.
*
* Requirements:
*
* 1) The header function MUST return a singlely rooted component that resolves
* to single rooted element, or return a single element that is either a <tr>
* or another acceptable tag that is marked as a display: table-row.
*
* 2) The header function MUST only include direct child elements that are either
* <td>'s or acceptable elements that are maked as display: table-cell. The
* renderer MUST return one element for each defined column even if there is
* no data to be rendered in the column.
*/
renderHeader?: (columns: Array<ITableColumn<T>>) => JSX.Element;
/**
* When a row's value is given as an ObservableValue with an undefined value,
* the list will render a Loading row for the content. The default will be
* a shimmer row that is semi random and matches the content.
*
* @param index This is the 0 based row index that should be rendered.
* @param details Additional details about this row.
*/
renderLoadingRow?: (index: number, details: ITableRowDetails<T>) => JSX.Element;
/**
* The requirements of this function are quite complicated and before writing
* a new row renderer you should ensure one doesnt already exist that solves
* your needs.
*
* Requirements:
*
* 1) The row function MUST return a singlely rooted component that resolves
* to single rooted element, or return a single element that is either a <tr>
* or another acceptable tag that is marked as a display: table-row.
*
* 2) The row function MUST only include direct child elements that are either
* <td>'s or acceptable elements that are maked as display: table-cell. The
* renderer MUST return one element for each defined column even if there is
* no data to be rendered in the column.
*
* 3) The row function MUST call onFocusItem when a either the row element of
* any of the rows child elements receive the focus. This is needed to ensure
* navigation within the table as well as in and out of the table function
* correctly.
*
* 4) The row function MUST ensure the className for each of the columns is
* added the cell for the given colmn.
*
* 5) The row function MAY dispatch events for behaviors but is not required.
* If any events are dispatched they should be documented on the row renderer.
*
* 6) The row function is responsible for all accessibility and focus
* management within the row.
*
*/
renderRow?: TableRowRenderer<T>;
/**
* renderSpacer can be supplied to override the spacer columns before and
* after the actual columns in the table. This is used to apply custom
* semantics to these areas. The standard spacers should be used unless there
* is a specific need otherwise.
*
* @param rowIndex - The index of the row to render for.
* @param left - True if this is the left spacer, false if it is the right spacer
*/
renderSpacer?: (rowIndex: number, left: boolean) => React.ReactNode | null;
/**
* Set to true to allow text selection for table rows.
*/
selectableText?: boolean;
/**
* onActivate is called when the row is activated. Activation occurs on
* the Enter keystroke or double click.
*
* @param event - This is the event that is causing the activation.
* @param tableRow - Details about the table row being activated.
*/
onActivate?: (event: React.SyntheticEvent<HTMLElement>, tableRow: ITableRow<T>) => void;
/**
* onFocus is called when a item in the list is focused. Preventing default
* on the focus event will prevent row selection from occuring even if
* selectOnFocus is set to true.
*
* @param event This is the event that is causing the activation.
* @param tableRow Details about the list row being activated.
*/
onFocus?: (event: React.SyntheticEvent<HTMLElement>, listRow: ITableRow<T>) => void;
/**
* onSelect is called when the row is selected. Selection occurs on the
* Space keystroke or click.
*
* @param event - This is the event that is causing the selection.
* @param tableRow - Details about the table row being selected.
*/
onSelect?: (event: React.SyntheticEvent<HTMLElement>, tableRow: ITableRow<T>) => void;
/**
* role defines the aria role of the table and defaults to "grid"
* If the table does not have any focusable elements within the rows, set the role as "table" instead of "grid"
*
* @default "grid"
*/
role?: string;
/**
* If the caller has variable height rows they can specify the rowHeight they
* want used to estimate the size of virtualized rows. This means that when rows
* are not rendered, the component will create virtual space for those rows to
* ensure the scrolling behavior acts appropriately.
*
* If the table has fixed size rows there is no need to specify a rowHeight. The
* table will determine the height of the rows after the initial render when
* the observer reports on page visibility.
*
* Question: How do I determine the rowHeight if their are variable height rows.
* This one is a tough question, and the general answer is come up with a fair
* average for the rows on a given page. If the select too large or too small
* scrolling behaviors can become a bit odd, generally select on the smaller
* side if you are unsure.
*/
rowHeight?: number;
/**
* An array of heights to be used when calculating the spacer heights. If not supplied, the heights used
* will be estimations.
*/
rowHeights?: number[];
/**
* scrollable should be set to true if the table is not contained in a
* scrolling element. This will ensure the table scrolls vertically within
* the table element itself.
*/
scrollable?: boolean;
/**
* A selection object can be supplied for managing the table selection. This
* is not required since the table offers onSelect as a delegate. If the caller
* wants multi-selction they must use an IListSelection that supports multi
* select.
*
* There is a basic ListSelection implementation available from the List
* component.
*/
selection?: IListSelection;
/**
* Should the table select a row when it is clicked?
*
*
* @default true
*/
selectRowOnClick?: boolean;
/**
* Using singleClickActivation will activate the item when the row is clicked.
* Where setting singleClickActivation to false will require a doubleclick to
* activate a given row.
*
* @default true
*/
singleClickActivation?: boolean;
/**
* Determines the width of the spacer cells on either side of each row.
*/
spacerWidth?: number;
/**
* A table can be defined with a set of breakpoints. These breakpoints will be
* used to control the column layout as the space available to the table changes.
*/
tableBreakpoints?: ITableBreakpoint[];
/**
* If virtualize false is supplied the list will render all the items supplied
* to it. This shouldn't be used unless you know you have a limited number of
* rows. Virtualization is used to avoid performance problems.
*/
virtualize?: boolean;
}
/**
* TableColumnLayout is used to define the general shape of the data for a given
* column. One of the purposes of this is an animation when the rows are loaded
* asynchronously.
*
* If the caller wants non-standard shapes a custom loading row function will need
* to be implemented. For any columns that fit the standard shapes the exported
* functions can be used.
*/
export declare enum TableColumnLayout {
/**
* If a column is noted as none, when an asynchronous row is loaded no
* animation will be added to this column.
*/
none = 0,
/**
* The row uses a single line of text. This is the default for a column that
* doesnt explicitly define a column layout
*/
singleLine = 1,
/**
* The row uses a single line of text with a small prefix.
*/
singleLinePrefix = 2,
/**
* The row uses two lines of text.
*/
twoLine = 3,
/**
* The row uses two lines of text with a large prefex.
*/
twoLinePrefix = 4
}
/**
* The ColumnStyles effect how the values for the column should be rendered.
*/
export declare enum TableColumnStyle {
/**
* Secondary colums should be rendered normally.
*/
Secondary = 1,
/**
* Primary columns should be rendered with emphasis.
*/
Primary = 2,
/**
* Tertiary columns should be rendered de-emphasized.
*/
Tertiary = 3
}
export interface ITableColumnBehaviorProps<T> {
tableProps: Partial<ITableProps<T>>;
columnIndex: number;
}
/**
* Sorting order for columns
*/
export declare enum SortOrder {
ascending = 0,
descending = 1
}
/**
* Justification of the content within a column
*/
export declare enum ColumnJustification {
Left = 0,
Right = 1
}
/**
* Within a table columns may optionally be sorted. The table will render an indicator
* in the header by default for the current sort order. SortProps should be supplied
* for each column that MAY be sorted even if it isn't currently sorted. This allows
* the table to know which columns are and which are not sortable.
*/
export interface IColumnSortProps {
/**
* This is deprecated, aria-sort is used instead.
*/
ariaLabelAscending?: string;
/**
* This is deprecated, aria-sort is used instead.
*/
ariaLabelDescending?: string;
/**
* If a column is tagged as sorted, the header will indicate the sort order with
* a visual icon in the if the header uses the standard renderer. If a custom
* renderer is used, it should handle the sortOrder.
*/
sortOrder?: SortOrder;
}
/**
* An ITableBreakpoint is used to define the layout of the columns when a given
* breakpoint it reached.
*/
export interface ITableBreakpoint {
/**
* The table is defined with series of breakpoints. Each of the breakpoints
* will have a mapping for the column widths. Columns widths should be
* defined the same here as within an ITableColumn, except 0 should be used
* for columns that are hidden at this breakpoint.
*/
breakpoint: number;
/**
* The width of each column in the table when this breakpoint is active.
* The width can be a positive number for a fixed width, a negative number
* for a proportional width, or 0 for a hidden column.
*/
columnWidths: number[];
}
/**
* An IMeasurementStyle is used to represent a fixed size. The fixed size may be
* based on a number of base measurement values.
*/
export declare enum IMeasurementStyle {
/**
* Pixels represented by the 'px' css measurement.
*/
Pixel = 0,
/**
* RootEMs represented by the 'rem' css measurement.
*/
REM = 1
}
/**
* ITableColumn is used to communicate details about the column, its layout
* styles, widths, and basic header information. It requires a function to
* render the contents of the cell be supplied.
*/
export interface ITableColumn<T> {
/**
* ariaLabel allows the caller to describe the elements contents to assistive
* technology. Used when no column name or icon is provided.
*/
ariaLabel?: string;
/**
* Behaviors can be added a column to monitor events and interact with the column.
*/
behaviors?: Array<IBehavior<ITableColumnBehaviorProps<T>, {}>>;
/**
* CSS className to that should be added to the table cell for each of the
* values in this column.
*/
className?: string;
/**
* This is used to help the table understand the general layout of the data.
* One of the general purposes of this value is used when data is loaded
* asynchronously the table will render a loading animation by default and
* the layout is used to help define the visuals for the animation.
*
* A column with no columnLayout defined will use the TableColumnLayout.singleLine
* style.
*/
columnLayout?: TableColumnLayout;
/**
* cell renders MAY use the column style to apply css or other behaviors to
* this column.
*/
columnStyle?: TableColumnStyle;
/**
* CSS className that will be added to the cell for each header element.
*/
headerClassName?: string;
/**
* CSS className that will be added to the header title.
*/
headerTitleClassName?: string;
/**
* tabindex of the table header. Defaults to 0 if not set
*/
headerTabIndex?: number;
/**
* If iconProps are supplied the Icon will be drawn to the left of the
* column name in the header cell.
*/
iconProps?: IIconProps;
/**
* Unique Id for this table. NOTE: If this column uses the renderSimpleCell function
* the id is used as the property name of the row object to render.
*/
id: string;
/**
* How to justify the content within cells. Will also effect the position of the sorting
* arrow
*/
justification?: ColumnJustification;
/**
* Mark as readonly if there are no interactive elements in the column
*
* @default false
*/
readonly?: boolean;
/**
* Set to mark the column field as required, primarily for editable grids. This will display an asterisk next to the column name.
*/
required?: boolean;
/**
* maxWidth defines how large the column MAY grow if the column is resizble.
* This must be an absolute number, it can't be supplied as a percentage.
*/
maxWidth?: number;
/**
* minWidth defines how small the column MAY shrink if the column is resizble.
* This must be an absolute number, it can't be supplied as a percentage.
*/
minWidth?: number;
/**
* The name of the column is used to render the column header string. If the
* column has no header shown or only an icon the name can be omitted.
*/
name?: string;
/**
* renderCell MUST be supplied for a column. This defines how the column values
* will be rendered in the table. Row rendering functions should use this
* function unless the row rendering function has a custom presentation for
* the row/column.
*
* @param rowIndex - This is the 0 based row index.
* @param columnIndex - This is the 0 based column index.
* @param tableColumn - This is the column definition for this cell.
* @param tableItem - This is the object being rendered in this row.
* @param ariaRowIndex - This is the index of the row that aria will read.
* @param role - This is the role of child "tr" element.
*/
renderCell: (rowIndex: number, columnIndex: number, tableColumn: ITableColumn<T>, tableItem: T, ariaRowIndex?: number, role?: string) => JSX.Element;
/**
* renderHeaderCell can be supplied to render a custom header. It will be
* the responsibility of the custom render function to manage all status
* of the header. This includes all accessibitlity and focus management.
*
* @param index - This is the 0 based column index.
* @param tableColumn - This is the column definition for the header being rendered.
* @param focuszoneId - Focuszone id that needs to be included if the header is focusable.
* @param isFirstActionableHeader - True for the first column header that gets focused on keyboard accessibility.
*/
renderHeaderCell?: (columnIndex: number, tableColumn: ITableColumn<T>, focuszoneId?: string, isFirstActionableHeader?: boolean) => JSX.Element;
/**
* onSize is triggered when the column header is sized by the user. This is
* a required property to enable the column to be resized.
*
* @param event - This is the mouse or keyboard event that has caused the resize to occur.
* @param index - This is the 0 based column index being resized.
* @param width - This is the updated width of the column. Note: this is called
* with values that conform to the defined min/max values.
* @param column - This is the column definition for the column being resized.
*/
onSize?: (event: MouseEvent | KeyboardEvent, columnIndex: number, width: number, column: ITableColumn<T>) => void;
/**
* Show the column divider for resizable columns
*
* @default false
*/
showSizerDivider?: boolean;
/**
* onSizeEnd is called when the currently active sizing operation has completed.
* This means the user has stopped sizing the column. Any actions the consumer
* wants to take when sizing ends, they should implement here.
*/
onSizeEnd?: () => void;
/**
* Within a table columns may optionally be sorted. The table will render an indicator
* in the header by default for the current sort order. SortProps should be supplied
* for each column that MAY be sorted even if it isn't currently sorted. This allows
* the table to know which columns are and which are not sortable.
*/
sortProps?: IColumnSortProps;
/**
* The width of a column can one of two values:
*
* Positive value - This is the exact width of the column in pixels. This
* would be something like 250 to have a 250px column.
*
* Negative value - This is the percentage of the remaining space in the
* containing element this column should consume. This would be something
* like -100 for 100% of the remaining space.
*
* NOTE: This is different than css since we dont want to do unneeded
* string processing, CSS would have you use a string like 250px or 100%.
*/
width: IReadonlyObservableValue<number> | number;
/**
* The widthStyle can be set when the width is a positive number. By default
* the width is interpreted as a fixed pixel value. If the column is going to have
* a small UI representation such as a Coin, User Image, and a button these will
* scale with font size and should be represented with root EM values. This
* corresponds to the css rem measurement.
*/
widthStyle?: IMeasurementStyle;
}
/**
* Table rendering interfaces follow:
*/
/**
* ITableHeaderCellProps<T> are used byt he standard TableHeaderCell component to
* render the header cells. If a caller wishes to use the standard header cell
* component they should supply these properties.
*/
export interface ITableHeaderCellProps<T> {
/**
* ariaLabel to pass to the header cell. By default, the cell is labelled by its content element.
* If specified, aria-hidden will be set to true on the content element to prevent dupplicate announcements.
*/
ariaLabel?: string;
/**
* column is the core information used to describe a table column.
*/
column: ITableColumn<T>;
/**
* columnIndex is used to define the index of this column.
*/
columnIndex: number;
/**
* An optional focuszoneId that should be added to the component when supplied.
* This allows the table to provider keyboard accessibility to the header
* through a focuszone.
*/
focuszoneId?: string;
/**
* Identifies the first column header that will get the focus via keyboard accessibility.
*/
isFirstActionableHeader?: boolean;
/**
* Defines the role of the header cell and defaults to "columnheader"
*/
role?: string;
}
/**
* Props that can be used to represent that data available to a custom row
* rendering component.
*
* See ITableProps.renderRow for parameter details.
*/
export interface ITableRowProps<T> {
/**
* css class names that should be added to the row element.
*/
className?: string;
/**
* Index of the row that should be rendered.
*/
index: number;
/**
* Details about how the table is rendering rows.
*/
details: ITableRowDetails<T>;
/**
* If the table row should be rendered as a link, the caller can supply the
* links properties. These are merged with the lists properties to build
* a list row that is a anchor instead of a table row.
* The only props which are forwarded are href, rel, target, and onClick.
*/
/** @deprecated Please include links inside the table cell render methods. Table row links are inaccessible and support for them will be removed in future versions. */
linkProps?: ILinkProps;
}
/**
* If the table uses renderSimpleCell to render a given cell in the table, the
* object supplied to the table should have ISimpleTableCell properties that
* match the id's in the column definitions.
*/
export interface ISimpleTableCell {
[prop: string]: ISimpleListCell | string | number;
}