quickly add sorting via clicks on column header

This commit is contained in:
Chris Kruining 2024-12-11 11:11:28 +01:00
parent f21fa5e224
commit de79467db3
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
3 changed files with 80 additions and 4 deletions

View file

@ -78,6 +78,20 @@
padding: var(--padding-m);
z-index: 1;
}
& > .cell {
grid-auto-flow: column;
justify-content: space-between;
& > svg {
transition: opacity .15s ease-in-out;
}
&:not(.sorted):not(:hover) > svg {
opacity: 0;
}
}
}
& .main {

View file

@ -2,12 +2,15 @@ import { Accessor, createContext, createEffect, createMemo, createSignal, For, J
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<T> = {
id: keyof T,
label: string,
sortable?: boolean,
readonly groupBy?: (rows: RowNode<T>[]) => Node<T>[],
};
@ -17,6 +20,8 @@ const TableContext = createContext<{
readonly groupBy: Accessor<string | undefined>,
readonly sort: Accessor<{ by: string, reversed?: boolean } | undefined>,
readonly cellRenderers: Accessor<Record<string, (cell: { value: any }) => JSX.Element>>,
setSort(setter: (current: { by: string, reversed?: boolean } | undefined) => { by: string, reversed: boolean } | undefined): void;
}>();
const useTable = () => useContext(TableContext)!
@ -46,13 +51,33 @@ type TableProps<T extends Record<string, any>> = {
export function Table<T extends Record<string, any>>(props: TableProps<T>) {
const [selection, setSelection] = createSignal<object[]>([]);
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<Column<T>[]>(() => props.columns ?? []);
const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None);
const groupBy = createMemo(() => props.groupBy as string | undefined);
const sort = createMemo(() => props.sort as any);
const cellRenderers = createMemo(() => props.children ?? {});
return <TableContext.Provider value={{ columns, selectionMode, groupBy, sort, cellRenderers }}>
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 <TableContext.Provider value={context}>
<SelectionProvider selection={setSelection} multiSelect>
<InnerTable class={props.class} rows={props.rows} />
</SelectionProvider>
@ -113,7 +138,38 @@ function Head<T extends Record<string, any>>(props: {}) {
</Show>
<For each={table.columns()}>{
column => <span class={css.cell}>{column.label}</span>
({ 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 <span class={`${css.cell} ${sort()?.by === by ? css.sorted : ''}`} onpointerdown={onPointerDown}>
{label}
<Switch>
<Match when={sortable && sort()?.by !== by}><FaSolidSort /></Match>
<Match when={sortable && sort()?.by === by && sort()?.reversed !== true}><FaSolidSortUp /></Match>
<Match when={sortable && sort()?.by === by && sort()?.reversed === true}><FaSolidSortDown /></Match>
</Switch>
</span>;
}
}</For>
</header>;
};

View file

@ -22,26 +22,32 @@ export default function Experimental() {
{
id: 'name',
label: 'Name',
sortable: true,
},
{
id: 'email',
label: 'Email',
sortable: true,
},
{
id: 'address',
label: 'Address',
sortable: true,
},
{
id: 'currency',
label: 'Currency',
sortable: true,
},
{
id: 'phone',
label: 'Phone',
sortable: true,
},
{
id: 'country',
label: 'Country',
sortable: true,
},
];
@ -106,7 +112,7 @@ export default function Experimental() {
<div class={css.content}>
<Table class={css.table} rows={people} columns={columns} groupBy={store.groupBy} sort={store.sort} selectionMode={store.selectionMode}>{{
address: (cell) => <input type="text" value={cell.value} />,
// email: (cell) => <input type="email" value={cell.value} />,
}}</Table>
</div>
</div >;