made tables more feature complete and started splitting of all the data handling to dataset.ts
This commit is contained in:
parent
17e49c23d8
commit
977670b9e0
7 changed files with 325 additions and 123 deletions
|
@ -1,114 +0,0 @@
|
||||||
import { Component, createMemo, createSignal, For, Match, Show, Switch } from "solid-js";
|
|
||||||
import { selectable, SelectionProvider, useSelection } from "~/features/selectable";
|
|
||||||
import css from './table.module.css';
|
|
||||||
|
|
||||||
selectable
|
|
||||||
|
|
||||||
type Row<T> = { kind: 'row', key: string, value: T }
|
|
||||||
type Group<T> = { kind: 'group', key: string, nodes: Node<T>[] };
|
|
||||||
type Node<T> = Row<T> | Group<T>;
|
|
||||||
|
|
||||||
export function Table<T extends Record<string, any>>(props: { class?: string, rows: T[], selectable?: boolean }) {
|
|
||||||
const [selection, setSelection] = createSignal<object[]>([]);
|
|
||||||
const columns = createMemo(() => ['#', ...Object.keys(props.rows.at(0) ?? {})]);
|
|
||||||
const selectable = createMemo(() => props.selectable ?? false);
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<SelectionProvider selection={setSelection} multiSelect>
|
|
||||||
{/* <Api api={props.api} /> */}
|
|
||||||
|
|
||||||
<_Table class={props.class} columns={columns()} rows={props.rows} />
|
|
||||||
</SelectionProvider>
|
|
||||||
</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TableProps<T extends Record<string, any>> = { class?: string, columns: (keyof T)[], rows: T[] };
|
|
||||||
|
|
||||||
function _Table<T extends Record<string, any>>(props: TableProps<T>) {
|
|
||||||
const columnCount = createMemo(() => props.columns.length - 1);
|
|
||||||
const nodes = createMemo<Node<T>[]>(() => {
|
|
||||||
const rows = Object.entries(props.rows).map<Row<T>>(([i, row]) => ({ kind: 'row', key: row['key'], value: row }));
|
|
||||||
|
|
||||||
const group = (nodes: Row<T>[]): Node<T>[] => nodes.every(n => n.key.includes('.') === false)
|
|
||||||
? nodes
|
|
||||||
: Object.entries(Object.groupBy(nodes, r => String(r.key).split('.').at(0)!))
|
|
||||||
.map<Group<T>>(([key, nodes]) => ({ kind: 'group', key, nodes: group(nodes!.map(n => ({ ...n, key: n.key.slice(key.length + 1) }))) }));
|
|
||||||
|
|
||||||
const grouped = group(rows);
|
|
||||||
|
|
||||||
return grouped;
|
|
||||||
});
|
|
||||||
|
|
||||||
return <section class={`${css.table} ${props.class}`} style={{ '--columns': columnCount() }}>
|
|
||||||
<Head headers={props.columns} />
|
|
||||||
|
|
||||||
<main class={css.main}>
|
|
||||||
<For each={nodes()}>{
|
|
||||||
node => <Node node={node} depth={0} />
|
|
||||||
}</For>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
};
|
|
||||||
|
|
||||||
function Head<T extends Record<string, any>>(props: { headers: (keyof T)[] }) {
|
|
||||||
const context = useSelection();
|
|
||||||
|
|
||||||
return <header class={css.header}>
|
|
||||||
<div class={css.cell}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={context.selection().length > 0 && context.selection().length === context.length()}
|
|
||||||
indeterminate={context.selection().length !== 0 && context.selection().length !== context.length()}
|
|
||||||
on:input={(e: InputEvent) => e.target.checked ? context.selectAll() : context.clear()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<For each={props.headers}>{
|
|
||||||
header => <span class={css.cell}>{header.toString()}</span>
|
|
||||||
}</For>
|
|
||||||
</header>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Node<T extends Record<string, any>>(props: { node: Node<T>, depth: number }) {
|
|
||||||
return <Switch>
|
|
||||||
<Match when={props.node.kind === 'row' ? props.node : undefined}>{
|
|
||||||
row => <Row key={row().key} value={row().value} depth={props.depth} />
|
|
||||||
}</Match>
|
|
||||||
|
|
||||||
<Match when={props.node.kind === 'group' ? props.node : undefined}>{
|
|
||||||
group => <Group key={group().key} nodes={group().nodes} depth={props.depth} />
|
|
||||||
}</Match>
|
|
||||||
</Switch>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Row<T extends Record<string, any>>(props: { key: string, value: T, depth: number }) {
|
|
||||||
const context = useSelection();
|
|
||||||
|
|
||||||
const values = createMemo(() => Object.entries(props.value));
|
|
||||||
const isSelected = context.isSelected(props.key);
|
|
||||||
|
|
||||||
return <div class={css.row} use:selectable={{ value: props.value, key: props.key }}>
|
|
||||||
<div class={css.cell}>
|
|
||||||
<input type="checkbox" checked={isSelected()} on:input={() => context.select([props.key])} on:pointerdown={e => e.stopPropagation()} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={css.cell}>
|
|
||||||
<span style={{ '--depth': props.depth }}>{props.key}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<For each={values()}>{
|
|
||||||
([k, v]) => <div class={css.cell}>{v}</div>
|
|
||||||
}</For>
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Group<T extends Record<string, any>>(props: { key: string, nodes: Node<T>[], depth: number }) {
|
|
||||||
return <details open>
|
|
||||||
<summary style={{ '--depth': props.depth }}>{props.key}</summary>
|
|
||||||
|
|
||||||
<For each={props.nodes}>{
|
|
||||||
node => <Node node={node} depth={props.depth + 1} />
|
|
||||||
}</For>
|
|
||||||
</details>;
|
|
||||||
};
|
|
27
src/components/table/dataset.ts
Normal file
27
src/components/table/dataset.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
|
||||||
|
export type RowNode<T> = { kind: 'row', key: string, value: T }
|
||||||
|
export type GroupNode<T> = { kind: 'group', key: string, groupedBy: keyof T, nodes: Node<T>[] };
|
||||||
|
export type Node<T> = RowNode<T> | GroupNode<T>;
|
||||||
|
|
||||||
|
export type DataSet<T extends Record<string, any>> = Node<T>[];
|
||||||
|
|
||||||
|
export const createDataSet = <T extends Record<string, any>>(data: T[]): Node<T>[] => {
|
||||||
|
return Object.entries(data).map<RowNode<T>>(([key, value]) => ({ kind: 'row', key, value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
type SortingFunction<T> = (a: T, b: T) => -1 | 0 | 1;
|
||||||
|
type SortOptions<T extends Record<string, any>> = { by: keyof T, reversed: boolean, with: SortingFunction<T> };
|
||||||
|
export const toSorted = <T extends Record<string, any>>(dataSet: DataSet<T>, sort: SortOptions<T>): DataSet<T> => {
|
||||||
|
const sorted = dataSet.toSorted((a, b) => sort.with(a.value[sort.by], b.value[sort.by]));
|
||||||
|
|
||||||
|
if (sort.reversed) {
|
||||||
|
sorted.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GroupingFunction<T> = (nodes: RowNode<T>[]) => Node<T>[];
|
||||||
|
type GroupOptions<T extends Record<string, any>> = { by: keyof T, with: GroupingFunction<T> };
|
||||||
|
export const toGrouped = <T extends Record<string, any>>(dataSet: DataSet<T>, group: GroupOptions<T>): DataSet<T> => group.with(dataSet as any);
|
5
src/components/table/index.tsx
Normal file
5
src/components/table/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
export type { Column } from './table';
|
||||||
|
export type { DataSet, GroupNode, RowNode, Node } from './dataset';
|
||||||
|
export { SelectionMode, Table } from './table';
|
||||||
|
export { createDataSet, toSorted, toGrouped } from './dataset';
|
|
@ -1,20 +1,24 @@
|
||||||
.table {
|
.table {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2em minmax(10em, max-content) repeat(var(--columns), auto);
|
grid-template-columns: repeat(var(--columns), minmax(0, auto));
|
||||||
align-content: start;
|
align-content: start;
|
||||||
padding-inline: 1px;
|
padding-inline: 1px;
|
||||||
margin-inline: -1px;
|
margin-inline: -1px;
|
||||||
|
|
||||||
block-size: 100%;
|
block-size: 100%;
|
||||||
|
|
||||||
|
&.selectable {
|
||||||
|
grid-template-columns: 2em repeat(calc(var(--columns) - 1), minmax(0, auto));
|
||||||
|
}
|
||||||
|
|
||||||
& input[type="checkbox"] {
|
& input[type="checkbox"] {
|
||||||
margin: .1em;
|
margin: .1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .cell {
|
& .cell {
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: .5em;
|
padding: var(--padding-m);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: var(--radii-m);
|
border-radius: var(--radii-m);
|
||||||
|
|
||||||
|
@ -89,14 +93,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > summary {
|
& > summary {
|
||||||
grid-column: 2 / span calc(1 + var(--columns));
|
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
padding-inline-start: calc(var(--depth) * 1em + .5em);
|
padding-inline-start: calc(var(--depth) * 1em + .5em);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .row > .cell > span {
|
& > .row > .cell {
|
||||||
padding-inline-start: calc(var(--depth) * 1em);
|
padding-inline-start: calc(var(--depth) * 1em + var(--padding-m));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
193
src/components/table/table.tsx
Normal file
193
src/components/table/table.tsx
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
selectable
|
||||||
|
|
||||||
|
export type Column<T> = {
|
||||||
|
id: keyof T,
|
||||||
|
label: string,
|
||||||
|
readonly groupBy?: (rows: RowNode<T>[]) => Node<T>[],
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableContext = createContext<{
|
||||||
|
readonly columns: Accessor<Column<any>[]>,
|
||||||
|
readonly selectionMode: Accessor<SelectionMode>,
|
||||||
|
readonly groupBy: Accessor<string | undefined>,
|
||||||
|
readonly sort: Accessor<{ by: string, reversed?: boolean } | undefined>,
|
||||||
|
readonly cellRenderers: Accessor<Record<string, (cell: { value: any }) => JSX.Element>>,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const useTable = () => useContext(TableContext)!
|
||||||
|
|
||||||
|
function defaultGroupingFunction<T>(groupBy: keyof T) {
|
||||||
|
return (nodes: RowNode<T>[]): Node<T>[] => Object.entries(Object.groupBy<any, RowNode<T>>(nodes, r => r.value[groupBy]))
|
||||||
|
.map<GroupNode<T>>(([key, nodes]) => ({ kind: 'group', key, groupedBy: groupBy, nodes: nodes! }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SelectionMode {
|
||||||
|
None,
|
||||||
|
Single,
|
||||||
|
Multiple
|
||||||
|
}
|
||||||
|
type TableProps<T extends Record<string, any>> = {
|
||||||
|
class?: string,
|
||||||
|
rows: T[],
|
||||||
|
columns: Column<T>[],
|
||||||
|
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<T extends Record<string, any>>(props: TableProps<T>) {
|
||||||
|
const [selection, setSelection] = createSignal<object[]>([]);
|
||||||
|
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 }}>
|
||||||
|
<SelectionProvider selection={setSelection} multiSelect>
|
||||||
|
<InnerTable class={props.class} rows={props.rows} />
|
||||||
|
</SelectionProvider>
|
||||||
|
</TableContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InnerTableProps<T extends Record<string, any>> = { class?: string, rows: T[] };
|
||||||
|
|
||||||
|
function InnerTable<T extends Record<string, any>>(props: InnerTableProps<T>) {
|
||||||
|
const table = useTable();
|
||||||
|
|
||||||
|
const selectable = createMemo(() => table.selectionMode() !== SelectionMode.None);
|
||||||
|
const columnCount = createMemo(() => table.columns().length + (selectable() ? 0 : -1));
|
||||||
|
const nodes = createMemo<Node<T>[]>(() => {
|
||||||
|
const columns = table.columns();
|
||||||
|
const groupBy = table.groupBy();
|
||||||
|
const sort = table.sort();
|
||||||
|
|
||||||
|
let kaas = createDataSet(props.rows);
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
kaas = toSorted(kaas, { by: sort.by, reversed: sort.reversed ?? false, with: (a, b) => a < b ? -1 : a > b ? 1 : 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupBy) {
|
||||||
|
kaas = toGrouped(kaas, { by: groupBy, with: columns.find(({ id }) => id === groupBy)?.groupBy ?? defaultGroupingFunction(groupBy) });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(kaas);
|
||||||
|
|
||||||
|
const rows = props.rows;
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
rows.sort((a, b) => a[sort.by] < b[sort.by] ? -1 : a[sort.by] > b[sort.by] ? 1 : 0);
|
||||||
|
|
||||||
|
if (sort.reversed === true) {
|
||||||
|
rows.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = Object.entries(rows).map<RowNode<T>>(([i, row]) => ({ kind: 'row', key: i, value: row }));
|
||||||
|
|
||||||
|
if (groupBy === undefined) {
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupingFunction = columns.find(({ id }) => id === groupBy)?.groupBy ?? defaultGroupingFunction(groupBy);
|
||||||
|
|
||||||
|
return groupingFunction(nodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return <section class={`${css.table} ${selectable() ? css.selectable : ''} ${props.class}`} style={{ '--columns': columnCount() }}>
|
||||||
|
<Head />
|
||||||
|
|
||||||
|
<main class={css.main}>
|
||||||
|
<For each={nodes()}>{
|
||||||
|
node => <Node node={node} depth={0} />
|
||||||
|
}</For>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
};
|
||||||
|
|
||||||
|
function Head<T extends Record<string, any>>(props: {}) {
|
||||||
|
const table = useTable();
|
||||||
|
const context = useSelection();
|
||||||
|
|
||||||
|
return <header class={css.header}>
|
||||||
|
<Show when={table.selectionMode() !== SelectionMode.None}>
|
||||||
|
<div class={css.cell}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={context.selection().length > 0 && context.selection().length === context.length()}
|
||||||
|
indeterminate={context.selection().length !== 0 && context.selection().length !== context.length()}
|
||||||
|
on:input={(e: InputEvent) => e.target.checked ? context.selectAll() : context.clear()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<For each={table.columns()}>{
|
||||||
|
column => <span class={css.cell}>{column.label}</span>
|
||||||
|
}</For>
|
||||||
|
</header>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Node<T extends Record<string, any>>(props: { node: Node<T>, depth: number, groupedBy?: keyof T }) {
|
||||||
|
return <Switch>
|
||||||
|
<Match when={props.node.kind === 'row' ? props.node : undefined}>{
|
||||||
|
row => <Row key={row().key} value={row().value} depth={props.depth} groupedBy={props.groupedBy} />
|
||||||
|
}</Match>
|
||||||
|
|
||||||
|
<Match when={props.node.kind === 'group' ? props.node : undefined}>{
|
||||||
|
group => <Group key={group().key} groupedBy={group().groupedBy} nodes={group().nodes} depth={props.depth} />
|
||||||
|
}</Match>
|
||||||
|
</Switch>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row<T extends Record<string, any>>(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 <div class={css.row} use:selectable={{ value: props.value, key: props.key }}>
|
||||||
|
<Show when={table.selectionMode() !== SelectionMode.None}>
|
||||||
|
<div class={css.cell}>
|
||||||
|
<input type="checkbox" checked={isSelected()} on:input={() => context.select([props.key])} on:pointerdown={e => e.stopPropagation()} />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<For each={values()}>{
|
||||||
|
([k, v]) => <div style={k === props.groupedBy ? { '--depth': props.depth } : {}} class={css.cell}>{table.cellRenderers()[k]?.({ value: v }) ?? v}</div>
|
||||||
|
}</For>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Group<T extends Record<string, any>>(props: { key: string, groupedBy: keyof T, nodes: Node<T>[], depth: number }) {
|
||||||
|
const table = useTable();
|
||||||
|
|
||||||
|
const gridColumn = createMemo(() => {
|
||||||
|
const groupedBy = props.groupedBy;
|
||||||
|
const columns = table.columns();
|
||||||
|
const selectable = table.selectionMode() !== SelectionMode.None;
|
||||||
|
|
||||||
|
return columns.findIndex(({ id }) => id === groupedBy) + (selectable ? 2 : 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <details open>
|
||||||
|
<summary style={{ '--depth': props.depth, 'grid-column-start': gridColumn() }}>{props.key}</summary>
|
||||||
|
|
||||||
|
<For each={props.nodes}>{
|
||||||
|
node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} />
|
||||||
|
}</For>
|
||||||
|
</details>;
|
||||||
|
};
|
|
@ -13,6 +13,12 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& fieldset {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
gap: var(--padding-m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .content {
|
& .content {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { Table } from "~/components/table";
|
import { Column, GroupNode, RowNode, Node, SelectionMode, Table } from "~/components/table";
|
||||||
import css from "./experimental.module.css";
|
import css from "./experimental.module.css";
|
||||||
import { Sidebar } from "~/components/sidebar";
|
import { Sidebar } from "~/components/sidebar";
|
||||||
|
import { createStore } from "solid-js/store";
|
||||||
|
import { createEffect, For } from "solid-js";
|
||||||
|
|
||||||
export default function Experimental() {
|
export default function Experimental() {
|
||||||
const rows = [
|
const rows = [
|
||||||
|
@ -23,13 +25,93 @@ export default function Experimental() {
|
||||||
{ key: 'key2.c.a', value: 70 },
|
{ key: 'key2.c.a', value: 70 },
|
||||||
{ key: 'key2.c.b', value: 80 },
|
{ key: 'key2.c.b', value: 80 },
|
||||||
{ key: 'key2.c.c', value: 90 },
|
{ key: 'key2.c.c', value: 90 },
|
||||||
|
|
||||||
|
{ key: 'aaaa', value: 200 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
type Entry = typeof rows[0];
|
||||||
|
const columns: Column<Entry>[] = [
|
||||||
|
{
|
||||||
|
id: 'key',
|
||||||
|
label: 'Key',
|
||||||
|
groupBy(rows: RowNode<Entry>[]) {
|
||||||
|
const group = (nodes: (RowNode<Entry> & { _key: string })[]): Node<Entry>[] => nodes.every(n => n._key.includes('.') === false)
|
||||||
|
? nodes
|
||||||
|
: Object.entries(Object.groupBy(nodes, r => String(r._key).split('.').at(0)!))
|
||||||
|
.map<GroupNode<Entry>>(([key, nodes]) => ({ kind: 'group', key, groupedBy: 'key', nodes: group(nodes!.map(n => ({ ...n, _key: n._key.slice(key.length + 1) }))) }));
|
||||||
|
|
||||||
|
return group(rows.map(row => ({ ...row, _key: row.value.key })));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'value',
|
||||||
|
label: 'Value',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [store, setStore] = createStore<{ selectionMode: SelectionMode, groupBy?: keyof Entry, sort?: { by: keyof Entry, reversed?: boolean } }>({
|
||||||
|
selectionMode: SelectionMode.None,
|
||||||
|
// groupBy: 'value',
|
||||||
|
// sortBy: 'key'
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log({ ...store });
|
||||||
|
});
|
||||||
|
|
||||||
return <div class={css.root}>
|
return <div class={css.root}>
|
||||||
<Sidebar as="aside" label={'Filters'} class={css.sidebar} />
|
<Sidebar as="aside" label={'Filters'} class={css.sidebar}>
|
||||||
|
<fieldset>
|
||||||
|
<legend>table properties</legend>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Selection mode
|
||||||
|
|
||||||
|
<select value={store.selectionMode} oninput={e => setStore('selectionMode', Number.parseInt(e.target.value))}>
|
||||||
|
<option value={SelectionMode.None}>None</option>
|
||||||
|
<option value={SelectionMode.Single}>Single</option>
|
||||||
|
<option value={SelectionMode.Multiple}>Multiple</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Group by
|
||||||
|
|
||||||
|
<select value={store.groupBy ?? ''} oninput={e => setStore('groupBy', (e.target.value || undefined) as any)}>
|
||||||
|
<option value=''>None</option>
|
||||||
|
<For each={columns}>{
|
||||||
|
column => <option value={column.id}>{column.label}</option>
|
||||||
|
}</For>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>table sorting</legend>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
by
|
||||||
|
|
||||||
|
<select value={store.sort?.by ?? ''} oninput={e => setStore('sort', prev => e.target.value ? { by: e.target.value as keyof Entry, reversed: prev?.reversed } : undefined)}>
|
||||||
|
<option value=''>None</option>
|
||||||
|
<For each={columns}>{
|
||||||
|
column => <option value={column.id}>{column.label}</option>
|
||||||
|
}</For>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
reversed
|
||||||
|
|
||||||
|
<input type="checkbox" checked={store.sort?.reversed ?? false} oninput={e => setStore('sort', prev => prev !== undefined ? { by: prev.by, reversed: e.target.checked || undefined } : undefined)} />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</Sidebar>
|
||||||
|
|
||||||
<div class={css.content}>
|
<div class={css.content}>
|
||||||
<Table rows={rows} />
|
<Table rows={rows} columns={columns} groupBy={store.groupBy} sort={store.sort} selectionMode={store.selectionMode}>{{
|
||||||
|
value: (cell) => <input type="number" value={cell.value} />,
|
||||||
|
}}</Table>
|
||||||
</div>
|
</div>
|
||||||
</div >;
|
</div >;
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue