@majoo-ui/react 2.1.1 → 2.1.3

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.
@@ -10,6 +10,10 @@ import { styled } from '../../config/stitches.config.esm.js';
10
10
  import Box from '../../layout/box/Box.esm.js';
11
11
  import Flex from '../../layout/flex/Flex.esm.js';
12
12
  import { TableContent, TableHead, TableHeaderCellContainer, TableHeadCellContent, TableBody, addOnCellStyle, TableFoot, TableFootRow, TableFootCell } from './Table.esm.js';
13
+ import { resolveCellCss } from './utils/columnStyles.esm.js';
14
+ import { footerValueOf } from './utils/footerValue.esm.js';
15
+ import { HeaderTooltip } from './utils/HeaderTooltip.esm.js';
16
+ import { isHeaderSortable, getAriaSort } from './utils/headerSort.esm.js';
13
17
  import Text from '../text/Text.esm.js';
14
18
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
15
19
  import { ContextMenuTrigger } from '../contextMenu/ContextMenu.esm.js';
@@ -80,7 +84,11 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
80
84
  hideHeader,
81
85
  decoration,
82
86
  rowVirtualizer,
83
- virtualized
87
+ virtualized,
88
+ footerData,
89
+ footerColumnIds,
90
+ headerRowSpan,
91
+ keyId = 'id'
84
92
  }, ref) => {
85
93
  var _a, _b, _c, _d;
86
94
  const {
@@ -178,26 +186,29 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
178
186
  children: jsxs(TableContent, Object.assign({}, getTableProps(), {
179
187
  children: [jsx(TableHead, {
180
188
  hiddenHeader: hideHeader,
181
- children: headerGroups.map(headerGroup => jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
189
+ children: (headerRowSpan ? headerGroups.slice(-1) : headerGroups).map(headerGroup => jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
182
190
  children: headerGroup.headers.filter((column, index) => filterMobileHeader(column, index)).map(column => {
183
191
  const headerRender = column.render('Header');
184
- return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(!column.unsortable ? column.getSortByToggleProps({
192
+ return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(isHeaderSortable(column) ? column.getSortByToggleProps({
185
193
  title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
186
194
  }) : {
187
195
  onClick: column.onToggle
188
196
  }), {
189
- children: typeof headerRender === 'string' ? jsx(TableHeaderCellContainer, {
197
+ "aria-sort": getAriaSort(column),
198
+ children: typeof headerRender === 'string' ? jsxs(TableHeaderCellContainer, {
190
199
  isMobile: true,
191
200
  isSortedDesc: column.isSortedDesc,
192
- unsortable: column.unsortable,
201
+ unsortable: !isHeaderSortable(column),
193
202
  isToggleable: !!column.onToggle,
194
203
  isToggled: column.isToggled,
195
- children: jsx(TableHeadCellContent, {
204
+ children: [jsx(TableHeadCellContent, {
196
205
  isMobile: true,
197
206
  heading: "headerTable",
198
207
  color: "secondary",
199
208
  children: headerRender.toUpperCase()
200
- })
209
+ }), column.headerTooltip && jsx(HeaderTooltip, {
210
+ label: column.headerTooltip
211
+ })]
201
212
  }) : headerRender
202
213
  }));
203
214
  })
@@ -281,6 +292,7 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
281
292
  children: cell.render('Cell')
282
293
  });
283
294
  return jsx(TableCell, Object.assign({}, cell.getCellProps(addOnCellStyle(cell.column.id)), {
295
+ css: resolveCellCss(cell),
284
296
  children: cell.column.accessor ? jsxs(Flex, {
285
297
  justify: "between",
286
298
  align: "center",
@@ -341,12 +353,12 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
341
353
  align: "center",
342
354
  justify: "between",
343
355
  gap: 5,
344
- css: {
356
+ css: Object.assign({
345
357
  backgroundColor: '$bgGray',
346
358
  padding: `$spacing-04 $spacing-05 ${'selection' in row.values ? '$spacing-04 $spacing-07' : ''}`,
347
359
  borderBottom: '1pt solid $bgBorder',
348
360
  alignItems: cell.column.accessor ? getAlign(cellAlign) : 'center'
349
- },
361
+ }, resolveCellCss(cell)),
350
362
  children: [jsx(Heading, {
351
363
  css: {
352
364
  padding: '3px 0px'
@@ -383,6 +395,48 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
383
395
  }) : null]
384
396
  }, row.id);
385
397
  })
398
+ }), footerData === null || footerData === void 0 ? void 0 : footerData.map((footerRow, footerRowIndex) => {
399
+ var _a;
400
+ return jsx(TableRow, {
401
+ children: jsx(TableCell, {
402
+ colSpan: visibleColumns.length,
403
+ css: {
404
+ backgroundColor: '$bgGray'
405
+ },
406
+ children: jsx(Flex, {
407
+ direction: "column",
408
+ children: visibleColumns.map((column, index) => {
409
+ // A column without a Footer of its own would only add an empty line.
410
+ if (column.isSubRow || !(footerColumnIds === null || footerColumnIds === void 0 ? void 0 : footerColumnIds.has(column.id))) return null;
411
+ const isTitle = getIsMobileHeader() ? column.isMobileHeader : index === indexDefaultHeader;
412
+ return jsxs(Flex, {
413
+ align: "center",
414
+ justify: "between",
415
+ gap: 5,
416
+ css: Object.assign({
417
+ padding: '$spacing-04 $spacing-05',
418
+ borderBottom: '1pt solid $bgBorder'
419
+ }, column.footerCss),
420
+ children: [!isTitle && jsx(Heading, {
421
+ css: {
422
+ padding: '3px 0px'
423
+ },
424
+ heading: "headerTable",
425
+ color: "secondary",
426
+ children: typeof column.Header === 'string' ? column.Header.toUpperCase() : column.render('Header')
427
+ }), jsx(Paragraph, {
428
+ paragraph: "shortContentBold",
429
+ children: column.render('Footer', {
430
+ footerRow,
431
+ footerRowIndex,
432
+ value: footerValueOf(column, footerRow, footerRowIndex)
433
+ })
434
+ })]
435
+ }, column.id);
436
+ })
437
+ })
438
+ })
439
+ }, (_a = footerRow[keyId]) !== null && _a !== void 0 ? _a : `footer-${footerRowIndex}`);
386
440
  }), (_d = decoration === null || decoration === void 0 ? void 0 : decoration.footerRender) === null || _d === void 0 ? void 0 : _d.call(decoration), isInfiniteScroll && paddingBottom > 0 && jsx("tr", {
387
441
  children: jsx("td", {
388
442
  style: {
@@ -4,14 +4,26 @@ import 'core-js/modules/es.iterator.constructor.js';
4
4
  import 'core-js/modules/es.iterator.filter.js';
5
5
  import 'core-js/modules/es.iterator.for-each.js';
6
6
  import 'core-js/modules/es.iterator.map.js';
7
+ import 'core-js/modules/es.iterator.some.js';
7
8
  import 'core-js/modules/es.json.parse.js';
8
9
  import 'core-js/modules/es.json.stringify.js';
10
+ import 'core-js/modules/es.map.get-or-insert.js';
11
+ import 'core-js/modules/es.map.get-or-insert-computed.js';
12
+ import 'core-js/modules/es.set.difference.v2.js';
13
+ import 'core-js/modules/es.set.intersection.v2.js';
14
+ import 'core-js/modules/es.set.is-disjoint-from.v2.js';
15
+ import 'core-js/modules/es.set.is-subset-of.v2.js';
16
+ import 'core-js/modules/es.set.is-superset-of.v2.js';
17
+ import 'core-js/modules/es.set.symmetric-difference.v2.js';
18
+ import 'core-js/modules/es.set.union.v2.js';
19
+ import 'core-js/modules/es.weak-map.get-or-insert.js';
20
+ import 'core-js/modules/es.weak-map.get-or-insert-computed.js';
9
21
  import { __rest } from 'tslib';
10
22
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
23
  import * as React from 'react';
12
24
  import { useState, useCallback, useMemo, useRef, useLayoutEffect, useEffect } from 'react';
13
25
  import InfiniteScroll from 'react-infinite-scroller';
14
- import { useTable, useSortBy, useExpanded, usePagination, useRowSelect, useColumnOrder, useMountedLayoutEffect } from 'react-table';
26
+ import { useTable, useSortBy, useExpanded, usePagination, useRowSelect, useColumnOrder, useMountedLayoutEffect, emptyRenderer } from 'react-table';
15
27
  import { SortableElement, SortableContainer, arrayMove } from 'react-sortable-hoc';
16
28
  import Text from '../text/Text.esm.js';
17
29
  import Paragraph from '../paragraph/Paragraph.esm.js';
@@ -28,7 +40,11 @@ import useMediaQuery from '../../utils/useMediaQuery.esm.js';
28
40
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
29
41
  import { ContextMenuTrigger, ContextMenu, ContextMenuItem, ContextMenuSeparator } from '../contextMenu/ContextMenu.esm.js';
30
42
  import MobileTable from './MobileTable.esm.js';
31
- import { useSticky } from 'react-table-sticky';
43
+ import { alignCss, headerAlignCss, resolveCellCss } from './utils/columnStyles.esm.js';
44
+ import { footerValueOf } from './utils/footerValue.esm.js';
45
+ import { HeaderTooltip } from './utils/HeaderTooltip.esm.js';
46
+ import { isHeaderSortable, getAriaSort } from './utils/headerSort.esm.js';
47
+ import { useSticky, getStickyValue, getMarginRight } from 'react-table-sticky';
32
48
  import { useTranslation } from 'react-i18next';
33
49
  import { useVirtual } from 'react-virtual';
34
50
  import { PlusPrimitive } from '@majoo-ui/icons';
@@ -274,6 +290,10 @@ const TableRow = styled('tr', {
274
290
  borderBottom: '1pt solid $bgBorder',
275
291
  '&:hover': {
276
292
  backgroundColor: '$bgGray',
293
+ // Body cells paint a background of their own (white, or a column's cellCss) over the row's.
294
+ '& > td': {
295
+ backgroundColor: '$bgGray'
296
+ },
277
297
  '& *[data-sticky-td="true"]': {
278
298
  backgroundColor: '$bgGray'
279
299
  }
@@ -380,6 +400,108 @@ const stickyShadowStyling = (column, tableScrollPosition, scrollAreaWidth) => {
380
400
  }
381
401
  return {};
382
402
  };
403
+ /**
404
+ * react-table-sticky only decorates header and body cells, so footer cells work out the same
405
+ * sticky offset themselves.
406
+ */
407
+ const getStickyFooterProps = (column, flatHeaders) => {
408
+ const sticky = getStickyValue(column);
409
+ if (!sticky) return {};
410
+ const margin = sticky === 'left' ? column.totalLeft : getMarginRight(column.id, flatHeaders.filter(header => header.depth === column.depth));
411
+ return {
412
+ style: {
413
+ position: 'sticky',
414
+ zIndex: 3,
415
+ [sticky]: `${margin}px`
416
+ },
417
+ 'data-sticky-td': true
418
+ };
419
+ };
420
+ /**
421
+ * react-table-sticky offsets a sticky column by react-table's `width` of the columns beside it, which
422
+ * stays at its 150px default unless set, so a numeric `colWidth` is passed on as that width.
423
+ */
424
+ const withStickyWidths = columns => {
425
+ const needsWidth = column => {
426
+ var _a;
427
+ return typeof column.colWidth === 'number' && column.width === undefined || ((_a = column.columns) === null || _a === void 0 ? void 0 : _a.some(needsWidth));
428
+ };
429
+ if (!columns.some(needsWidth)) return columns;
430
+ return columns.map(column => Object.assign(Object.assign(Object.assign({}, column), typeof column.colWidth === 'number' && column.width === undefined && {
431
+ width: column.colWidth
432
+ }), column.columns && {
433
+ columns: withStickyWidths(column.columns)
434
+ }));
435
+ };
436
+ const isMissingRowId = value => value === undefined || value === null || value === '';
437
+ /**
438
+ * Cells per header row. With `spanRows`, a column outside any group is drawn once across every
439
+ * header row instead of under the empty placeholder cells react-table puts above it.
440
+ */
441
+ const buildHeaderRows = (headerGroups, spanRows) => {
442
+ if (!spanRows) {
443
+ return headerGroups.map(headerGroup => headerGroup.headers.map(column => ({
444
+ column,
445
+ rowSpan: 1
446
+ })));
447
+ }
448
+ const coveredIds = new Set();
449
+ headerGroups.forEach(headerGroup => headerGroup.headers.forEach(header => {
450
+ if (header.placeholderOf) coveredIds.add(header.placeholderOf.id);
451
+ }));
452
+ return headerGroups.map(headerGroup => headerGroup.headers.filter(header => !coveredIds.has(header.id)).map(header => {
453
+ let column = header;
454
+ let rowSpan = 1;
455
+ while (column.placeholderOf) {
456
+ column = column.placeholderOf;
457
+ rowSpan += 1;
458
+ }
459
+ return {
460
+ column,
461
+ rowSpan
462
+ };
463
+ }));
464
+ };
465
+ /**
466
+ * rowSpan of the body cells whose column sets `rowSpan`, looked up by column id then row id. The
467
+ * first row of a run of equal keys spans the run; the rows it covers get 0 and render no cell. A
468
+ * run never crosses an expanded row, whose sub rows sit in between. `anchors` maps every row of a
469
+ * run to its first row, the one whose cell a hover anywhere in the run highlights.
470
+ */
471
+ const getCellRowSpans = (rows, spanColumns) => {
472
+ const spans = new Map();
473
+ const anchors = new Map();
474
+ spanColumns.forEach(column => {
475
+ // Nested by column: joining ids into one string would let row `0` + column `1.x` collide with
476
+ // sub row `0.1` + column `x`.
477
+ const columnSpans = new Map();
478
+ const columnAnchors = new Map();
479
+ spans.set(column.id, columnSpans);
480
+ anchors.set(column.id, columnAnchors);
481
+ let anchorId = null;
482
+ let anchorKey;
483
+ rows.forEach((row, index) => {
484
+ var _a;
485
+ if (!row) return;
486
+ const key = typeof column.rowSpan === 'function' ? column.rowSpan(row) : row.values[column.id];
487
+ const hasKey = key !== null && key !== undefined;
488
+ if (anchorId !== null && hasKey && key === anchorKey && !((_a = rows[index - 1]) === null || _a === void 0 ? void 0 : _a.isExpanded)) {
489
+ columnSpans.set(anchorId, columnSpans.get(anchorId) + 1);
490
+ columnSpans.set(row.id, 0);
491
+ columnAnchors.set(row.id, anchorId);
492
+ return;
493
+ }
494
+ anchorId = hasKey ? row.id : null;
495
+ anchorKey = key;
496
+ columnSpans.set(row.id, 1);
497
+ columnAnchors.set(row.id, row.id);
498
+ });
499
+ });
500
+ return {
501
+ spans,
502
+ anchors
503
+ };
504
+ };
383
505
  const formattingData = (rawData, rowGroup) => {
384
506
  if ((rowGroup === null || rowGroup === void 0 ? void 0 : rowGroup.groupKey) && !(rowGroup === null || rowGroup === void 0 ? void 0 : rowGroup.isManuallyFormatted)) {
385
507
  const {
@@ -624,7 +746,11 @@ function Table({
624
746
  draggable = false,
625
747
  onDragEnd,
626
748
  isExpandableTree,
627
- subRows = 'subRows'
749
+ subRows = 'subRows',
750
+ footerData,
751
+ initialSortBy,
752
+ disableSortRemove = false,
753
+ headerRowSpan = false
628
754
  }) {
629
755
  var _a, _b, _c, _d;
630
756
  const [openMenu, setOpen] = useState(false);
@@ -639,6 +765,7 @@ function Table({
639
765
  }, [filterHeader]);
640
766
  const [filteredHeader, setFilteredHeader] = useState([]);
641
767
  const [hasDarkParent, setHasDarkParent] = useState(false);
768
+ const [hoveredRowId, setHoveredRowId] = useState(null);
642
769
  const {
643
770
  t
644
771
  } = useTranslation('MajooUI/table');
@@ -661,18 +788,52 @@ function Table({
661
788
  expanded: controlledExpanded
662
789
  });
663
790
  }
791
+ if (initialSortBy) {
792
+ Object.assign(initialState, {
793
+ sortBy: initialSortBy
794
+ });
795
+ }
796
+ // Otherwise react-table's default of 10 rows shows until the pageSize effect below runs.
797
+ const initialPageSize = withoutLimit ? totalData : rowLimit;
798
+ if (typeof initialPageSize === 'number') {
799
+ Object.assign(initialState, {
800
+ pageSize: initialPageSize
801
+ });
802
+ }
664
803
  return Object.keys(initialState).length > 0 ? initialState : undefined;
665
804
  };
666
805
  const records = useMemo(() => formattingData(rawData, rowGroup), [rawData, rowGroup]);
806
+ // A row without a `keyId` is identified by its JSON; identical rows would share that id, so every
807
+ // repeat gets an occurrence suffix.
808
+ const fallbackRowIds = useMemo(() => {
809
+ const ids = new WeakMap();
810
+ const occurrences = new Map();
811
+ const visit = rowList => {
812
+ if (!Array.isArray(rowList)) return;
813
+ rowList.forEach(row => {
814
+ var _a;
815
+ if (!row || typeof row !== 'object') return;
816
+ if (isMissingRowId(row[keyId])) {
817
+ const json = JSON.stringify(row);
818
+ const count = (_a = occurrences.get(json)) !== null && _a !== void 0 ? _a : 0;
819
+ occurrences.set(json, count + 1);
820
+ ids.set(row, count ? `${json}#${count}` : json);
821
+ }
822
+ visit(row[subRows]);
823
+ });
824
+ };
825
+ visit(records);
826
+ return ids;
827
+ }, [records, keyId, subRows]);
667
828
  const filteredColumns = useMemo(() => {
668
829
  if (draggable) {
669
- return [rowDraggableColumn,
830
+ return withStickyWidths([rowDraggableColumn,
670
831
  // remove all action column
671
832
  ...columns.filter(item => !['menu', 'expander', 'multiple-expander', 'selection'].includes(item.id))].map(col => Object.assign(Object.assign({}, col), {
672
833
  unsortable: false
673
- }));
834
+ })));
674
835
  }
675
- return columns;
836
+ return withStickyWidths(columns);
676
837
  }, [draggable, columns]);
677
838
  const instance = useTable({
678
839
  columns: filteredColumns,
@@ -686,17 +847,22 @@ function Table({
686
847
  defaultCanSort: true,
687
848
  pageCount: totalPage || Math.ceil(totalData / limit),
688
849
  autoResetSortBy: false,
850
+ disableSortRemove,
689
851
  autoResetExpanded,
690
852
  onExpandedChange,
691
853
  autoResetPage: false,
692
854
  autoResetSelectedRows: false,
693
- getRowId: showDuplicatedData ? undefined : row => row[keyId] || JSON.stringify(row),
855
+ getRowId: showDuplicatedData ? undefined : row => {
856
+ var _a;
857
+ return isMissingRowId(row[keyId]) ? (_a = fallbackRowIds.get(row)) !== null && _a !== void 0 ? _a : JSON.stringify(row) : row[keyId];
858
+ },
694
859
  getSubRows: row => row[subRows] || []
695
860
  }, useSortBy, useExpanded, usePagination, useRowSelect, useSticky, useColumnOrder);
696
861
  const {
697
862
  getTableProps,
698
863
  getTableBodyProps,
699
864
  headerGroups,
865
+ flatHeaders,
700
866
  visibleColumns,
701
867
  prepareRow,
702
868
  rows,
@@ -892,7 +1058,7 @@ function Table({
892
1058
  return jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
893
1059
  children: visibleHeaders.map((column, index) => {
894
1060
  const headerRender = column.render('Header');
895
- return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, !column.unsortable ? column.getSortByToggleProps({
1061
+ return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, isHeaderSortable(column) ? column.getSortByToggleProps({
896
1062
  title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
897
1063
  }) : {
898
1064
  onClick: column.onToggle
@@ -906,7 +1072,7 @@ function Table({
906
1072
  css: Object.assign(Object.assign(Object.assign({}, stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), column.headerCss),
907
1073
  children: typeof headerRender === 'string' ? jsx(TableHeaderCellContainer, {
908
1074
  isSortedDesc: column.isSortedDesc,
909
- unsortable: column.unsortable,
1075
+ unsortable: !isHeaderSortable(column),
910
1076
  isToggleable: !!column.onToggle,
911
1077
  isToggled: column.isToggled,
912
1078
  darkTheme: hasDarkParent,
@@ -924,6 +1090,35 @@ function Table({
924
1090
  }))]
925
1091
  });
926
1092
  };
1093
+ const renderTableFoot = () => {
1094
+ if (!(footerData === null || footerData === void 0 ? void 0 : footerData.length)) return null;
1095
+ let footerColumns = visibleColumns.filter(column => !column.isSubRow);
1096
+ if (filteredHeader.length > 0 || filterHeader.length > 0) {
1097
+ footerColumns = footerColumns.filter(column => filteredHeader.includes(column.id) || filterHeader.includes(column.id));
1098
+ }
1099
+ return jsx(TableFoot, {
1100
+ css: {
1101
+ zIndex: '$docked'
1102
+ },
1103
+ children: footerData.map((footerRow, footerRowIndex) => {
1104
+ var _a;
1105
+ return jsx(TableFootRow, {
1106
+ children: footerColumns.map(column => jsx(TableCell, Object.assign({}, getStickyFooterProps(column, flatHeaders), {
1107
+ css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1108
+ padding: '20px 8px',
1109
+ fontWeight: '600',
1110
+ verticalAlign: cellAlign
1111
+ }), stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), alignCss(column)), column.footerCss),
1112
+ children: column.render('Footer', {
1113
+ footerRow,
1114
+ footerRowIndex,
1115
+ value: footerValueOf(column, footerRow, footerRowIndex)
1116
+ })
1117
+ }), column.id))
1118
+ }, (_a = footerRow[keyId]) !== null && _a !== void 0 ? _a : footerRowIndex);
1119
+ })
1120
+ });
1121
+ };
927
1122
  const handleRowClick = (row, i) => {
928
1123
  var _a, _b, _c, _d;
929
1124
  let clickable = false;
@@ -1026,6 +1221,10 @@ function Table({
1026
1221
  const tableData = isExpandableTree ? rows : page;
1027
1222
  return virtualized ? isInfiniteScroll ? virtualRows : tableData : tableData;
1028
1223
  }, [virtualized, isExpandableTree, isInfiniteScroll, rows, page, virtualRows]);
1224
+ const rowSpanColumns = useMemo(() => visibleColumns.filter(column => column.rowSpan), [visibleColumns]);
1225
+ // Row groups interleave decorator rows and dragging reorders rows, so neither merges cells.
1226
+ // Bail out before touching the rows: virtualized tables recompute this on every scroll.
1227
+ const cellRowSpans = useMemo(() => rowGroup || draggable || !rowSpanColumns.length ? null : getCellRowSpans(tableRowData.map(dataRow => virtualized && isInfiniteScroll ? page === null || page === void 0 ? void 0 : page[dataRow === null || dataRow === void 0 ? void 0 : dataRow.index] : dataRow), rowSpanColumns), [rowGroup, draggable, rowSpanColumns, tableRowData, virtualized, isInfiniteScroll, page]);
1029
1228
  const onSortEnd = ({
1030
1229
  oldIndex,
1031
1230
  newIndex
@@ -1088,15 +1287,22 @@ function Table({
1088
1287
  hiddenHeader: hideHeader,
1089
1288
  css: headerCss,
1090
1289
  isInfiniteScroll: isInfiniteScroll,
1091
- children: headerGroups.map(headerGroup => {
1092
- let visibleHeaders = headerGroup.headers.filter(column => !column.isSubRow);
1290
+ children: buildHeaderRows(headerGroups, headerRowSpan).map((headerCells, headerRowIndex) => {
1291
+ let visibleHeaders = headerCells.filter(({
1292
+ column
1293
+ }) => !column.isSubRow);
1093
1294
  if (filteredHeader.length > 0 || filterHeader.length > 0) {
1094
- visibleHeaders = visibleHeaders.filter(item => filteredHeader.includes(item.id) || filterHeader.includes(item.id));
1295
+ visibleHeaders = visibleHeaders.filter(({
1296
+ column: item
1297
+ }) => filteredHeader.includes(item.id) || filterHeader.includes(item.id));
1095
1298
  }
1096
- return jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
1097
- children: visibleHeaders.map((column, index) => {
1299
+ return jsx(TableHeadRow, Object.assign({}, headerGroups[headerRowIndex].getHeaderGroupProps(), {
1300
+ children: visibleHeaders.map(({
1301
+ column,
1302
+ rowSpan
1303
+ }, index) => {
1098
1304
  const headerRender = column.render('Header');
1099
- return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, !column.unsortable ? column.getSortByToggleProps({
1305
+ return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, isHeaderSortable(column) ? column.getSortByToggleProps({
1100
1306
  title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
1101
1307
  }) : {
1102
1308
  onClick: column.onToggle
@@ -1107,26 +1313,33 @@ function Table({
1107
1313
  minWidth: column.colMinWidth
1108
1314
  }
1109
1315
  }]), {
1316
+ "aria-sort": getAriaSort(column),
1317
+ rowSpan: rowSpan > 1 ? rowSpan : undefined,
1110
1318
  css: Object.assign(Object.assign(Object.assign(Object.assign({}, stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), column.headerCss), {
1111
1319
  position: 'sticky',
1112
1320
  zIndex: column.sticky ? '5 !important' : 3,
1113
1321
  top: 0
1114
1322
  }),
1115
- children: typeof headerRender === 'string' ? jsx(TableHeaderCellContainer, {
1323
+ children: typeof headerRender === 'string' ? jsxs(TableHeaderCellContainer, {
1116
1324
  isSortedDesc: column.isSortedDesc,
1117
- unsortable: id ? true : column.unsortable,
1325
+ unsortable: !isHeaderSortable(column),
1118
1326
  isToggleable: !!column.onToggle,
1119
1327
  isToggled: column.isToggled,
1120
1328
  darkTheme: hasDarkParent,
1121
- css: {
1329
+ css: Object.assign({
1122
1330
  width: column.colWidth,
1123
1331
  maxWidth: column.colMaxWidth,
1124
1332
  minWidth: column.colMinWidth
1125
- },
1126
- children: jsx(TableHeadCellContent, {
1333
+ }, headerAlignCss(column)),
1334
+ children: [jsx(TableHeadCellContent, {
1127
1335
  heading: "headerTable",
1336
+ css: Object.assign({
1337
+ color: 'inherit'
1338
+ }, alignCss(column)),
1128
1339
  children: headerRender.toUpperCase()
1129
- })
1340
+ }), column.headerTooltip && jsx(HeaderTooltip, {
1341
+ label: column.headerTooltip
1342
+ })]
1130
1343
  }) : headerRender
1131
1344
  }));
1132
1345
  })
@@ -1174,13 +1387,13 @@ function Table({
1174
1387
  const collId = (_b = cell === null || cell === void 0 ? void 0 : cell.column) === null || _b === void 0 ? void 0 : _b.id;
1175
1388
  const exceptionId = (_c = [].concat(additionalExceptionIds)) === null || _c === void 0 ? void 0 : _c.includes(collId);
1176
1389
  return jsx(TableCell, Object.assign({}, cell.getCellProps(addOnCellStyle(cell.column.id)), {
1177
- css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1390
+ css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1178
1391
  padding: !isContextMenu || exceptionId ? collId === 'expander' || collId === 'multiple-expander' ? '20px 4px' : '20px 8px' : '',
1179
1392
  verticalAlign: cell.column.accessor ? cellAlign : 'center',
1180
1393
  cursor: onRowClick ? 'pointer' : 'unset'
1181
1394
  }), stickyPositionStyling(cell.column)), stickyShadowStyling(cell.column, tableScrollPosition, scrollAreaWidth)), id && {
1182
1395
  paddingRight: index + 1 === _row.length ? 30 : null
1183
- }),
1396
+ }), alignCss(cell.column)), resolveCellCss(cell)),
1184
1397
  onClick: () => {
1185
1398
  if (!exceptionId) {
1186
1399
  handleRowClick(row, i);
@@ -1213,6 +1426,7 @@ function Table({
1213
1426
  }, row.id);
1214
1427
  })
1215
1428
  })) : jsxs(TableBody, Object.assign({}, getTableBodyProps(), {
1429
+ onMouseLeave: cellRowSpans ? () => setHoveredRowId(null) : undefined,
1216
1430
  children: [isInfiniteScroll && paddingTop > 0 && jsx("tr", {
1217
1431
  children: jsx("td", {
1218
1432
  style: {
@@ -1232,7 +1446,10 @@ function Table({
1232
1446
  wrapper: child => tableRowData && tableRowData.length > 0 ? jsx(TableRow, {
1233
1447
  css: {
1234
1448
  '&:hover': {
1235
- backgroundColor: 'unset'
1449
+ backgroundColor: 'unset',
1450
+ '& > td': {
1451
+ backgroundColor: 'unset'
1452
+ }
1236
1453
  }
1237
1454
  },
1238
1455
  children: jsx(TableCell, {
@@ -1288,21 +1505,27 @@ function Table({
1288
1505
  })), jsx(TableRow, Object.assign({
1289
1506
  css: customStyle
1290
1507
  }, row && (row === null || row === void 0 ? void 0 : row.getRowProps) ? row === null || row === void 0 ? void 0 : row.getRowProps() : {}, {
1508
+ onMouseEnter: cellRowSpans ? () => setHoveredRowId(row.id) : undefined,
1291
1509
  children: row.cells.filter(cell => filteredHeader.length > 0 || filterHeader.length > 0 ? filteredHeader.includes(cell.column.id) || filterHeader.includes(cell.column.id) : cell).filter(cell => !cell.column.isSubRow).map((cell, index, _row) => {
1292
- var _a, _b, _c;
1293
- let visibleHeaders = (_a = headerGroups === null || headerGroups === void 0 ? void 0 : headerGroups[0]) === null || _a === void 0 ? void 0 : _a.headers.filter(column => !column.isSubRow);
1510
+ var _a, _b, _c, _d, _e;
1511
+ const cellRowSpan = (_a = cellRowSpans === null || cellRowSpans === void 0 ? void 0 : cellRowSpans.spans.get(cell.column.id)) === null || _a === void 0 ? void 0 : _a.get(row.id);
1512
+ if (cellRowSpan === 0) return null;
1513
+ let visibleHeaders = (_b = headerGroups === null || headerGroups === void 0 ? void 0 : headerGroups[0]) === null || _b === void 0 ? void 0 : _b.headers.filter(column => !column.isSubRow);
1294
1514
  if (filterHeader.length > 0) {
1295
1515
  visibleHeaders = visibleHeaders.filter(item => filterHeader.includes(item.id));
1296
1516
  }
1297
- const collId = (_b = cell === null || cell === void 0 ? void 0 : cell.column) === null || _b === void 0 ? void 0 : _b.id;
1298
- const exceptionId = (_c = ['expander', 'multiple-expander', 'selection', 'menu', 'eye'].concat(additionalExceptionIds)) === null || _c === void 0 ? void 0 : _c.includes(collId);
1517
+ const collId = (_c = cell === null || cell === void 0 ? void 0 : cell.column) === null || _c === void 0 ? void 0 : _c.id;
1518
+ const exceptionId = (_d = ['expander', 'multiple-expander', 'selection', 'menu', 'eye'].concat(additionalExceptionIds)) === null || _d === void 0 ? void 0 : _d.includes(collId);
1299
1519
  return jsx(TableCell, Object.assign({}, cell.getCellProps(addOnCellStyle(cell.column.id)), {
1300
- css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1520
+ rowSpan: cellRowSpan > 1 ? cellRowSpan : undefined,
1521
+ css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1301
1522
  padding: !isContextMenu || exceptionId ? collId === 'expander' || collId === 'multiple-expander' ? '20px 4px' : '20px 8px' : '',
1302
1523
  verticalAlign: cell.column.accessor ? cellAlign : 'center',
1303
1524
  cursor: onRowClick ? 'pointer' : 'unset'
1304
1525
  }), stickyPositionStyling(cell.column)), stickyShadowStyling(cell.column, tableScrollPosition, scrollAreaWidth)), id && {
1305
1526
  paddingRight: index + 1 === _row.length ? 30 : null
1527
+ }), alignCss(cell.column)), resolveCellCss(cell)), cellRowSpan > 1 && ((_e = cellRowSpans.anchors.get(cell.column.id)) === null || _e === void 0 ? void 0 : _e.get(hoveredRowId)) === row.id && {
1528
+ backgroundColor: '$bgGray'
1306
1529
  }),
1307
1530
  onClick: () => {
1308
1531
  if (!exceptionId) {
@@ -1385,7 +1608,7 @@ function Table({
1385
1608
  }
1386
1609
  })
1387
1610
  })]
1388
- }))]
1611
+ })), renderTableFoot()]
1389
1612
  }))
1390
1613
  }), renderContextMenu()]
1391
1614
  })
@@ -1468,7 +1691,11 @@ function Table({
1468
1691
  additionalExceptionIds: additionalExceptionIds,
1469
1692
  hideHeader: hideHeader,
1470
1693
  decoration: decoration,
1471
- rowVirtualizer: rowVirtualizer
1694
+ rowVirtualizer: rowVirtualizer,
1695
+ footerData: footerData,
1696
+ footerColumnIds: (footerData === null || footerData === void 0 ? void 0 : footerData.length) ? new Set(visibleColumns.filter(column => column.Footer !== emptyRenderer).map(column => column.id)) : undefined,
1697
+ headerRowSpan: headerRowSpan,
1698
+ keyId: keyId
1472
1699
  })]
1473
1700
  });
1474
1701
  }
@@ -0,0 +1,26 @@
1
+ import 'react';
2
+ import { CircleQuestionOutline } from '@majoo-ui/icons';
3
+ import Box from '../../../layout/box/Box.esm.js';
4
+ import { jsx } from 'react/jsx-runtime';
5
+ import Tooltip from '../../tooltip/Tooltip.esm.js';
6
+
7
+ const HeaderTooltip = ({
8
+ label
9
+ }) => /*#__PURE__*/jsx(Tooltip, {
10
+ label: label,
11
+ side: "top",
12
+ children: /*#__PURE__*/jsx(Box, {
13
+ as: "span",
14
+ css: {
15
+ display: 'flex',
16
+ cursor: 'help'
17
+ },
18
+ onClick: event => event.stopPropagation(),
19
+ children: /*#__PURE__*/jsx(CircleQuestionOutline, {
20
+ size: 16,
21
+ color: "currentColor"
22
+ })
23
+ })
24
+ });
25
+
26
+ export { HeaderTooltip };
@@ -0,0 +1,22 @@
1
+ const JUSTIFY_CONTENT = {
2
+ left: 'flex-start',
3
+ center: 'center',
4
+ right: 'flex-end'
5
+ };
6
+ /** A column's `cellCss` for one cell, called with the cell when it is a function. */
7
+ const resolveCellCss = cell => {
8
+ const {
9
+ cellCss
10
+ } = cell.column;
11
+ return typeof cellCss === 'function' ? cellCss(cell) : cellCss;
12
+ };
13
+ /** Horizontal alignment of a column's body cells, footer cells and header label text. */
14
+ const alignCss = column => column.align ? {
15
+ textAlign: column.align
16
+ } : undefined;
17
+ /** The header label sits in a flex container, which follows the column's alignment through justify-content. */
18
+ const headerAlignCss = column => column.align ? {
19
+ justifyContent: JUSTIFY_CONTENT[column.align]
20
+ } : undefined;
21
+
22
+ export { alignCss, headerAlignCss, resolveCellCss };
@@ -0,0 +1,7 @@
1
+ /** What a column's accessor reads from a footer row: the `value` its `Footer` receives. */
2
+ const footerValueOf = (column, footerRow, footerRowIndex) => typeof column.accessor === 'function' ? column.accessor(footerRow, footerRowIndex, {
3
+ subRows: [],
4
+ depth: 0
5
+ }) : undefined;
6
+
7
+ export { footerValueOf };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Whether a header click should toggle the sort. react-table's `defaultCanSort` makes every header
3
+ * sortable, but toggling a group header or the placeholder above an ungrouped column throws (neither
4
+ * is in `allColumns`), and a column without an accessor has nothing to sort on unless it brings its
5
+ * own `sortType`.
6
+ */
7
+ const isHeaderSortable = column => !column.unsortable && !column.columns && !column.placeholderOf && (Boolean(column.accessor) || typeof column.sortType === 'function');
8
+ /** `aria-sort` of a header cell; only a header that can sort announces a sort state. */
9
+ const getAriaSort = column => {
10
+ if (!isHeaderSortable(column)) return undefined;
11
+ if (!column.isSorted) return 'none';
12
+ return column.isSortedDesc ? 'descending' : 'ascending';
13
+ };
14
+
15
+ export { getAriaSort, isHeaderSortable };
package/index.esm.css CHANGED
@@ -1,3 +1,7 @@
1
+ .recharts-pie-sector {
2
+ outline: none;
3
+ }
4
+
1
5
  .rdw-option-wrapper {
2
6
  padding: 5px;
3
7
  min-width: 25px;
@@ -1045,10 +1049,6 @@
1045
1049
  counter-reset: ol4;
1046
1050
  }
1047
1051
 
1048
- .recharts-pie-sector {
1049
- outline: none;
1050
- }
1051
-
1052
1052
  .ReactCrop {
1053
1053
  position: relative;
1054
1054
  display: inline-block;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majoo-ui/react",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "main": "./index.esm.js",
5
5
  "sideEffects": [
6
6
  "*.css",
@@ -34,6 +34,10 @@ interface MobileTableProps {
34
34
  decoration?: Decoration;
35
35
  rowVirtualizer: IRowVirtualizer;
36
36
  virtualized?: boolean;
37
+ footerData?: Record<string, any>[];
38
+ footerColumnIds?: Set<string>;
39
+ headerRowSpan?: boolean;
40
+ keyId?: string;
37
41
  }
38
42
  declare const MobileTable: React.ForwardRefExoticComponent<MobileTableProps & React.RefAttributes<HTMLDivElement>>;
39
43
  export default MobileTable;
@@ -0,0 +1,10 @@
1
+ /** A column's `cellCss` for one cell, called with the cell when it is a function. */
2
+ export declare const resolveCellCss: (cell: any) => any;
3
+ /** Horizontal alignment of a column's body cells, footer cells and header label text. */
4
+ export declare const alignCss: (column: any) => {
5
+ textAlign: any;
6
+ } | undefined;
7
+ /** The header label sits in a flex container, which follows the column's alignment through justify-content. */
8
+ export declare const headerAlignCss: (column: any) => {
9
+ justifyContent: any;
10
+ } | undefined;
@@ -0,0 +1,2 @@
1
+ /** What a column's accessor reads from a footer row: the `value` its `Footer` receives. */
2
+ export declare const footerValueOf: (column: any, footerRow: any, footerRowIndex: any) => any;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Whether a header click should toggle the sort. react-table's `defaultCanSort` makes every header
3
+ * sortable, but toggling a group header or the placeholder above an ungrouped column throws (neither
4
+ * is in `allColumns`), and a column without an accessor has nothing to sort on unless it brings its
5
+ * own `sortType`.
6
+ */
7
+ export declare const isHeaderSortable: (column: any) => boolean;
8
+ /** `aria-sort` of a header cell; only a header that can sort announces a sort state. */
9
+ export declare const getAriaSort: (column: any) => "none" | "ascending" | "descending" | undefined;