ehscan-react-table 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 beeplaced
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # ehscan-react-table
2
+
3
+ - ...
4
+
5
+ # Usage
6
+
7
+ ### Implementation
8
+
9
+ ```jsx
10
+
11
+ ```
12
+
13
+ ### Styling
14
+
15
+ ----
16
+ # Changelog
17
+
18
+ All notable changes to this project will be documented in this file.
19
+ ---
@@ -0,0 +1 @@
1
+ export { Table } from './elements/Table';
@@ -0,0 +1,2 @@
1
+ // ehscan-react-table entry
2
+ export { Table } from './elements/Table';
@@ -0,0 +1 @@
1
+ export declare function debounce<T extends (...args: any[]) => void>(func: T, delay: number): T;
@@ -0,0 +1,8 @@
1
+ // utils/debounce.ts (optional to keep code organized)
2
+ export function debounce(func, delay) {
3
+ let timeoutId;
4
+ return function (...args) {
5
+ clearTimeout(timeoutId);
6
+ timeoutId = setTimeout(() => func.apply(this, args), delay);
7
+ };
8
+ }
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ totalItems: number;
3
+ currentEntries: number;
4
+ limit: number;
5
+ skip: number;
6
+ currentPage: number;
7
+ onPageChange: (page: number) => void;
8
+ }
9
+ declare const Pagination: React.FC<Props>;
10
+ export default Pagination;
@@ -0,0 +1,51 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import PaginationButton from "./PaginationButton";
4
+ import styles from '../style/table.module.css';
5
+ const Pagination = ({ totalItems, currentEntries, limit, skip, currentPage, onPageChange }) => {
6
+ const [totalPages, setTotalPages] = useState(0);
7
+ const handlePrevious = () => {
8
+ if (currentPage > 1)
9
+ onPageChange(currentPage - 1);
10
+ };
11
+ const handleNext = () => {
12
+ if (currentPage < totalPages)
13
+ onPageChange(currentPage + 1);
14
+ };
15
+ const handlePageClick = (page) => {
16
+ console.log(page);
17
+ onPageChange(page);
18
+ };
19
+ useEffect(() => {
20
+ setTotalPages(Math.ceil(totalItems / limit));
21
+ }, [totalItems]);
22
+ const getVisiblePages = (currentPage, totalPages, maxVisible = 10) => {
23
+ const pages = [];
24
+ if (totalPages <= maxVisible) {
25
+ return Array.from({ length: totalPages }, (_, i) => i + 1);
26
+ }
27
+ if (currentPage <= 5) {
28
+ pages.push(1, 2, 3, 4, 5, "...", totalPages - 2, totalPages - 1, totalPages);
29
+ return pages;
30
+ }
31
+ if (currentPage >= totalPages - 4) {
32
+ pages.push(1, 2, 3, "...", totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages);
33
+ return pages;
34
+ }
35
+ pages.push(1, "...", currentPage - 1, currentPage, currentPage + 1, "...", totalPages);
36
+ return pages;
37
+ };
38
+ const visiblePages = getVisiblePages(currentPage, totalPages);
39
+ const PaginationOutput = () => {
40
+ const from = skip + 1;
41
+ const to = currentEntries > totalItems ? totalItems : currentEntries;
42
+ if (skip === undefined || currentEntries === undefined || totalItems === undefined)
43
+ return null;
44
+ return _jsxs("div", { className: styles.paginationend, children: [from, " - ", to, " / ", totalItems] });
45
+ };
46
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.paginationwrapper, children: totalPages > 1 && (_jsxs(_Fragment, { children: [_jsx(PaginationButton, { index: 'prev', action: handlePrevious, display: totalPages > 2 && currentPage > 1 }), visiblePages.map((p, i) => {
47
+ const isEllipsis = p === "...";
48
+ return (_jsx(PaginationButton, { txt: p, isActive: p === currentPage, disabled: isEllipsis, action: isEllipsis ? undefined : () => handlePageClick(p) }, i));
49
+ }), _jsx(PaginationButton, { index: 'next', action: handleNext, display: totalPages > 2 && currentPage !== totalPages })] })) }), _jsx(PaginationOutput, {})] }));
50
+ };
51
+ export default Pagination;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface PaginationButtonProps {
3
+ index?: string | number;
4
+ txt?: string | number;
5
+ isActive?: boolean;
6
+ disabled?: boolean;
7
+ action?: () => void;
8
+ display?: boolean;
9
+ }
10
+ declare const PaginationButton: React.FC<PaginationButtonProps>;
11
+ export default PaginationButton;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import PaginationRipple from './PaginationRipple';
4
+ import styles from '../table.module.css';
5
+ const PaginationButton = ({ index, txt, disabled, isActive, action, display }) => {
6
+ const buttonRef = useRef(null);
7
+ const handleRipple = PaginationRipple();
8
+ const handleButtonClick = (event) => {
9
+ if (display === false)
10
+ return;
11
+ handleRipple(event, buttonRef);
12
+ if (!disabled && action)
13
+ action();
14
+ };
15
+ const PrevBtn = () => {
16
+ return (_jsx(_Fragment, { children: _jsx("svg", { className: styles.pagbtnsvg, width: "24px", height: "24px", fill: "white", version: "1.1", viewBox: "0 -960 960 960", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "m632-80-400-400 400-400 71 71-329 329 329 329z" }) }) }));
17
+ };
18
+ const NextBtn = () => {
19
+ return (_jsx(_Fragment, { children: _jsx("svg", { className: styles.pagbtnsvg, xmlns: "http://www.w3.org/2000/svg", height: "24px", viewBox: "0 -960 960 960", width: "24px", fill: "white", children: _jsx("path", { d: "m321-80-71-71 329-329-329-329 71-71 400 400L321-80Z" }) }) }));
20
+ };
21
+ return (_jsxs("div", { ref: buttonRef, className: `${styles.paginationbutton} ${isActive ? styles.paginationbuttonactive : ""} ${display === false ? styles.paginationbuttonhide : ""}`, onClick: (e) => handleButtonClick(e), children: [index === 'prev' && _jsx(PrevBtn, {}), index === 'next' && _jsx(NextBtn, {}), txt && _jsx("div", { className: "_txt", children: txt })] }, index));
22
+ };
23
+ export default PaginationButton;
@@ -0,0 +1,3 @@
1
+ import { MouseEvent } from 'react';
2
+ declare const PaginationRipple: () => (event: MouseEvent<HTMLDivElement>, buttonRef: React.RefObject<HTMLDivElement | null>) => void;
3
+ export default PaginationRipple;
@@ -0,0 +1,22 @@
1
+ import { useCallback } from 'react';
2
+ import styles from '../table.module.css';
3
+ const PaginationRipple = () => {
4
+ const handleRipple = useCallback((event, buttonRef) => {
5
+ const button = buttonRef.current;
6
+ if (!button)
7
+ return;
8
+ const rect = button.getBoundingClientRect();
9
+ const size = Math.max(rect.width, rect.height);
10
+ const x = event.clientX - rect.left - size / 2;
11
+ const y = event.clientY - rect.top - size / 2;
12
+ const ripple = document.createElement('span');
13
+ ripple.style.width = ripple.style.height = `${size}px`;
14
+ ripple.style.left = `${x}px`;
15
+ ripple.style.top = `${y}px`;
16
+ ripple.className = styles.pagripple;
17
+ button.appendChild(ripple);
18
+ //setTimeout(() => ripple.remove(), 600);
19
+ }, []);
20
+ return handleRipple;
21
+ };
22
+ export default PaginationRipple;
@@ -0,0 +1,32 @@
1
+ type TableColumn = {
2
+ tag: string;
3
+ title?: string;
4
+ type?: string;
5
+ search?: boolean;
6
+ };
7
+ type SortOrder = {
8
+ tag: string;
9
+ dir: "asc" | "desc";
10
+ };
11
+ type Props = {
12
+ columns: TableColumn[];
13
+ rows: any[];
14
+ sortOrder: SortOrder;
15
+ setSortOrder: (order: SortOrder) => void;
16
+ cellComponents: Record<string, React.ComponentType<{
17
+ row: any;
18
+ col: string;
19
+ }>>;
20
+ setSelectedIds: React.Dispatch<React.SetStateAction<string[]>>;
21
+ searchTermArraySetter?: (arr: {
22
+ tag: string;
23
+ term: string;
24
+ }[]) => void;
25
+ setSearchTermArraySetter?: (arr: {
26
+ tag: string;
27
+ term: string;
28
+ }[]) => void;
29
+ fallback: () => React.ReactNode;
30
+ };
31
+ export declare const Table: React.FC<Props>;
32
+ export {};
@@ -0,0 +1,130 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
+ import { useEffect, useState, useRef } from "react";
12
+ import TableCellSelectHeadCol from "./TableCellSelectHeadCol";
13
+ import { debounce } from "./Debounce";
14
+ import styles from '../style/table.module.css';
15
+ export const Table = ({ columns, rows, sortOrder, setSortOrder, cellComponents, setSelectedIds, searchTermArraySetter, setSearchTermArraySetter, fallback }) => {
16
+ const [wrapperBottom, setWrapperBottom] = useState(undefined);
17
+ const headerRef = useRef(null);
18
+ const tableSearchTerms = useRef({});
19
+ const [openCol, setOpenCol] = useState(undefined);
20
+ const [checkAll, setCheckAll] = useState(false);
21
+ useEffect(() => {
22
+ if (searchTermArraySetter !== undefined)
23
+ return;
24
+ try { //Reset SearchTerm
25
+ tableSearchTerms.current = {};
26
+ }
27
+ catch (error) {
28
+ console.log(error);
29
+ }
30
+ }, [searchTermArraySetter]);
31
+ useEffect(() => {
32
+ const calculateHeight = () => {
33
+ if (headerRef.current) {
34
+ const { bottom } = headerRef.current.getBoundingClientRect();
35
+ setWrapperBottom(bottom);
36
+ }
37
+ };
38
+ calculateHeight();
39
+ window.addEventListener("resize", calculateHeight);
40
+ return () => window.removeEventListener("resize", calculateHeight);
41
+ }, []);
42
+ const checkHead = (entry) => {
43
+ if (entry === undefined)
44
+ return;
45
+ setCheckAll(!entry);
46
+ setSelectedIds(prev => {
47
+ let next = prev;
48
+ if (!entry)
49
+ next = [...prev, ...rows.map(r => r.id)];
50
+ if (entry)
51
+ next = prev.filter(id => !rows.some(r => r.id === id));
52
+ return [...new Set(next)];
53
+ });
54
+ };
55
+ const debouncedSave = useRef(debounce((entry) => {
56
+ const doSave = () => __awaiter(void 0, void 0, void 0, function* () {
57
+ try {
58
+ yield filterRows(entry);
59
+ }
60
+ catch (err) {
61
+ console.error('Autosave failed:', err);
62
+ }
63
+ });
64
+ doSave();
65
+ }, 800));
66
+ const filterRows = (entry) => __awaiter(void 0, void 0, void 0, function* () {
67
+ const { tag, term } = entry;
68
+ tableSearchTerms.current[tag] = term;
69
+ const searchTermsArray = Object.entries(tableSearchTerms.current).map(([tag, term]) => ({
70
+ tag,
71
+ term: term
72
+ }));
73
+ if (setSearchTermArraySetter)
74
+ setSearchTermArraySetter(searchTermsArray);
75
+ });
76
+ const RemoveSearchEntry = () => _jsx("svg", { className: styles.removesearchsvg, xmlns: "http://www.w3.org/2000/svg", height: "24px", viewBox: "0 -960 960 960", width: "24px", fill: "#48752C", children: _jsx("path", { d: "m336-280-56-56 144-144-144-143 56-56 144 144 143-144 56 56-144 143 144 144-56 56-143-144-144 144Z" }) });
77
+ const HeadSearchBar = ({ content, tag }) => {
78
+ var _a;
79
+ const inputRef = useRef(null);
80
+ const [value, setValue] = useState((_a = tableSearchTerms.current[tag]) !== null && _a !== void 0 ? _a : "");
81
+ const hasLabel = value.length > 0;
82
+ const inputTag = `search_${tag.toLowerCase()}`;
83
+ useEffect(() => {
84
+ if (inputRef.current && openCol === tag) {
85
+ inputRef.current.focus();
86
+ }
87
+ }, [value]);
88
+ const changeHeadSearch = (newValue) => {
89
+ setValue(newValue);
90
+ setOpenCol(tag);
91
+ debouncedSave.current({ tag, term: newValue });
92
+ };
93
+ return (_jsxs("div", { className: styles.headsearchwrapper, children: [_jsx("div", { className: `${styles.headsearchlable}${hasLabel ? ` ${styles.show}` : ""}`, children: _jsx("label", { htmlFor: inputTag, children: content }) }), _jsxs("div", { className: `${styles.searchwrapper}${hasLabel || openCol === tag ? ` ${styles.focused}` : ""}`, children: [_jsx("input", { id: inputTag, ref: inputRef, className: styles.headsearch, type: "text", value: value, onFocus: () => setOpenCol(tag), onChange: (e) => changeHeadSearch(e.target.value), placeholder: content, maxLength: 150, spellCheck: false }), value !== "" && _jsx("div", { onClick: () => changeHeadSearch(""), children: _jsx(RemoveSearchEntry, {}) })] })] }));
94
+ };
95
+ const HeadColSort = ({ tag }) => {
96
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "sort-col", onClick: () => setSortOrder({ tag, dir: sortOrder.dir === 'desc' ? 'asc' : 'desc' }), children: [sortOrder.tag === tag && _jsx(SortButton, {}), openCol === tag && sortOrder.tag !== tag && _jsx(EmptySort, {})] }) }));
97
+ };
98
+ const HeadColMain = ({ col }) => {
99
+ const { tag, search, title } = col;
100
+ const colTitle = title || tag;
101
+ return (_jsx("th", { children: _jsxs("div", { className: styles.headcolcell, children: [_jsx(HeadColSort, { tag: tag }), _jsx("div", { className: styles.headcolcellmain, children: search ? _jsx(HeadSearchBar, { content: colTitle, tag: tag }) : _jsx("div", { children: colTitle }) })] }) }));
102
+ };
103
+ const HeadCols = () => {
104
+ return (_jsxs(_Fragment, { children: [columns.map((col, i) => {
105
+ const { tag, type } = col;
106
+ if (type === "checkbox") {
107
+ return (_jsx("th", { className: styles.thcheckhead, style: { "--custom-width": `32px` }, children: _jsx(TableCellSelectHeadCol, { checkHead: checkHead, checkAll: checkAll }) }, `checkbox-${tag !== null && tag !== void 0 ? tag : i}`));
108
+ }
109
+ return _jsx(HeadColMain, { col: col }, tag !== null && tag !== void 0 ? tag : i);
110
+ }), _jsx(EndHeadCol, {})] }));
111
+ };
112
+ const SortButton = () => {
113
+ switch (sortOrder.dir) {
114
+ case 'desc': return _jsx("svg", { className: styles.sortsvg, xmlns: "http://www.w3.org/2000/svg", height: "24px", viewBox: "0 -960 960 960", width: "24px", fill: "white", children: _jsx("path", { d: "M480-240 240-480l56-56 144 144v-368h80v368l144-144 56 56-240 240Z" }) });
115
+ default: return _jsx("svg", { className: styles.sortsvg, xmlns: "http://www.w3.org/2000/svg", height: "24px", viewBox: "0 -960 960 960", width: "24px", fill: "white", children: _jsx("path", { d: "M440-240v-368L296-464l-56-56 240-240 240 240-56 56-144-144v368h-80Z" }) });
116
+ }
117
+ };
118
+ const EmptySort = () => _jsx("svg", { className: styles.sortsvg, xmlns: "http://www.w3.org/2000/svg", height: "24px", viewBox: "0 -960 960 960", width: "24px", fill: "white", children: _jsx("path", { d: "M480-120 300-300l58-58 122 122 122-122 58 58-180 180ZM358-598l-58-58 180-180 180 180-58 58-122-122-122 122Z" }) });
119
+ const EndHeadCol = () => _jsx("th", { style: { "--custom-width": `10px` } }, 'endHeadCol');
120
+ const EndCol = () => _jsx("td", {}, 'endCol');
121
+ const TableBody = () => {
122
+ return (_jsx("tbody", { className: `${styles.tablebody} ${styles._tbl}`, children: rows && rows.map((row, rowIndex) => (_jsxs("tr", { className: styles.deftabletr, children: [columns.map((col, ci) => {
123
+ var _a;
124
+ const type = (_a = col.type) !== null && _a !== void 0 ? _a : "def";
125
+ const CellComponent = cellComponents[type];
126
+ return (_jsx("td", { children: _jsx(CellComponent, { row: row, col: col.tag }) }, ci));
127
+ }), _jsx(EndCol, {})] }, `row-${rowIndex}`))) }, 'ext-tbody'));
128
+ };
129
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: `${styles.tablewrapper} ${styles['_tbl']}`, style: { height: `calc(100vh - ${wrapperBottom}px)` }, children: [_jsxs("table", { className: styles.exttable, children: [_jsx("thead", { children: _jsx("tr", { className: styles.trstickyhead, children: _jsx(HeadCols, {}) }) }), rows && rows.length > 0 && _jsx(TableBody, {})] }), rows && rows.length === 0 && fallback()] }) }));
130
+ };
@@ -0,0 +1,11 @@
1
+ type SelectProps = {
2
+ rowIndex: number;
3
+ checked?: boolean;
4
+ };
5
+ type Props = {
6
+ rowIndex: number;
7
+ checked?: boolean;
8
+ selectRow: (args: SelectProps, shift: boolean) => void;
9
+ };
10
+ declare const TableCellSelect: React.FC<Props>;
11
+ export default TableCellSelect;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import TableChecklistItem from "./TableChecklistItem";
3
+ const TableCellSelect = ({ rowIndex, checked, selectRow }) => {
4
+ const checkClick = (event) => {
5
+ const shift = event.shiftKey;
6
+ selectRow({ rowIndex, checked: !checked }, shift);
7
+ };
8
+ return (_jsx(_Fragment, { children: _jsx("div", { onClick: (e) => { e.stopPropagation(); checkClick(e); }, children: _jsx(TableChecklistItem, { checked: checked !== null && checked !== void 0 ? checked : false }) }) }));
9
+ };
10
+ export default TableCellSelect;
@@ -0,0 +1,6 @@
1
+ type Props = {
2
+ checkAll: boolean;
3
+ checkHead: (args: boolean) => void;
4
+ };
5
+ declare const TableCellSelectHeadCol: React.FC<Props>;
6
+ export default TableCellSelectHeadCol;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import ChecklistItemSquare from "./TableChecklistItem";
3
+ const TableCellSelectHeadCol = ({ checkHead, checkAll }) => {
4
+ return (_jsx("div", { onClick: (event) => {
5
+ event.stopPropagation();
6
+ if (checkAll !== undefined)
7
+ checkHead(checkAll);
8
+ }, children: _jsx(ChecklistItemSquare, { checked: checkAll }) }));
9
+ };
10
+ export default TableCellSelectHeadCol;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ checked: boolean;
4
+ };
5
+ declare const TableChecklistItem: React.FC<Props>;
6
+ export default TableChecklistItem;
@@ -0,0 +1,25 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
+ import { useRef } from 'react';
12
+ import PaginationRipple from './PaginationRipple';
13
+ import styles from '../style/table.module.css';
14
+ const TableChecklistItem = ({ checked }) => {
15
+ const buttonRef = useRef(null);
16
+ const handleRipple = PaginationRipple();
17
+ const toggleCheck = (event) => __awaiter(void 0, void 0, void 0, function* () {
18
+ handleRipple(event, buttonRef);
19
+ });
20
+ const CheckBoxItemSquare = ({ selected }) => {
21
+ return (_jsx(_Fragment, { children: _jsxs("svg", { width: "60", height: "60", version: "1.1", viewBox: "0 -960 2400 2400", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { className: styles.cbcircle, d: "m1200-692.58c-767.96 0-932.58 164.63-932.58 932.58 0 767.98 164.63 932.58 932.58 932.58 767.98 0 932.58-164.63 932.58-932.58 0-767.96-164.63-932.58-932.58-932.58z", fill: "white" }), selected && _jsx("path", { className: styles.cbpath, d: "m1566.4-147.66c-33.43-0.026-66.888 12.734-92.524 38.331l-421.88 421.25-125.8-126.04c-51.193-51.272-133.7-51.348-184.97-0.1555-51.272 51.193-51.348 133.7-0.15551 184.97l218.09 218.48c38.437 38.497 94.554 48.106 141.82 28.846 15.829-6.3918 30.738-15.985 43.618-28.846l514.09-513.39c51.272-51.193 51.348-133.7 0.1554-184.97-25.596-25.636-59.014-38.46-92.446-38.486z", fill: "#666" })] }) }));
22
+ };
23
+ return (_jsx(_Fragment, { children: _jsx("div", { ref: buttonRef, className: styles.checkboxtable, onClick: (e) => toggleCheck(e), children: _jsx(CheckBoxItemSquare, { selected: checked }) }) }));
24
+ };
25
+ export default TableChecklistItem;
@@ -0,0 +1,494 @@
1
+ /* --------------------------
2
+ General Settings
3
+ -------------------------- */
4
+ html,
5
+ body,
6
+ input,
7
+ textarea {
8
+ height: 100%;
9
+ font-family: Arial, sans-serif;
10
+ font-family: Inter, sans-serif;
11
+ font-style: 400;
12
+ font-optical-sizing: auto;
13
+ -webkit-font-smoothing: antialiased;
14
+ text-rendering: optimizelegibility;
15
+ letter-spacing: 0.072px;
16
+ overflow: hidden;
17
+ color: #222;
18
+ }
19
+
20
+ html,
21
+ body {
22
+ margin: 0;
23
+ padding: 0;
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+
28
+ html,
29
+ body {
30
+ touch-action: none;
31
+ overscroll-behavior: none;
32
+ }
33
+
34
+ * {
35
+ margin: 0;
36
+ padding: 0;
37
+ box-sizing: border-box;
38
+ user-select: none;
39
+ }
40
+
41
+ /* --------------------------
42
+ TableView
43
+ -------------------------- */
44
+ .tableouterwrapper {
45
+ width: 100%;
46
+ height: 100vh;
47
+ display: flex;
48
+ flex-direction: column;
49
+ --svg-w-h: 30px;
50
+ }
51
+
52
+ .tablefooter {
53
+ display: flex;
54
+ width: 100%;
55
+ background-color: var(--ext-table-footer-bck-clr, darkgoldenrod);
56
+ height: var(--ext-table-footer-height, 40px);
57
+ position: absolute;
58
+ bottom: 0;
59
+ width: -webkit-fill-available;
60
+ display: grid;
61
+ align-items: center;
62
+ padding: 0 10px;
63
+ grid-template-columns: 1fr auto;
64
+ }
65
+
66
+ /* --------------------------
67
+ Table
68
+ -------------------------- */
69
+ .tablebody{
70
+ background-color: var(--ext-table-body-bck-clr, ghostwhite);
71
+ }
72
+
73
+ .tablebody tr {
74
+ width: 100%;
75
+ transition: background-color 0.2s;
76
+ cursor: pointer;
77
+ }
78
+
79
+ .tablebody tr:nth-child(even) {
80
+ background-color: var(--ext-table-body-bck-clr-even, white);
81
+ }
82
+
83
+ .tablebody tr:hover {
84
+ background-color: #e0e7ff;
85
+ }
86
+
87
+ .tablewrapper {
88
+ font-family: sans-serif;
89
+ display: flex;
90
+ flex-direction: column;
91
+ background-color: var(--ext-new-table-wrapper-bck-clr, transparent);
92
+ -webkit-user-select: none;
93
+ user-select: none;
94
+ height: calc(100% - var(--ext-table-footer-height, 40px) - var(--ext-table-actionbar-height, 50px));
95
+ overflow-y: auto;
96
+ }
97
+
98
+ table.exttable th,
99
+ table.exttable td {
100
+ text-align: left;
101
+ }
102
+
103
+ table.exttable td {
104
+ padding: var(--ext-table-td-padding, 5px);
105
+ border: var(--ext-table-cell-border, 1px dashed lightgrey);
106
+ border-top: none;
107
+ }
108
+
109
+ table.exttable thead {
110
+ background-color: var(--ext-table-body-bck-clr, white);
111
+ font-weight: bold;
112
+ height: var(--ext-table-header-height, 40px);
113
+ }
114
+
115
+ table.exttable {
116
+ width: 100%;
117
+ border-collapse: collapse;
118
+ table-layout: fixed;
119
+ }
120
+
121
+ .trstickyhead {
122
+ position: sticky;
123
+ top: 0;
124
+ z-index: 1001;
125
+ box-shadow: 0 0 30px #523f690d;
126
+ background-color: var(--ext-table-header-bkc-clr, darkgoldenrod);
127
+ }
128
+
129
+ .thcheckhead {
130
+ width: var(--custom-width) !important;
131
+ padding: var(--table-th-padding, 5px);
132
+ }
133
+
134
+ /* --------------------------
135
+ Table scrollbar
136
+ --------------------------- */
137
+ ._tbl {
138
+ scrollbar-width: thin;
139
+ scrollbar-color: var(--ext-table-body-scrollbar-clr, lightslategray) var(--ext-table-body-bck-clr, white);
140
+ }
141
+
142
+ ._tbl::-webkit-scrollbar {
143
+ width: 6px;
144
+ background-color: var(--ext-table-body-bck-clr);
145
+ transition: width 0.2s ease;
146
+ }
147
+
148
+ ._tbl::-webkit-scrollbar-thumb {
149
+ background-color: white;
150
+ border-radius: 12px;
151
+ transition: background-color 0.2s ease;
152
+ }
153
+
154
+ ._tbl:hover::-webkit-scrollbar-thumb {
155
+ background-color: #555;
156
+ }
157
+
158
+ ._tbl:hover::-webkit-scrollbar {
159
+ width: 6px;
160
+ }
161
+
162
+ /* --------------------------
163
+ Searchbar & Input
164
+ -------------------------- */
165
+ input.headsearch:focus {
166
+ outline: none;
167
+ box-shadow: none;
168
+ border-color: inherit;
169
+ }
170
+
171
+ input.headsearch {
172
+ color: var(--ext-tab-input-clr, white);
173
+ }
174
+
175
+ input.headsearch::placeholder {
176
+ color: var(--ext-tab-input-clr, white);
177
+ opacity: 1;
178
+ }
179
+
180
+ input.headsearch {
181
+ color: var(--ext-tab-input-clr, white);
182
+ opacity: 1;
183
+ }
184
+
185
+ svg.removesearchsvg{
186
+ fill: var(--ext-tab-input-clr, white);
187
+ cursor: pointer;
188
+ --svg-w-h: 20px;
189
+ }
190
+
191
+ .sortsvg{
192
+ fill: var(--ext-tab-sort-svg-clr, white);
193
+ --svg-w-h: 20px
194
+ }
195
+
196
+ .searchwrapper{
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ gap: 3px;
201
+ border: 1px solid transparent;
202
+ padding-right: 5px;
203
+ background: transparent;
204
+ border-radius: var(--ext-search-wrapper-padding, 4px);
205
+ height: var(--ext-search-wrapper-height, 25px);
206
+ }
207
+
208
+ .searchwrapper:hover{
209
+ background: rgba(0,0,0,0.2);
210
+ }
211
+
212
+ .focused {
213
+ border: var(--ext-search-wrapper-focused-border, 1px dashed white);
214
+ color: white;
215
+ }
216
+
217
+ svg {
218
+ width: var(--svg-w-h);
219
+ height: var(--svg-w-h);
220
+ display: flex;
221
+ }
222
+
223
+ .actionbar{
224
+ background-color: #9ca3af;
225
+ height: var(--ext-table-actionbar-height, 50px);
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ }
230
+
231
+ .exttablediv {
232
+ display: -webkit-box;
233
+ -webkit-line-clamp: 2;
234
+ line-clamp: 2;
235
+ -webkit-box-orient: vertical;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: normal;
239
+ color: var(--ext-table-cell-clr, lightslategrey);
240
+ }
241
+
242
+ input.headsearch {
243
+ border: none;
244
+ border-radius: 4px;
245
+ width: 98%;
246
+ padding: 1px 6px 3px 6px;
247
+ box-sizing: border-box;
248
+ background-color: transparent;
249
+ }
250
+
251
+ input.headsearch:hover {
252
+ border: none;
253
+ cursor: pointer;
254
+ }
255
+
256
+ input.headsearch:focus {
257
+ border: none;
258
+ }
259
+
260
+ .headcolcell{
261
+ display: flex;
262
+ gap: var(--ext-table-head-col-cell-gap, 3px);
263
+ align-items: center;
264
+ justify-content: center;
265
+ }
266
+
267
+ .headcolcellmain{
268
+ flex: 1;
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: center;
272
+ }
273
+
274
+ .deftabletr > td, .trstickyhead > th {
275
+ padding: 0;
276
+ text-align: left;
277
+ color: #374151;
278
+ box-sizing: border-box;
279
+ font-size: 80%;
280
+ padding: 5px;
281
+ width: var(--custom-width) !important;
282
+ min-width: 30px;
283
+ }
284
+
285
+ .deftabletr > td:first-child{
286
+ border-left: none
287
+ }
288
+
289
+ .deftabletr > td.end, .deftabletr > th.end {
290
+ width: 10px;
291
+ max-width: 15px;
292
+ border-right: none;
293
+ }
294
+
295
+ .ext-new-table-header th {
296
+ padding: 0 5px 0 0;
297
+ position: relative;
298
+ }
299
+
300
+ .ext-new-table-header th.th-check-head {
301
+ padding: 0 0 0 5px;
302
+ }
303
+
304
+ .ext-new-resize-header-main {
305
+ flex: 1;
306
+ white-space: nowrap;
307
+ overflow: hidden;
308
+ text-overflow: ellipsis;
309
+ color: #111827;
310
+ font-weight: 600;
311
+ letter-spacing: 0.5px;
312
+ }
313
+
314
+ .headsearchwrapper {
315
+ position: relative;
316
+ }
317
+
318
+ .sort-col {
319
+ display: flex;
320
+ }
321
+
322
+ .headsearchlable {
323
+ position: fixed;
324
+ margin-left: 6px;
325
+ margin-top: 0;
326
+ font-size: var(--ext-table-header-font-size, 50%);
327
+ background-color: var(--ext-table-header-bkc-clr, darkgoldenrod);
328
+ color: var(--ext-table-header-clr, darkslategrey);
329
+ border-radius: 4px;
330
+ padding: 2px 5px;
331
+ opacity: 0;
332
+ transition: opacity 0.3s ease, margin-top 0.3s ease;
333
+ }
334
+
335
+ .show {
336
+ opacity: 1;
337
+ margin-top: -5px;
338
+ z-index: 999;
339
+ }
340
+
341
+ /* --------------------------
342
+ Resizable column wrapper
343
+ -------------------------- */
344
+
345
+ .thheaddef {
346
+ position: relative;
347
+ padding: 0;
348
+ }
349
+
350
+ .ext-new-resize-header {
351
+ display: flex;
352
+ justify-content: left;
353
+ align-items: center;
354
+ padding: 0;
355
+ }
356
+
357
+ .ext-new-resize-handle {
358
+ width: 5px;
359
+ cursor: col-resize;
360
+ height: 100%;
361
+ position: absolute;
362
+ right: 0;
363
+ top: 0;
364
+ z-index: 5;
365
+ }
366
+
367
+ /* --------------------------
368
+ Resize handle hover effect
369
+ --------------------------- */
370
+
371
+ .ext-new-resize-handle {
372
+ transition: background-color 0.2s ease;
373
+ }
374
+
375
+ .ext-new-resize-handle:hover {
376
+ background-color: rgba(0, 0, 0, 0.1);
377
+ }
378
+
379
+ .fallbackwrapper {
380
+ height: calc(100% - var(--ext-table-footer-height, 40px));
381
+ display: flex;
382
+ align-items: center;
383
+ justify-content: center;
384
+ background-color: var(--fallback-wrapper-bck-clr, wheat);
385
+ }
386
+
387
+ .checkboxtable {
388
+ --btn-bck-clr: transparent;
389
+ display: flex;
390
+ justify-content: center;
391
+ width: fit-content;
392
+ --btn-padding: 2px;
393
+ background-color: var(--btn-bck-clr);
394
+ --btn-border-radius: 50px;
395
+ --ripple-box-shadow: none;
396
+ user-select: none;
397
+ --ripple-effect-bck: rgb(0 0 0 / 15%);
398
+ --svg-w-h: 20px;
399
+ }
400
+
401
+ .cbcircle {
402
+ fill: var(--ext-table-cb-corpus-fill, red)
403
+ }
404
+
405
+ .cbpath {
406
+ fill: var(--ext-table-cb-path-fill, white)
407
+ }
408
+
409
+ /* --------------------------
410
+ PAGINATION
411
+ -------------------------- */
412
+ .paginationwrapper {
413
+ display: flex;
414
+ gap: 5px;
415
+ width: 100%;
416
+ color: gray;
417
+ font-size: 85%;
418
+ }
419
+
420
+ .paginationend {
421
+ color: var(--ext-table-pag-clr, lightslategrey);
422
+ font-size: 80%;
423
+ font-weight: var(--ext-table-pag-font-weight, 400);
424
+ white-space: nowrap;
425
+ }
426
+
427
+ /* --------------------------
428
+ RIPPLE
429
+ -------------------------- */
430
+ .paginationbutton {
431
+ font-size: 90%;
432
+ white-space: nowrap;
433
+ display: flex;
434
+ align-items: center;
435
+ justify-content: center;
436
+ cursor: pointer;
437
+ height: var(--ext-table-pag-h, 30px);
438
+ min-width: var(--ext-table-pag-w, 30px);
439
+ color: var(--ext-table-pag-btn-clr, darkblue);
440
+ line-height: var(--ext-table-btn-line-height, 1.5);
441
+ font-weight: var(--ext-table-btn-font-weight, 500);
442
+ background-color: var(--ext-table-pag-btn-bck-clr, white);
443
+ border-radius: var(--ext-table-pag-btn-border-radius, 12px);
444
+ box-shadow: var(--ext-table-pag-ripple-box-shadow, rgb(100 100 111 / 20%) 0px 7px 29px 0px);
445
+ position: relative;
446
+ overflow: hidden;
447
+ user-select: none;
448
+ width: fit-content;
449
+ }
450
+
451
+ .pagbtnsvg{
452
+ width: var(--pag-btn-svg-w, 15px);
453
+ height: var(--pag-btn-svg-h, 15px);
454
+ fill: var(--ext-table-pag-btn-clr)
455
+ }
456
+
457
+ .pagnumber {
458
+ width: var(--ext-table-pag-w);
459
+ }
460
+
461
+ .paginationbuttonhide{
462
+ opacity: .3;
463
+ }
464
+
465
+ .paginationbuttonactive {
466
+ background-color: var(--ext-table-pag-btn-bck-clr-active, darkblue);
467
+ color: var(--ext-table-pag-btn-clr-active, yellow);
468
+ font-weight: 500;
469
+ }
470
+
471
+ .pagripple {
472
+ background: var(--ripple-effect-bck, rgb(0 0 0 / 15%));
473
+ position: absolute;
474
+ border-radius: var(--ext-table-pag-btn-border-radius, 12px);
475
+ transform: scale(0);
476
+ animation: ripple-animation 0.6s linear;
477
+ pointer-events: none;
478
+ transform-origin: center center;
479
+ will-change: transform, opacity;
480
+ }
481
+
482
+ /* Only devices that can hover (desktop/laptop) */
483
+ @media (hover: hover) and (pointer: fine) {
484
+ ._ripple {
485
+ cursor: pointer;
486
+ }
487
+ }
488
+
489
+ @keyframes ripple-animation {
490
+ to {
491
+ transform: scale(4);
492
+ opacity: 0;
493
+ }
494
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "ehscan-react-table",
3
+ "version": "0.0.1",
4
+ "description": "components",
5
+ "main": "dist/Components.js",
6
+ "types": "dist/Components.d.ts",
7
+ "scripts": {
8
+ "build": "tsc && npm run copy-css",
9
+ "copy-css": "rsync -a src/style/ dist/style/",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": [
13
+ "react",
14
+ "state",
15
+ "global-state",
16
+ "store",
17
+ "minimal"
18
+ ],
19
+ "author": "beeplaced",
20
+ "license": "MIT",
21
+ "peerDependencies": {
22
+ "react": "^18.3.1"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "devDependencies": {
28
+ "@types/glob": "8",
29
+ "@types/react": "^19.1.12",
30
+ "@types/react-dom": "^19.1.9",
31
+ "glob": "8",
32
+ "minimatch": "5",
33
+ "typescript": "^5.6.3"
34
+ }
35
+ }