[Feature] Add language #19

Merged
chris-kruining merged 25 commits from feature/add-language into main 2024-12-19 15:22:42 +00:00
3 changed files with 80 additions and 4 deletions
Showing only changes of commit de79467db3 - Show all commits

View file

@ -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 {

View file

@ -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>;
}; };

View file

@ -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 >;