\n Organization members can only see data for the most recently-updated\n repositories. To see all repositories, talk to your organization\n administrator about becoming a security manager.\n
\n \n {\n return \n },\n },\n {\n header: 'Updated',\n field: 'updatedAt',\n renderCell: (row) => {\n return \n },\n },\n {\n header: 'Dependabot',\n field: 'securityFeatures.dependabot',\n renderCell: (row) => {\n return row.securityFeatures.dependabot.length > 0 ? (\n \n {row.securityFeatures.dependabot.map((feature) => {\n return \n })}\n \n ) : null\n },\n },\n {\n header: 'Code scanning',\n field: 'securityFeatures.codeScanning',\n renderCell: (row) => {\n return row.securityFeatures.codeScanning.length > 0 ? (\n \n {row.securityFeatures.codeScanning.map((feature) => {\n return \n })}\n \n ) : null\n },\n },\n ]}\n />\n \n >\n)"
diff --git a/src/DataTable/DataTable.docs.json b/src/DataTable/DataTable.docs.json
index 88a45f4466d..2cfaf0b1194 100644
--- a/src/DataTable/DataTable.docs.json
+++ b/src/DataTable/DataTable.docs.json
@@ -19,6 +19,15 @@
{
"id": "components-datatable-features--with-action"
},
+ {
+ "id": "components-datatable-features--with-row-action"
+ },
+ {
+ "id": "components-datatable-features--with-row-actions"
+ },
+ {
+ "id": "components-datatable-features--with-row-action-menu"
+ },
{
"id": "components-datatable-features--with-custom-heading"
}
diff --git a/src/DataTable/DataTable.features.stories.tsx b/src/DataTable/DataTable.features.stories.tsx
index 259984e47af..738d65cc0ef 100644
--- a/src/DataTable/DataTable.features.stories.tsx
+++ b/src/DataTable/DataTable.features.stories.tsx
@@ -1,12 +1,16 @@
-import {DownloadIcon, PlusIcon} from '@primer/octicons-react'
+import {DownloadIcon, KebabHorizontalIcon, PencilIcon, PlusIcon, TrashIcon} from '@primer/octicons-react'
+import {action} from '@storybook/addon-actions'
import {Meta} from '@storybook/react'
import React from 'react'
+import {ActionList} from '../ActionList'
+import {ActionMenu} from '../ActionMenu'
import {Button, IconButton} from '../Button'
import {DataTable, Table} from '../DataTable'
import Heading from '../Heading'
import Label from '../Label'
import LabelGroup from '../LabelGroup'
import RelativeTime from '../RelativeTime'
+import VisuallyHidden from '../_VisuallyHidden'
export default {
title: 'Components/DataTable/Features',
@@ -578,6 +582,273 @@ export const WithActionsOnly = () => (
>
)
+export const WithRowAction = () => (
+
+
+ Repositories
+
+
+ A subtitle could appear here to give extra context to the data.
+
+ {
+ return
+ },
+ },
+ {
+ header: 'Updated',
+ field: 'updatedAt',
+ renderCell: row => {
+ return
+ },
+ },
+ {
+ header: 'Dependabot',
+ field: 'securityFeatures.dependabot',
+ renderCell: row => {
+ return row.securityFeatures.dependabot.length > 0 ? (
+
+ {row.securityFeatures.dependabot.map(feature => {
+ return
+ })}
+
+ ) : null
+ },
+ },
+ {
+ header: 'Code scanning',
+ field: 'securityFeatures.codeScanning',
+ renderCell: row => {
+ return row.securityFeatures.codeScanning.length > 0 ? (
+
+ {row.securityFeatures.codeScanning.map(feature => {
+ return
+ })}
+
+ ) : null
+ },
+ },
+ {
+ id: 'actions',
+ header: () => Actions,
+ renderCell: row => {
+ return (
+ {
+ action('Download')(row)
+ }}
+ />
+ )
+ },
+ },
+ ]}
+ />
+
+)
+
+export const WithRowActions = () => (
+
+
+ Repositories
+
+
+ A subtitle could appear here to give extra context to the data.
+
+ {
+ return
+ },
+ },
+ {
+ header: 'Updated',
+ field: 'updatedAt',
+ renderCell: row => {
+ return
+ },
+ },
+ {
+ header: 'Dependabot',
+ field: 'securityFeatures.dependabot',
+ renderCell: row => {
+ return row.securityFeatures.dependabot.length > 0 ? (
+
+ {row.securityFeatures.dependabot.map(feature => {
+ return
+ })}
+
+ ) : null
+ },
+ },
+ {
+ header: 'Code scanning',
+ field: 'securityFeatures.codeScanning',
+ renderCell: row => {
+ return row.securityFeatures.codeScanning.length > 0 ? (
+
+ {row.securityFeatures.codeScanning.map(feature => {
+ return
+ })}
+
+ ) : null
+ },
+ },
+ {
+ id: 'actions',
+ header: () => Actions,
+ renderCell: row => {
+ return (
+ <>
+ {
+ action('Edit')(row)
+ }}
+ />
+ {
+ action('Delete')(row)
+ }}
+ />
+ >
+ )
+ },
+ },
+ ]}
+ />
+
+)
+
+export const WithRowActionMenu = () => (
+
+
+ Repositories
+
+
+ A subtitle could appear here to give extra context to the data.
+
+ {
+ return
+ },
+ },
+ {
+ header: 'Updated',
+ field: 'updatedAt',
+ renderCell: row => {
+ return
+ },
+ },
+ {
+ header: 'Dependabot',
+ field: 'securityFeatures.dependabot',
+ renderCell: row => {
+ return row.securityFeatures.dependabot.length > 0 ? (
+
+ {row.securityFeatures.dependabot.map(feature => {
+ return
+ })}
+
+ ) : null
+ },
+ },
+ {
+ header: 'Code scanning',
+ field: 'securityFeatures.codeScanning',
+ renderCell: row => {
+ return row.securityFeatures.codeScanning.length > 0 ? (
+
+ {row.securityFeatures.codeScanning.map(feature => {
+ return
+ })}
+
+ ) : null
+ },
+ },
+ {
+ id: 'actions',
+ header: () => Actions,
+ renderCell: row => {
+ return (
+
+
+
+
+
+
+ {
+ action('Copy')(row)
+ }}
+ >
+ Copy row
+
+ Edit row
+ Export row as CSV
+
+ Delete row
+
+
+
+ )
+ },
+ },
+ ]}
+ />
+
+)
+
export const WithCustomHeading = () => (
<>
diff --git a/src/DataTable/DataTable.tsx b/src/DataTable/DataTable.tsx
index e9cf399b0e0..7b6ccb20de3 100644
--- a/src/DataTable/DataTable.tsx
+++ b/src/DataTable/DataTable.tsx
@@ -81,11 +81,15 @@ function DataTable({
actions.sortBy(header)
}}
>
- {header.column.header}
+ {typeof header.column.header === 'string' ? header.column.header : header.column.header()}
)
}
- return {header.column.header}
+ return (
+
+ {typeof header.column.header === 'string' ? header.column.header : header.column.header()}
+
+ )
})}
diff --git a/src/DataTable/column.ts b/src/DataTable/column.ts
index 7a97202ae27..ba4e0bef4e8 100644
--- a/src/DataTable/column.ts
+++ b/src/DataTable/column.ts
@@ -9,7 +9,7 @@ export interface Column {
* Provide the name of the column. This will be rendered as a table header
* within the table itself
*/
- header: string
+ header: string | (() => React.ReactNode)
/**
* Optionally provide a field to render for this column. This may be the key
@@ -19,7 +19,7 @@ export interface Column {
* Alternatively, you may provide a `renderCell` for this column to render the
* field in a row
*/
- field: ObjectPaths
+ field?: ObjectPaths
/**
* Provide a custom component or render prop to render the data for this
diff --git a/src/DataTable/useTable.ts b/src/DataTable/useTable.ts
index 35dba41f34c..ec68b5f4640 100644
--- a/src/DataTable/useTable.ts
+++ b/src/DataTable/useTable.ts
@@ -80,6 +80,10 @@ export function useTable({
const headers = columns.map(column => {
const id = column.id ?? column.field
+ if (id === undefined) {
+ throw new Error(`Expected either an \`id\` or \`field\` to be defined for a Column`)
+ }
+
const sortable = column.sortBy !== undefined && column.sortBy !== false
return {
id,
@@ -133,6 +137,10 @@ export function useTable({
setRowOrder(rowOrder => {
return rowOrder.slice().sort((a, b) => {
+ if (header.column.field === undefined) {
+ return 0
+ }
+
const valueA = get(a, header.column.field)
const valueB = get(b, header.column.field)
@@ -159,7 +167,10 @@ export function useTable({
column: header.column,
rowHeader: header.column.rowHeader ?? false,
getValue() {
- return get(row, header.column.field)
+ if (header.column.field !== undefined) {
+ return get(row, header.column.field)
+ }
+ throw new Error(`Unable to get value for column header ${header.id}`)
},
}
})
@@ -203,7 +214,7 @@ function getInitialSortState(
}
return {
- id: initialSortColumn,
+ id: `${initialSortColumn}`,
direction: initialSortDirection ?? DEFAULT_SORT_DIRECTION,
}
}
@@ -223,8 +234,19 @@ function getInitialSortState(
return null
}
+ const id = column.id ?? column.field
+ if (id === undefined) {
+ if (__DEV__) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `Warning: Unable to find an \`id\` or \`field\` for the column: ${column}. Please set one of these properties on the column.`,
+ )
+ }
+ return null
+ }
+
return {
- id: column.id ?? column.field,
+ id,
direction: initialSortDirection,
}
}