Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
353881dead | |||
6238d1e8b7 | |||
47a9829d9b | |||
04d28f880f | |||
6b96a85554 | |||
d13f56b24a | |||
79b616dd95 |
9 changed files with 976 additions and 730 deletions
|
@ -1,6 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Application} from "@kernelui/core";
|
import {Application} from "@kernelui/core";
|
||||||
import {DemoTable} from "./DemoTable";
|
import {DemoTable} from "./DemoTable";
|
||||||
|
import {RemoveDemoTable} from "./RemoveDemoTable";
|
||||||
|
|
||||||
export function DemoApp()
|
export function DemoApp()
|
||||||
{
|
{
|
||||||
|
@ -9,6 +10,8 @@ export function DemoApp()
|
||||||
<h1>Random table</h1>
|
<h1>Random table</h1>
|
||||||
|
|
||||||
<DemoTable />
|
<DemoTable />
|
||||||
|
|
||||||
|
<RemoveDemoTable />
|
||||||
</Application>
|
</Application>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
95
demo/RemoveDemoTable.tsx
Normal file
95
demo/RemoveDemoTable.tsx
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import {createSmartable} from "../src/Smartable/Smartable";
|
||||||
|
import {createColumn, createColumns} from "../src/Smartable/Column";
|
||||||
|
import {StringFilter} from "../src/Smartable/Filters/StringFilter";
|
||||||
|
import { Cell } from "../src/Smartable/Cell";
|
||||||
|
|
||||||
|
|
||||||
|
// Create main table.
|
||||||
|
const Smartable = createSmartable({
|
||||||
|
columns: createColumns(
|
||||||
|
createColumn("name", {
|
||||||
|
title: "Name",
|
||||||
|
filter: StringFilter,
|
||||||
|
}),
|
||||||
|
createColumn("actions", {
|
||||||
|
title: "Actions",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function RemoveDemoTable()
|
||||||
|
{
|
||||||
|
const [rows, setRows] = useState<string[]>([
|
||||||
|
"Formica rufa",
|
||||||
|
"Lasius niger",
|
||||||
|
"Camponotus pennsylvanicus",
|
||||||
|
"Solenopsis invicta",
|
||||||
|
"Atta cephalotes",
|
||||||
|
"Pogonomyrmex barbatus",
|
||||||
|
"Myrmica rubra",
|
||||||
|
"Dorymyrmex insanus",
|
||||||
|
"Pheidole megacephala",
|
||||||
|
"Crematogaster scutellaris",
|
||||||
|
"Tetramorium caespitum",
|
||||||
|
"Tapinoma sessile",
|
||||||
|
"Linepithema humile",
|
||||||
|
"Monomorium pharaonis",
|
||||||
|
"Odontomachus bauri",
|
||||||
|
"Paraponera clavata",
|
||||||
|
"Oecophylla smaragdina",
|
||||||
|
"Pseudomyrmex gracilis",
|
||||||
|
"Eciton burchellii",
|
||||||
|
"Anoplolepis gracilipes",
|
||||||
|
"Acromyrmex octospinosus",
|
||||||
|
"Acanthomyops claviger",
|
||||||
|
"Dorylus nigricans",
|
||||||
|
"Neivamyrmex nigrescens",
|
||||||
|
"Hypoponera punctatissima",
|
||||||
|
"Solenopsis geminata",
|
||||||
|
"Camponotus chromaiodes",
|
||||||
|
"Brachymyrmex depilis",
|
||||||
|
"Ectatomma ruidum",
|
||||||
|
"Proceratium silaceum",
|
||||||
|
"Cephalotes atratus",
|
||||||
|
"Neoponera villosa",
|
||||||
|
"Dinoponera gigantea",
|
||||||
|
"Prenolepis imparis",
|
||||||
|
"Lasius flavus",
|
||||||
|
"Formica fusca",
|
||||||
|
"Myrmecia gulosa",
|
||||||
|
"Solenopsis molesta",
|
||||||
|
"Camponotus herculeanus",
|
||||||
|
"Cataulacus granulatus",
|
||||||
|
"Daceton armigerum",
|
||||||
|
"Polyrhachis dives",
|
||||||
|
"Pheidole dentata",
|
||||||
|
"Tetramorium immigrans",
|
||||||
|
"Messor barbarus",
|
||||||
|
"Cardiocondyla obscurior",
|
||||||
|
"Nylanderia flavipes",
|
||||||
|
"Forelius pruinosus",
|
||||||
|
"Amblyopone pallipes",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Smartable.Table data={rows.map((row, rowIndex) => ({
|
||||||
|
cells: {
|
||||||
|
name: {
|
||||||
|
data: row,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
data: null,
|
||||||
|
element: (
|
||||||
|
<Cell>
|
||||||
|
<button className={"remove"}
|
||||||
|
onClick={() => setRows(rows.toSpliced(rowIndex, 1))}>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</Cell>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))} disableFilter={true} disableSort={true} />
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import "@kernelui/core/lib/style.css";
|
import "@kernelui/core/lib/index.css";
|
||||||
import "../src/styles/smartable.less";
|
import "../src/styles/smartable.less";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
18
package.json
18
package.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.4",
|
"version": "1.1.2",
|
||||||
"name": "@kernelui/smartable",
|
"name": "@kernelui/smartable",
|
||||||
"description": "Kernel UI Smartable.",
|
"description": "Kernel UI Smartable.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -17,22 +17,22 @@
|
||||||
"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
|
"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kernelui/core": "^1.2.5",
|
"@kernelui/core": "^1.8.2",
|
||||||
"@phosphor-icons/react": "^2.1.7",
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react": "^4.3.0",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^7.0.1",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.11",
|
"vite": "^6.0.1",
|
||||||
"vite-plugin-dts": "^3.9.1"
|
"vite-plugin-dts": "^4.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@kernelui/core": "^1.1.2"
|
"@kernelui/core": "^1.8.2"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.5.0"
|
"packageManager": "yarn@4.5.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,12 @@ class AsyncManager<CK extends ColumnKey>
|
||||||
*/
|
*/
|
||||||
protected rowsLoaded: boolean = false;
|
protected rowsLoaded: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if rows need to be reinitialized or not.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected reinitRows: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rows data.
|
* Rows data.
|
||||||
* @protected
|
* @protected
|
||||||
|
@ -129,6 +135,9 @@ class AsyncManager<CK extends ColumnKey>
|
||||||
// Ignore undefined value.
|
// Ignore undefined value.
|
||||||
if (rowsDefinitions == undefined) return;
|
if (rowsDefinitions == undefined) return;
|
||||||
|
|
||||||
|
// Rows have been reinitialized.
|
||||||
|
this.reinitRows = true;
|
||||||
|
|
||||||
// Initialize rows data and cells definitions.
|
// Initialize rows data and cells definitions.
|
||||||
this.rowsData = [];
|
this.rowsData = [];
|
||||||
this.cellsDefinitions = [];
|
this.cellsDefinitions = [];
|
||||||
|
@ -226,6 +235,7 @@ class AsyncManager<CK extends ColumnKey>
|
||||||
{
|
{
|
||||||
if (!(
|
if (!(
|
||||||
// Checking that there is at least one changed value.
|
// Checking that there is at least one changed value.
|
||||||
|
this.reinitRows ||
|
||||||
this.rowsData.length > 0 || this.cellsDefinitions.some((rowCells) => (
|
this.rowsData.length > 0 || this.cellsDefinitions.some((rowCells) => (
|
||||||
Object.keys(rowCells).length > 0
|
Object.keys(rowCells).length > 0
|
||||||
))
|
))
|
||||||
|
@ -235,11 +245,14 @@ class AsyncManager<CK extends ColumnKey>
|
||||||
|
|
||||||
// Initialize new data.
|
// Initialize new data.
|
||||||
const newData = {
|
const newData = {
|
||||||
rows: !this.rowsLoaded ? undefined : [
|
rows: !this.rowsLoaded ? undefined : this.reinitRows ? [] : [
|
||||||
...(this.currentDataState?.rows ?? [])
|
...(this.currentDataState?.rows ?? [])
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Rows have been reinitialized.
|
||||||
|
this.reinitRows = false;
|
||||||
|
|
||||||
for (const [rowId, newRow] of this.rowsData?.entries())
|
for (const [rowId, newRow] of this.rowsData?.entries())
|
||||||
{ // Update value of each new row.
|
{ // Update value of each new row.
|
||||||
newData.rows[rowId] = {
|
newData.rows[rowId] = {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {RowInstance, RowLoader} from "./Row";
|
||||||
import {useAsyncManager} from "./AsyncManager";
|
import {useAsyncManager} from "./AsyncManager";
|
||||||
import {ColumnHeading} from "./Columns/ColumnHeading";
|
import {ColumnHeading} from "./Columns/ColumnHeading";
|
||||||
import {sortRows} from "./Sort";
|
import {sortRows} from "./Sort";
|
||||||
import {AutoPaginate} from "@kernelui/core";
|
import {AutoPaginate, classes} from "@kernelui/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smartable instance component properties.
|
* Smartable instance component properties.
|
||||||
|
@ -66,10 +66,10 @@ export function PaginatedInstance<CK extends ColumnKey>(props: InstancePropertie
|
||||||
/**
|
/**
|
||||||
* Base component for a Smartable table.
|
* Base component for a Smartable table.
|
||||||
*/
|
*/
|
||||||
export function Table<CK extends ColumnKey>({columns, children}: React.PropsWithChildren<InstanceProperties<CK>>)
|
export function Table<CK extends ColumnKey>({className, columns, children}: React.PropsWithChildren<InstanceProperties<CK>>)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<table className={"smartable"}>
|
<table className={classes("smartable", className)}>
|
||||||
<thead>
|
<thead>
|
||||||
<ColumnsHeadings columns={columns} />
|
<ColumnsHeadings columns={columns} />
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -85,9 +85,12 @@ export function Table<CK extends ColumnKey>({columns, children}: React.PropsWith
|
||||||
*/
|
*/
|
||||||
export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Columns<CK>})
|
export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Columns<CK>})
|
||||||
{
|
{
|
||||||
|
// Get feature disable options.
|
||||||
|
const {disableSort, disableFilter} = useTable<CK>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr className={"headings"}>
|
<tr className={classes("headings", disableSort === true ? "disable-sort" : undefined)}>
|
||||||
{ // Showing title of each column.
|
{ // Showing title of each column.
|
||||||
Object.keys(columns).map((key) => (
|
Object.keys(columns).map((key) => (
|
||||||
<AutoColumnContextProvider key={key} columnKey={key}>
|
<AutoColumnContextProvider key={key} columnKey={key}>
|
||||||
|
@ -96,17 +99,21 @@ export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Colum
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className={"filters"}>
|
{ // Add filters if filter feature is not disabled.
|
||||||
{ // Add columns filters, if there are some.
|
disableFilter !== true && (
|
||||||
(Object.entries(columns) as [CK, Column][]).map(([columnKey, column]) => (
|
<tr className={"filters"}>
|
||||||
column.filter && (
|
{ // Add columns filters, if there are some.
|
||||||
<AutoColumnContextProvider key={columnKey as string} columnKey={columnKey}>
|
(Object.entries(columns) as [CK, Column][]).map(([columnKey, column]) => (
|
||||||
<td>{column.filter.element}</td>
|
column.filter && (
|
||||||
</AutoColumnContextProvider>
|
<AutoColumnContextProvider key={columnKey as string} columnKey={columnKey}>
|
||||||
)
|
<td>{column.filter.element}</td>
|
||||||
))
|
</AutoColumnContextProvider>
|
||||||
}
|
)
|
||||||
</tr>
|
))
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -154,7 +161,7 @@ export function TableBody<CK extends ColumnKey>({pagination}: {
|
||||||
sortedRows ? (
|
sortedRows ? (
|
||||||
sortedRows.map((rowData, index) => (
|
sortedRows.map((rowData, index) => (
|
||||||
// Rendering each row from its definition.
|
// Rendering each row from its definition.
|
||||||
<RowInstance row={rowData} />
|
<RowInstance key={index} row={rowData} />
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<RowLoader />
|
<RowLoader />
|
||||||
|
|
|
@ -15,6 +15,11 @@ export type SmartableData<CK extends ColumnKey> = Promisable<(Promisable<RowDefi
|
||||||
*/
|
*/
|
||||||
export interface SmartableProperties<CK extends ColumnKey>
|
export interface SmartableProperties<CK extends ColumnKey>
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Table custom class name.
|
||||||
|
*/
|
||||||
|
className?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table data.
|
* Table data.
|
||||||
*/
|
*/
|
||||||
|
@ -44,6 +49,16 @@ export interface SmartableProperties<CK extends ColumnKey>
|
||||||
*/
|
*/
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable sort feature.
|
||||||
|
*/
|
||||||
|
disableSort?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable filter feature.
|
||||||
|
*/
|
||||||
|
disableFilter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,81 +1,89 @@
|
||||||
tr.headings
|
tr.headings
|
||||||
{
|
{
|
||||||
th
|
&.disable-sort
|
||||||
{
|
{
|
||||||
position: relative;
|
th { pointer-events: none; }
|
||||||
|
}
|
||||||
|
|
||||||
cursor: pointer;
|
&:not(.disable-sort)
|
||||||
|
{
|
||||||
&::before, &::after
|
th
|
||||||
{ // 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);
|
position: relative;
|
||||||
|
|
||||||
width: 2px;
|
cursor: pointer;
|
||||||
height: 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
&::after
|
|
||||||
{
|
|
||||||
right: calc(0.33em - 3px);
|
|
||||||
|
|
||||||
width: 6px;
|
&::before, &::after
|
||||||
height: 6px;
|
{ // Sorting order indicator.
|
||||||
border-radius: 6px;
|
transition: height 0.2s ease, background 0.2s ease, top 0.2s ease, bottom 0.2s ease;
|
||||||
}
|
|
||||||
|
|
||||||
&.asc, &.desc
|
content: "";
|
||||||
{
|
position: absolute;
|
||||||
&::after, &::before
|
top: 0;
|
||||||
{
|
bottom: 0;
|
||||||
background: var(--primary);
|
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
background: var(--background-darkest);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before
|
&::before
|
||||||
{
|
{
|
||||||
height: 0.8em;
|
right: calc(0.33em - 1px);
|
||||||
|
|
||||||
|
width: 2px;
|
||||||
|
height: 0;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
}
|
&::after
|
||||||
|
{
|
||||||
|
right: calc(0.33em - 3px);
|
||||||
|
|
||||||
&.asc::after
|
width: 6px;
|
||||||
{
|
height: 6px;
|
||||||
top: 0.5em;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
&.desc::after
|
|
||||||
{
|
|
||||||
bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order
|
&.asc, &.desc
|
||||||
{ // Sort order indicator.
|
{
|
||||||
position: relative;
|
&::after, &::before
|
||||||
top: 0;
|
{
|
||||||
bottom: 0;
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
display: block;
|
&::before
|
||||||
margin: auto 0;
|
{
|
||||||
padding: 0.3em 0.3em 0;
|
height: 0.8em;
|
||||||
align-items: center;
|
}
|
||||||
justify-content: center;
|
}
|
||||||
color: var(--foreground-lightest);
|
|
||||||
font-size: 0.7em;
|
|
||||||
|
|
||||||
float: right;
|
&.asc::after
|
||||||
|
{
|
||||||
|
top: 0.5em;
|
||||||
|
}
|
||||||
|
&.desc::after
|
||||||
|
{
|
||||||
|
bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order
|
||||||
|
{ // Sort order indicator.
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
margin: auto 0;
|
||||||
|
padding: 0.3em 0.3em 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--foreground-lightest);
|
||||||
|
font-size: 0.7em;
|
||||||
|
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue