@tcn/ui-table 2.4.1 → 2.4.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.
Files changed (40) hide show
  1. package/dist/components/cells/header_cell.d.ts.map +1 -1
  2. package/dist/components/cells/header_cell.js +17 -16
  3. package/dist/components/cells/header_cell.js.map +1 -1
  4. package/dist/components/table/table.d.ts.map +1 -1
  5. package/dist/components/table/table.js +35 -34
  6. package/dist/components/table/table.js.map +1 -1
  7. package/dist/components/table/table_column.d.ts +1 -0
  8. package/dist/components/table/table_column.d.ts.map +1 -1
  9. package/dist/components/table/table_column.js.map +1 -1
  10. package/dist/components/table/table_presenter.d.ts +1 -0
  11. package/dist/components/table/table_presenter.d.ts.map +1 -1
  12. package/dist/components/table/table_presenter.js.map +1 -1
  13. package/dist/components/table_filter_panel/field_filters/field_filter_props.js +2 -0
  14. package/dist/components/table_filter_panel/field_filters/field_filter_props.js.map +1 -0
  15. package/dist/components/table_filter_panel/field_filters/field_filter_strategy.js +2 -0
  16. package/dist/components/table_filter_panel/field_filters/field_filter_strategy.js.map +1 -0
  17. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.d.ts.map +1 -1
  18. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.js +28 -40
  19. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.js.map +1 -1
  20. package/dist/components/table_filter_panel/field_filters/select_field_filter.d.ts.map +1 -1
  21. package/dist/components/table_filter_panel/field_filters/select_field_filter.js +22 -36
  22. package/dist/components/table_filter_panel/field_filters/select_field_filter.js.map +1 -1
  23. package/dist/components/table_filter_panel/table_filter_panel.d.ts.map +1 -1
  24. package/dist/components/table_filter_panel/table_filter_panel.js +18 -18
  25. package/dist/components/table_filter_panel/table_filter_panel.js.map +1 -1
  26. package/dist/mulit_select_field_filter.css +1 -0
  27. package/dist/table.css +1 -1
  28. package/package.json +17 -17
  29. package/src/__stories__/sample_data.ts +25 -3
  30. package/src/__stories__/table.stories.tsx +59 -39
  31. package/src/components/cells/header_cell.tsx +2 -1
  32. package/src/components/table/table.module.css +20 -0
  33. package/src/components/table/table.tsx +6 -1
  34. package/src/components/table/table_column.tsx +1 -0
  35. package/src/components/table/table_presenter.ts +1 -0
  36. package/src/components/table_filter_panel/field_filters/mulit_select_field_filter.module.css +8 -0
  37. package/src/components/table_filter_panel/field_filters/mulit_select_field_filter.tsx +24 -35
  38. package/src/components/table_filter_panel/field_filters/select_field_filter.tsx +21 -32
  39. package/src/components/table_filter_panel/table_filter_panel.tsx +8 -10
  40. package/tsconfig.json +4 -33
@@ -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;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
+ {"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,2CA6BvB;AAED,eAAO,MAAM,uBAAuB,oEAEnC,CAAC"}
@@ -2,13 +2,13 @@ import { jsx as e, jsxs as l } from "react/jsx-runtime";
2
2
  import { createContext as f, useState as b } from "react";
3
3
  import { TableFilterPanelPresenter as m } from "./table_filter_panel_presenter.js";
4
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({
5
+ import { Header as d, Scaffold as u, Section as y } from "@tcn/ui/layouts";
6
+ import { Spacer as h } from "@tcn/ui/stacks";
7
+ import { Button as P } from "@tcn/ui/actions";
8
+ import { CrossIcon as F } from "@tcn/icons/cross_icon.js";
9
+ import { Title as x } from "@tcn/ui/typography";
10
+ import '../../table_filter_panel.css';const S = "_table-filter-panel-body_c016eb0", T = "_table-filter-panel-section_cfd2eb9", r = { "table-filter-panel-body": S, "table-filter-panel-section": T };
11
+ function H({
12
12
  children: n,
13
13
  dataSource: i,
14
14
  className: a,
@@ -17,32 +17,32 @@ function I({
17
17
  ...c
18
18
  }) {
19
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: [
20
+ return /* @__PURE__ */ e(_.Provider, { value: s, children: /* @__PURE__ */ l(p, { className: `tcn-table-filter-panel ${a}`, ...c, children: [
21
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, {}) })
22
+ /* @__PURE__ */ e(x, { children: o }),
23
+ /* @__PURE__ */ e(h, {}),
24
+ t && /* @__PURE__ */ e(P, { utility: !0, hierarchy: "tertiary", onClick: t, children: /* @__PURE__ */ e(F, {}) })
25
25
  ] }),
26
26
  /* @__PURE__ */ e(
27
- y,
27
+ u,
28
28
  {
29
29
  className: `${r["table-filter-panel-body"]} tcn-table-filter-panel-body`,
30
- children: /* @__PURE__ */ e(h, { children: /* @__PURE__ */ e(
31
- u,
30
+ children: /* @__PURE__ */ e(
31
+ y,
32
32
  {
33
33
  className: `${r["table-filter-panel-section"]} tcn-table-filter-panel-section`,
34
34
  children: n
35
35
  }
36
- ) })
36
+ )
37
37
  }
38
38
  )
39
39
  ] }) });
40
40
  }
41
- const B = f(
41
+ const _ = f(
42
42
  null
43
43
  );
44
44
  export {
45
- I as TableFilterPanel,
46
- B as TableFilterPanelContext
45
+ H as TableFilterPanel,
46
+ _ as TableFilterPanelContext
47
47
  };
48
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 { 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;"}
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 { 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 <Scaffold\n className={`${styles['table-filter-panel-body']} tcn-table-filter-panel-body`}\n >\n <Section\n className={`${styles['table-filter-panel-section']} tcn-table-filter-panel-section`}\n >\n {children}\n </Section>\n </Scaffold>\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","Scaffold","styles","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,UAAA,gBAAAV;AAAA,UAACW;AAAA,UAAA;AAAA,YACC,WAAW,GAAGD,EAAO,4BAA4B,CAAC;AAAA,YAEjD,UAAAnB;AAAA,UAAA;AAAA,QAAA;AAAA,MACH;AAAA,IAAA;AAAA,EACF,EAAA,CACF,EAAA,CACF;AAEJ;AAEO,MAAMU,IAA0BW;AAAA,EACrC;AACF;"}
@@ -0,0 +1 @@
1
+ @layer tcn-system{._multiselect-filter_193bbb6 .tcn-multiselect-values-container{padding:0}}
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}: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 thead tr th{padding-inline:0!important}._table-body_b8c928c thead tr th>div>*{padding-inline:var(--padding-medium)}._table-body_b8c928c thead tr th:first-child>div>*{padding-inline-start:var(--table-pad-inline)}._table-body_b8c928c thead tr th:last-child>div>*{padding-inline-end:var(--table-pad-inline)}._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.1",
3
+ "version": "2.4.3",
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/state": "1.3.2",
44
- "@tcn/resource-store": "2.5.6",
45
43
  "@tcn/icons": "2.3.0",
46
- "@tcn/ui": "0.13.1"
44
+ "@tcn/state": "1.3.3",
45
+ "@tcn/ui": "0.15.0",
46
+ "@tcn/resource-store": "2.5.7"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": "^18.2.0",
@@ -53,21 +53,21 @@
53
53
  "start": "pnpm storybook",
54
54
  "build": "vite build",
55
55
  "clean": "rm -rf dist storybook-static",
56
- "clean-all": "pnpm clean && rm -rf node_modules",
56
+ "clean:all": "pnpm clean && rm -rf node_modules",
57
57
  "test": "vitest run",
58
- "test-coverage": "vitest run --coverage",
59
- "check-all": "concurrently 'pnpm check-types' 'pnpm run biome check .'",
60
- "check-types": "tsc --project tsconfig.typecheck.json --noEmit",
61
- "check-format": "pnpm run biome format .",
62
- "check-lint": "pnpm run biome lint .",
63
- "check-imports": "pnpm run biome check --formatter-enabled=false --linter-enabled=false --assist-enabled=true",
64
- "fix-all": "pnpm run biome check --write",
65
- "fix-format": "pnpm run biome format --write",
66
- "fix-lint": "pnpm run biome lint --write",
67
- "fix-imports": "pnpm run biome check --write --unsafe --formatter-enabled=false --linter-enabled=false --assist-enabled=true",
68
- "publish-dry-run": "pnpm build && pnpm publish --dry-run --force --no-git-checks",
58
+ "test:coverage": "vitest run --coverage",
59
+ "check:all": "concurrently 'pnpm check:types' 'pnpm run biome check .'",
60
+ "check:types": "tsc --project tsconfig.typecheck.json --noEmit",
61
+ "check:format": "pnpm run biome format .",
62
+ "check:lint": "pnpm run biome lint .",
63
+ "check:imports": "pnpm run biome check --formatter-enabled=false --linter-enabled=false --assist-enabled=true",
64
+ "fix:all": "pnpm run biome check --write",
65
+ "fix:format": "pnpm run biome format --write",
66
+ "fix:lint": "pnpm run biome lint --write",
67
+ "fix:imports": "pnpm run biome check --write --unsafe --formatter-enabled=false --linter-enabled=false --assist-enabled=true",
68
+ "publish:dry-run": "pnpm build && pnpm publish --dry-run --force --no-git-checks",
69
69
  "biome": "pnpm exec biome",
70
- "storybook": "bash ../../scripts/ensure-addon-built.sh && storybook dev",
70
+ "storybook": "bash ../../scripts/ensure-blackcat-addon-built.sh && storybook dev",
71
71
  "storybook:silent": "storybook dev --no-open --disable-telemetry --quiet",
72
72
  "storybook:build": "storybook build"
73
73
  }
@@ -1,3 +1,25 @@
1
+ export const OCCUPATIONS = [
2
+ 'Adventurer',
3
+ 'Artist',
4
+ 'Assassin',
5
+ 'Chef',
6
+ 'Child',
7
+ 'Detective',
8
+ 'King',
9
+ 'Merchant',
10
+ 'Mistborn',
11
+ 'Musician',
12
+ 'Queen',
13
+ 'Scientist',
14
+ 'Soldier',
15
+ 'Soother',
16
+ 'Student',
17
+ 'Tin-eye',
18
+ 'Writer',
19
+ ] as const;
20
+
21
+ export type Occupation = (typeof OCCUPATIONS)[number];
22
+
1
23
  export type DataItem = {
2
24
  id: string;
3
25
  name: string;
@@ -6,12 +28,12 @@ export type DataItem = {
6
28
  email: string;
7
29
  city: string;
8
30
  country: string;
9
- occupation: string;
31
+ occupation: Occupation;
10
32
  isActive: boolean;
11
33
  birthdate: Date;
12
34
  };
13
35
 
14
- export const items = [
36
+ export const items: DataItem[] = [
15
37
  {
16
38
  id: 'one',
17
39
  name: 'Sam Spade And the Longest Name that you have ever seen and it will just keep going cause we are going to test the table and see how it handles this',
@@ -1370,7 +1392,7 @@ export const items = [
1370
1392
  },
1371
1393
  ];
1372
1394
 
1373
- export const stickyItems = [
1395
+ export const stickyItems: DataItem[] = [
1374
1396
  {
1375
1397
  id: 'two',
1376
1398
  name: 'Milo',
@@ -12,7 +12,7 @@ import {
12
12
  import { Signal, useSignalValue } from '@tcn/state';
13
13
  import { Button } from '@tcn/ui/actions';
14
14
  import { Checkbox } from '@tcn/ui/inputs';
15
- import { Footer, Header, Rail, Side, Body } from '@tcn/ui/layouts';
15
+ import { Footer, Header, Rail, Side, Scaffold } from '@tcn/ui/layouts';
16
16
  import { Box, HStack, Spacer, VStack, ZStack } from '@tcn/ui/stacks';
17
17
  import { Panel } from '@tcn/ui/surfaces';
18
18
  import { Title } from '@tcn/ui/typography';
@@ -20,12 +20,14 @@ import { GlobalSearch } from '../components/global_search.js';
20
20
  import { Table } from '../components/table/table.js';
21
21
  import { TableColumn } from '../components/table/table_column.js';
22
22
  import { DateFieldFilter } from '../components/table_filter_panel/field_filters/date_field_filter.js';
23
+ import { MulitSelectFieldFilter } from '../components/table_filter_panel/field_filters/mulit_select_field_filter.js';
23
24
  import { NumberFieldFilter } from '../components/table_filter_panel/field_filters/number_field_filter.js';
24
25
  import { NumberRangeFieldFilter } from '../components/table_filter_panel/field_filters/number_range_field_filter.js';
26
+ import { SelectFieldFilter } from '../components/table_filter_panel/field_filters/select_field_filter.js';
25
27
  import { StringFieldFilter } from '../components/table_filter_panel/field_filters/string_field_filter.js';
26
28
  import { TableFilterPanel } from '../components/table_filter_panel/table_filter_panel.js';
27
29
  import { TablePager } from '../components/table_pager.js';
28
- import { DataItem, items, stickyItems } from './sample_data.js';
30
+ import { DataItem, items, OCCUPATIONS, stickyItems } from './sample_data.js';
29
31
  import styles from './table.module.css';
30
32
 
31
33
  const meta: Meta = {
@@ -310,7 +312,7 @@ export function WithTableHeaderAndTableFooter() {
310
312
  <Spacer />
311
313
  <GlobalSearch dataSource={source} />
312
314
  </Header>
313
- <Body height="flex">
315
+ <Scaffold>
314
316
  <Table
315
317
  dataSource={source}
316
318
  onRowClick={item => {
@@ -325,7 +327,7 @@ export function WithTableHeaderAndTableFooter() {
325
327
  <TableColumn heading="Occupation" fieldName="occupation" />
326
328
  <TableColumn heading="Active" fieldName="isActive" />
327
329
  </Table>
328
- </Body>
330
+ </Scaffold>
329
331
  <Footer>
330
332
  <TablePager dataSource={source} />
331
333
  </Footer>
@@ -413,40 +415,49 @@ export function WithFilterPanel() {
413
415
  <StoryWrapper>
414
416
  <Panel height="100%">
415
417
  <Header>The Table</Header>
416
- <Body>
417
- <Rail>
418
- {/* TODO: Refactor to remove duplicate column logic (outer Side and inner Aside - in TableFilterPanel ) */}
419
- <Side padding="0px">
420
- <Box minWidth="300px" enableResizeOnEnd height="100%" maxHeight="100%">
421
- <TableFilterPanel
422
- dataSource={source}
423
- onClose={() => window.alert('onClose callback called.')}
424
- >
425
- <StringFieldFilter fieldName="name" label="Name (string)" />
426
- <NumberFieldFilter fieldName="age" label="Age (number)" />
427
- <DateFieldFilter fieldName="birthdate" label="Birthdate (date range)" />
428
- <NumberRangeFieldFilter fieldName="age" label="Age (number range)" />
429
- </TableFilterPanel>
430
- </Box>
431
- </Side>
432
- <Body>
433
- <Table dataSource={source} height="100%">
434
- <TableColumn heading="Name" fieldName="name" sticky="start" />
435
- <TableColumn heading="Age" fieldName="age" width={150} canSort />
436
- <TableColumn heading="Email" fieldName="email" width={300} />
437
- <TableColumn heading="City" fieldName="city" width={300} />
438
- <TableColumn
439
- heading="Birthdate"
440
- fieldName="birthdate"
441
- width={300}
442
- render={(i: DataItem) => i.birthdate.toLocaleDateString()}
418
+ <Rail>
419
+ {/* TODO: Refactor to remove duplicate column logic (outer Side and inner Aside - in TableFilterPanel ) */}
420
+ <Side padding="0px">
421
+ <Box minWidth="300px" enableResizeOnEnd height="100%" maxHeight="100%">
422
+ <TableFilterPanel
423
+ dataSource={source}
424
+ onClose={() => window.alert('onClose callback called.')}
425
+ >
426
+ <StringFieldFilter fieldName="name" label="Name (string)" />
427
+ <NumberFieldFilter fieldName="age" label="Age (number)" />
428
+ <DateFieldFilter fieldName="birthdate" label="Birthdate (date range)" />
429
+ <NumberRangeFieldFilter fieldName="age" label="Age (number range)" />
430
+ <SelectFieldFilter
431
+ fieldName="isActive"
432
+ label="Active (select)"
433
+ options={[
434
+ { label: 'Active', value: 'Yes' },
435
+ { label: 'Inactive', value: 'No' },
436
+ ]}
437
+ />
438
+ <MulitSelectFieldFilter
439
+ fieldName="occupation"
440
+ label="Occupation (multi-select)"
441
+ options={OCCUPATIONS.map(o => ({ label: o, value: o }))}
443
442
  />
444
- <TableColumn heading="Occupation" fieldName="occupation" width={200} />
445
- <TableColumn heading="Active" fieldName="isActive" />
446
- </Table>
447
- </Body>
448
- </Rail>
449
- </Body>
443
+ </TableFilterPanel>
444
+ </Box>
445
+ </Side>
446
+ <Table dataSource={source} height="100%">
447
+ <TableColumn heading="Name" fieldName="name" sticky="start" />
448
+ <TableColumn heading="Age" fieldName="age" width={150} canSort />
449
+ <TableColumn heading="Email" fieldName="email" width={300} />
450
+ <TableColumn heading="City" fieldName="city" width={300} />
451
+ <TableColumn
452
+ heading="Birthdate"
453
+ fieldName="birthdate"
454
+ width={300}
455
+ render={(i: DataItem) => i.birthdate.toLocaleDateString()}
456
+ />
457
+ <TableColumn heading="Occupation" fieldName="occupation" width={200} />
458
+ <TableColumn heading="Active" fieldName="isActive" />
459
+ </Table>
460
+ </Rail>
450
461
  <Footer>
451
462
  <TablePager dataSource={source} />
452
463
  </Footer>
@@ -548,6 +559,13 @@ const ColumnCheck = ({ presenter }: { presenter: SelectionPresenter<DataItem> })
548
559
  );
549
560
  };
550
561
 
562
+ /**
563
+ * Demonstrates row selection with a checkbox column.
564
+ *
565
+ * Recommendation: set `canResize={false}` on fixed-width utility columns (e.g. checkboxes,
566
+ * icons, actions). The resize handle overlaps narrow column content and the column has no
567
+ * meaningful width to expose to the user.
568
+ */
551
569
  export function SelectionTable() {
552
570
  const [source] = useState(() => {
553
571
  return new StaticDataSource<DataItem>(items, [
@@ -568,12 +586,14 @@ export function SelectionTable() {
568
586
  <StoryWrapper>
569
587
  <Panel>
570
588
  <Header>Selection Table</Header>
571
- <Body>
589
+ <Scaffold>
572
590
  <Table dataSource={source}>
591
+ {/* canResize={false} recommended for fixed-width utility columns — resize handle overlaps narrow content */}
573
592
  <TableColumn
574
593
  heading={<ColumnCheck presenter={presenter} />}
575
594
  sticky="start"
576
595
  width={48}
596
+ canResize={false}
577
597
  render={(item: DataItem) => <RowCheck item={item} presenter={presenter} />}
578
598
  />
579
599
  <TableColumn heading="Name" fieldName="name" />
@@ -584,7 +604,7 @@ export function SelectionTable() {
584
604
  <TableColumn heading="Occupation" fieldName="occupation" />
585
605
  <TableColumn heading="Active" fieldName="isActive" />
586
606
  </Table>
587
- </Body>
607
+ </Scaffold>
588
608
  </Panel>
589
609
  </StoryWrapper>
590
610
  );
@@ -43,8 +43,9 @@ export function HeaderCell({
43
43
  overflow="hidden"
44
44
  minWidth="24px"
45
45
  maxWidth="unset"
46
+ height="100%"
46
47
  width={width}
47
- enableResizeOnEnd
48
+ enableResizeOnEnd={onResize != null}
48
49
  onWidthResize={handleResize}
49
50
  onClick={e => e.stopPropagation()}
50
51
  >
@@ -49,6 +49,26 @@
49
49
  z-index: 2;
50
50
  }
51
51
 
52
+ /* Shim: #277 — remove when #283 is resolved (Resizeable decorator on TH replaces this).
53
+ * Theme padding-inline on thead th conflicts with the resize Box sizing — the Box has the
54
+ * same fixed width as the TH, so theme padding causes the Box to overflow the TH content
55
+ * area, pushing the resize handle and sort action out of alignment.
56
+ * Zero out padding on the TH and re-apply it on the inner content child (under the Box)
57
+ * so it doesn't affect Box width or resize handle position. */
58
+ .table-body thead tr th {
59
+ padding-inline: 0 !important;
60
+ }
61
+ .table-body thead tr th > div > * {
62
+ padding-inline: var(--padding-medium);
63
+ }
64
+ .table-body thead tr th:first-child > div > * {
65
+ padding-inline-start: var(--table-pad-inline);
66
+ }
67
+ .table-body thead tr th:last-child > div > * {
68
+ padding-inline-end: var(--table-pad-inline);
69
+ }
70
+ /* End shim: #277 */
71
+
52
72
  .table-body th > div {
53
73
  display: flex;
54
74
  align-items: center;
@@ -72,6 +72,7 @@ export function Table<T>({
72
72
  width: column.props.width ?? 100,
73
73
  sortMode: 'none',
74
74
  canSort: column.props.canSort ?? false,
75
+ canResize: column.props.canResize ?? true,
75
76
  heading: column.props.heading,
76
77
  footer: column.props.footer,
77
78
  sticky: column.props.sticky,
@@ -107,7 +108,11 @@ export function Table<T>({
107
108
  index={index}
108
109
  heading={wrapContent(column.heading)}
109
110
  sticky={column.sticky}
110
- onResize={newSize => presenter.setColumnWidth(index, newSize)}
111
+ onResize={
112
+ column.canResize !== false
113
+ ? newSize => presenter.setColumnWidth(index, newSize)
114
+ : undefined
115
+ }
111
116
  width={column.width}
112
117
  sortMode={column.sortMode}
113
118
  onSortModeChange={() => presenter.setNextColumnSortMode(index)}
@@ -4,6 +4,7 @@ type BaseTableColumnProps<T> = {
4
4
  render?: (item: T) => React.ReactNode;
5
5
  sticky?: 'start' | 'end';
6
6
  width?: number;
7
+ canResize?: boolean;
7
8
  };
8
9
 
9
10
  type SortableTableColumnProps<T> = BaseTableColumnProps<T> & {
@@ -16,6 +16,7 @@ export type ColumnInfo = {
16
16
  footer?: ReactNode;
17
17
  sticky?: 'start' | 'end';
18
18
  fieldName?: string;
19
+ canResize?: boolean;
19
20
  };
20
21
 
21
22
  export class TablePresenter {
@@ -0,0 +1,8 @@
1
+ @layer tcn-system {
2
+ /* FIXME: MultiselectValues renders with padding: 4px and min-width: 100% (content-box),
3
+ causing it to overflow its parent. Zeroing the padding here as a local workaround until
4
+ the component supports box-sizing: border-box or a className passthrough. */
5
+ .multiselect-filter :global(.tcn-multiselect-values-container) {
6
+ padding: 0;
7
+ }
8
+ }
@@ -1,11 +1,10 @@
1
1
  import { useSignalValue } from '@tcn/state';
2
2
  import { Multiselect, Option } from '@tcn/ui/inputs';
3
- import { Box, HStack, VStack } from '@tcn/ui/stacks';
4
- import { Title } from '@tcn/ui/typography';
5
3
  import { FieldFilterProps } from './field_filter_props.js';
6
4
  import { MultiSelectFieldFilterPresenter } from './multi_select_field_filter_presenter.js';
7
5
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
8
- import { ClearFilterButton } from './clear_button.js';
6
+ import { ClearableField } from './clearable_field.js';
7
+ import styles from './mulit_select_field_filter.module.css';
9
8
 
10
9
  export type MulitSelectFieldFilterProps = FieldFilterProps & {
11
10
  options: { label: string; value: string | boolean | number }[];
@@ -25,38 +24,28 @@ export function MulitSelectFieldFilter({
25
24
  .filter(Boolean) ?? [];
26
25
 
27
26
  return (
28
- <VStack gap="4px">
29
- <Box width="flex">
30
- <Title size="md">{label}</Title>
31
- </Box>
32
- <HStack>
33
- <Box width="flex">
34
- <Multiselect
35
- value={selectedLabels}
36
- onChange={selectedLabels => {
37
- const realValues = selectedLabels
38
- .map(label => options.find(option => option.label === label)?.value)
39
- .filter(value => value !== undefined) as (string | boolean | number)[];
27
+ <ClearableField
28
+ label={label}
29
+ onClear={() => presenter.setValue(null)}
30
+ isClearable={values == null || values.length === 0}
31
+ className={styles['multiselect-filter']}
32
+ >
33
+ <Multiselect
34
+ value={selectedLabels}
35
+ onChange={selectedLabels => {
36
+ const realValues = selectedLabels
37
+ .map(label => options.find(option => option.label === label)?.value)
38
+ .filter(value => value !== undefined) as (string | boolean | number)[];
40
39
 
41
- presenter.setValue(realValues.length > 0 ? realValues : null);
42
- }}
43
- >
44
- {options.map(option => (
45
- <Option
46
- key={option.value.toString()}
47
- value={option.label}
48
- label={option.label}
49
- >
50
- {option.label}
51
- </Option>
52
- ))}
53
- </Multiselect>
54
- </Box>
55
- <ClearFilterButton
56
- onClick={() => presenter.setValue(null)}
57
- disabled={values == null || values.length === 0}
58
- />
59
- </HStack>
60
- </VStack>
40
+ presenter.setValue(realValues.length > 0 ? realValues : null);
41
+ }}
42
+ >
43
+ {options.map(option => (
44
+ <Option key={option.value.toString()} value={option.label} label={option.label}>
45
+ {option.label}
46
+ </Option>
47
+ ))}
48
+ </Multiselect>
49
+ </ClearableField>
61
50
  );
62
51
  }
@@ -1,11 +1,9 @@
1
1
  import { useSignalValue } from '@tcn/state';
2
2
  import { Option, Select } from '@tcn/ui/inputs';
3
- import { Box, HStack, VStack } from '@tcn/ui/stacks';
4
- import { Title } from '@tcn/ui/typography';
5
3
  import { FieldFilterProps } from './field_filter_props.js';
6
4
  import { SelectFieldFilterPresenter } from './select_field_filter_presenter.js';
7
5
  import { useFieldFilterStrategy } from './use_field_filter_strategy.js';
8
- import { ClearFilterButton } from './clear_button.js';
6
+ import { ClearableField } from './clearable_field.js';
9
7
 
10
8
  export type SelectFieldFilterProps = FieldFilterProps & {
11
9
  options: { label: string; value: string | boolean | number }[];
@@ -18,34 +16,25 @@ export function SelectFieldFilter({ fieldName, label, options }: SelectFieldFilt
18
16
  const valueLabel = options.find(option => option.value === value)?.label ?? '';
19
17
 
20
18
  return (
21
- <VStack gap="4px">
22
- <Box width="flex">
23
- <Title size="md">{label}</Title>
24
- </Box>
25
- <HStack>
26
- <Select
27
- value={valueLabel}
28
- onChange={value => {
29
- const realValue = options.find(option => option.label === value)?.value;
30
- presenter.setValue(realValue ?? null);
31
- }}
32
- width="flex"
33
- >
34
- {options.map(option => (
35
- <Option
36
- key={option.value.toString()}
37
- value={option.label}
38
- label={option.label}
39
- >
40
- {option.label}
41
- </Option>
42
- ))}
43
- </Select>
44
- <ClearFilterButton
45
- onClick={() => presenter.setValue(null)}
46
- disabled={value == null}
47
- />
48
- </HStack>
49
- </VStack>
19
+ <ClearableField
20
+ label={label}
21
+ onClear={() => presenter.setValue(null)}
22
+ isClearable={value == null}
23
+ >
24
+ <Select
25
+ value={valueLabel}
26
+ onChange={value => {
27
+ const realValue = options.find(option => option.label === value)?.value;
28
+ presenter.setValue(realValue ?? null);
29
+ }}
30
+ width="flex"
31
+ >
32
+ {options.map(option => (
33
+ <Option key={option.value.toString()} value={option.label} label={option.label}>
34
+ {option.label}
35
+ </Option>
36
+ ))}
37
+ </Select>
38
+ </ClearableField>
50
39
  );
51
40
  }