Smartable tables basic structure.
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
036861bf4d
commit
73ee303c5d
8 changed files with 449 additions and 6 deletions
|
@ -1,4 +1,5 @@
|
||||||
import "@kernelui/core/lib/style.css";
|
import "@kernelui/core/lib/style.css";
|
||||||
|
import "../src/styles/smartable.less";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {createRoot} from "react-dom/client";
|
import {createRoot} from "react-dom/client";
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
|
"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kernelui/core": "^1.0.1"
|
"@kernelui/core": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
|
|
68
src/Smartable/Cell.tsx
Normal file
68
src/Smartable/Cell.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import React, {useContext} from "react";
|
||||||
|
import {ColumnKey, useColumn} from "./Column";
|
||||||
|
import {Smartable} from "./Smartable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable cell definition.
|
||||||
|
*/
|
||||||
|
export interface CellDefinition<T = any>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Cell data.
|
||||||
|
*/
|
||||||
|
data: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendered cell element.
|
||||||
|
*/
|
||||||
|
element?: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default cell component.
|
||||||
|
*/
|
||||||
|
export function Cell({children}: React.PropsWithChildren<{}>)
|
||||||
|
{
|
||||||
|
// Get cell data.
|
||||||
|
const {data} = useCell();
|
||||||
|
|
||||||
|
// Try to render cell data to string when no children given.
|
||||||
|
return (
|
||||||
|
<td>{children ?? String(data)}</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table cell context data.
|
||||||
|
*/
|
||||||
|
export interface CellContextData<CK extends ColumnKey, T = any> extends CellDefinition<T>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const CellContext = React.createContext<CellContextData<ColumnKey>>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get current cell data.
|
||||||
|
*/
|
||||||
|
export function useCell<CK extends ColumnKey, T = any>(smartable?: Smartable<CK>): CellContextData<CK, T>
|
||||||
|
{
|
||||||
|
return useContext(CellContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cell instance component.
|
||||||
|
*/
|
||||||
|
export function CellInstance<T>({cell}: {cell: CellDefinition<T>})
|
||||||
|
{
|
||||||
|
// Get column data.
|
||||||
|
const {column} = useColumn();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellContext.Provider value={cell}>
|
||||||
|
{ // Trying to render cell-specific element, then column-specific element, then default element.
|
||||||
|
//TODO add row-specific element?
|
||||||
|
cell.element ?? column.cellElement ?? <Cell />
|
||||||
|
}
|
||||||
|
</CellContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
104
src/Smartable/Column.tsx
Normal file
104
src/Smartable/Column.tsx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import React, {useContext} from "react";
|
||||||
|
import {Smartable, useTable} from "./Smartable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic column key type.
|
||||||
|
*/
|
||||||
|
export type ColumnKey = string|symbol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable columns definition.
|
||||||
|
*/
|
||||||
|
export type Columns<K extends ColumnKey> = Record<K, Column>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define columns for a Smartable.
|
||||||
|
* @param columns
|
||||||
|
*/
|
||||||
|
export function createColumns<K extends ColumnKey>(...columns: [K, Column][]): Columns<K>
|
||||||
|
{
|
||||||
|
return Object.fromEntries(
|
||||||
|
columns,
|
||||||
|
) as Columns<K>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable column definition.
|
||||||
|
*/
|
||||||
|
export interface Column
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Column title element.
|
||||||
|
*/
|
||||||
|
title: React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column cell default element.
|
||||||
|
*/
|
||||||
|
cellElement?: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a column for a Smartable.
|
||||||
|
* @param key The column key, which should be unique per Smartable instance.
|
||||||
|
* @param column The column definition.
|
||||||
|
*/
|
||||||
|
export function createColumn<K extends ColumnKey>(key: K, column: Column): [K, Column]
|
||||||
|
{
|
||||||
|
return [key, column];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table column context data.
|
||||||
|
*/
|
||||||
|
export interface ColumnContextData<CK extends ColumnKey>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Column key.
|
||||||
|
*/
|
||||||
|
key: ColumnKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column definition.
|
||||||
|
*/
|
||||||
|
column: Column;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ColumnContext = React.createContext<ColumnContextData<ColumnKey>>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get current column data.
|
||||||
|
*/
|
||||||
|
export function useColumn<CK extends ColumnKey>(smartable?: Smartable<CK>): ColumnContextData<CK>
|
||||||
|
{
|
||||||
|
return useContext(ColumnContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default column heading component.
|
||||||
|
*/
|
||||||
|
export function ColumnHeading()
|
||||||
|
{
|
||||||
|
// Get current column data.
|
||||||
|
const {column} = useColumn();
|
||||||
|
return <th>{column.title}</th>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto column context provider from table context.
|
||||||
|
*/
|
||||||
|
export function AutoColumnContextProvider({columnKey, children}: React.PropsWithChildren<{columnKey: ColumnKey;}>)
|
||||||
|
{
|
||||||
|
// Get table data.
|
||||||
|
const table = useTable();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColumnContext.Provider value={{
|
||||||
|
key: columnKey,
|
||||||
|
// Get current column data from table data.
|
||||||
|
column: table.columns[columnKey],
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</ColumnContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
80
src/Smartable/Instance.tsx
Normal file
80
src/Smartable/Instance.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import React from "react";
|
||||||
|
import {Column, ColumnContext, ColumnHeading, ColumnKey, Columns} from "./Column";
|
||||||
|
import {SmartableProperties, useTable} from "./Smartable";
|
||||||
|
import {Async, Promisable} from "@kernelui/core";
|
||||||
|
import {RowDefinition, RowInstance} from "./Row";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable instance component properties.
|
||||||
|
*/
|
||||||
|
export interface InstanceProperties<CK extends ColumnKey> extends SmartableProperties<CK>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Table columns.
|
||||||
|
*/
|
||||||
|
columns: Columns<CK>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main component for a Smartable table.
|
||||||
|
*/
|
||||||
|
export function Instance<CK extends ColumnKey>({columns}: InstanceProperties<CK>)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<ColumnsHeadings columns={columns} />
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<TableBody />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Columns headings of a Smartable table.
|
||||||
|
*/
|
||||||
|
export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Columns<CK>})
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
}}>
|
||||||
|
<ColumnHeading/>
|
||||||
|
</ColumnContext.Provider>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableBody<CK extends ColumnKey>()
|
||||||
|
{
|
||||||
|
// Get data from table.
|
||||||
|
const {data} = useTable();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Async<(Promisable<RowDefinition<CK>>)[]> promise={data}>
|
||||||
|
{(rowsData) => (
|
||||||
|
// Rendering defined rows.
|
||||||
|
<>
|
||||||
|
{ // Rendering each row.
|
||||||
|
rowsData.map((rowData, index) => (
|
||||||
|
// Rendering current row from its definition.
|
||||||
|
<Async<RowDefinition<CK>> key={index} promise={rowData}>
|
||||||
|
{(rowDefinition) => (
|
||||||
|
<RowInstance row={rowDefinition} />
|
||||||
|
)}
|
||||||
|
</Async>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Async>
|
||||||
|
);
|
||||||
|
}
|
99
src/Smartable/Row.tsx
Normal file
99
src/Smartable/Row.tsx
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import React, {useContext, useMemo} from "react";
|
||||||
|
import {AutoColumnContextProvider, ColumnKey} from "./Column";
|
||||||
|
import {CellDefinition, CellInstance} from "./Cell";
|
||||||
|
import {Smartable} from "./Smartable";
|
||||||
|
import {Async, Promisable} from "@kernelui/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable row cells.
|
||||||
|
*/
|
||||||
|
export type RowCells<CK extends ColumnKey, T = any> = Record<CK, Promisable<CellDefinition<T>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable row data.
|
||||||
|
*/
|
||||||
|
export interface RowData<CK extends ColumnKey, T = any>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Cells definition.
|
||||||
|
*/
|
||||||
|
cells: RowCells<CK, T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendered row element.
|
||||||
|
*/
|
||||||
|
element?: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable row definition.
|
||||||
|
*/
|
||||||
|
export type RowDefinition<CK extends ColumnKey> = RowCells<CK>|RowData<CK>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table row context data.
|
||||||
|
*/
|
||||||
|
export interface RowContextData<CK extends ColumnKey, T = any> extends RowData<CK, T>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const RowContext = React.createContext<RowContextData<ColumnKey>>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get current row data.
|
||||||
|
*/
|
||||||
|
export function useRow<CK extends ColumnKey, T = any>(smartable?: Smartable<CK>): RowContextData<CK, T>
|
||||||
|
{
|
||||||
|
return useContext(RowContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default row component.
|
||||||
|
*/
|
||||||
|
export function Row()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<RowCells />
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RowCells()
|
||||||
|
{
|
||||||
|
// Get row data.
|
||||||
|
const row = useRow();
|
||||||
|
|
||||||
|
return (
|
||||||
|
Object.entries(row.cells).map(([columnKey, cellData]) => (
|
||||||
|
<AutoColumnContextProvider key={columnKey} columnKey={columnKey}>
|
||||||
|
<Async<CellDefinition> promise={cellData}>
|
||||||
|
{(cellDefinition) => (
|
||||||
|
<CellInstance cell={cellDefinition} />
|
||||||
|
)}
|
||||||
|
</Async>
|
||||||
|
</AutoColumnContextProvider>
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row instance component.
|
||||||
|
*/
|
||||||
|
export function RowInstance<CK extends ColumnKey>({row}: { row: RowDefinition<CK> })
|
||||||
|
{
|
||||||
|
// Get row context value from given row definition.
|
||||||
|
const rowContextValue = useMemo(() => (
|
||||||
|
// If a simple RowCells<CK> object is given, converting it to a RowData<CK>
|
||||||
|
!("cells" in row) ? { cells: row } : row
|
||||||
|
), [row]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RowContext.Provider value={rowContextValue}>
|
||||||
|
{ // Trying to render row-specific element, then default element.
|
||||||
|
//TODO add table-specific element?
|
||||||
|
rowContextValue.element ?? <Row />
|
||||||
|
}
|
||||||
|
</RowContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
91
src/Smartable/Smartable.tsx
Normal file
91
src/Smartable/Smartable.tsx
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import React, {useContext} from "react";
|
||||||
|
import {Instance} from "./Instance";
|
||||||
|
import {ColumnKey, Columns} from "./Column";
|
||||||
|
import {RowDefinition} from "./Row";
|
||||||
|
import {Promisable} from "@kernelui/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable data type.
|
||||||
|
*/
|
||||||
|
export type SmartableData<CK extends ColumnKey> = Promisable<(Promisable<RowDefinition<CK>>)[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable properties.
|
||||||
|
*/
|
||||||
|
export interface SmartableProperties<CK extends ColumnKey>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Table data.
|
||||||
|
*/
|
||||||
|
data: SmartableData<CK>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default row element.
|
||||||
|
*/
|
||||||
|
rowElement?: React.ReactElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default column heading element.
|
||||||
|
*/
|
||||||
|
columnHeadingElement?: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable main object.
|
||||||
|
*/
|
||||||
|
export type Smartable<CK extends ColumnKey> = {
|
||||||
|
/**
|
||||||
|
* Table component.
|
||||||
|
*/
|
||||||
|
Table: React.FunctionComponent<SmartableProperties<CK>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a new Smartable.
|
||||||
|
*/
|
||||||
|
export function createSmartable<CK extends ColumnKey>({columns}: {
|
||||||
|
/**
|
||||||
|
* Smartable table columns.
|
||||||
|
*/
|
||||||
|
columns: Columns<CK>;
|
||||||
|
}): Smartable<CK>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
Table: (props: SmartableProperties<CK>) => {
|
||||||
|
return (
|
||||||
|
<TableContext.Provider value={{
|
||||||
|
columns: columns,
|
||||||
|
...props,
|
||||||
|
}}>
|
||||||
|
<Instance columns={columns} {...props} />
|
||||||
|
</TableContext.Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable table context data.
|
||||||
|
*/
|
||||||
|
export interface TableContextData<CK extends ColumnKey> extends SmartableProperties<CK>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Current table columns.
|
||||||
|
*/
|
||||||
|
columns: Columns<CK>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartable table context.
|
||||||
|
*/
|
||||||
|
const TableContext = React.createContext<TableContextData<ColumnKey>>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get current Smartable table context.
|
||||||
|
* @param smartable The Smartable main object.
|
||||||
|
*/
|
||||||
|
export function useTable<CK extends ColumnKey>(smartable?: Smartable<CK>): TableContextData<CK>
|
||||||
|
{
|
||||||
|
return useContext(TableContext);
|
||||||
|
}
|
10
yarn.lock
10
yarn.lock
|
@ -563,9 +563,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@kernelui/core@npm:^1.0.1":
|
"@kernelui/core@npm:^1.1.0":
|
||||||
version: 1.0.1
|
version: 1.1.0
|
||||||
resolution: "@kernelui/core@npm:1.0.1::__archiveUrl=https%3A%2F%2Fcode.zeptotech.net%2Fapi%2Fpackages%2FUIKernel%2Fnpm%2F%2540kernelui%252Fcore%2F-%2F1.0.1%2Fcore-1.0.1.tgz"
|
resolution: "@kernelui/core@npm:1.1.0::__archiveUrl=https%3A%2F%2Fcode.zeptotech.net%2Fapi%2Fpackages%2FUIKernel%2Fnpm%2F%2540kernelui%252Fcore%2F-%2F1.1.0%2Fcore-1.1.0.tgz"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@floating-ui/react": "npm:^0.26.17"
|
"@floating-ui/react": "npm:^0.26.17"
|
||||||
"@fontsource-variable/jetbrains-mono": "npm:^5.0.21"
|
"@fontsource-variable/jetbrains-mono": "npm:^5.0.21"
|
||||||
|
@ -577,7 +577,7 @@ __metadata:
|
||||||
react-merge-refs: "npm:^2.1.1"
|
react-merge-refs: "npm:^2.1.1"
|
||||||
react-router-dom: "npm:^6.24.1"
|
react-router-dom: "npm:^6.24.1"
|
||||||
uuid: "npm:^10.0.0"
|
uuid: "npm:^10.0.0"
|
||||||
checksum: 10c0/39b65996ca12eb3384db24a97c554c6a84e88088862dcdbd31331227f8d9a4fc91544367a9c5cabc6c6dc9154c8b904028b719eca2f6811297f8578e11b07638
|
checksum: 10c0/24ade4c41c2d0f55301324550ee548127532b14217f4104e2de8ee3a3243adad11b61abe1c4275f1978d3175201392ac99188c89f02b7191ea97f6cdac698e4f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -585,7 +585,7 @@ __metadata:
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@kernelui/smartable@workspace:."
|
resolution: "@kernelui/smartable@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
"@kernelui/core": "npm:^1.0.1"
|
"@kernelui/core": "npm:^1.1.0"
|
||||||
"@types/react": "npm:^18.3.3"
|
"@types/react": "npm:^18.3.3"
|
||||||
"@types/react-dom": "npm:^18.3.0"
|
"@types/react-dom": "npm:^18.3.0"
|
||||||
"@vitejs/plugin-react": "npm:^4.3.0"
|
"@vitejs/plugin-react": "npm:^4.3.0"
|
||||||
|
|
Loading…
Reference in a new issue