diff --git a/src/components/table.ts b/src/components/table.ts index ad2d7d2..3fffe71 100644 --- a/src/components/table.ts +++ b/src/components/table.ts @@ -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' @@ -38,6 +39,7 @@ export function selectTable(logger: Logger) { props: Omit, 'data' | 'columns'> & { data: Row[] columns?: (Column | string)[] + rowMenuItems?: (row: Row) => z.infer[] } ) { type DataList = typeof props['data'] @@ -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).key) + Number((row as z.infer).key) ) const rows = props.data.filter((_, idx) => indices.includes(idx)) @@ -66,10 +68,11 @@ export function selectTable(logger: Logger) { } export function displayTable(logger: Logger) { - return function displayTable( + return function displayTable = any>( props: Omit, 'data' | 'columns'> & { data: Row[] columns?: (Column | string)[] + rowMenuItems?: (row: Row) => z.infer[] } ) { const columns = columnsBuilder(props, column => @@ -77,7 +80,7 @@ export function displayTable(logger: Logger) { ) const data = props.data.map((row, idx) => - tableRowSerializer(idx, row, columns) + tableRowSerializer(idx, row, columns, props.rowMenuItems) ) return { diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index ada0fce..5aab4f2 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -10,6 +10,7 @@ import { table_basic, table_custom_columns, table_custom, + table_actions, } from './selectFromTable' import unauthorized from './unauthorized' import './ghostHost' @@ -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, diff --git a/src/examples/basic/selectFromTable.ts b/src/examples/basic/selectFromTable.ts index 14306d7..1367bf0 100644 --- a/src/examples/basic/selectFromTable.ts +++ b/src/examples/basic/selectFromTable.ts @@ -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', diff --git a/src/ioSchema.ts b/src/ioSchema.ts index e811fa8..49d44e1 100644 --- a/src/ioSchema.ts +++ b/src/ioSchema.ts @@ -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 diff --git a/src/utils/table.ts b/src/utils/table.ts index a746aef..21c19ae 100644 --- a/src/utils/table.ts +++ b/src/utils/table.ts @@ -3,6 +3,7 @@ import { internalTableColumn, tableRow, internalTableRow, + menuItem, } from '../ioSchema' import { z } from 'zod' import Logger from '../classes/Logger' @@ -55,10 +56,11 @@ export function columnsWithoutRender( /** * Applies cell renderers to a row. */ -export function tableRowSerializer( +export function tableRowSerializer>( idx: number, - row: z.infer, - columns: z.infer[] + row: T, + columns: z.infer[], + menuBuilder?: (row: T) => z.infer[] ): z.infer { const key = idx.toString() @@ -81,5 +83,6 @@ export function tableRowSerializer( return { key, data: finalRow, + menu: menuBuilder ? menuBuilder(row) : undefined, } }