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 "../src/styles/smartable.less";
|
||||
|
||||
import React from "react";
|
||||
import {createRoot} from "react-dom/client";
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kernelui/core": "^1.0.1"
|
||||
"@kernelui/core": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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
|
||||
linkType: hard
|
||||
|
||||
"@kernelui/core@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
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"
|
||||
"@kernelui/core@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
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:
|
||||
"@floating-ui/react": "npm:^0.26.17"
|
||||
"@fontsource-variable/jetbrains-mono": "npm:^5.0.21"
|
||||
|
@ -577,7 +577,7 @@ __metadata:
|
|||
react-merge-refs: "npm:^2.1.1"
|
||||
react-router-dom: "npm:^6.24.1"
|
||||
uuid: "npm:^10.0.0"
|
||||
checksum: 10c0/39b65996ca12eb3384db24a97c554c6a84e88088862dcdbd31331227f8d9a4fc91544367a9c5cabc6c6dc9154c8b904028b719eca2f6811297f8578e11b07638
|
||||
checksum: 10c0/24ade4c41c2d0f55301324550ee548127532b14217f4104e2de8ee3a3243adad11b61abe1c4275f1978d3175201392ac99188c89f02b7191ea97f6cdac698e4f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -585,7 +585,7 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "@kernelui/smartable@workspace:."
|
||||
dependencies:
|
||||
"@kernelui/core": "npm:^1.0.1"
|
||||
"@kernelui/core": "npm:^1.1.0"
|
||||
"@types/react": "npm:^18.3.3"
|
||||
"@types/react-dom": "npm:^18.3.0"
|
||||
"@vitejs/plugin-react": "npm:^4.3.0"
|
||||
|
|
Loading…
Reference in a new issue