@ltht-react/table 2.0.3 → 2.0.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.
@@ -0,0 +1,233 @@
1
+ import {
2
+ CSS_RESET,
3
+ TRANSLUCENT_MID_GREY,
4
+ SCROLLBAR,
5
+ TRANSLUCENT_BRIGHT_BLUE,
6
+ BTN_COLOURS,
7
+ TABLE_COLOURS,
8
+ StickyTableData,
9
+ StickyTableHead,
10
+ getZIndex,
11
+ } from '@ltht-react/styles'
12
+ import styled from '@emotion/styled'
13
+ import Icon, { IconButton } from '@ltht-react/icon'
14
+ import { Axis } from '@ltht-react/types'
15
+
16
+ const ScrollableContainer = styled.div<IScrollableContainer>`
17
+ ${CSS_RESET};
18
+ background-color: white;
19
+ ${({ tableHeaderAxis, maxWidth, maxHeight }) => `
20
+ display: ${tableHeaderAxis === 'y' ? 'inline-flex' : 'inline-block'};
21
+ max-width: ${maxWidth ?? '100%'};
22
+ max-height: ${maxHeight ?? '100%'};
23
+ `}
24
+ border-radius: 6px;
25
+ overflow: auto;
26
+ &::-webkit-scrollbar {
27
+ width: 7px;
28
+ height: 7px;
29
+ border: 0;
30
+ }
31
+ &::-webkit-scrollbar-thumb {
32
+ background: ${SCROLLBAR};
33
+ border-radius: 10px;
34
+ }
35
+ `
36
+ const StyledTable = styled.table`
37
+ border-spacing: 0px;
38
+ border-radius: 6px;
39
+ `
40
+ const StyledTableHeader = styled.th<IStyledTableCell>`
41
+ background-color: ${TABLE_COLOURS.HEADER};
42
+ border: thin solid ${TABLE_COLOURS.BORDER};
43
+ font-weight: bold;
44
+ padding: 0.5rem;
45
+
46
+ ${({ stickyWidth }) =>
47
+ stickyWidth !== undefined &&
48
+ `
49
+ position: sticky !important;
50
+ left: ${stickyWidth}px;
51
+ top: 0;
52
+ z-index: ${getZIndex(StickyTableData)};`}
53
+ `
54
+ const StyledTableData = styled.td<IStyledTableCell>`
55
+ border: thin solid ${TABLE_COLOURS.BORDER};
56
+ white-space: normal;
57
+ text-align: center;
58
+ padding: 0.15rem;
59
+
60
+ &:first-of-type {
61
+ background-color: ${TABLE_COLOURS.HEADER} !important;
62
+ font-weight: bold;
63
+ }
64
+
65
+ ${({ stickyWidth }) =>
66
+ stickyWidth !== undefined &&
67
+ `
68
+ background-color: ${TABLE_COLOURS.STRIPE_LIGHT};
69
+ position: sticky !important;
70
+ left: ${stickyWidth}px;
71
+ top: 0;
72
+ z-index: ${getZIndex(StickyTableData)};`}
73
+
74
+ ${({ tableHeaderAxis }) =>
75
+ tableHeaderAxis === 'y' &&
76
+ `
77
+ &:nth-of-type(even) {
78
+ background-color: ${TABLE_COLOURS.STRIPE_DARK};
79
+ }`}
80
+ `
81
+ const StyledTableRow = styled.tr<IStyledTableCell>`
82
+ ${({ tableHeaderAxis }) =>
83
+ tableHeaderAxis === 'x' &&
84
+ `
85
+ &:nth-of-type(odd) {
86
+ background-color: ${TABLE_COLOURS.STRIPE_DARK};
87
+ }`}
88
+ `
89
+ const PaginationContainer = styled.div`
90
+ ${CSS_RESET};
91
+ margin-top: 5px;
92
+ display: block;
93
+ `
94
+ const StyledPaginationPageInput = styled.input`
95
+ ${CSS_RESET};
96
+ width: 50px;
97
+ border: 1px solid gray;
98
+ `
99
+ const StyledPaginationPageSelect = styled.select`
100
+ ${CSS_RESET};
101
+ width: 45px;
102
+ display: inline-block;
103
+ font-size: 0.9rem;
104
+ border: 1px solid gray;
105
+ `
106
+ const StyledHideOnMobile = styled.span`
107
+ font-size: 1.1em;
108
+ padding: 2px;
109
+ @media (max-width: 320px) {
110
+ display: none;
111
+ }
112
+ `
113
+ const StyledPaginationButtonDiv = styled.div`
114
+ float: right;
115
+ display: flex;
116
+ justify-content: space-between;
117
+ align-items: stretch;
118
+ `
119
+ const StyledPageCountDiv = styled.div`
120
+ margin-right: 5px;
121
+ margin-left: 5px;
122
+ display: flex;
123
+ font-size: 1.1em;
124
+ padding: 1px;
125
+ `
126
+ const StyledStandardButton = styled(IconButton)`
127
+ color: ${BTN_COLOURS.STANDARD.TEXT};
128
+ background-color: ${BTN_COLOURS.STANDARD.VALUE};
129
+ padding: 2px 5px;
130
+ border-radius: 5px;
131
+ &:hover {
132
+ background-color: ${BTN_COLOURS.STANDARD.HOVER};
133
+ }
134
+
135
+ &:disabled {
136
+ background-color: ${BTN_COLOURS.STANDARD.DISABLED};
137
+ }
138
+ `
139
+ const StyledPaginationButton = styled(IconButton)`
140
+ padding: 2px 5px;
141
+ background-color: ${TRANSLUCENT_BRIGHT_BLUE};
142
+ color: black;
143
+ border: 1px solid ${TRANSLUCENT_MID_GREY};
144
+ margin: 0 2.5px;
145
+ border-radius: 3px;
146
+
147
+ &:disabled {
148
+ background-color: inherit;
149
+ color: gray;
150
+ border-color: ${TRANSLUCENT_MID_GREY};
151
+ pointer-events: none;
152
+ }
153
+ `
154
+ const StyledSpinnerIcon = styled(Icon)`
155
+ margin: 3px 0;
156
+ font-size: 1.1em;
157
+ padding: 1.5px;
158
+ `
159
+ const StyledNextPageButtonContainer = styled.div<IStyledNextPageButtonContainer>`
160
+ display: ${({ hidden, elementPosition }) => {
161
+ if (!hidden) {
162
+ return elementPosition === 'bottom' ? 'flex' : 'inline-flex'
163
+ }
164
+
165
+ return 'none'
166
+ }};
167
+ justify-content: center;
168
+ cursor: pointer;
169
+ align-items: center;
170
+ padding: 10px;
171
+ font-size: 1.3em;
172
+ border: solid 2px #eeeeee;
173
+ border-left: solid 1px #eeeeee;
174
+
175
+ :hover {
176
+ background-color: #f3f6f6;
177
+ }
178
+ `
179
+
180
+ const StyledSpinnerContainer = styled.div<IStyledNextPageButtonContainer>`
181
+ display: ${({ hidden, elementPosition }) => {
182
+ if (!hidden) {
183
+ return elementPosition === 'bottom' ? 'flex' : 'inline-flex'
184
+ }
185
+
186
+ return 'none'
187
+ }};
188
+ justify-content: center;
189
+ cursor: pointer;
190
+ align-items: center;
191
+ padding: 5px;
192
+ `
193
+ const StyledTHead = styled.thead`
194
+ position: sticky;
195
+ left: 0;
196
+ top: 0;
197
+ z-index: ${getZIndex(StickyTableHead)};
198
+ `
199
+
200
+ interface IStyledTableCell {
201
+ stickyWidth?: number
202
+ tableHeaderAxis?: string
203
+ }
204
+
205
+ interface IStyledNextPageButtonContainer {
206
+ elementPosition: 'right' | 'bottom'
207
+ }
208
+
209
+ interface IScrollableContainer {
210
+ tableHeaderAxis: Axis
211
+ maxHeight?: string
212
+ maxWidth?: string
213
+ }
214
+
215
+ export {
216
+ StyledTable,
217
+ StyledTableHeader,
218
+ StyledTableRow,
219
+ StyledTableData,
220
+ PaginationContainer,
221
+ StyledPaginationPageInput,
222
+ StyledPaginationPageSelect,
223
+ StyledHideOnMobile,
224
+ StyledPaginationButton,
225
+ ScrollableContainer,
226
+ StyledPaginationButtonDiv,
227
+ StyledPageCountDiv,
228
+ StyledStandardButton,
229
+ StyledSpinnerIcon,
230
+ StyledNextPageButtonContainer,
231
+ StyledTHead,
232
+ StyledSpinnerContainer,
233
+ }
@@ -0,0 +1,161 @@
1
+ import { FC, useEffect, useMemo, useRef, useState } from 'react'
2
+ import {
3
+ getCoreRowModel,
4
+ useReactTable,
5
+ getExpandedRowModel,
6
+ getSortedRowModel,
7
+ ExpandedState,
8
+ SortingState,
9
+ PaginationState,
10
+ ColumnDef,
11
+ SortingFn,
12
+ } from '@tanstack/react-table'
13
+ import { Axis } from '@ltht-react/types'
14
+ import { ScrollableContainer } from './table-styled-components'
15
+ import useScrollRef from './useScrollRef'
16
+ import { handleDataUpdate, handleDataUpdateForManualPagination, handleScrollEvent } from './table-methods'
17
+ import TableComponent, { TableNavigationButton, TableSpinner } from './table-component'
18
+ import { CellProps } from './table-cell'
19
+
20
+ const Table: FC<IProps> = ({
21
+ tableData,
22
+ staticColumns = 0,
23
+ currentPage = 1,
24
+ pageSize: pageSizeParam = 10,
25
+ headerAxis = 'x',
26
+ manualPagination = false,
27
+ getCanNextPage = () => false,
28
+ nextPage = () => null,
29
+ isFetching = false,
30
+ keepPreviousData = false,
31
+ maxHeight,
32
+ maxWidth,
33
+ enableSorting = true,
34
+ sortingFunctions = undefined,
35
+ }) => {
36
+ const scrollableDivElement = useRef(null)
37
+ const scrollState = useScrollRef(scrollableDivElement)
38
+
39
+ const [expanded, setExpanded] = useState<ExpandedState>({})
40
+ const [sorting, setSorting] = useState<SortingState>([])
41
+ const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
42
+ pageIndex: currentPage - 1,
43
+ pageSize: pageSizeParam,
44
+ })
45
+
46
+ const pagination = useMemo(() => ({ pageIndex, pageSize }), [pageIndex, pageSize])
47
+ const [data, setData] = useState<DataEntity[]>([])
48
+ const [columns, setColumns] = useState<ColumnDef<DataEntity>[]>([])
49
+ const [pageCount, setPageCount] = useState<number>(-1)
50
+
51
+ useEffect(() => {
52
+ if (!manualPagination) {
53
+ handleDataUpdate(tableData, pageIndex, pageSize, headerAxis, setColumns, setData, setPageCount)
54
+ }
55
+ }, [pageIndex, pageSize, headerAxis, tableData, manualPagination])
56
+
57
+ useEffect(() => {
58
+ if (manualPagination) {
59
+ handleDataUpdateForManualPagination(tableData, headerAxis, keepPreviousData, setColumns, setData)
60
+ }
61
+ }, [headerAxis, tableData, manualPagination, keepPreviousData])
62
+
63
+ const table = useReactTable({
64
+ data,
65
+ columns,
66
+ ...(!manualPagination ? { pageCount } : {}),
67
+ state: {
68
+ expanded,
69
+ sorting,
70
+ ...(!manualPagination ? { pagination } : {}),
71
+ },
72
+ manualPagination: true,
73
+ onExpandedChange: setExpanded,
74
+ onSortingChange: setSorting,
75
+ enableSorting,
76
+ sortingFns: sortingFunctions,
77
+ getSubRows: (row) => row.subRows,
78
+ getCoreRowModel: getCoreRowModel(),
79
+ getExpandedRowModel: getExpandedRowModel(),
80
+ getSortedRowModel: getSortedRowModel(),
81
+ ...(!manualPagination ? { onPaginationChange: setPagination } : {}),
82
+ })
83
+
84
+ useEffect(() => {
85
+ if (!scrollState) {
86
+ return
87
+ }
88
+
89
+ if (!manualPagination) {
90
+ handleScrollEvent(table, headerAxis, scrollState)
91
+ }
92
+ }, [scrollState, table, headerAxis, manualPagination])
93
+
94
+ const getNextPage = () => {
95
+ if (manualPagination) {
96
+ nextPage()
97
+ } else {
98
+ table.nextPage()
99
+ }
100
+ }
101
+
102
+ return (
103
+ <ScrollableContainer ref={scrollableDivElement} tableHeaderAxis={headerAxis} {...{ maxHeight, maxWidth }}>
104
+ <TableComponent table={table} staticColumns={staticColumns} headerAxis={headerAxis} />
105
+ {manualPagination ? (
106
+ <TableSpinner position={headerAxis === 'x' ? 'bottom' : 'right'} hidden={!isFetching} />
107
+ ) : null}
108
+ <TableNavigationButton
109
+ position={headerAxis === 'x' ? 'bottom' : 'right'}
110
+ hidden={isFetching || (manualPagination ? !getCanNextPage() : !table.getCanNextPage())}
111
+ clickHandler={getNextPage}
112
+ />
113
+ </ScrollableContainer>
114
+ )
115
+ }
116
+
117
+ interface IProps extends ITableConfig, IPaginationProps, ITableDimensionProps {
118
+ tableData: TableData
119
+ }
120
+
121
+ export interface ITableConfig {
122
+ staticColumns?: 0 | 1 | 2
123
+ headerAxis?: Axis
124
+ enableSorting?: boolean
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ sortingFunctions?: Record<string, SortingFn<any>> | undefined
127
+ }
128
+
129
+ export interface IPaginationProps {
130
+ currentPage?: number
131
+ pageSize?: number
132
+ manualPagination?: boolean
133
+ nextPage?: () => void
134
+ getCanNextPage?: () => boolean
135
+ isFetching?: boolean
136
+ keepPreviousData?: boolean
137
+ }
138
+
139
+ export interface ITableDimensionProps {
140
+ maxWidth?: string
141
+ maxHeight?: string
142
+ }
143
+
144
+ type DataEntity = Record<string, CellProps | DataEntity[]> & {
145
+ subRows?: DataEntity[]
146
+ }
147
+
148
+ interface Header {
149
+ type: 'accessor' | 'group' | 'display'
150
+ id: string
151
+ cellProps: CellProps
152
+ subHeaders?: Header[]
153
+ }
154
+
155
+ interface TableData {
156
+ headers: Header[]
157
+ rows: DataEntity[]
158
+ }
159
+
160
+ export default Table
161
+ export { DataEntity, CellProps, Header, TableData }
@@ -0,0 +1,37 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ const useDimensionsRef = (elementRef: React.RefObject<HTMLElement>, parentElementRef: React.RefObject<HTMLElement>) => {
4
+ const [dimensions, setDimensions] = useState({
5
+ width: 0,
6
+ height: 0,
7
+ })
8
+
9
+ useEffect(() => {
10
+ const getDimensions = () => ({
11
+ width: (elementRef && elementRef.current && elementRef.current.offsetWidth) || 0,
12
+ height: (elementRef && elementRef.current && elementRef.current.offsetHeight) || 0,
13
+ })
14
+
15
+ const handleResize = () => {
16
+ setDimensions(getDimensions())
17
+ }
18
+
19
+ if (elementRef.current) {
20
+ setDimensions(getDimensions())
21
+ }
22
+
23
+ const parentElementResizeObserver = new ResizeObserver((_e: ResizeObserverEntry[]) => handleResize())
24
+
25
+ if (parentElementRef?.current) {
26
+ parentElementResizeObserver.observe(parentElementRef.current)
27
+ }
28
+
29
+ return () => {
30
+ parentElementResizeObserver?.disconnect()
31
+ }
32
+ }, [elementRef, parentElementRef])
33
+
34
+ return dimensions
35
+ }
36
+
37
+ export default useDimensionsRef
@@ -0,0 +1,36 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ const useScrollRef = (elementRef: React.RefObject<HTMLElement>) => {
4
+ const [scrollState, setScrollState] = useState<ScrollState>()
5
+ const element = elementRef.current
6
+
7
+ useEffect(() => {
8
+ const getScrollState = (): ScrollState => ({
9
+ scrollWidth: element?.scrollWidth ?? 0,
10
+ scrollHeight: element?.scrollHeight ?? 0,
11
+ currentXScroll: (element?.scrollLeft ?? 0) + (element?.clientWidth ?? 0),
12
+ currentYScroll: (element?.scrollTop ?? 0) + (element?.clientHeight ?? 0),
13
+ })
14
+
15
+ const handleScroll = () => {
16
+ setScrollState(getScrollState())
17
+ }
18
+
19
+ element?.addEventListener('scroll', handleScroll)
20
+
21
+ return () => {
22
+ element?.removeEventListener('scroll', handleScroll)
23
+ }
24
+ }, [element])
25
+
26
+ return scrollState
27
+ }
28
+
29
+ export interface ScrollState {
30
+ scrollWidth: number
31
+ scrollHeight: number
32
+ currentXScroll: number
33
+ currentYScroll: number
34
+ }
35
+
36
+ export default useScrollRef
@@ -0,0 +1,33 @@
1
+ import Table, { IPaginationProps, ITableDimensionProps, ITableConfig, TableData } from '../molecules/table'
2
+
3
+ const GenericTable = <TColumn, TRow>({
4
+ columnData,
5
+ rowData,
6
+ mapToTableData,
7
+ headerAxis = 'x',
8
+ pageSize = 10,
9
+ currentPage = 1,
10
+ keepPreviousData = true,
11
+ ...props
12
+ }: IProps<TColumn, TRow>) => {
13
+ const tableData = mapToTableData(columnData, rowData)
14
+
15
+ return (
16
+ <Table
17
+ tableData={tableData}
18
+ headerAxis={headerAxis}
19
+ pageSize={pageSize}
20
+ currentPage={currentPage}
21
+ keepPreviousData={keepPreviousData}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+
27
+ interface IProps<TColumn, TRow> extends ITableConfig, IPaginationProps, ITableDimensionProps {
28
+ columnData: TColumn
29
+ rowData: TRow
30
+ mapToTableData: (colData: TColumn, rowData: TRow) => TableData
31
+ }
32
+
33
+ export default GenericTable