@tcn/ui-table 2.3.15 → 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/components/table_filter_panel/table_filter_panel.d.ts +2 -1
- package/dist/components/table_filter_panel/table_filter_panel.d.ts.map +1 -1
- package/dist/components/table_filter_panel/table_filter_panel.js +39 -44
- package/dist/components/table_filter_panel/table_filter_panel.js.map +1 -1
- package/dist/table.css +1 -1
- package/dist/table_filter_panel.css +1 -1
- package/package.json +5 -5
- package/src/__stories__/table.stories.tsx +149 -19
- package/src/components/cells/header_cell.tsx +2 -2
- package/src/components/table/table.module.css +0 -10
- package/src/components/table_filter_panel/table_filter_panel.module.css +0 -5
- package/src/components/table_filter_panel/table_filter_panel.stories.tsx +4 -3
- package/src/components/table_filter_panel/table_filter_panel.tsx +17 -15
|
@@ -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;"}
|
|
@@ -7,7 +7,8 @@ export interface TableFilterPanelProps extends Omit<PanelProps, 'children'> {
|
|
|
7
7
|
children: ReactElement<FieldFilterProps>[] | ReactElement<FieldFilterProps>;
|
|
8
8
|
dataSource: DataSource<any>;
|
|
9
9
|
onClose?: () => void;
|
|
10
|
+
title?: string;
|
|
10
11
|
}
|
|
11
|
-
export declare function TableFilterPanel({ children, dataSource, className, onClose, ...props }: TableFilterPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function TableFilterPanel({ children, dataSource, className, onClose, title, ...props }: TableFilterPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
12
13
|
export declare const TableFilterPanelContext: import('react').Context<TableFilterPanelPresenter<unknown> | null>;
|
|
13
14
|
//# sourceMappingURL=table_filter_panel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table_filter_panel.d.ts","sourceRoot":"","sources":["../../../src/components/table_filter_panel/table_filter_panel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAA2B,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"table_filter_panel.d.ts","sourceRoot":"","sources":["../../../src/components/table_filter_panel/table_filter_panel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAA2B,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQ1D,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC;IACzE,QAAQ,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAAE,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;IAC5E,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,UAAU,EACV,SAAS,EACT,OAAO,EACP,KAAuB,EACvB,GAAG,KAAK,EACT,EAAE,qBAAqB,2CA+BvB;AAED,eAAO,MAAM,uBAAuB,oEAEnC,CAAC"}
|
|
@@ -1,53 +1,48 @@
|
|
|
1
|
-
import { jsx as e, jsxs as
|
|
2
|
-
import { createContext as
|
|
3
|
-
import { TableFilterPanelPresenter as
|
|
4
|
-
import {
|
|
5
|
-
import { Header as
|
|
6
|
-
import { Spacer as
|
|
7
|
-
import { Button as
|
|
8
|
-
import { CrossIcon as
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { jsx as e, jsxs as l } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as f, useState as b } from "react";
|
|
3
|
+
import { TableFilterPanelPresenter as m } from "./table_filter_panel_presenter.js";
|
|
4
|
+
import { Aside as p } from "@tcn/ui/surfaces";
|
|
5
|
+
import { Header as d, Body as y, Scaffold as h, Section as u } from "@tcn/ui/layouts";
|
|
6
|
+
import { Spacer as P } from "@tcn/ui/stacks";
|
|
7
|
+
import { Button as F } from "@tcn/ui/actions";
|
|
8
|
+
import { CrossIcon as x } from "@tcn/icons/cross_icon.js";
|
|
9
|
+
import { Title as S } from "@tcn/ui/typography";
|
|
10
|
+
import '../../table_filter_panel.css';const T = "_table-filter-panel-body_c016eb0", _ = "_table-filter-panel-section_cfd2eb9", r = { "table-filter-panel-body": T, "table-filter-panel-section": _ };
|
|
11
|
+
function I({
|
|
12
|
+
children: n,
|
|
13
|
+
dataSource: i,
|
|
14
|
+
className: a,
|
|
15
|
+
onClose: t,
|
|
16
|
+
title: o = "Table Filters",
|
|
17
|
+
...c
|
|
16
18
|
}) {
|
|
17
|
-
const [
|
|
18
|
-
return /* @__PURE__ */ e(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
d,
|
|
19
|
+
const [s] = b(() => new m(i));
|
|
20
|
+
return /* @__PURE__ */ e(B.Provider, { value: s, children: /* @__PURE__ */ l(p, { className: `tcn-table-filter-panel ${a}`, ...c, children: [
|
|
21
|
+
/* @__PURE__ */ l(d, { children: [
|
|
22
|
+
/* @__PURE__ */ e(S, { children: o }),
|
|
23
|
+
/* @__PURE__ */ e(P, {}),
|
|
24
|
+
t && /* @__PURE__ */ e(F, { utility: !0, hierarchy: "tertiary", onClick: t, children: /* @__PURE__ */ e(x, {}) })
|
|
25
|
+
] }),
|
|
26
|
+
/* @__PURE__ */ e(
|
|
27
|
+
y,
|
|
28
|
+
{
|
|
29
|
+
className: `${r["table-filter-panel-body"]} tcn-table-filter-panel-body`,
|
|
30
|
+
children: /* @__PURE__ */ e(h, { children: /* @__PURE__ */ e(
|
|
31
|
+
u,
|
|
31
32
|
{
|
|
32
|
-
className: `${
|
|
33
|
-
children:
|
|
34
|
-
y,
|
|
35
|
-
{
|
|
36
|
-
className: `${t["table-filter-panel-section"]} tcn-table-filter-panel-section`,
|
|
37
|
-
children: a
|
|
38
|
-
}
|
|
39
|
-
)
|
|
33
|
+
className: `${r["table-filter-panel-section"]} tcn-table-filter-panel-section`,
|
|
34
|
+
children: n
|
|
40
35
|
}
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
) });
|
|
36
|
+
) })
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
] }) });
|
|
45
40
|
}
|
|
46
|
-
const
|
|
41
|
+
const B = f(
|
|
47
42
|
null
|
|
48
43
|
);
|
|
49
44
|
export {
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
I as TableFilterPanel,
|
|
46
|
+
B as TableFilterPanelContext
|
|
52
47
|
};
|
|
53
48
|
//# sourceMappingURL=table_filter_panel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table_filter_panel.js","sources":["../../../src/components/table_filter_panel/table_filter_panel.tsx"],"sourcesContent":["import { DataSource } from '@tcn/resource-store';\nimport { ReactElement, useState, createContext } from 'react';\nimport { FieldFilterProps } from './field_filters/field_filter_props.js';\nimport { TableFilterPanelPresenter } from './table_filter_panel_presenter.js';\nimport {
|
|
1
|
+
{"version":3,"file":"table_filter_panel.js","sources":["../../../src/components/table_filter_panel/table_filter_panel.tsx"],"sourcesContent":["import { DataSource } from '@tcn/resource-store';\nimport { ReactElement, useState, createContext } from 'react';\nimport { FieldFilterProps } from './field_filters/field_filter_props.js';\nimport { TableFilterPanelPresenter } from './table_filter_panel_presenter.js';\nimport { Aside, type PanelProps } from '@tcn/ui/surfaces';\nimport { Body, Header, Scaffold, Section } from '@tcn/ui/layouts';\nimport styles from './table_filter_panel.module.css';\nimport { Spacer } from '@tcn/ui/stacks';\nimport { Button } from '@tcn/ui/actions';\nimport { CrossIcon } from '@tcn/icons/cross_icon.js';\nimport { Title } from '@tcn/ui/typography';\n\nexport interface TableFilterPanelProps extends Omit<PanelProps, 'children'> {\n children: ReactElement<FieldFilterProps>[] | ReactElement<FieldFilterProps>;\n dataSource: DataSource<any>;\n onClose?: () => void;\n title?: string;\n}\n\nexport function TableFilterPanel({\n children,\n dataSource,\n className,\n onClose,\n title = 'Table Filters',\n ...props\n}: TableFilterPanelProps) {\n const [presenter] = useState(() => {\n return new TableFilterPanelPresenter(dataSource);\n });\n\n return (\n <TableFilterPanelContext.Provider value={presenter}>\n <Aside className={`tcn-table-filter-panel ${className}`} {...props}>\n <Header>\n <Title>{title}</Title>\n <Spacer />\n {onClose && (\n <Button utility hierarchy=\"tertiary\" onClick={onClose}>\n <CrossIcon />\n </Button>\n )}\n </Header>\n <Body\n className={`${styles['table-filter-panel-body']} tcn-table-filter-panel-body`}\n >\n <Scaffold>\n <Section\n className={`${styles['table-filter-panel-section']} tcn-table-filter-panel-section`}\n >\n {children}\n </Section>\n </Scaffold>\n </Body>\n </Aside>\n </TableFilterPanelContext.Provider>\n );\n}\n\nexport const TableFilterPanelContext = createContext<TableFilterPanelPresenter | null>(\n null\n);\n"],"names":["TableFilterPanel","children","dataSource","className","onClose","title","props","presenter","useState","TableFilterPanelPresenter","jsx","TableFilterPanelContext","jsxs","Aside","Header","Title","Spacer","Button","CrossIcon","Body","styles","Scaffold","Section","createContext"],"mappings":";;;;;;;;;;AAmBO,SAASA,EAAiB;AAAA,EAC/B,UAAAC;AAAA,EACA,YAAAC;AAAA,EACA,WAAAC;AAAA,EACA,SAAAC;AAAA,EACA,OAAAC,IAAQ;AAAA,EACR,GAAGC;AACL,GAA0B;AACxB,QAAM,CAACC,CAAS,IAAIC,EAAS,MACpB,IAAIC,EAA0BP,CAAU,CAChD;AAED,SACE,gBAAAQ,EAACC,EAAwB,UAAxB,EAAiC,OAAOJ,GACvC,UAAA,gBAAAK,EAACC,GAAA,EAAM,WAAW,0BAA0BV,CAAS,IAAK,GAAGG,GAC3D,UAAA;AAAA,IAAA,gBAAAM,EAACE,GAAA,EACC,UAAA;AAAA,MAAA,gBAAAJ,EAACK,KAAO,UAAAV,EAAA,CAAM;AAAA,wBACbW,GAAA,EAAO;AAAA,MACPZ,KACC,gBAAAM,EAACO,GAAA,EAAO,SAAO,IAAC,WAAU,YAAW,SAASb,GAC5C,UAAA,gBAAAM,EAACQ,GAAA,CAAA,CAAU,EAAA,CACb;AAAA,IAAA,GAEJ;AAAA,IACA,gBAAAR;AAAA,MAACS;AAAA,MAAA;AAAA,QACC,WAAW,GAAGC,EAAO,yBAAyB,CAAC;AAAA,QAE/C,4BAACC,GAAA,EACC,UAAA,gBAAAX;AAAA,UAACY;AAAA,UAAA;AAAA,YACC,WAAW,GAAGF,EAAO,4BAA4B,CAAC;AAAA,YAEjD,UAAAnB;AAAA,UAAA;AAAA,QAAA,EACH,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,EAAA,CACF,EAAA,CACF;AAEJ;AAEO,MAAMU,IAA0BY;AAAA,EACrC;AACF;"}
|
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}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
._table-filter-
|
|
1
|
+
._table-filter-panel-body_c016eb0{padding-block:8px}._table-filter-panel-body_c016eb0 ._table-filter-panel-section_cfd2eb9{gap:8px}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tcn/ui-table",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React table component library",
|
|
6
6
|
"author": "TCN",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"clarity-pattern-parser": "^11.5.4",
|
|
42
|
-
"@tcn/icons": "2.3.0",
|
|
43
42
|
"@tcn/aip-160": "1.2.5",
|
|
44
|
-
"@tcn/
|
|
45
|
-
"@tcn/
|
|
46
|
-
"@tcn/
|
|
43
|
+
"@tcn/state": "1.3.2",
|
|
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,14 +3,16 @@ 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';
|
|
13
|
-
import {
|
|
14
|
+
import { Checkbox } from '@tcn/ui/inputs';
|
|
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';
|
|
16
18
|
import { Title } from '@tcn/ui/typography';
|
|
@@ -308,7 +310,7 @@ export function WithTableHeaderAndTableFooter() {
|
|
|
308
310
|
<Spacer />
|
|
309
311
|
<GlobalSearch dataSource={source} />
|
|
310
312
|
</Header>
|
|
311
|
-
<
|
|
313
|
+
<Body height="flex">
|
|
312
314
|
<Table
|
|
313
315
|
dataSource={source}
|
|
314
316
|
onRowClick={item => {
|
|
@@ -323,7 +325,7 @@ export function WithTableHeaderAndTableFooter() {
|
|
|
323
325
|
<TableColumn heading="Occupation" fieldName="occupation" />
|
|
324
326
|
<TableColumn heading="Active" fieldName="isActive" />
|
|
325
327
|
</Table>
|
|
326
|
-
</
|
|
328
|
+
</Body>
|
|
327
329
|
<Footer>
|
|
328
330
|
<TablePager dataSource={source} />
|
|
329
331
|
</Footer>
|
|
@@ -411,21 +413,14 @@ export function WithFilterPanel() {
|
|
|
411
413
|
<StoryWrapper>
|
|
412
414
|
<Panel height="100%">
|
|
413
415
|
<Header>The Table</Header>
|
|
414
|
-
<
|
|
416
|
+
<Body>
|
|
415
417
|
<Rail>
|
|
418
|
+
{/* TODO: Refactor to remove duplicate column logic (outer Side and inner Aside - in TableFilterPanel ) */}
|
|
416
419
|
<Side padding="0px">
|
|
417
|
-
<Box
|
|
418
|
-
minWidth="300px"
|
|
419
|
-
enableResizeOnEnd
|
|
420
|
-
overflowY="auto"
|
|
421
|
-
height="100%"
|
|
422
|
-
style={{
|
|
423
|
-
scrollbarGutter: 'stable', // Not sure if there is a better way to prevent the scrollbar appearing causing a horizontal scroll - to to resizing, we fix the width of the content, so adding a vertical scrollbar causing a horizontal overflow.
|
|
424
|
-
}}
|
|
425
|
-
>
|
|
420
|
+
<Box minWidth="300px" enableResizeOnEnd height="100%" maxHeight="100%">
|
|
426
421
|
<TableFilterPanel
|
|
427
422
|
dataSource={source}
|
|
428
|
-
onClose={() => window.alert('
|
|
423
|
+
onClose={() => window.alert('onClose callback called.')}
|
|
429
424
|
>
|
|
430
425
|
<StringFieldFilter fieldName="name" label="Name (string)" />
|
|
431
426
|
<NumberFieldFilter fieldName="age" label="Age (number)" />
|
|
@@ -434,8 +429,8 @@ export function WithFilterPanel() {
|
|
|
434
429
|
</TableFilterPanel>
|
|
435
430
|
</Box>
|
|
436
431
|
</Side>
|
|
437
|
-
<
|
|
438
|
-
<Table dataSource={source} height="100%"
|
|
432
|
+
<Body>
|
|
433
|
+
<Table dataSource={source} height="100%">
|
|
439
434
|
<TableColumn heading="Name" fieldName="name" sticky="start" />
|
|
440
435
|
<TableColumn heading="Age" fieldName="age" width={150} canSort />
|
|
441
436
|
<TableColumn heading="Email" fieldName="email" width={300} />
|
|
@@ -449,9 +444,9 @@ export function WithFilterPanel() {
|
|
|
449
444
|
<TableColumn heading="Occupation" fieldName="occupation" width={200} />
|
|
450
445
|
<TableColumn heading="Active" fieldName="isActive" />
|
|
451
446
|
</Table>
|
|
452
|
-
</
|
|
447
|
+
</Body>
|
|
453
448
|
</Rail>
|
|
454
|
-
</
|
|
449
|
+
</Body>
|
|
455
450
|
<Footer>
|
|
456
451
|
<TablePager dataSource={source} />
|
|
457
452
|
</Footer>
|
|
@@ -460,4 +455,139 @@ export function WithFilterPanel() {
|
|
|
460
455
|
);
|
|
461
456
|
}
|
|
462
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
|
+
|
|
463
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"],
|
|
@@ -3,10 +3,11 @@ import {
|
|
|
3
3
|
StaticNumberField,
|
|
4
4
|
StaticStringField,
|
|
5
5
|
} from '@tcn/resource-store';
|
|
6
|
-
import
|
|
6
|
+
import { useState } from 'react';
|
|
7
7
|
import { items } from '../../__stories__/sample_data.js';
|
|
8
8
|
import { NumberFieldFilter } from './field_filters/number_field_filter.js';
|
|
9
9
|
import { TableFilterPanel } from './table_filter_panel.js';
|
|
10
|
+
import { VStack } from '@tcn/ui/stacks';
|
|
10
11
|
|
|
11
12
|
type DataItem = {
|
|
12
13
|
id: string;
|
|
@@ -37,10 +38,10 @@ export const Demo = () => {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
return (
|
|
40
|
-
<
|
|
41
|
+
<VStack width="300px" height="100%">
|
|
41
42
|
<TableFilterPanel dataSource={source}>
|
|
42
43
|
<NumberFieldFilter fieldName="age" label="Age" />
|
|
43
44
|
</TableFilterPanel>
|
|
44
|
-
</
|
|
45
|
+
</VStack>
|
|
45
46
|
);
|
|
46
47
|
};
|
|
@@ -2,17 +2,19 @@ import { DataSource } from '@tcn/resource-store';
|
|
|
2
2
|
import { ReactElement, useState, createContext } from 'react';
|
|
3
3
|
import { FieldFilterProps } from './field_filters/field_filter_props.js';
|
|
4
4
|
import { TableFilterPanelPresenter } from './table_filter_panel_presenter.js';
|
|
5
|
-
import {
|
|
6
|
-
import { Header,
|
|
5
|
+
import { Aside, type PanelProps } from '@tcn/ui/surfaces';
|
|
6
|
+
import { Body, Header, Scaffold, Section } from '@tcn/ui/layouts';
|
|
7
7
|
import styles from './table_filter_panel.module.css';
|
|
8
8
|
import { Spacer } from '@tcn/ui/stacks';
|
|
9
9
|
import { Button } from '@tcn/ui/actions';
|
|
10
10
|
import { CrossIcon } from '@tcn/icons/cross_icon.js';
|
|
11
|
+
import { Title } from '@tcn/ui/typography';
|
|
11
12
|
|
|
12
13
|
export interface TableFilterPanelProps extends Omit<PanelProps, 'children'> {
|
|
13
14
|
children: ReactElement<FieldFilterProps>[] | ReactElement<FieldFilterProps>;
|
|
14
15
|
dataSource: DataSource<any>;
|
|
15
16
|
onClose?: () => void;
|
|
17
|
+
title?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export function TableFilterPanel({
|
|
@@ -20,6 +22,7 @@ export function TableFilterPanel({
|
|
|
20
22
|
dataSource,
|
|
21
23
|
className,
|
|
22
24
|
onClose,
|
|
25
|
+
title = 'Table Filters',
|
|
23
26
|
...props
|
|
24
27
|
}: TableFilterPanelProps) {
|
|
25
28
|
const [presenter] = useState(() => {
|
|
@@ -28,12 +31,9 @@ export function TableFilterPanel({
|
|
|
28
31
|
|
|
29
32
|
return (
|
|
30
33
|
<TableFilterPanelContext.Provider value={presenter}>
|
|
31
|
-
<
|
|
32
|
-
className={`${styles['table-filter-panel']} tcn-table-filter-panel ${className}`}
|
|
33
|
-
{...props}
|
|
34
|
-
>
|
|
34
|
+
<Aside className={`tcn-table-filter-panel ${className}`} {...props}>
|
|
35
35
|
<Header>
|
|
36
|
-
|
|
36
|
+
<Title>{title}</Title>
|
|
37
37
|
<Spacer />
|
|
38
38
|
{onClose && (
|
|
39
39
|
<Button utility hierarchy="tertiary" onClick={onClose}>
|
|
@@ -41,16 +41,18 @@ export function TableFilterPanel({
|
|
|
41
41
|
</Button>
|
|
42
42
|
)}
|
|
43
43
|
</Header>
|
|
44
|
-
<
|
|
44
|
+
<Body
|
|
45
45
|
className={`${styles['table-filter-panel-body']} tcn-table-filter-panel-body`}
|
|
46
46
|
>
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
<Scaffold>
|
|
48
|
+
<Section
|
|
49
|
+
className={`${styles['table-filter-panel-section']} tcn-table-filter-panel-section`}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</Section>
|
|
53
|
+
</Scaffold>
|
|
54
|
+
</Body>
|
|
55
|
+
</Aside>
|
|
54
56
|
</TableFilterPanelContext.Provider>
|
|
55
57
|
);
|
|
56
58
|
}
|