Add column sort state.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
0efb60bb66
commit
3dd366b51d
5 changed files with 192 additions and 17 deletions
|
@ -1,4 +1,4 @@
|
|||
import React, {useContext} from "react";
|
||||
import React, {useCallback, useContext} from "react";
|
||||
import {Smartable, useTable} from "./Smartable";
|
||||
|
||||
/**
|
||||
|
@ -48,6 +48,31 @@ export function createColumn<K extends ColumnKey>(key: K, column: Column): [K, C
|
|||
return [key, column];
|
||||
}
|
||||
|
||||
/**
|
||||
* Column sort type.
|
||||
*/
|
||||
export enum SortType
|
||||
{
|
||||
ASC = "asc",
|
||||
DESC = "desc",
|
||||
}
|
||||
|
||||
/**
|
||||
* Column sort state.
|
||||
*/
|
||||
export interface SortState
|
||||
{
|
||||
/**
|
||||
* Sort type (ascending or descending).
|
||||
*/
|
||||
type: SortType;
|
||||
|
||||
/**
|
||||
* Sort order.
|
||||
*/
|
||||
order: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table column context data.
|
||||
*/
|
||||
|
@ -62,6 +87,11 @@ export interface ColumnContextData<CK extends ColumnKey>
|
|||
* Column definition.
|
||||
*/
|
||||
column: Column;
|
||||
|
||||
/**
|
||||
* Column sort state.
|
||||
*/
|
||||
sortState?: SortState;
|
||||
}
|
||||
|
||||
export const ColumnContext = React.createContext<ColumnContextData<ColumnKey>>(undefined);
|
||||
|
@ -80,8 +110,37 @@ export function useColumn<CK extends ColumnKey>(smartable?: Smartable<CK>): Colu
|
|||
export function ColumnHeading()
|
||||
{
|
||||
// Get current column data.
|
||||
const {column} = useColumn();
|
||||
return <th>{column.title}</th>;
|
||||
const {key, column, sortState} = useColumn();
|
||||
|
||||
// Get column sort state setter.
|
||||
const {setColumnSortState} = useTable();
|
||||
|
||||
// Initialize handle click function.
|
||||
const handleClick = useCallback((event: React.MouseEvent) => {
|
||||
if (event.button == 0)
|
||||
{ // Normal click (usually left click).
|
||||
// Toggle sort type.
|
||||
setColumnSortState(key, sortState?.type == SortType.ASC ? SortType.DESC : SortType.ASC);
|
||||
}
|
||||
else if (event.button == 2 || event.button == 1)
|
||||
{ // Alt click (usually right or middle click).
|
||||
// Reset sort type.
|
||||
setColumnSortState(key, null);
|
||||
}
|
||||
}, [key, sortState, setColumnSortState]);
|
||||
|
||||
// Disable context menu function.
|
||||
const disableContextMenu = useCallback((event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<th className={sortState?.type ?? undefined}
|
||||
onMouseDown={handleClick} onContextMenu={disableContextMenu}>
|
||||
{column.title}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,6 +156,8 @@ export function AutoColumnContextProvider({columnKey, children}: React.PropsWith
|
|||
key: columnKey,
|
||||
// Get current column data from table data.
|
||||
column: table.columns[columnKey],
|
||||
// Get current column sort state from table data.
|
||||
sortState: table.columnsSortState?.[columnKey],
|
||||
}}>
|
||||
{children}
|
||||
</ColumnContext.Provider>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import {Column, ColumnContext, ColumnHeading, ColumnKey, Columns} from "./Column";
|
||||
import {AutoColumnContextProvider, Column, ColumnContext, ColumnHeading, ColumnKey, Columns} from "./Column";
|
||||
import {SmartableProperties, useTable} from "./Smartable";
|
||||
import {Async, Promisable} from "@kernelui/core";
|
||||
import {RowDefinition, RowInstance} from "./Row";
|
||||
|
@ -40,13 +40,10 @@ export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Colum
|
|||
return (
|
||||
<tr className={"headings"}>
|
||||
{ // Showing title of each column.
|
||||
Object.entries(columns).map(([key, column]) => (
|
||||
<ColumnContext.Provider key={key} value={{
|
||||
key: key,
|
||||
column: column as Column,
|
||||
}}>
|
||||
Object.keys(columns).map((key) => (
|
||||
<AutoColumnContextProvider key={key} columnKey={key}>
|
||||
<ColumnHeading />
|
||||
</ColumnContext.Provider>
|
||||
</AutoColumnContextProvider>
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {useContext} from "react";
|
||||
import React, {useCallback, useContext, useMemo, useState} from "react";
|
||||
import {Instance} from "./Instance";
|
||||
import {ColumnKey, Columns} from "./Column";
|
||||
import {ColumnKey, Columns, SortState, SortType} from "./Column";
|
||||
import {RowDefinition} from "./Row";
|
||||
import {Promisable} from "@kernelui/core";
|
||||
|
||||
|
@ -80,14 +80,54 @@ export function createSmartable<CK extends ColumnKey>({columns}: {
|
|||
|
||||
return {
|
||||
Table: (props: SmartableProperties<CK>) => {
|
||||
// Initialize sort state.
|
||||
const [sortState, setSortState] = useState({} as Record<CK, SortState>);
|
||||
|
||||
// Filter columns from the given property.
|
||||
const filteredColumns = props.shownColumns ? filterColumns(columns, props.shownColumns) : columns;
|
||||
|
||||
return (
|
||||
<TableContext.Provider value={{
|
||||
// Set sort state of a specific column.
|
||||
const setColumnSortState = useCallback((key: CK, sortType: SortType|null): void => {
|
||||
// Copy current sort state.
|
||||
let newSortState = {...sortState};
|
||||
|
||||
if (sortType)
|
||||
// A new sort type for given column has been set.
|
||||
newSortState[key] = {
|
||||
// Setting new sort type.
|
||||
type: sortType,
|
||||
// Keeping current order, or creating a new one (from the current state size).
|
||||
order: newSortState?.[key]?.order ?? (Object.keys(sortState).length + 1),
|
||||
};
|
||||
else if (newSortState[key])
|
||||
{ // Sort type for given column has been reset, removing it.
|
||||
const removedOrderKey = newSortState[key]?.order;
|
||||
delete newSortState[key];
|
||||
|
||||
// Decrement all remaining greater orders by one, as there is now one less sorted column.
|
||||
newSortState = Object.fromEntries((Object.entries(newSortState) as [CK, SortState][]).map(
|
||||
// For each column sort state...
|
||||
([columnKey, {order: columnSortOrder, ...columnSortState}]) => (
|
||||
//... copy the current column sort state, just decrement its order.
|
||||
[columnKey, {order: columnSortOrder - (removedOrderKey < columnSortOrder ? 1 : 0), ...columnSortState} as SortState]
|
||||
)
|
||||
)) as Record<CK, SortState>;
|
||||
}
|
||||
|
||||
// Set new sort state.
|
||||
setSortState(newSortState);
|
||||
}, [sortState, setSortState]);
|
||||
|
||||
// Initialize table context value.
|
||||
const contextValue = useMemo<TableContextData<CK>>(() => ({
|
||||
columns: filteredColumns,
|
||||
columnsSortState: sortState,
|
||||
setColumnSortState: setColumnSortState,
|
||||
...props,
|
||||
}}>
|
||||
}), [filteredColumns, sortState, setSortState, props]);
|
||||
|
||||
return (
|
||||
<TableContext.Provider value={contextValue}>
|
||||
<Instance columns={filteredColumns} {...props} />
|
||||
</TableContext.Provider>
|
||||
);
|
||||
|
@ -105,6 +145,18 @@ export interface TableContextData<CK extends ColumnKey> extends SmartablePropert
|
|||
* Current table columns.
|
||||
*/
|
||||
columns: Columns<CK>;
|
||||
|
||||
/**
|
||||
* Current table columns sort state.
|
||||
*/
|
||||
columnsSortState: Record<CK, SortState>;
|
||||
|
||||
/**
|
||||
* Set current table columns sort state.
|
||||
* @param key The column key for which to set the sort type.
|
||||
* @param sortType The sort type to set for the given column. NULL to reset sort state.
|
||||
*/
|
||||
setColumnSortState: (key: CK, sortType: SortType|null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
64
src/styles/_headings.less
Normal file
64
src/styles/_headings.less
Normal file
|
@ -0,0 +1,64 @@
|
|||
tr.headings
|
||||
{
|
||||
th
|
||||
{
|
||||
position: relative;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&::before, &::after
|
||||
{ // Sorting order indicator.
|
||||
transition: height 0.2s ease, background 0.2s ease, top 0.2s ease, bottom 0.2s ease;
|
||||
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: block;
|
||||
margin: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
background: var(--background-darkest);
|
||||
}
|
||||
|
||||
&::before
|
||||
{
|
||||
right: calc(0.33em - 1px);
|
||||
|
||||
width: 2px;
|
||||
height: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
&::after
|
||||
{
|
||||
right: calc(0.33em - 3px);
|
||||
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&.asc, &.desc
|
||||
{
|
||||
&::after, &::before
|
||||
{
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
&::before
|
||||
{
|
||||
height: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
&.asc::after
|
||||
{
|
||||
top: 0.5em;
|
||||
}
|
||||
&.desc::after
|
||||
{
|
||||
bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
@import "_headings";
|
Loading…
Reference in a new issue