@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.
- package/dist/components/cells/header_cell.js +21 -21
- package/dist/components/cells/header_cell.js.map +1 -1
- package/dist/table.css +1 -1
- package/package.json +4 -4
- package/src/__stories__/table.stories.tsx +138 -1
- package/src/components/cells/header_cell.tsx +2 -2
- package/src/components/table/table.module.css +0 -10
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import { jsx as e, jsxs as
|
|
2
|
-
import { useCallback as
|
|
3
|
-
import { Box as
|
|
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
|
|
7
|
-
function
|
|
8
|
-
heading:
|
|
6
|
+
import { SortControl as b } from "./sort_control.js";
|
|
7
|
+
function N({
|
|
8
|
+
heading: r,
|
|
9
9
|
sticky: l,
|
|
10
|
-
onResize:
|
|
10
|
+
onResize: n,
|
|
11
11
|
width: o,
|
|
12
12
|
sortMode: a,
|
|
13
13
|
onSortModeChange: s,
|
|
14
|
-
canSort:
|
|
14
|
+
canSort: c
|
|
15
15
|
}) {
|
|
16
|
-
const m = l != null ? 2 : 1,
|
|
16
|
+
const m = l != null ? 2 : 1, d = h(
|
|
17
17
|
({ width: t }) => {
|
|
18
|
-
|
|
18
|
+
n?.(Math.max(t, 20));
|
|
19
19
|
},
|
|
20
|
-
[
|
|
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
|
-
|
|
29
|
+
i,
|
|
30
30
|
{
|
|
31
|
-
|
|
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:
|
|
37
|
+
onWidthResize: d,
|
|
38
38
|
onClick: (t) => t.stopPropagation(),
|
|
39
|
-
children: /* @__PURE__ */
|
|
39
|
+
children: /* @__PURE__ */ p(x, { children: [
|
|
40
40
|
/* @__PURE__ */ e(
|
|
41
|
-
|
|
41
|
+
i,
|
|
42
42
|
{
|
|
43
43
|
minWidth: "24px",
|
|
44
44
|
className: "ellipsis",
|
|
45
45
|
style: { alignItems: "center", display: "flex" },
|
|
46
|
-
children:
|
|
46
|
+
children: r
|
|
47
47
|
}
|
|
48
48
|
),
|
|
49
49
|
/* @__PURE__ */ e(
|
|
50
|
-
|
|
50
|
+
b,
|
|
51
51
|
{
|
|
52
|
-
canSort:
|
|
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
|
-
|
|
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
|
|
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}
|
|
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.
|
|
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/
|
|
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
|
-
|
|
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"],
|