diff --git a/demo/demo.tsx b/demo/demo.tsx index 24ff49f..cdb2ea3 100644 --- a/demo/demo.tsx +++ b/demo/demo.tsx @@ -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"; diff --git a/package.json b/package.json index 7a76137..a751c64 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Smartable/Cell.tsx b/src/Smartable/Cell.tsx new file mode 100644 index 0000000..acf1fec --- /dev/null +++ b/src/Smartable/Cell.tsx @@ -0,0 +1,69 @@ +import React, {useContext} from "react"; +import {ColumnKey, useColumn} from "./Column"; +import {Smartable} from "./Smartable"; +import {useRow} from "./Row"; + +/** + * Smartable cell definition. + */ +export interface CellDefinition +{ + /** + * 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 ( + {children ?? String(data)} + ); +} + +/** + * Table cell context data. + */ +export interface CellContextData extends CellDefinition +{ +} + +const CellContext = React.createContext>(undefined); + +/** + * Hook to get current cell data. + */ +export function useCell(smartable?: Smartable): CellContextData +{ + return useContext(CellContext); +} + +/** + * Cell instance component. + */ +export function CellInstance({cell}: {cell: CellDefinition}) +{ + // Get column data. + const {column} = useColumn(); + const row = useRow(); + + return ( + + { // Trying to render cell-specific element, then column-specific element, then default element. + cell.element ?? column.cellElement ?? row.cellElement ?? + } + + ) +} diff --git a/src/Smartable/Column.tsx b/src/Smartable/Column.tsx new file mode 100644 index 0000000..7566096 --- /dev/null +++ b/src/Smartable/Column.tsx @@ -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 = Record; + +/** + * Define columns for a Smartable. + * @param columns + */ +export function createColumns(...columns: [K, Column][]): Columns +{ + return Object.fromEntries( + columns, + ) as Columns; +} + +/** + * 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(key: K, column: Column): [K, Column] +{ + return [key, column]; +} + +/** + * Table column context data. + */ +export interface ColumnContextData +{ + /** + * Column key. + */ + key: ColumnKey; + + /** + * Column definition. + */ + column: Column; +} + +export const ColumnContext = React.createContext>(undefined); + +/** + * Hook to get current column data. + */ +export function useColumn(smartable?: Smartable): ColumnContextData +{ + return useContext(ColumnContext); +} + +/** + * Default column heading component. + */ +export function ColumnHeading() +{ + // Get current column data. + const {column} = useColumn(); + return {column.title}; +} + +/** + * Auto column context provider from table context. + */ +export function AutoColumnContextProvider({columnKey, children}: React.PropsWithChildren<{columnKey: ColumnKey;}>) +{ + // Get table data. + const table = useTable(); + + return ( + + {children} + + ) +} diff --git a/src/Smartable/Instance.tsx b/src/Smartable/Instance.tsx new file mode 100644 index 0000000..c807793 --- /dev/null +++ b/src/Smartable/Instance.tsx @@ -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 extends SmartableProperties +{ + /** + * Table columns. + */ + columns: Columns; +} + +/** + * Main component for a Smartable table. + */ +export function Instance({columns}: InstanceProperties) +{ + return ( + + + + + + + +
+ ); +} + +/** + * Columns headings of a Smartable table. + */ +export function ColumnsHeadings({columns}: {columns: Columns}) +{ + return ( + + { // Showing title of each column. + Object.entries(columns).map(([key, column]) => ( + + + + )) + } + + ); +} + +export function TableBody() +{ + // Get data from table. + const {data} = useTable(); + + return ( + >)[]> promise={data}> + {(rowsData) => ( + // Rendering defined rows. + <> + { // Rendering each row. + rowsData.map((rowData, index) => ( + // Rendering current row from its definition. + > key={index} promise={rowData}> + {(rowDefinition) => ( + + )} + + )) + } + + )} + + ); +} diff --git a/src/Smartable/Row.tsx b/src/Smartable/Row.tsx new file mode 100644 index 0000000..323788d --- /dev/null +++ b/src/Smartable/Row.tsx @@ -0,0 +1,105 @@ +import React, {useContext, useMemo} from "react"; +import {AutoColumnContextProvider, ColumnKey} from "./Column"; +import {CellDefinition, CellInstance} from "./Cell"; +import {Smartable, useTable} from "./Smartable"; +import {Async, Promisable} from "@kernelui/core"; + +/** + * Smartable row cells. + */ +export type RowCells = Record>>; + +/** + * Smartable row data. + */ +export interface RowData +{ + /** + * Cells definition. + */ + cells: RowCells; + + /** + * Rendered row element. + */ + element?: React.ReactElement; + + /** + * Rendered row cell element. + */ + cellElement?: React.ReactElement; +} + +/** + * Smartable row definition. + */ +export type RowDefinition = RowCells|RowData; + +/** + * Table row context data. + */ +export interface RowContextData extends RowData +{ +} + +const RowContext = React.createContext>(undefined); + +/** + * Hook to get current row data. + */ +export function useRow(smartable?: Smartable): RowContextData +{ + return useContext(RowContext); +} + +/** + * Default row component. + */ +export function Row() +{ + return ( + + + + ); +} + +export function RowCells() +{ + // Get row data. + const row = useRow(); + + return ( + Object.entries(row.cells).map(([columnKey, cellData]) => ( + + promise={cellData}> + {(cellDefinition) => ( + + )} + + + )) + ); +} + +/** + * Row instance component. + */ +export function RowInstance({row}: { row: RowDefinition }) +{ + // Get row context value from given row definition. + const rowContextValue = useMemo(() => ( + // If a simple RowCells object is given, converting it to a RowData + !("cells" in row) ? { cells: row } : row + ), [row]); + + const {rowElement} = useTable(); + + return ( + + { // Trying to render row-specific element, then table-specific element, then default element. + rowContextValue.element ?? rowElement ?? + } + + ); +} diff --git a/src/Smartable/Smartable.tsx b/src/Smartable/Smartable.tsx new file mode 100644 index 0000000..9403c40 --- /dev/null +++ b/src/Smartable/Smartable.tsx @@ -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 = Promisable<(Promisable>)[]>; + +/** + * Smartable properties. + */ +export interface SmartableProperties +{ + /** + * Table data. + */ + data: SmartableData; + + /** + * Default row element. + */ + rowElement?: React.ReactElement; + + /** + * Default column heading element. + */ + columnHeadingElement?: React.ReactElement; +} + +/** + * Smartable main object. + */ +export type Smartable = { + /** + * Table component. + */ + Table: React.FunctionComponent>; +}; + +/** + * Define a new Smartable. + */ +export function createSmartable({columns}: { + /** + * Smartable table columns. + */ + columns: Columns; +}): Smartable +{ + return { + Table: (props: SmartableProperties) => { + return ( + + + + ); + }, + + }; +} + +/** + * Smartable table context data. + */ +export interface TableContextData extends SmartableProperties +{ + /** + * Current table columns. + */ + columns: Columns; +} + +/** + * Smartable table context. + */ +const TableContext = React.createContext>(undefined); + +/** + * Hook to get current Smartable table context. + * @param smartable The Smartable main object. + */ +export function useTable(smartable?: Smartable): TableContextData +{ + return useContext(TableContext); +} diff --git a/yarn.lock b/yarn.lock index fc598a8..7b7535e 100644 --- a/yarn.lock +++ b/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"