import { Accessor, createContext, createEffect, createMemo, createSignal, For, JSX, Match, Show, Switch, useContext } from "solid-js"; import { selectable, SelectionProvider, useSelection } from "~/features/selectable"; import { type RowNode, type GroupNode, type Node, createDataSet, toSorted, toGrouped } from './dataset'; import css from './table.module.css'; import { createStore } from "solid-js/store"; import { FaSolidSort, FaSolidSortDown, FaSolidSortUp } from "solid-icons/fa"; selectable export type Column = { id: keyof T, label: string, sortable?: boolean, readonly groupBy?: (rows: RowNode[]) => Node[], }; const TableContext = createContext<{ readonly columns: Accessor[]>, readonly selectionMode: Accessor, readonly groupBy: Accessor, readonly sort: Accessor<{ by: string, reversed?: boolean } | undefined>, readonly cellRenderers: Accessor JSX.Element>>, setSort(setter: (current: { by: string, reversed?: boolean } | undefined) => { by: string, reversed: boolean } | undefined): void; }>(); const useTable = () => useContext(TableContext)! function defaultGroupingFunction(groupBy: keyof T) { return (nodes: RowNode[]): Node[] => Object.entries(Object.groupBy>(nodes, r => r.value[groupBy])) .map>(([key, nodes]) => ({ kind: 'group', key, groupedBy: groupBy, nodes: nodes! })); } export enum SelectionMode { None, Single, Multiple } type TableProps> = { class?: string, rows: T[], columns: Column[], groupBy?: keyof T, sort?: { by: keyof T, reversed?: boolean, }, selectionMode?: SelectionMode, children?: { [K in keyof T]?: (cell: { value: T[K] }) => JSX.Element }, }; export function Table>(props: TableProps) { const [selection, setSelection] = createSignal([]); const [state, setState] = createStore({ sort: props.sort ? { by: props.sort.by as string, reversed: props.sort.reversed } : undefined, }); createEffect(() => { setState('sort', props.sort ? { by: props.sort.by as string, reversed: props.sort.reversed } : undefined); }); const columns = createMemo[]>(() => props.columns ?? []); const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None); const groupBy = createMemo(() => props.groupBy as string | undefined); const cellRenderers = createMemo(() => props.children ?? {}); const context = { columns, selectionMode, groupBy, sort: createMemo(() => state.sort), cellRenderers, setSort(setter: (current: { by: string, reversed?: boolean } | undefined) => { by: string, reversed: boolean } | undefined) { setState('sort', setter); }, }; return ; }; type InnerTableProps> = { class?: string, rows: T[] }; function InnerTable>(props: InnerTableProps) { const table = useTable(); const selectable = createMemo(() => table.selectionMode() !== SelectionMode.None); const columnCount = createMemo(() => table.columns().length + (selectable() ? 0 : -1)); const nodes = createMemo[]>(() => { const columns = table.columns(); const groupBy = table.groupBy(); const sort = table.sort(); let dataset = createDataSet(props.rows); if (sort) { dataset = toSorted(dataset, { by: sort.by, reversed: sort.reversed ?? false, with: (a, b) => a < b ? -1 : a > b ? 1 : 0 }) } if (groupBy) { dataset = toGrouped(dataset, { by: groupBy, with: columns.find(({ id }) => id === groupBy)?.groupBy ?? defaultGroupingFunction(groupBy) }); } return dataset; }); return
{ node => }
}; function Head>(props: {}) { const table = useTable(); const context = useSelection(); return
{ ({ id, label, sortable }) => { const sort = createMemo(() => table.sort()); const by = String(id); const onPointerDown = (e: PointerEvent) => { if (sortable !== true) { return; } table.setSort(current => { if (current?.by !== by) { return { by, reversed: false }; } if (current.reversed === true) { return undefined; } return { by, reversed: true }; }); }; return {label} ; } }
; }; function Node>(props: { node: Node, depth: number, groupedBy?: keyof T }) { return { row => } { group => } ; } function Row>(props: { key: string, value: T, depth: number, groupedBy?: keyof T }) { const table = useTable(); const context = useSelection(); const values = createMemo(() => Object.entries(props.value)); const isSelected = context.isSelected(props.key); return
{ ([k, v]) =>
{table.cellRenderers()[k]?.({ value: v }) ?? v}
}
; }; function Group>(props: { key: string, groupedBy: keyof T, nodes: Node[], depth: number }) { const table = useTable(); return
{props.key} { node => }
; };