@tcn/ui-table 2.4.0 → 2.4.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.
@@ -1,55 +1,55 @@
1
- import { jsx as e, jsxs as c } from "react/jsx-runtime";
2
- import { useCallback as x } from "react";
3
- import { Box as r, HStack as h } from "@tcn/ui/stacks";
1
+ import { jsx as e, jsxs as p } from "react/jsx-runtime";
2
+ import { useCallback as h } from "react";
3
+ import { Box as i, HStack as x } from "@tcn/ui/stacks";
4
4
  import { TH as f } from "@tcn/ui/layouts";
5
5
  import { c as u } from "../../cell.module-WpHnQBVu.js";
6
- import { SortControl as C } from "./sort_control.js";
7
- function H({
8
- heading: n,
6
+ import { SortControl as b } from "./sort_control.js";
7
+ function N({
8
+ heading: r,
9
9
  sticky: l,
10
- onResize: i,
10
+ onResize: n,
11
11
  width: o,
12
12
  sortMode: a,
13
13
  onSortModeChange: s,
14
- canSort: d
14
+ canSort: c
15
15
  }) {
16
- const m = l != null ? 2 : 1, p = x(
16
+ const m = l != null ? 2 : 1, d = h(
17
17
  ({ width: t }) => {
18
- i?.(Math.max(t, 20));
18
+ n?.(Math.max(t, 20));
19
19
  },
20
- [i]
20
+ [n]
21
21
  );
22
22
  return /* @__PURE__ */ e(
23
23
  f,
24
24
  {
25
- className: u["table-cell"],
25
+ className: `tcn-header-cell ${u["table-cell"]}`,
26
26
  "data-stick-to": l,
27
27
  style: { width: `${o}px`, zIndex: m },
28
28
  children: /* @__PURE__ */ e(
29
- r,
29
+ i,
30
30
  {
31
- padding: "0px 8px",
31
+ className: "tcn-table-cell-content",
32
32
  overflow: "hidden",
33
33
  minWidth: "24px",
34
34
  maxWidth: "unset",
35
35
  width: o,
36
36
  enableResizeOnEnd: !0,
37
- onWidthResize: p,
37
+ onWidthResize: d,
38
38
  onClick: (t) => t.stopPropagation(),
39
- children: /* @__PURE__ */ c(h, { children: [
39
+ children: /* @__PURE__ */ p(x, { children: [
40
40
  /* @__PURE__ */ e(
41
- r,
41
+ i,
42
42
  {
43
43
  minWidth: "24px",
44
44
  className: "ellipsis",
45
45
  style: { alignItems: "center", display: "flex" },
46
- children: n
46
+ children: r
47
47
  }
48
48
  ),
49
49
  /* @__PURE__ */ e(
50
- C,
50
+ b,
51
51
  {
52
- canSort: d,
52
+ canSort: c,
53
53
  onSortModeChange: s,
54
54
  sortMode: a
55
55
  }
@@ -61,6 +61,6 @@ function H({
61
61
  );
62
62
  }
63
63
  export {
64
- H as HeaderCell
64
+ N as HeaderCell
65
65
  };
66
66
  //# sourceMappingURL=header_cell.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"header_cell.js","sources":["../../../src/components/cells/header_cell.tsx"],"sourcesContent":["import React, { useCallback } from 'react';\n\nimport { Box, HStack, type OnWidthResizePayload } from '@tcn/ui/stacks';\n\nimport { TH } from '@tcn/ui/layouts';\nimport cellStyles from './cell.module.css';\nimport { SortControl, type SortControlProps } from './sort_control.js';\n\nexport interface HeaderCellProps extends SortControlProps {\n heading: React.ReactNode;\n index: number;\n sticky?: 'start' | 'end';\n onResize?: (width: number) => void;\n width?: number;\n}\n\nexport function HeaderCell({\n heading,\n sticky,\n onResize,\n width,\n sortMode,\n onSortModeChange,\n canSort,\n}: HeaderCellProps) {\n const zIndex = sticky != null ? 2 : 1;\n\n const handleResize = useCallback(\n ({ width }: OnWidthResizePayload) => {\n onResize?.(Math.max(width, 20));\n },\n [onResize]\n );\n\n return (\n <TH\n className={cellStyles['table-cell']}\n data-stick-to={sticky}\n style={{ width: `${width}px`, zIndex }}\n >\n <Box\n padding=\"0px 8px\" // FIXME: should be on theme ideally.\n overflow=\"hidden\"\n minWidth=\"24px\"\n maxWidth=\"unset\"\n width={width}\n enableResizeOnEnd\n onWidthResize={handleResize}\n onClick={e => e.stopPropagation()}\n >\n <HStack>\n <Box\n minWidth=\"24px\"\n className=\"ellipsis\"\n style={{ alignItems: 'center', display: 'flex' }}\n >\n {heading}\n </Box>\n\n <SortControl\n canSort={canSort}\n onSortModeChange={onSortModeChange}\n sortMode={sortMode}\n />\n </HStack>\n </Box>\n </TH>\n );\n}\n"],"names":["HeaderCell","heading","sticky","onResize","width","sortMode","onSortModeChange","canSort","zIndex","handleResize","useCallback","jsx","TH","cellStyles","Box","e","HStack","SortControl"],"mappings":";;;;;;AAgBO,SAASA,EAAW;AAAA,EACzB,SAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,SAAAC;AACF,GAAoB;AAClB,QAAMC,IAASN,KAAU,OAAO,IAAI,GAE9BO,IAAeC;AAAA,IACnB,CAAC,EAAE,OAAAN,QAAkC;AACnC,MAAAD,IAAW,KAAK,IAAIC,GAAO,EAAE,CAAC;AAAA,IAChC;AAAA,IACA,CAACD,CAAQ;AAAA,EAAA;AAGX,SACE,gBAAAQ;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAW,YAAY;AAAA,MAClC,iBAAeX;AAAA,MACf,OAAO,EAAE,OAAO,GAAGE,CAAK,MAAM,QAAAI,EAAA;AAAA,MAE9B,UAAA,gBAAAG;AAAA,QAACG;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,UAAS;AAAA,UACT,UAAS;AAAA,UACT,UAAS;AAAA,UACT,OAAAV;AAAA,UACA,mBAAiB;AAAA,UACjB,eAAeK;AAAA,UACf,SAAS,CAAAM,MAAKA,EAAE,gBAAA;AAAA,UAEhB,4BAACC,GAAA,EACC,UAAA;AAAA,YAAA,gBAAAL;AAAA,cAACG;AAAA,cAAA;AAAA,gBACC,UAAS;AAAA,gBACT,WAAU;AAAA,gBACV,OAAO,EAAE,YAAY,UAAU,SAAS,OAAA;AAAA,gBAEvC,UAAAb;AAAA,cAAA;AAAA,YAAA;AAAA,YAGH,gBAAAU;AAAA,cAACM;AAAA,cAAA;AAAA,gBACC,SAAAV;AAAA,gBACA,kBAAAD;AAAA,gBACA,UAAAD;AAAA,cAAA;AAAA,YAAA;AAAA,UACF,EAAA,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"header_cell.js","sources":["../../../src/components/cells/header_cell.tsx"],"sourcesContent":["import React, { useCallback } from 'react';\n\nimport { Box, HStack, type OnWidthResizePayload } from '@tcn/ui/stacks';\n\nimport { TH } from '@tcn/ui/layouts';\nimport cellStyles from './cell.module.css';\nimport { SortControl, type SortControlProps } from './sort_control.js';\n\nexport interface HeaderCellProps extends SortControlProps {\n heading: React.ReactNode;\n index: number;\n sticky?: 'start' | 'end';\n onResize?: (width: number) => void;\n width?: number;\n}\n\nexport function HeaderCell({\n heading,\n sticky,\n onResize,\n width,\n sortMode,\n onSortModeChange,\n canSort,\n}: HeaderCellProps) {\n const zIndex = sticky != null ? 2 : 1;\n\n const handleResize = useCallback(\n ({ width }: OnWidthResizePayload) => {\n onResize?.(Math.max(width, 20));\n },\n [onResize]\n );\n\n return (\n <TH\n className={`tcn-header-cell ${cellStyles['table-cell']}`}\n data-stick-to={sticky}\n style={{ width: `${width}px`, zIndex }}\n >\n <Box\n className=\"tcn-table-cell-content\"\n overflow=\"hidden\"\n minWidth=\"24px\"\n maxWidth=\"unset\"\n width={width}\n enableResizeOnEnd\n onWidthResize={handleResize}\n onClick={e => e.stopPropagation()}\n >\n <HStack>\n <Box\n minWidth=\"24px\"\n className=\"ellipsis\"\n style={{ alignItems: 'center', display: 'flex' }}\n >\n {heading}\n </Box>\n\n <SortControl\n canSort={canSort}\n onSortModeChange={onSortModeChange}\n sortMode={sortMode}\n />\n </HStack>\n </Box>\n </TH>\n );\n}\n"],"names":["HeaderCell","heading","sticky","onResize","width","sortMode","onSortModeChange","canSort","zIndex","handleResize","useCallback","jsx","TH","cellStyles","Box","e","HStack","SortControl"],"mappings":";;;;;;AAgBO,SAASA,EAAW;AAAA,EACzB,SAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,SAAAC;AACF,GAAoB;AAClB,QAAMC,IAASN,KAAU,OAAO,IAAI,GAE9BO,IAAeC;AAAA,IACnB,CAAC,EAAE,OAAAN,QAAkC;AACnC,MAAAD,IAAW,KAAK,IAAIC,GAAO,EAAE,CAAC;AAAA,IAChC;AAAA,IACA,CAACD,CAAQ;AAAA,EAAA;AAGX,SACE,gBAAAQ;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,WAAW,mBAAmBC,EAAW,YAAY,CAAC;AAAA,MACtD,iBAAeX;AAAA,MACf,OAAO,EAAE,OAAO,GAAGE,CAAK,MAAM,QAAAI,EAAA;AAAA,MAE9B,UAAA,gBAAAG;AAAA,QAACG;AAAA,QAAA;AAAA,UACC,WAAU;AAAA,UACV,UAAS;AAAA,UACT,UAAS;AAAA,UACT,UAAS;AAAA,UACT,OAAAV;AAAA,UACA,mBAAiB;AAAA,UACjB,eAAeK;AAAA,UACf,SAAS,CAAAM,MAAKA,EAAE,gBAAA;AAAA,UAEhB,4BAACC,GAAA,EACC,UAAA;AAAA,YAAA,gBAAAL;AAAA,cAACG;AAAA,cAAA;AAAA,gBACC,UAAS;AAAA,gBACT,WAAU;AAAA,gBACV,OAAO,EAAE,YAAY,UAAU,SAAS,OAAA;AAAA,gBAEvC,UAAAb;AAAA,cAAA;AAAA,YAAA;AAAA,YAGH,gBAAAU;AAAA,cAACM;AAAA,cAAA;AAAA,gBACC,SAAAV;AAAA,gBACA,kBAAAD;AAAA,gBACA,UAAAD;AAAA,cAAA;AAAA,YAAA;AAAA,UACF,EAAA,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;"}
package/dist/table.css CHANGED
@@ -1 +1 @@
1
- ._table-body_b8c928c{border-spacing:0;width:auto;min-width:100%;height:auto;min-height:100%;table-layout:fixed;display:table}._table-body_b8c928c thead th,._table-body_b8c928c thead td{padding:0}:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) th[data-stick-to=start],:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) td[data-stick-to=start]{border-inline-end:1px solid #ccc}:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) th[data-stick-to=end],:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) td[data-stick-to=end]{border-inline-start:1px solid #ccc}._table-body_b8c928c tr[data-clickable=true]{cursor:pointer}._table-body_b8c928c thead tr th{position:sticky;top:0;z-index:1;box-sizing:border-box}._table-body_b8c928c thead{position:sticky;top:0;z-index:3}._table-body_b8c928c tbody{position:relative;z-index:1}._table-body_b8c928c tfoot{position:relative;z-index:2}._table-body_b8c928c th>div{display:flex;align-items:center}._table-body_b8c928c td>div{height:100%;display:flex;align-items:center}._table-body_b8c928c tfoot{position:sticky;bottom:0}._table-body_b8c928c td,._table-body_b8c928c th{vertical-align:middle}._table-body_b8c928c[data-is-clickable=true] td{cursor:pointer}
1
+ ._table-body_b8c928c{border-spacing:0;width:auto;min-width:100%;height:auto;min-height:100%;table-layout:fixed;display:table}:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) th[data-stick-to=start],:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) td[data-stick-to=start]{border-inline-end:1px solid #ccc}:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) th[data-stick-to=end],:is(._table-body_b8c928c thead tr,._table-body_b8c928c tbody tr) td[data-stick-to=end]{border-inline-start:1px solid #ccc}._table-body_b8c928c tr[data-clickable=true]{cursor:pointer}._table-body_b8c928c thead tr th{position:sticky;top:0;z-index:1;box-sizing:border-box}._table-body_b8c928c thead{position:sticky;top:0;z-index:3}._table-body_b8c928c tbody{position:relative;z-index:1}._table-body_b8c928c tfoot{position:relative;z-index:2}._table-body_b8c928c th>div{display:flex;align-items:center}._table-body_b8c928c td>div{height:100%;display:flex;align-items:center}._table-body_b8c928c tfoot{position:sticky;bottom:0}._table-body_b8c928c td,._table-body_b8c928c th{vertical-align:middle}._table-body_b8c928c[data-is-clickable=true] td{cursor:pointer}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tcn/ui-table",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "type": "module",
5
5
  "description": "React table component library",
6
6
  "author": "TCN",
@@ -40,10 +40,10 @@
40
40
  "dependencies": {
41
41
  "clarity-pattern-parser": "^11.5.4",
42
42
  "@tcn/aip-160": "1.2.5",
43
- "@tcn/icons": "2.3.0",
44
- "@tcn/resource-store": "2.5.6",
45
43
  "@tcn/state": "1.3.2",
46
- "@tcn/ui": "0.13.0"
44
+ "@tcn/resource-store": "2.5.6",
45
+ "@tcn/icons": "2.3.0",
46
+ "@tcn/ui": "0.13.1"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": "^18.2.0",
@@ -3,13 +3,15 @@ import React, { useCallback, useState } from 'react';
3
3
 
4
4
  import { CrossIcon } from '@tcn/icons/cross_icon.js';
5
5
  import {
6
+ type DataSource,
6
7
  StaticDataSource,
7
8
  StaticDateField,
8
9
  StaticNumberField,
9
10
  StaticStringField,
10
11
  } from '@tcn/resource-store';
11
- import { useSignalValue } from '@tcn/state';
12
+ import { Signal, useSignalValue } from '@tcn/state';
12
13
  import { Button } from '@tcn/ui/actions';
14
+ import { Checkbox } from '@tcn/ui/inputs';
13
15
  import { Footer, Header, Rail, Side, Body } from '@tcn/ui/layouts';
14
16
  import { Box, HStack, Spacer, VStack, ZStack } from '@tcn/ui/stacks';
15
17
  import { Panel } from '@tcn/ui/surfaces';
@@ -453,4 +455,139 @@ export function WithFilterPanel() {
453
455
  );
454
456
  }
455
457
 
458
+ // Selection Table
459
+
460
+ type SelectionStatus = 'none' | 'some' | 'all';
461
+
462
+ class SelectionPresenter<T extends { id: string }> {
463
+ private _selected = new Signal<Set<string>>(new Set());
464
+ private _status = new Signal<SelectionStatus>('none');
465
+ private _dataSource: DataSource<T>;
466
+
467
+ constructor(dataSource: DataSource<T>) {
468
+ this._dataSource = dataSource;
469
+ }
470
+
471
+ get broadcasts() {
472
+ return {
473
+ selected: this._selected.broadcast,
474
+ status: this._status.broadcast,
475
+ };
476
+ }
477
+
478
+ private getTotalRowCount(): number {
479
+ return this._dataSource.broadcasts.currentResults.get().length;
480
+ }
481
+
482
+ private getSelectedCount(): number {
483
+ return this._selected.get().size;
484
+ }
485
+
486
+ private getNextStatus() {
487
+ const selectedCount = this.getSelectedCount();
488
+ const totalCount = this.getTotalRowCount();
489
+ if (selectedCount === totalCount) return 'all';
490
+ if (selectedCount === 0) return 'none';
491
+ if (selectedCount < totalCount) return 'some';
492
+ return 'none';
493
+ }
494
+
495
+ private setStatus() {
496
+ const status = this.getNextStatus();
497
+ this._status.set(status);
498
+ }
499
+
500
+ toggleRow(row: T) {
501
+ this._selected.transform(selected => {
502
+ if (selected.has(row.id)) selected.delete(row.id);
503
+ else selected.add(row.id);
504
+ return selected;
505
+ });
506
+ this.setStatus();
507
+ }
508
+
509
+ private getAllIds(): string[] {
510
+ return this._dataSource.broadcasts.currentResults.get().map((r: T) => r.id);
511
+ }
512
+
513
+ toggleAllRows() {
514
+ const status = this._status.get();
515
+ if (status === 'all') {
516
+ this._selected.set(new Set());
517
+ } else {
518
+ this._selected.set(new Set(this.getAllIds()));
519
+ }
520
+ this.setStatus();
521
+ }
522
+ }
523
+
524
+ const RowCheck = ({
525
+ item,
526
+ presenter,
527
+ }: {
528
+ item: DataItem;
529
+ presenter: SelectionPresenter<DataItem>;
530
+ }) => {
531
+ const selected = useSignalValue(presenter.broadcasts.selected);
532
+ const status = useSignalValue(presenter.broadcasts.status);
533
+ const isChecked = selected.has(item.id) || status === 'all';
534
+ return <Checkbox checked={isChecked} onChange={() => presenter.toggleRow(item)} />;
535
+ };
536
+
537
+ const ColumnCheck = ({ presenter }: { presenter: SelectionPresenter<DataItem> }) => {
538
+ const status = useSignalValue(presenter.broadcasts.status);
539
+ const isChecked = status === 'all';
540
+
541
+ return (
542
+ <Checkbox
543
+ checked={isChecked}
544
+ data-indeterminate={status === 'some'}
545
+ onChange={() => presenter.toggleAllRows()}
546
+ onClick={e => e.stopPropagation()}
547
+ />
548
+ );
549
+ };
550
+
551
+ export function SelectionTable() {
552
+ const [source] = useState(() => {
553
+ return new StaticDataSource<DataItem>(items, [
554
+ new StaticStringField('id', i => i.id),
555
+ new StaticStringField('name', i => i.name),
556
+ new StaticNumberField('age', i => i.age),
557
+ new StaticStringField('email', i => i.email),
558
+ new StaticStringField('city', i => i.city),
559
+ new StaticStringField('country', i => i.country),
560
+ new StaticStringField('occupation', i => i.occupation),
561
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
562
+ ]);
563
+ });
564
+
565
+ const [presenter] = useState(() => new SelectionPresenter<DataItem>(source));
566
+
567
+ return (
568
+ <StoryWrapper>
569
+ <Panel>
570
+ <Header>Selection Table</Header>
571
+ <Body>
572
+ <Table dataSource={source}>
573
+ <TableColumn
574
+ heading={<ColumnCheck presenter={presenter} />}
575
+ sticky="start"
576
+ width={48}
577
+ render={(item: DataItem) => <RowCheck item={item} presenter={presenter} />}
578
+ />
579
+ <TableColumn heading="Name" fieldName="name" />
580
+ <TableColumn heading="Age" fieldName="age" />
581
+ <TableColumn heading="Email" fieldName="email" />
582
+ <TableColumn heading="City" fieldName="city" />
583
+ <TableColumn heading="Country" fieldName="country" />
584
+ <TableColumn heading="Occupation" fieldName="occupation" />
585
+ <TableColumn heading="Active" fieldName="isActive" />
586
+ </Table>
587
+ </Body>
588
+ </Panel>
589
+ </StoryWrapper>
590
+ );
591
+ }
592
+
456
593
  export default meta;
@@ -34,12 +34,12 @@ export function HeaderCell({
34
34
 
35
35
  return (
36
36
  <TH
37
- className={cellStyles['table-cell']}
37
+ className={`tcn-header-cell ${cellStyles['table-cell']}`}
38
38
  data-stick-to={sticky}
39
39
  style={{ width: `${width}px`, zIndex }}
40
40
  >
41
41
  <Box
42
- padding="0px 8px" // FIXME: should be on theme ideally.
42
+ className="tcn-table-cell-content"
43
43
  overflow="hidden"
44
44
  minWidth="24px"
45
45
  maxWidth="unset"
@@ -8,16 +8,6 @@
8
8
  display: table;
9
9
  }
10
10
 
11
- /* FIXME: These styles should be handled at the theme level
12
- but tcn ui does not handle resizing or sticky logic. */
13
- /* Reset padding for header - due to resize logic */
14
- .table-body thead {
15
- th,
16
- td {
17
- padding: 0;
18
- }
19
- }
20
-
21
11
  .table-body thead tr,
22
12
  .table-body tbody tr {
23
13
  th[data-stick-to="start"],