quickly add sorting via clicks on column header
This commit is contained in:
parent
f21fa5e224
commit
de79467db3
3 changed files with 80 additions and 4 deletions
|
@ -78,6 +78,20 @@
|
||||||
padding: var(--padding-m);
|
padding: var(--padding-m);
|
||||||
z-index: 1;
|
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 {
|
& .main {
|
||||||
|
|
|
@ -2,12 +2,15 @@ import { Accessor, createContext, createEffect, createMemo, createSignal, For, J
|
||||||
import { selectable, SelectionProvider, useSelection } from "~/features/selectable";
|
import { selectable, SelectionProvider, useSelection } from "~/features/selectable";
|
||||||
import { type RowNode, type GroupNode, type Node, createDataSet, toSorted, toGrouped } from './dataset';
|
import { type RowNode, type GroupNode, type Node, createDataSet, toSorted, toGrouped } from './dataset';
|
||||||
import css from './table.module.css';
|
import css from './table.module.css';
|
||||||
|
import { createStore } from "solid-js/store";
|
||||||
|
import { FaSolidSort, FaSolidSortDown, FaSolidSortUp } from "solid-icons/fa";
|
||||||
|
|
||||||
selectable
|
selectable
|
||||||
|
|
||||||
export type Column<T> = {
|
export type Column<T> = {
|
||||||
id: keyof T,
|
id: keyof T,
|
||||||
label: string,
|
label: string,
|
||||||
|
sortable?: boolean,
|
||||||
readonly groupBy?: (rows: RowNode<T>[]) => Node<T>[],
|
readonly groupBy?: (rows: RowNode<T>[]) => Node<T>[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,6 +20,8 @@ const TableContext = createContext<{
|
||||||
readonly groupBy: Accessor<string | undefined>,
|
readonly groupBy: Accessor<string | undefined>,
|
||||||
readonly sort: Accessor<{ by: string, reversed?: boolean } | undefined>,
|
readonly sort: Accessor<{ by: string, reversed?: boolean } | undefined>,
|
||||||
readonly cellRenderers: Accessor<Record<string, (cell: { value: any }) => JSX.Element>>,
|
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)!
|
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>) {
|
export function Table<T extends Record<string, any>>(props: TableProps<T>) {
|
||||||
const [selection, setSelection] = createSignal<object[]>([]);
|
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 columns = createMemo<Column<T>[]>(() => props.columns ?? []);
|
||||||
const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None);
|
const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None);
|
||||||
const groupBy = createMemo(() => props.groupBy as string | undefined);
|
const groupBy = createMemo(() => props.groupBy as string | undefined);
|
||||||
const sort = createMemo(() => props.sort as any);
|
|
||||||
const cellRenderers = createMemo(() => props.children ?? {});
|
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>
|
<SelectionProvider selection={setSelection} multiSelect>
|
||||||
<InnerTable class={props.class} rows={props.rows} />
|
<InnerTable class={props.class} rows={props.rows} />
|
||||||
</SelectionProvider>
|
</SelectionProvider>
|
||||||
|
@ -113,7 +138,38 @@ function Head<T extends Record<string, any>>(props: {}) {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<For each={table.columns()}>{
|
<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>
|
}</For>
|
||||||
</header>;
|
</header>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,26 +22,32 @@ export default function Experimental() {
|
||||||
{
|
{
|
||||||
id: 'name',
|
id: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'email',
|
id: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'address',
|
id: 'address',
|
||||||
label: 'Address',
|
label: 'Address',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'currency',
|
id: 'currency',
|
||||||
label: 'Currency',
|
label: 'Currency',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'phone',
|
id: 'phone',
|
||||||
label: 'Phone',
|
label: 'Phone',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'country',
|
id: 'country',
|
||||||
label: 'Country',
|
label: 'Country',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -106,7 +112,7 @@ export default function Experimental() {
|
||||||
|
|
||||||
<div class={css.content}>
|
<div class={css.content}>
|
||||||
<Table class={css.table} rows={people} columns={columns} groupBy={store.groupBy} sort={store.sort} selectionMode={store.selectionMode}>{{
|
<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>
|
}}</Table>
|
||||||
</div>
|
</div>
|
||||||
</div >;
|
</div >;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue