Compare commits

...

12 commits
v1.0.0 ... main

Author SHA1 Message Date
353881dead
Fix CSS import in demo.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-20 12:16:12 +01:00
6238d1e8b7
Version 1.1.2
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-29 18:20:03 +01:00
47a9829d9b
Update dependencies.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-29 18:19:35 +01:00
04d28f880f
Allow to set className of a Smartable.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-25 19:13:57 +02:00
6b96a85554
Add a way to disable sort and filter features with properties.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-25 18:51:26 +02:00
d13f56b24a
Add missing key of table rows.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-25 15:42:34 +02:00
79b616dd95
Fix row removal and add a demo table to test it.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-24 22:44:00 +02:00
649d608c89
Fix package externals for correct library build.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-23 17:02:09 +02:00
cefae670ab
Fix exported library classes.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-23 16:41:14 +02:00
2376d780cc
Add router dom to peer dependencies and fix library build configuration.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-09-23 10:57:13 +02:00
5e14ebf780
Set KernelUI Core as a peer dependency.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-23 00:17:15 +02:00
222242163d
Fix dev dependencies after changes in core package.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-23 00:08:07 +02:00
13 changed files with 1115 additions and 870 deletions

View file

@ -1,6 +1,7 @@
import React from "react";
import {Application} from "@kernelui/core";
import {DemoTable} from "./DemoTable";
import {RemoveDemoTable} from "./RemoveDemoTable";
export function DemoApp()
{
@ -9,6 +10,8 @@ export function DemoApp()
<h1>Random table</h1>
<DemoTable />
<RemoveDemoTable />
</Application>
)
}

95
demo/RemoveDemoTable.tsx Normal file
View 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} />
);
}

View file

@ -1,4 +1,4 @@
import "@kernelui/core/lib/style.css";
import "@kernelui/core/lib/index.css";
import "../src/styles/smartable.less";
import React from "react";

View file

@ -1,2 +1,18 @@
import "./src/styles/smartable.less";
export * from "./src/Smartable/AsyncManager";
export * from "./src/Smartable/Cell";
export * from "./src/Smartable/Column";
export * from "./src/Smartable/Instance";
export * from "./src/Smartable/Row";
export * from "./src/Smartable/Smartable";
export * from "./src/Smartable/Sort";
export * from "./src/Smartable/Cells/ClickableCell";
export * from "./src/Smartable/Columns/ColumnFilter";
export * from "./src/Smartable/Columns/ColumnHeading";
export * from "./src/Smartable/Filters/EnumFilter";
export * from "./src/Smartable/Filters/NumberFilter";
export * from "./src/Smartable/Filters/StringFilter";

View file

@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.1.2",
"name": "@kernelui/smartable",
"description": "Kernel UI Smartable.",
"scripts": {
@ -16,18 +16,23 @@
"publishConfig": {
"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
},
"dependencies": {
"@kernelui/core": "^1.1.0"
},
"devDependencies": {
"@kernelui/core": "^1.8.2",
"@phosphor-icons/react": "^2.1.7",
"@types/node": "^22.0.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"less": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.0.1",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vite-plugin-dts": "^3.9.1"
"vite": "^6.0.1",
"vite-plugin-dts": "^4.3.0"
},
"packageManager": "yarn@4.2.2"
"peerDependencies": {
"@kernelui/core": "^1.8.2"
},
"packageManager": "yarn@4.5.0"
}

View file

@ -91,6 +91,12 @@ class AsyncManager<CK extends ColumnKey>
*/
protected rowsLoaded: boolean = false;
/**
* Tells if rows need to be reinitialized or not.
* @protected
*/
protected reinitRows: boolean = false;
/**
* Rows data.
* @protected
@ -129,6 +135,9 @@ class AsyncManager<CK extends ColumnKey>
// Ignore undefined value.
if (rowsDefinitions == undefined) return;
// Rows have been reinitialized.
this.reinitRows = true;
// Initialize rows data and cells definitions.
this.rowsData = [];
this.cellsDefinitions = [];
@ -226,6 +235,7 @@ class AsyncManager<CK extends ColumnKey>
{
if (!(
// Checking that there is at least one changed value.
this.reinitRows ||
this.rowsData.length > 0 || this.cellsDefinitions.some((rowCells) => (
Object.keys(rowCells).length > 0
))
@ -235,11 +245,14 @@ class AsyncManager<CK extends ColumnKey>
// Initialize new data.
const newData = {
rows: !this.rowsLoaded ? undefined : [
rows: !this.rowsLoaded ? undefined : this.reinitRows ? [] : [
...(this.currentDataState?.rows ?? [])
],
};
// Rows have been reinitialized.
this.reinitRows = false;
for (const [rowId, newRow] of this.rowsData?.entries())
{ // Update value of each new row.
newData.rows[rowId] = {

View file

@ -4,7 +4,7 @@ import {ColumnFilter, useFilterState} from "../Columns/ColumnFilter";
/**
* Filter value regex.
*/
export const filterRegex = /^([=!><])?([0-9]+)$/;
const filterRegex = /^([=!><])?([0-9]+)$/;
/**
* Number column filter.

View file

@ -5,7 +5,7 @@ import {normalizeString} from "@kernelui/core";
/**
* Filter value regex.
*/
export const filterRegex = /^([=!])?([^=!].+)$/;
const filterRegex = /^([=!])?([^=!].+)$/;
/**
* String column filter.

View file

@ -5,7 +5,7 @@ import {RowInstance, RowLoader} from "./Row";
import {useAsyncManager} from "./AsyncManager";
import {ColumnHeading} from "./Columns/ColumnHeading";
import {sortRows} from "./Sort";
import {AutoPaginate} from "@kernelui/core";
import {AutoPaginate, classes} from "@kernelui/core";
/**
* Smartable instance component properties.
@ -66,10 +66,10 @@ export function PaginatedInstance<CK extends ColumnKey>(props: InstancePropertie
/**
* 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 (
<table className={"smartable"}>
<table className={classes("smartable", className)}>
<thead>
<ColumnsHeadings columns={columns} />
</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>})
{
// Get feature disable options.
const {disableSort, disableFilter} = useTable<CK>();
return (
<>
<tr className={"headings"}>
<tr className={classes("headings", disableSort === true ? "disable-sort" : undefined)}>
{ // Showing title of each column.
Object.keys(columns).map((key) => (
<AutoColumnContextProvider key={key} columnKey={key}>
@ -96,17 +99,21 @@ export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Colum
))
}
</tr>
<tr className={"filters"}>
{ // Add columns filters, if there are some.
(Object.entries(columns) as [CK, Column][]).map(([columnKey, column]) => (
column.filter && (
<AutoColumnContextProvider key={columnKey as string} columnKey={columnKey}>
<td>{column.filter.element}</td>
</AutoColumnContextProvider>
)
))
}
</tr>
{ // Add filters if filter feature is not disabled.
disableFilter !== true && (
<tr className={"filters"}>
{ // Add columns filters, if there are some.
(Object.entries(columns) as [CK, Column][]).map(([columnKey, column]) => (
column.filter && (
<AutoColumnContextProvider key={columnKey as string} columnKey={columnKey}>
<td>{column.filter.element}</td>
</AutoColumnContextProvider>
)
))
}
</tr>
)
}
</>
);
}
@ -154,7 +161,7 @@ export function TableBody<CK extends ColumnKey>({pagination}: {
sortedRows ? (
sortedRows.map((rowData, index) => (
// Rendering each row from its definition.
<RowInstance row={rowData} />
<RowInstance key={index} row={rowData} />
))
) : (
<RowLoader />

View file

@ -15,6 +15,11 @@ export type SmartableData<CK extends ColumnKey> = Promisable<(Promisable<RowDefi
*/
export interface SmartableProperties<CK extends ColumnKey>
{
/**
* Table custom class name.
*/
className?: string;
/**
* Table data.
*/
@ -44,6 +49,16 @@ export interface SmartableProperties<CK extends ColumnKey>
*/
pageSize: number;
};
/**
* Disable sort feature.
*/
disableSort?: boolean;
/**
* Disable filter feature.
*/
disableFilter?: boolean;
}
/**

View file

@ -1,81 +1,89 @@
tr.headings
{
th
&.disable-sort
{
position: relative;
th { pointer-events: none; }
}
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
&:not(.disable-sort)
{
th
{
right: calc(0.33em - 1px);
position: relative;
width: 2px;
height: 0;
border-radius: 2px;
}
&::after
{
right: calc(0.33em - 3px);
cursor: pointer;
width: 6px;
height: 6px;
border-radius: 6px;
}
&::before, &::after
{ // Sorting order indicator.
transition: height 0.2s ease, background 0.2s ease, top 0.2s ease, bottom 0.2s ease;
&.asc, &.desc
{
&::after, &::before
{
background: var(--primary);
content: "";
position: absolute;
top: 0;
bottom: 0;
display: block;
margin: auto;
box-sizing: border-box;
background: var(--background-darkest);
}
&::before
{
height: 0.8em;
right: calc(0.33em - 1px);
width: 2px;
height: 0;
border-radius: 2px;
}
}
&::after
{
right: calc(0.33em - 3px);
&.asc::after
{
top: 0.5em;
}
&.desc::after
{
bottom: 0.5em;
}
width: 6px;
height: 6px;
border-radius: 6px;
}
.order
{ // Sort order indicator.
position: relative;
top: 0;
bottom: 0;
&.asc, &.desc
{
&::after, &::before
{
background: var(--primary);
}
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;
&::before
{
height: 0.8em;
}
}
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;
}
}
}
}

View file

@ -16,7 +16,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
fileName: "index",
},
rollupOptions: {
external: ["react"],
external: ["react", "react-dom", "react-router-dom", "@phosphor-icons/react", "@kernelui/core"],
},
},

1639
yarn.lock

File diff suppressed because it is too large Load diff