simple-table-core 0.1.2 → 0.1.4
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/.storybook/main.ts +18 -0
- package/.storybook/preview.ts +14 -0
- package/README.md +75 -7
- package/assets/table-example.png +0 -0
- package/dist/index.js +1 -0
- package/package.json +31 -11
- package/rollup.config.js +27 -0
- package/src/components/Animate.tsx +14 -4
- package/src/components/SimpleTable/SimpleTable.tsx +101 -18
- package/src/components/SimpleTable/TableBody.tsx +59 -31
- package/src/components/SimpleTable/TableCell.tsx +12 -11
- package/src/components/SimpleTable/TableFooter.tsx +61 -0
- package/src/components/SimpleTable/TableHeader.tsx +34 -20
- package/src/components/SimpleTable/TableHeaderCell.tsx +35 -27
- package/src/components/SimpleTable/TableLastColumnCell.tsx +17 -0
- package/src/components/SimpleTable/TableRowSeparator.tsx +5 -0
- package/src/consts/SampleData.ts +99 -36
- package/src/hooks/useSelection.ts +1 -0
- package/src/hooks/useTableHeaderCell.ts +10 -6
- package/src/icons/AngleLeftIcon.tsx +15 -0
- package/src/icons/AngleRightIcon.tsx +15 -0
- package/src/index.tsx +2 -15
- package/src/stories/SimpleTable.stories.ts +16 -0
- package/src/stories/SimpleTableExample.tsx +17 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/styles/simple-table.css +242 -0
- package/src/types/HeaderObject.ts +3 -0
- package/Outline.txt +0 -63
- package/notes.txt +0 -3
- package/public/favicon.ico +0 -0
- package/public/index.html +0 -15
- package/src/App.tsx +0 -21
- package/src/styles/index.css +0 -14
- package/src/styles/table.css +0 -134
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StorybookConfig } from "@storybook/react-webpack5";
|
|
2
|
+
|
|
3
|
+
const config: StorybookConfig = {
|
|
4
|
+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
|
5
|
+
addons: [
|
|
6
|
+
"@storybook/preset-create-react-app",
|
|
7
|
+
"@storybook/addon-onboarding",
|
|
8
|
+
"@storybook/addon-links",
|
|
9
|
+
"@storybook/addon-essentials",
|
|
10
|
+
"@chromatic-com/storybook",
|
|
11
|
+
"@storybook/addon-interactions",
|
|
12
|
+
],
|
|
13
|
+
framework: {
|
|
14
|
+
name: "@storybook/react-webpack5",
|
|
15
|
+
options: {},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export default config;
|
package/README.md
CHANGED
|
@@ -1,12 +1,80 @@
|
|
|
1
|
-
# Simple
|
|
1
|
+
# Simple Table
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Simple Table is a React grid package designed to provide a flexible and easy-to-use table component for your React applications. Visit our website at [www.simple-table.com](http://www.simple-table.com) for more information and documentation.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Props
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The Simple Table component accepts the following props:
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
- **defaultHeaders**: An array of `HeaderObject` defining the table headers. Each `HeaderObject` includes:
|
|
12
|
+
|
|
13
|
+
- **label**: A string representing the display name of the column header.
|
|
14
|
+
- **accessor**: A string used to access the corresponding data in each row.
|
|
15
|
+
- **width**: A number specifying the width of the column.
|
|
16
|
+
- **cellRenderer**: An optional function that takes a row object and returns a `ReactNode` for custom cell rendering.
|
|
17
|
+
|
|
18
|
+
- **enableColumnResizing**: A boolean to enable or disable column resizing. Default is `true`.
|
|
19
|
+
- **height**: The height of the table.
|
|
20
|
+
- **hideFooter**: A boolean to hide or show the footer. Default is `false`.
|
|
21
|
+
- **nextIcon**: A React element to display as the next page icon. Default is `<AngleRightIcon />`.
|
|
22
|
+
- **prevIcon**: A React element to display as the previous page icon. Default is `<AngleLeftIcon />`.
|
|
23
|
+
- **rows**: An array of data rows to be displayed in the table.
|
|
24
|
+
- **rowsPerPage**: The number of rows to display per page. Default is `10`.
|
|
25
|
+
- **shouldPaginate**: A boolean to enable or disable pagination. Default is `true`.
|
|
26
|
+
|
|
27
|
+
## Customizable Styles
|
|
28
|
+
|
|
29
|
+
All styles for the Simple Table are customizable and can be found in the `table.css` file. You can modify these styles to fit the design needs of your application.
|
|
30
|
+
|
|
31
|
+
### CSS Variables
|
|
32
|
+
|
|
33
|
+
You can override the following CSS variables to customize the appearance of the table:
|
|
34
|
+
|
|
35
|
+
- `--st-border-radius`
|
|
36
|
+
- `--st-table-border-color`
|
|
37
|
+
- `--st-border-width`
|
|
38
|
+
- `--st-resize-handle-color`
|
|
39
|
+
- `--st-separator-border-color`
|
|
40
|
+
- `--st-odd-row-background-color`
|
|
41
|
+
- `--st-dragging-background-color`
|
|
42
|
+
- `--st-selected-cell-background-color`
|
|
43
|
+
- `--st-selected-first-cell-background-color`
|
|
44
|
+
- `--st-border-top-color`
|
|
45
|
+
- `--st-border-bottom-color`
|
|
46
|
+
- `--st-border-left-color`
|
|
47
|
+
- `--st-border-right-color`
|
|
48
|
+
- `--st-border-top-white-color`
|
|
49
|
+
- `--st-footer-background-color`
|
|
50
|
+
|
|
51
|
+
### CSS Class Names
|
|
52
|
+
|
|
53
|
+
The following CSS class names are used in the table and can be customized:
|
|
54
|
+
|
|
55
|
+
- `.st-table-wrapper`
|
|
56
|
+
- `.st-table`
|
|
57
|
+
- `.st-table-header-cell`
|
|
58
|
+
- `.st-table-cell`
|
|
59
|
+
- `.st-table-header-label`
|
|
60
|
+
- `.st-table-header-resize-handle`
|
|
61
|
+
- `.st-table-row-separator`
|
|
62
|
+
- `.st-table-cell-odd-row`
|
|
63
|
+
- `.st-dragging`
|
|
64
|
+
- `.st-table-cell-selected`
|
|
65
|
+
- `.st-table-cell-selected-first`
|
|
66
|
+
- `.border-top-blue`
|
|
67
|
+
- `.border-bottom-blue`
|
|
68
|
+
- `.border-left-blue`
|
|
69
|
+
- `.border-right-blue`
|
|
70
|
+
- `.border-top-white`
|
|
71
|
+
- `.st-footer`
|
|
72
|
+
- `.st-next-prev-btn`
|
|
73
|
+
- `.st-page-btn`
|
|
74
|
+
- `.st-page-btn.active`
|
|
75
|
+
|
|
76
|
+
For more detailed usage and examples, please refer to our [documentation](http://www.simple-table.com/docs).
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
Binary file
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsxs as e,jsx as r,Fragment as t}from"react/jsx-runtime";import o,{useState as n,useRef as a,useCallback as l,useEffect as s,Children as c,useLayoutEffect as i,forwardRef as d,createRef as u,Fragment as f,useReducer as b,useMemo as g}from"react";var v=function(){return v=Object.assign||function(e){for(var r,t=1,o=arguments.length;t<o;t++)for(var n in r=arguments[t])Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n]);return e},v.apply(this,arguments)};function h(e,r,t){if(t||2===arguments.length)for(var o,n=0,a=r.length;n<a;n++)!o&&n in r||(o||(o=Array.prototype.slice.call(r,0,n)),o[n]=r[n]);return e.concat(o||Array.prototype.slice.call(r))}"function"==typeof SuppressedError&&SuppressedError;var p=function(e){var r={};return c.forEach(e,(function(e){if(e.ref&&e.ref.current){var t=e.ref.current.getBoundingClientRect();r[e.key]=t}})),r},m=function(e){var r,t,l=e.allowHorizontalAnimate,c=void 0===l||l,d=e.children,u=e.pause,f=n({}),b=f[0],g=f[1],v=n({}),h=v[0],m=v[1],w=(r=d,t=a(),s((function(){t.current=r}),[r]),t.current);return i((function(){var e=p(d);g(e)}),[d]),i((function(){var e=p(w);m(e)}),[w]),s((function(){u||Object.keys(h).length&&o.Children.forEach(d,(function(e){var r=e.ref.current,t=h[e.key],o=b[e.key],n=t.left-o.left,a=c?0:t.top-o.top,l=Math.abs(n),s=Math.abs(a);(l>10||s>10)&&requestAnimationFrame((function(){r.style.transform="translate(".concat(n,"px, ").concat(a,"px)"),r.style.transition="transform 0s",requestAnimationFrame((function(){r.style.transform="",r.style.transition="transform 500ms"}))}))}))}),[b,h,d,u,c]),d},w=!1,y=function(e){var r=e.draggedHeaderRef,t=e.headersRef,o=e.hoveredHeaderRef,n=e.onTableHeaderDragEnd;return{handleDragStart:function(e){r.current=e},handleDragOver:function(e){!function(e){var a;if(!w&&(o.current=e,e.accessor!==(null===(a=r.current)||void 0===a?void 0:a.accessor)&&null!==r.current&&!w)){if(w=!0,!t.current)return;var l=h([],t.current,!0),s=l.findIndex((function(e){var t;return e.accessor===(null===(t=r.current)||void 0===t?void 0:t.accessor)})),c=l.findIndex((function(r){return r.accessor===e.accessor}));if(void 0===s||void 0===c)return;var i=l.splice(s,1)[0];l.splice(c,0,i),JSON.stringify(l)!==JSON.stringify(t.current)&&setTimeout((function(){n(l),setTimeout((function(){w=!1}),500)}),50)}}(e)},handleDragEnd:function(){r.current=null,o.current=null}}},x=function(e,r){var t=!0,o=!0;return function(){for(var n=[],a=0;a<arguments.length;a++)n[a]=arguments[a];if(t)return t=!1,void setTimeout((function(){return o=!1}),r);o||(e.apply(this,n),o=!0,setTimeout((function(){return o=!1}),r))}},C=d((function(t,o){var l,s=t.draggedHeaderRef,c=t.enableColumnResizing,i=t.forceUpdate,d=t.headersRef,u=t.hoveredHeaderRef,f=t.index,b=t.onSort,g=t.onTableHeaderDragEnd,h=t.setIsWidthDragging,p=a({pageX:0,pageY:0}),m=n(!1),w=m[0],C=m[1],k=null===(l=d.current)||void 0===l?void 0:l[f],D=y({draggedHeaderRef:s,headersRef:d,hoveredHeaderRef:u,onTableHeaderDragEnd:g}),M=D.handleDragStart,R=D.handleDragOver,L=D.handleDragEnd,E=a(x((function(e){R(e)}),100)).current;return k?e("div",v({className:"st-table-header-cell ".concat(k===u.current?"st-hovered":""," ").concat(w?"st-dragging":""),ref:o,style:{width:k.width}},{children:[r("div",v({className:"st-table-header-label",draggable:!0,onClick:function(){return b(f)},onDragStart:function(){return function(e){C(!0),M(e)}(k)},onDragOver:function(e){var r=e.pageX,t=e.pageY;r===p.current.pageX&&t===p.current.pageY||(p.current={pageX:r,pageY:t},e.preventDefault(),E(k,e))},onDragEnd:function(){C(!1),L()}},{children:null==k?void 0:k.label})),c&&r("div",{className:"st-table-header-resize-handle",onMouseDown:function(e){h(!0),e.preventDefault();var r=e.clientX;if(k){var t=k.width,o=x((function(e){var o=Math.max(t+(e.clientX-r),10);k&&(d.current[f].width=o,i())}),10),n=function(){document.removeEventListener("mousemove",o),document.removeEventListener("mouseup",n),h(!1)};document.addEventListener("mousemove",o),document.addEventListener("mouseup",n)}}})]})):null})),k=d((function(e,t){return e.visible?r("div",{className:"st-table-cell ".concat(e.isLastRow?"st-table-cell-last-row":""),ref:t}):r("div",{ref:t})})),D=function(){return r("div",{className:"st-table-row-separator"})},M=function(o){var n,l=o.enableColumnResizing,s=o.forceUpdate,c=o.headersRef,i=o.isWidthDragging,d=o.onSort,f=o.onTableHeaderDragEnd,b=o.setIsWidthDragging,g=o.shouldDisplayLastColumnCell,h=a(null),p=a(null);return e(t,{children:[e(m,v({pause:i},{children:[null===(n=c.current)||void 0===n?void 0:n.map((function(e,t){return r(C,{draggedHeaderRef:h,enableColumnResizing:l,forceUpdate:s,headersRef:c,hoveredHeaderRef:p,index:t,onSort:d,onTableHeaderDragEnd:f,ref:u(),setIsWidthDragging:b},e.accessor)})),r(k,{ref:u(),visible:g})]})),r(D,{})]})},R=d((function(e,t){var o=e.rowIndex,n=e.colIndex,a=e.content,l=e.isSelected,s=e.isTopLeftCell,c=e.borderClass,i=e.onMouseDown,d=e.onMouseOver,u=e.isLastRow,f=o%2==0;return r("div",v({onMouseDown:function(){return i(o,n)},onMouseOver:function(){return d(o,n)},ref:t,className:"st-table-cell ".concat(l?s?"st-table-cell-selected-first-cell ".concat(c):"st-table-cell-selected ".concat(c):""," ").concat(u?"st-table-cell-last-row":"","\n ").concat(f?"st-table-cell-odd-row":"")},{children:a}))})),L=function(o){var n=o.getBorderClass,a=o.handleMouseDown,l=o.handleMouseOver,s=o.headers,c=o.isSelected,i=o.isTopLeftCell,d=o.isWidthDragging,b=o.shouldDisplayLastColumnCell,g=o.shouldPaginate,h=o.sortedRows;return r(t,{children:h.map((function(t,o){return e(f,{children:[e(m,v({allowHorizontalAnimate:g,pause:d},{children:[s.map((function(e,s){var d=t[e.accessor];return e.cellRenderer&&(d=e.cellRenderer(t)),r(R,{borderClass:n(o,s),colIndex:s,content:d,isSelected:c(o,s),isTopLeftCell:i(o,s),onMouseDown:function(){return a(o,s)},onMouseOver:function(){return l(o,s)},ref:u(),rowIndex:o,isLastRow:o===h.length-1},e.accessor)})),r(k,{isLastRow:o===h.length-1,ref:u(),visible:b})]})),o!==h.length-1&&r(D,{})]},t.id)}))})},E=function(t){var o=t.currentPage,n=t.hideFooter,a=t.nextIcon,l=t.onPageChange,s=t.prevIcon,c=t.rowsPerPage,i=t.totalRows,d=Math.ceil(i/c),u=function(e){e>=1&&e<=d&&l(e)};return n?null:e("div",v({className:"st-footer"},{children:[r("button",v({className:"st-next-prev-btn",onClick:function(){return u(o-1)},disabled:1===o},{children:s})),r("button",v({className:"st-next-prev-btn",onClick:function(){return u(o+1)},disabled:o===d},{children:a})),Array.from({length:d},(function(e,t){return r("button",v({onClick:function(){return u(t+1)},className:"st-page-btn ".concat(o===t+1?"active":"")},{children:t+1}),t)}))]}))},S=function(){return r("svg",v({className:"angle-icon",viewBox:"0 0 24 24",width:"24",height:"24",xmlns:"http://www.w3.org/2000/svg"},{children:r("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})}))},T=function(){return r("svg",v({className:"angle-icon",viewBox:"0 0 24 24",width:"24",height:"24",xmlns:"http://www.w3.org/2000/svg"},{children:r("path",{d:"M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z"})}))};!function(e,r){void 0===r&&(r={});var t=r.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],n=document.createElement("style");n.type="text/css","top"===t&&o.firstChild?o.insertBefore(n,o.firstChild):o.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}}('@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap");:root{--slate-50:#f8fafc;--slate-100:#f1f5f9;--slate-200:#e2e8f0;--slate-300:#cbd5e1;--slate-400:#94a3b8;--slate-500:#64748b;--slate-600:#475569;--slate-700:#334155;--slate-800:#1e293b;--slate-900:#0f172a;--blue-50:#eff6ff;--blue-100:#dbeafe;--blue-200:#bfdbfe;--blue-300:#93c5fd;--blue-400:#60a5fa;--blue-500:#3b82f6;--blue-600:#2563eb;--blue-700:#1d4ed8;--blue-800:#1e40af;--blue-900:#1e3a8a;--orange-50:#fff7ed;--orange-100:#ffedd5;--orange-200:#fed7aa;--orange-300:#fdba74;--orange-400:#fb923c;--orange-500:#f97316;--orange-600:#ea580c;--orange-700:#c2410c;--orange-800:#9a3412;--orange-900:#7c2d12;--amber-50:#fffbeb;--amber-100:#fef3c7;--amber-200:#fde68a;--amber-300:#fcd34d;--amber-400:#fbbf24;--amber-500:#f59e0b;--amber-600:#d97706;--amber-700:#b45309;--amber-800:#92400e;--amber-900:#78350f;--st-border-radius:4px;--st-table-border-color:var(--slate-300);--st-border-width:1px;--st-resize-handle-color:var(--slate-300);--st-separator-border-color:var(--slate-300);--st-odd-row-background-color:var(--slate-100);--st-dragging-background-color:var(--blue-100);--st-selected-cell-background-color:var(--blue-200);--st-selected-first-cell-background-color:var(--amber-100);--st-border-top-color:var(--blue-500);--st-border-bottom-color:var(--blue-500);--st-border-left-color:var(--blue-500);--st-border-right-color:var(--blue-500);--st-border-top-white-color:#fff;--st-footer-background-color:#fff}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Nunito}.st-table-wrapper{border:var(--st-border-width) solid var(--st-table-border-color);border-radius:var(--st-border-radius);height:auto;max-height:100dvh;overflow:auto}.st-table{border-collapse:collapse;display:grid;table-layout:auto;white-space:nowrap;width:100%}.st-table-header-cell{background-color:#fff;position:sticky;top:0}.st-table-cell,.st-table-header-cell{border:var(--st-border-width) solid #0000;color:var(--slate-800);cursor:pointer;overflow:hidden}.st-table-cell,.st-table-header-label{overflow:hidden;padding:8px;text-align:left;text-overflow:ellipsis;user-select:none;white-space:nowrap}.st-table-header-resize-handle{background-color:var(--st-resize-handle-color);border-radius:.25rem;bottom:0;cursor:col-resize;margin-bottom:.25rem;margin-top:.25rem;position:absolute;right:0;top:0;width:5px}.st-table-row-separator{background-color:var(--st-separator-border-color);grid-column:1/-1;height:1px}.st-table-cell-odd-row{background-color:var(--st-odd-row-background-color)}.st-dragging{background-color:var(--st-dragging-background-color)}.st-table-cell-selected{background-color:var(--st-selected-cell-background-color)}.st-table-cell-selected-first{background-color:var(--st-selected-first-cell-background-color)}.border-top-blue{border-top:var(--st-border-width) solid var(--st-border-top-color)}.border-bottom-blue{border-bottom:var(--st-border-width) solid var(--st-border-bottom-color)}.border-left-blue{border-left:var(--st-border-width) solid var(--st-border-left-color)}.border-right-blue{border-right:var(--st-border-width) solid var(--st-border-right-color)}.border-top-white{border-top:var(--st-border-width) solid var(--st-border-top-white-color)}.st-footer{background-color:var(--st-footer-background-color);border-top:var(--st-border-width) solid var(--st-table-border-color);bottom:0;left:0;padding:8px;position:sticky}.st-footer,.st-next-prev-btn{align-items:center;display:flex}.st-next-prev-btn{background-color:initial;border:none;border-radius:4px;color:var(--slate-600);cursor:pointer;justify-content:center;padding:4px;transition:background-color .3s ease}.st-next-prev-btn:hover{background-color:var(--slate-100)}.st-page-btn{background-color:initial;border:none;border-radius:var(--st-border-radius);color:var(--slate-600);cursor:pointer;margin-left:4px;padding:4px;transition:background-color .3s ease}.st-page-btn:hover{background-color:var(--slate-100)}.st-page-btn.active{background-color:var(--blue-500);color:#fff}');var N=function(t){var o,c=t.defaultHeaders,i=t.enableColumnResizing,d=void 0===i||i,u=t.height,f=t.hideFooter,p=void 0!==f&&f,m=t.nextIcon,w=void 0===m?r(T,{}):m,y=t.prevIcon,x=void 0===y?r(S,{}):y,C=t.rows,k=t.rowsPerPage,D=void 0===k?10:k,R=t.shouldPaginate,N=void 0===R||R,O=n(!1),H=O[0],I=O[1],z=a(c),P=n(C),j=P[0],B=P[1],W=n(null),A=W[0],U=W[1],F=b((function(e){return e+1}),0)[1],X=n(1),Y=X[0],q=X[1],J=a(null),K=g((function(){return!!J.current&&z.current.reduce((function(e,r){return e+r.width}),0)<J.current.clientWidth}),[]),G=function(e,r){var t=n([]),o=t[0],c=t[1],i=a(!1),d=a(null),u=l((function(){var t=o.reduce((function(t,o){var n=o.row,a=o.col;return t[n]||(t[n]=[]),t[n][a]=e[n][r[a].accessor],t}),{}),n=Object.values(t).map((function(e){return Object.values(e).join("\t")})).join("\n");navigator.clipboard.writeText(n)}),[o,e,r]);s((function(){var e=function(e){(e.ctrlKey||e.metaKey)&&"c"===e.key&&u()};return document.addEventListener("keydown",e),function(){document.removeEventListener("keydown",e)}}),[u,o]);var f=function(e,r){return o.some((function(t){return t.row===e&&t.col===r}))};return{selectedCells:o,handleMouseDown:function(e,r){i.current=!0,d.current={row:e,col:r},c([{row:e,col:r}])},handleMouseOver:function(e,r){if(i.current&&d.current){for(var t=[],o=Math.min(d.current.row,e),n=Math.max(d.current.row,e),a=Math.min(d.current.col,r),l=Math.max(d.current.col,r),s=o;s<=n;s++)for(var u=a;u<=l;u++)t.push({row:s,col:u});c(t)}},handleMouseUp:function(){i.current=!1,d.current=null},isSelected:f,getBorderClass:function(e,r){var t=[];return f(e-1,r)||t.push("border-top-blue"),f(e+1,r)||t.push("border-bottom-blue"),f(e,r-1)||t.push("border-left-blue"),f(e,r+1)||t.push("border-right-blue"),t.join(" ")},isTopLeftCell:function(e,r){return e===Math.min.apply(Math,o.map((function(e){return e.row})))&&r===Math.min.apply(Math,o.map((function(e){return e.col})))},setSelectedCells:c}}(j,z.current),Q=G.handleMouseDown,V=G.handleMouseOver,Z=G.handleMouseUp,$=G.isSelected,_=G.getBorderClass,ee=G.isTopLeftCell,re=G.setSelectedCells;s((function(){var e=function(e){e.target.closest(".st-table-cell")||re([])};return document.addEventListener("mousedown",e),function(){document.removeEventListener("mousedown",e)}}),[re]);var te=N?j.slice((Y-1)*D,Y*D):j;return e("div",v({ref:J,className:"st-table-wrapper",style:u?{height:u}:{}},{children:[e("div",v({className:"st-table",onMouseUp:Z,onMouseLeave:Z,style:{gridTemplateColumns:"".concat(null===(o=z.current)||void 0===o?void 0:o.map((function(e){return"".concat(e.width,"px")})).join(" ")," 1fr")}},{children:[r(M,{enableColumnResizing:d,forceUpdate:F,headersRef:z,isWidthDragging:H,onSort:function(e){var r=function(e,r,t,o){var n=e[o],a="ascending";return t&&t.key.accessor===n.accessor&&"ascending"===t.direction&&(a="descending"),{sortedData:h([],r,!0).sort((function(e,r){return e[n.accessor]<r[n.accessor]?"ascending"===a?-1:1:e[n.accessor]>r[n.accessor]?"ascending"===a?1:-1:0})),newSortConfig:{key:n,direction:a}}}(z.current,j,A,e),t=r.sortedData,o=r.newSortConfig;B(t),U(o)},onTableHeaderDragEnd:function(e){z.current=e,F()},setIsWidthDragging:I,shouldDisplayLastColumnCell:K}),r(L,{getBorderClass:_,handleMouseDown:Q,handleMouseOver:V,headers:z.current,isSelected:$,isTopLeftCell:ee,isWidthDragging:H,shouldDisplayLastColumnCell:K,shouldPaginate:N,sortedRows:te})]})),N&&r(E,{currentPage:Y,hideFooter:p,onPageChange:q,rowsPerPage:D,totalRows:j.length,nextIcon:w,prevIcon:x})]}))};export{N as SimpleTable};
|
package/package.json
CHANGED
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-table-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"main": "dist/index.js",
|
|
4
5
|
"dependencies": {
|
|
5
|
-
"react": "^18.3.1"
|
|
6
|
-
"react-dom": "^18.3.1"
|
|
6
|
+
"react": "^18.3.1"
|
|
7
7
|
},
|
|
8
8
|
"devDependencies": {
|
|
9
|
+
"@babel/preset-react": "^7.25.7",
|
|
10
|
+
"@chromatic-com/storybook": "^1.9.0",
|
|
11
|
+
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
12
|
+
"@rollup/plugin-typescript": "^12.1.0",
|
|
13
|
+
"@storybook/addon-essentials": "^8.3.5",
|
|
14
|
+
"@storybook/addon-interactions": "^8.3.5",
|
|
15
|
+
"@storybook/addon-links": "^8.3.5",
|
|
16
|
+
"@storybook/addon-onboarding": "^8.3.5",
|
|
17
|
+
"@storybook/blocks": "^8.3.5",
|
|
18
|
+
"@storybook/preset-create-react-app": "^8.3.5",
|
|
19
|
+
"@storybook/react": "^8.3.5",
|
|
20
|
+
"@storybook/react-webpack5": "^8.3.5",
|
|
21
|
+
"@storybook/test": "^8.3.5",
|
|
9
22
|
"@types/node": "^16.18.111",
|
|
10
23
|
"@types/react": "^18.3.9",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
24
|
+
"eslint-plugin-storybook": "^0.9.0",
|
|
25
|
+
"prop-types": "^15.8.1",
|
|
26
|
+
"rollup": "^2.79.2",
|
|
27
|
+
"rollup-plugin-babel": "^4.4.0",
|
|
28
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
29
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
30
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
31
|
+
"storybook": "^8.3.5",
|
|
32
|
+
"typescript": "^4.9.5",
|
|
33
|
+
"webpack": "^5.95.0"
|
|
14
34
|
},
|
|
15
35
|
"scripts": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"eject": "react-scripts eject"
|
|
36
|
+
"build": "rollup -c",
|
|
37
|
+
"start": "storybook dev -p 6006",
|
|
38
|
+
"build-storybook": "storybook build"
|
|
20
39
|
},
|
|
21
40
|
"eslintConfig": {
|
|
22
41
|
"extends": [
|
|
23
42
|
"react-app",
|
|
24
|
-
"react-app/jest"
|
|
43
|
+
"react-app/jest",
|
|
44
|
+
"plugin:storybook/recommended"
|
|
25
45
|
]
|
|
26
46
|
},
|
|
27
47
|
"browserslist": {
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import babel from "@rollup/plugin-babel";
|
|
2
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
3
|
+
import postcss from "rollup-plugin-postcss";
|
|
4
|
+
import typescript from "@rollup/plugin-typescript";
|
|
5
|
+
import { terser } from "rollup-plugin-terser";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
input: "src/index.tsx",
|
|
9
|
+
output: {
|
|
10
|
+
file: "dist/index.js",
|
|
11
|
+
format: "esm",
|
|
12
|
+
},
|
|
13
|
+
plugins: [
|
|
14
|
+
postcss({
|
|
15
|
+
plugins: [],
|
|
16
|
+
minimize: true,
|
|
17
|
+
}),
|
|
18
|
+
babel({
|
|
19
|
+
exclude: "node_modules/**",
|
|
20
|
+
presets: ["@babel/preset-react"],
|
|
21
|
+
}),
|
|
22
|
+
resolve(),
|
|
23
|
+
typescript(),
|
|
24
|
+
terser(),
|
|
25
|
+
],
|
|
26
|
+
external: ["react", "react/jsx-runtime"],
|
|
27
|
+
};
|
|
@@ -3,11 +3,16 @@ import usePrevious from "../hooks/usePrevious";
|
|
|
3
3
|
import calculateBoundingBoxes from "../helpers/calculateBoundingBoxes";
|
|
4
4
|
|
|
5
5
|
interface AnimateProps {
|
|
6
|
+
allowHorizontalAnimate?: boolean;
|
|
6
7
|
children: any;
|
|
7
8
|
pause?: boolean;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
const Animate = ({
|
|
11
|
+
const Animate = ({
|
|
12
|
+
allowHorizontalAnimate = true,
|
|
13
|
+
children,
|
|
14
|
+
pause,
|
|
15
|
+
}: AnimateProps) => {
|
|
11
16
|
const [boundingBox, setBoundingBox] = useState<any>({});
|
|
12
17
|
const [prevBoundingBox, setPrevBoundingBox] = useState<any>({});
|
|
13
18
|
const prevChildren = usePrevious(children);
|
|
@@ -33,9 +38,14 @@ const Animate = ({ children, pause }: AnimateProps) => {
|
|
|
33
38
|
const lastBox = boundingBox[child.key];
|
|
34
39
|
|
|
35
40
|
const changeInX = firstBox.left - lastBox.left;
|
|
36
|
-
const changeInY =
|
|
41
|
+
const changeInY = !allowHorizontalAnimate
|
|
42
|
+
? firstBox.top - lastBox.top
|
|
43
|
+
: 0;
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
const absoluteChangeInX = Math.abs(changeInX);
|
|
46
|
+
const absoluteChangeInY = Math.abs(changeInY);
|
|
47
|
+
|
|
48
|
+
if (absoluteChangeInX > 10 || absoluteChangeInY > 10) {
|
|
39
49
|
requestAnimationFrame(() => {
|
|
40
50
|
// Before the DOM paints, invert child to old position
|
|
41
51
|
domNode.style.transform = `translate(${changeInX}px, ${changeInY}px)`;
|
|
@@ -51,7 +61,7 @@ const Animate = ({ children, pause }: AnimateProps) => {
|
|
|
51
61
|
}
|
|
52
62
|
});
|
|
53
63
|
}
|
|
54
|
-
}, [boundingBox, prevBoundingBox, children, pause]);
|
|
64
|
+
}, [boundingBox, prevBoundingBox, children, pause, allowHorizontalAnimate]);
|
|
55
65
|
|
|
56
66
|
return children;
|
|
57
67
|
};
|
|
@@ -1,25 +1,65 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useReducer,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useMemo,
|
|
8
|
+
} from "react";
|
|
2
9
|
import useSelection from "../../hooks/useSelection";
|
|
3
10
|
import TableHeader from "./TableHeader";
|
|
4
11
|
import { onSort } from "../../utils/sortUtils";
|
|
5
|
-
import Animate from "../Animate";
|
|
6
|
-
import TableRow from "./TableBody";
|
|
7
|
-
import HeaderObject from "../../types/HeaderObject";
|
|
8
12
|
import TableBody from "./TableBody";
|
|
13
|
+
import HeaderObject from "../../types/HeaderObject";
|
|
14
|
+
import TableFooter from "./TableFooter";
|
|
15
|
+
import AngleLeftIcon from "../../icons/AngleLeftIcon";
|
|
16
|
+
import AngleRightIcon from "../../icons/AngleRightIcon";
|
|
17
|
+
import "../../styles/simple-table.css";
|
|
9
18
|
|
|
10
19
|
interface SpreadsheetProps {
|
|
11
20
|
defaultHeaders: HeaderObject[];
|
|
12
|
-
|
|
21
|
+
enableColumnResizing?: boolean;
|
|
22
|
+
height?: string;
|
|
23
|
+
hideFooter?: boolean;
|
|
24
|
+
nextIcon?: ReactNode;
|
|
25
|
+
prevIcon?: ReactNode;
|
|
26
|
+
rows: { [key: string]: string | number | boolean | undefined | null }[];
|
|
27
|
+
rowsPerPage?: number;
|
|
28
|
+
shouldPaginate?: boolean;
|
|
13
29
|
}
|
|
14
30
|
|
|
15
|
-
const SimpleTable = ({
|
|
31
|
+
const SimpleTable = ({
|
|
32
|
+
defaultHeaders,
|
|
33
|
+
enableColumnResizing = true,
|
|
34
|
+
height,
|
|
35
|
+
hideFooter = false,
|
|
36
|
+
nextIcon = <AngleRightIcon />,
|
|
37
|
+
prevIcon = <AngleLeftIcon />,
|
|
38
|
+
rows,
|
|
39
|
+
rowsPerPage = 10,
|
|
40
|
+
shouldPaginate = true,
|
|
41
|
+
}: SpreadsheetProps) => {
|
|
16
42
|
const [isWidthDragging, setIsWidthDragging] = useState(false);
|
|
17
|
-
const
|
|
43
|
+
const headersRef = useRef(defaultHeaders);
|
|
18
44
|
const [sortedRows, setSortedRows] = useState(rows);
|
|
19
45
|
const [sortConfig, setSortConfig] = useState<{
|
|
20
46
|
key: HeaderObject;
|
|
21
47
|
direction: string;
|
|
22
48
|
} | null>(null);
|
|
49
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
50
|
+
|
|
51
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
52
|
+
|
|
53
|
+
const tableRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
|
|
55
|
+
const shouldDisplayLastColumnCell = useMemo(() => {
|
|
56
|
+
if (!tableRef.current) return false;
|
|
57
|
+
const totalColumnWidth = headersRef.current.reduce(
|
|
58
|
+
(acc, header) => acc + header.width,
|
|
59
|
+
0
|
|
60
|
+
);
|
|
61
|
+
return totalColumnWidth < tableRef.current.clientWidth;
|
|
62
|
+
}, []);
|
|
23
63
|
|
|
24
64
|
const {
|
|
25
65
|
handleMouseDown,
|
|
@@ -28,11 +68,12 @@ const SimpleTable = ({ defaultHeaders, rows }: SpreadsheetProps) => {
|
|
|
28
68
|
isSelected,
|
|
29
69
|
getBorderClass,
|
|
30
70
|
isTopLeftCell,
|
|
31
|
-
|
|
71
|
+
setSelectedCells,
|
|
72
|
+
} = useSelection(sortedRows, headersRef.current);
|
|
32
73
|
|
|
33
74
|
const handleSort = (columnIndex: number) => {
|
|
34
75
|
const { sortedData, newSortConfig } = onSort(
|
|
35
|
-
|
|
76
|
+
headersRef.current,
|
|
36
77
|
sortedRows,
|
|
37
78
|
sortConfig,
|
|
38
79
|
columnIndex
|
|
@@ -40,41 +81,83 @@ const SimpleTable = ({ defaultHeaders, rows }: SpreadsheetProps) => {
|
|
|
40
81
|
setSortedRows(sortedData);
|
|
41
82
|
setSortConfig(newSortConfig);
|
|
42
83
|
};
|
|
84
|
+
|
|
43
85
|
const onTableHeaderDragEnd = (newHeaders: HeaderObject[]) => {
|
|
44
|
-
|
|
86
|
+
headersRef.current = newHeaders;
|
|
87
|
+
forceUpdate();
|
|
45
88
|
};
|
|
46
89
|
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
92
|
+
const target = event.target as HTMLElement;
|
|
93
|
+
if (!target.closest(".st-table-cell")) {
|
|
94
|
+
setSelectedCells([]);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
99
|
+
return () => {
|
|
100
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
101
|
+
};
|
|
102
|
+
}, [setSelectedCells]);
|
|
103
|
+
|
|
104
|
+
const currentRows = shouldPaginate
|
|
105
|
+
? sortedRows.slice(
|
|
106
|
+
(currentPage - 1) * rowsPerPage,
|
|
107
|
+
currentPage * rowsPerPage
|
|
108
|
+
)
|
|
109
|
+
: sortedRows;
|
|
110
|
+
|
|
47
111
|
return (
|
|
48
|
-
<div
|
|
112
|
+
<div
|
|
113
|
+
ref={tableRef}
|
|
114
|
+
className="st-table-wrapper"
|
|
115
|
+
style={height ? { height } : {}}
|
|
116
|
+
>
|
|
49
117
|
<div
|
|
50
118
|
className="st-table"
|
|
51
119
|
onMouseUp={handleMouseUp}
|
|
52
120
|
onMouseLeave={handleMouseUp}
|
|
53
121
|
style={{
|
|
54
|
-
gridTemplateColumns:
|
|
122
|
+
gridTemplateColumns: `${headersRef.current
|
|
55
123
|
?.map((header) => `${header.width}px`)
|
|
56
|
-
.join(" ")
|
|
124
|
+
.join(" ")} 1fr`,
|
|
57
125
|
}}
|
|
58
126
|
>
|
|
59
127
|
<TableHeader
|
|
60
|
-
|
|
128
|
+
enableColumnResizing={enableColumnResizing}
|
|
129
|
+
forceUpdate={forceUpdate}
|
|
130
|
+
headersRef={headersRef}
|
|
131
|
+
isWidthDragging={isWidthDragging}
|
|
61
132
|
onSort={handleSort}
|
|
62
133
|
onTableHeaderDragEnd={onTableHeaderDragEnd}
|
|
63
|
-
setHeaders={setHeaders}
|
|
64
134
|
setIsWidthDragging={setIsWidthDragging}
|
|
65
|
-
|
|
135
|
+
shouldDisplayLastColumnCell={shouldDisplayLastColumnCell}
|
|
66
136
|
/>
|
|
67
137
|
<TableBody
|
|
68
138
|
getBorderClass={getBorderClass}
|
|
69
139
|
handleMouseDown={handleMouseDown}
|
|
70
140
|
handleMouseOver={handleMouseOver}
|
|
71
|
-
headers={
|
|
141
|
+
headers={headersRef.current}
|
|
72
142
|
isSelected={isSelected}
|
|
73
143
|
isTopLeftCell={isTopLeftCell}
|
|
74
|
-
sortedRows={sortedRows}
|
|
75
144
|
isWidthDragging={isWidthDragging}
|
|
145
|
+
shouldDisplayLastColumnCell={shouldDisplayLastColumnCell}
|
|
146
|
+
shouldPaginate={shouldPaginate}
|
|
147
|
+
sortedRows={currentRows}
|
|
76
148
|
/>
|
|
77
149
|
</div>
|
|
150
|
+
{shouldPaginate && (
|
|
151
|
+
<TableFooter
|
|
152
|
+
currentPage={currentPage}
|
|
153
|
+
hideFooter={hideFooter}
|
|
154
|
+
onPageChange={setCurrentPage}
|
|
155
|
+
rowsPerPage={rowsPerPage}
|
|
156
|
+
totalRows={sortedRows.length}
|
|
157
|
+
nextIcon={nextIcon}
|
|
158
|
+
prevIcon={prevIcon}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
78
161
|
</div>
|
|
79
162
|
);
|
|
80
163
|
};
|
|
@@ -1,51 +1,79 @@
|
|
|
1
|
-
import { createRef,
|
|
1
|
+
import { createRef, Fragment } from "react";
|
|
2
2
|
import TableCell from "./TableCell";
|
|
3
3
|
import Animate from "../Animate";
|
|
4
4
|
import HeaderObject from "../../types/HeaderObject";
|
|
5
|
+
import TableLastColumnCell from "./TableLastColumnCell";
|
|
6
|
+
import TableRowSeparator from "./TableRowSeparator";
|
|
5
7
|
|
|
6
|
-
interface
|
|
7
|
-
headers: HeaderObject[];
|
|
8
|
-
isSelected: (rowIndex: number, columnIndex: number) => boolean;
|
|
9
|
-
isTopLeftCell: (rowIndex: number, columnIndex: number) => boolean;
|
|
8
|
+
interface TableBodyProps {
|
|
10
9
|
getBorderClass: (rowIndex: number, columnIndex: number) => string;
|
|
11
10
|
handleMouseDown: (rowIndex: number, columnIndex: number) => void;
|
|
12
11
|
handleMouseOver: (rowIndex: number, columnIndex: number) => void;
|
|
13
|
-
|
|
12
|
+
headers: HeaderObject[];
|
|
13
|
+
isSelected: (rowIndex: number, columnIndex: number) => boolean;
|
|
14
|
+
isTopLeftCell: (rowIndex: number, columnIndex: number) => boolean;
|
|
14
15
|
isWidthDragging: boolean;
|
|
16
|
+
shouldDisplayLastColumnCell: boolean;
|
|
17
|
+
shouldPaginate: boolean;
|
|
18
|
+
sortedRows: { [key: string]: any }[];
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
const
|
|
18
|
-
headers,
|
|
19
|
-
isSelected,
|
|
20
|
-
isTopLeftCell,
|
|
21
|
+
const TableBody = ({
|
|
21
22
|
getBorderClass,
|
|
22
23
|
handleMouseDown,
|
|
23
24
|
handleMouseOver,
|
|
24
|
-
|
|
25
|
+
headers,
|
|
26
|
+
isSelected,
|
|
27
|
+
isTopLeftCell,
|
|
25
28
|
isWidthDragging,
|
|
26
|
-
|
|
29
|
+
shouldDisplayLastColumnCell,
|
|
30
|
+
shouldPaginate,
|
|
31
|
+
sortedRows,
|
|
32
|
+
}: TableBodyProps & { shouldDisplayLastColumnCell: boolean }) => {
|
|
27
33
|
return (
|
|
28
34
|
<>
|
|
29
|
-
{sortedRows.map((row, rowIndex) =>
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
{sortedRows.map((row, rowIndex) => {
|
|
36
|
+
return (
|
|
37
|
+
<Fragment key={row.id}>
|
|
38
|
+
<Animate
|
|
39
|
+
allowHorizontalAnimate={shouldPaginate}
|
|
40
|
+
pause={isWidthDragging}
|
|
41
|
+
>
|
|
42
|
+
{headers.map((header, columnIndex) => {
|
|
43
|
+
let content = row[header.accessor];
|
|
44
|
+
|
|
45
|
+
if (header.cellRenderer) {
|
|
46
|
+
content = header.cellRenderer(row);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<TableCell
|
|
51
|
+
borderClass={getBorderClass(rowIndex, columnIndex)}
|
|
52
|
+
colIndex={columnIndex}
|
|
53
|
+
content={content}
|
|
54
|
+
isSelected={isSelected(rowIndex, columnIndex)}
|
|
55
|
+
isTopLeftCell={isTopLeftCell(rowIndex, columnIndex)}
|
|
56
|
+
key={header.accessor}
|
|
57
|
+
onMouseDown={() => handleMouseDown(rowIndex, columnIndex)}
|
|
58
|
+
onMouseOver={() => handleMouseOver(rowIndex, columnIndex)}
|
|
59
|
+
ref={createRef()}
|
|
60
|
+
rowIndex={rowIndex}
|
|
61
|
+
isLastRow={rowIndex === sortedRows.length - 1}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
<TableLastColumnCell
|
|
66
|
+
isLastRow={rowIndex === sortedRows.length - 1}
|
|
67
|
+
ref={createRef()}
|
|
68
|
+
visible={shouldDisplayLastColumnCell}
|
|
69
|
+
/>
|
|
70
|
+
</Animate>
|
|
71
|
+
{rowIndex !== sortedRows.length - 1 && <TableRowSeparator />}
|
|
72
|
+
</Fragment>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
47
75
|
</>
|
|
48
76
|
);
|
|
49
77
|
};
|
|
50
78
|
|
|
51
|
-
export default
|
|
79
|
+
export default TableBody;
|
|
@@ -9,6 +9,7 @@ interface TableCellProps {
|
|
|
9
9
|
borderClass: string;
|
|
10
10
|
onMouseDown: (rowIndex: number, colIndex: number) => void;
|
|
11
11
|
onMouseOver: (rowIndex: number, colIndex: number) => void;
|
|
12
|
+
isLastRow: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const TableCell = forwardRef(
|
|
@@ -22,26 +23,26 @@ const TableCell = forwardRef(
|
|
|
22
23
|
borderClass,
|
|
23
24
|
onMouseDown,
|
|
24
25
|
onMouseOver,
|
|
26
|
+
isLastRow,
|
|
25
27
|
}: TableCellProps,
|
|
26
28
|
ref: LegacyRef<HTMLTableCellElement>
|
|
27
29
|
) => {
|
|
30
|
+
const isOddRow = rowIndex % 2 === 0;
|
|
28
31
|
return (
|
|
29
32
|
<div
|
|
30
33
|
onMouseDown={() => onMouseDown(rowIndex, colIndex)}
|
|
31
34
|
onMouseOver={() => onMouseOver(rowIndex, colIndex)}
|
|
32
35
|
ref={ref}
|
|
36
|
+
className={`st-table-cell ${
|
|
37
|
+
isSelected
|
|
38
|
+
? isTopLeftCell
|
|
39
|
+
? `st-table-cell-selected-first-cell ${borderClass}`
|
|
40
|
+
: `st-table-cell-selected ${borderClass}`
|
|
41
|
+
: ""
|
|
42
|
+
} ${isLastRow ? "st-table-cell-last-row" : ""}
|
|
43
|
+
${isOddRow ? "st-table-cell-odd-row" : ""}`}
|
|
33
44
|
>
|
|
34
|
-
|
|
35
|
-
className={`st-table-cell ${
|
|
36
|
-
isSelected
|
|
37
|
-
? isTopLeftCell
|
|
38
|
-
? `st-table-cell-selected-first-cell ${borderClass}`
|
|
39
|
-
: `st-table-cell-selected ${borderClass}`
|
|
40
|
-
: ""
|
|
41
|
-
}`}
|
|
42
|
-
>
|
|
43
|
-
{content}
|
|
44
|
-
</div>
|
|
45
|
+
{content}
|
|
45
46
|
</div>
|
|
46
47
|
);
|
|
47
48
|
}
|