Skip to content

Commit

Permalink
Merge pull request #763 from interval/table-row-actions
Browse files Browse the repository at this point in the history
Add dropdown menus to tables
  • Loading branch information
danphilibin authored Aug 18, 2022
2 parents f8dadf7 + d52fd65 commit 604ec72
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 11 deletions.
13 changes: 8 additions & 5 deletions src/components/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import {
T_IO_PROPS,
tableColumn,
tableRow,
newInternalTableRow,
internalTableRow,
T_IO_RETURNS,
serializableRecord,
menuItem,
} from '../ioSchema'
import { columnsBuilder, tableRowSerializer } from '../utils/table'
import Logger from '../classes/Logger'
Expand Down Expand Up @@ -38,6 +39,7 @@ export function selectTable(logger: Logger) {
props: Omit<T_IO_PROPS<'SELECT_TABLE'>, 'data' | 'columns'> & {
data: Row[]
columns?: (Column<Row> | string)[]
rowMenuItems?: (row: Row) => z.infer<typeof menuItem>[]
}
) {
type DataList = typeof props['data']
Expand All @@ -47,14 +49,14 @@ export function selectTable(logger: Logger) {
)

const data = props.data.map((row, idx) =>
tableRowSerializer(idx, row, columns)
tableRowSerializer(idx, row, columns, props.rowMenuItems)
)

return {
props: { ...props, data, columns },
getValue(response: T_IO_RETURNS<'SELECT_TABLE'>) {
const indices = response.map(row =>
Number((row as z.infer<typeof newInternalTableRow>).key)
Number((row as z.infer<typeof internalTableRow>).key)
)

const rows = props.data.filter((_, idx) => indices.includes(idx))
Expand All @@ -66,18 +68,19 @@ export function selectTable(logger: Logger) {
}

export function displayTable(logger: Logger) {
return function displayTable<Row = any>(
return function displayTable<Row extends z.input<typeof tableRow> = any>(
props: Omit<T_IO_PROPS<'DISPLAY_TABLE'>, 'data' | 'columns'> & {
data: Row[]
columns?: (Column<Row> | string)[]
rowMenuItems?: (row: Row) => z.infer<typeof menuItem>[]
}
) {
const columns = columnsBuilder(props, column =>
logger.error(missingColumnMessage('io.display.table')(column))
)

const data = props.data.map((row, idx) =>
tableRowSerializer(idx, row, columns)
tableRowSerializer(idx, row, columns, props.rowMenuItems)
)

return {
Expand Down
2 changes: 2 additions & 0 deletions src/examples/basic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
table_basic,
table_custom_columns,
table_custom,
table_actions,
} from './selectFromTable'
import unauthorized from './unauthorized'
import './ghostHost'
Expand Down Expand Up @@ -201,6 +202,7 @@ const interval = new Interval({
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet quam in lorem sagittis accumsan malesuada nec mauris. Nulla cursus dolor id augue sodales, et consequat elit mattis. Suspendisse nec sollicitudin ex. Pellentesque laoreet nulla nec malesuada consequat. Donec blandit leo id tincidunt tristique. Mauris vehicula metus sed ex bibendum, nec bibendum urna tincidunt. Curabitur porttitor euismod velit sed interdum. Suspendisse at dapibus eros. Vestibulum varius, est vel luctus pellentesque, risus lorem ullamcorper est, a ullamcorper metus dolor eget neque. Donec sit amet nulla tempus, fringilla magna eu, bibendum tortor. Nam pulvinar diam id vehicula posuere. Praesent non turpis et nibh dictum suscipit non nec ante. Phasellus vulputate egestas nisl a dapibus. Duis augue lorem, mattis auctor condimentum a, convallis sed elit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque bibendum, magna vel pharetra fermentum, eros mi vulputate enim, in consectetur est quam quis felis.',
}
},
table_actions,
table_basic,
table_custom,
table_custom_columns,
Expand Down
27 changes: 27 additions & 0 deletions src/examples/basic/selectFromTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ export const table_basic: IntervalActionHandler = async io => {
await io.display.object('Selected', { data: selections })
}

export const table_actions: IntervalActionHandler = async io => {
const simpleCharges = charges.map((ch, idx) => ({
id: idx,
name: ch.name,
email: faker.internet.email(),
amount: ch.amount,
address1: faker.address.streetAddress(),
address2: faker.address.secondaryAddress(),
city: faker.address.city(),
state: faker.address.state(),
zip: faker.address.zipCode(),
}))

const selections = await io.display.table('Charges', {
data: simpleCharges,
rowMenuItems: row => [
{
label: 'Edit',
action: 'edit_user',
params: { email: row.email },
},
],
})

await io.display.object('Selected', { data: selections })
}

export const table_custom: IntervalActionHandler = async io => {
const options = [
'id',
Expand Down
26 changes: 23 additions & 3 deletions src/ioSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,33 @@ export const tableRow = z
// If no columns specified, we'll just serialize any nested objects.
.or(z.object({}).passthrough())

export const newInternalTableRow = z.object({
export const menuItem = z.intersection(
z.object({
label: z.string(),
theme: z.enum(['default', 'danger']).default('default').optional(),
}),
z.union([
z.object({
action: z.string(),
params: serializableRecord.optional(),
disabled: z.boolean().optional(),
}),
z.object({
url: z.string(),
disabled: z.boolean().optional(),
}),
z.object({
disabled: z.literal(true),
}),
])
)

export const internalTableRow = z.object({
key: z.string(),
data: tableRow,
menu: z.array(menuItem).optional(),
})

export const internalTableRow = z.union([newInternalTableRow, tableRow])

export const tableColumn = z.object({
label: z.string(),
renderCell: z
Expand Down
9 changes: 6 additions & 3 deletions src/utils/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
internalTableColumn,
tableRow,
internalTableRow,
menuItem,
} from '../ioSchema'
import { z } from 'zod'
import Logger from '../classes/Logger'
Expand Down Expand Up @@ -55,10 +56,11 @@ export function columnsWithoutRender(
/**
* Applies cell renderers to a row.
*/
export function tableRowSerializer(
export function tableRowSerializer<T extends z.infer<typeof tableRow>>(
idx: number,
row: z.infer<typeof tableRow>,
columns: z.infer<typeof tableColumn>[]
row: T,
columns: z.infer<typeof tableColumn>[],
menuBuilder?: (row: T) => z.infer<typeof menuItem>[]
): z.infer<typeof internalTableRow> {
const key = idx.toString()

Expand All @@ -81,5 +83,6 @@ export function tableRowSerializer(
return {
key,
data: finalRow,
menu: menuBuilder ? menuBuilder(row) : undefined,
}
}

0 comments on commit 604ec72

Please sign in to comment.