@tcn/ui-table 2.4.0 → 2.4.2

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 +28 -27
  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 +195 -38
  31. package/src/components/cells/header_cell.tsx +4 -3
  32. package/src/components/table/table.module.css +20 -10
  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":"select_field_filter.js","sources":["../../../../src/components/table_filter_panel/field_filters/select_field_filter.tsx"],"sourcesContent":["import { useSignalValue } from '@tcn/state';\nimport { Option, Select } from '@tcn/ui/inputs';\nimport { Box, HStack, VStack } from '@tcn/ui/stacks';\nimport { Title } from '@tcn/ui/typography';\nimport { FieldFilterProps } from './field_filter_props.js';\nimport { SelectFieldFilterPresenter } from './select_field_filter_presenter.js';\nimport { useFieldFilterStrategy } from './use_field_filter_strategy.js';\nimport { ClearFilterButton } from './clear_button.js';\n\nexport type SelectFieldFilterProps = FieldFilterProps & {\n options: { label: string; value: string | boolean | number }[];\n};\n\nexport function SelectFieldFilter({ fieldName, label, options }: SelectFieldFilterProps) {\n const presenter = useFieldFilterStrategy(SelectFieldFilterPresenter, fieldName);\n\n const value = useSignalValue(presenter.broadcasts.value);\n const valueLabel = options.find(option => option.value === value)?.label ?? '';\n\n return (\n <VStack gap=\"4px\">\n <Box width=\"flex\">\n <Title size=\"md\">{label}</Title>\n </Box>\n <HStack>\n <Select\n value={valueLabel}\n onChange={value => {\n const realValue = options.find(option => option.label === value)?.value;\n presenter.setValue(realValue ?? null);\n }}\n width=\"flex\"\n >\n {options.map(option => (\n <Option\n key={option.value.toString()}\n value={option.label}\n label={option.label}\n >\n {option.label}\n </Option>\n ))}\n </Select>\n <ClearFilterButton\n onClick={() => presenter.setValue(null)}\n disabled={value == null}\n />\n </HStack>\n </VStack>\n );\n}\n"],"names":["SelectFieldFilter","fieldName","label","options","presenter","useFieldFilterStrategy","SelectFieldFilterPresenter","value","useSignalValue","valueLabel","option","jsxs","VStack","jsx","Box","Title","HStack","Select","realValue","Option","ClearFilterButton"],"mappings":";;;;;;;;AAaO,SAASA,EAAkB,EAAE,WAAAC,GAAW,OAAAC,GAAO,SAAAC,KAAmC;AACvF,QAAMC,IAAYC,EAAuBC,GAA4BL,CAAS,GAExEM,IAAQC,EAAeJ,EAAU,WAAW,KAAK,GACjDK,IAAaN,EAAQ,KAAK,CAAAO,MAAUA,EAAO,UAAUH,CAAK,GAAG,SAAS;AAE5E,SACE,gBAAAI,EAACC,GAAA,EAAO,KAAI,OACV,UAAA;AAAA,IAAA,gBAAAC,EAACC,GAAA,EAAI,OAAM,QACT,UAAA,gBAAAD,EAACE,KAAM,MAAK,MAAM,aAAM,EAAA,CAC1B;AAAA,sBACCC,GAAA,EACC,UAAA;AAAA,MAAA,gBAAAH;AAAA,QAACI;AAAA,QAAA;AAAA,UACC,OAAOR;AAAA,UACP,UAAU,CAAAF,MAAS;AACjB,kBAAMW,IAAYf,EAAQ,KAAK,OAAUO,EAAO,UAAUH,CAAK,GAAG;AAClE,YAAAH,EAAU,SAASc,KAAa,IAAI;AAAA,UACtC;AAAA,UACA,OAAM;AAAA,UAEL,UAAAf,EAAQ,IAAI,CAAAO,MACX,gBAAAG;AAAA,YAACM;AAAA,YAAA;AAAA,cAEC,OAAOT,EAAO;AAAA,cACd,OAAOA,EAAO;AAAA,cAEb,UAAAA,EAAO;AAAA,YAAA;AAAA,YAJHA,EAAO,MAAM,SAAA;AAAA,UAAS,CAM9B;AAAA,QAAA;AAAA,MAAA;AAAA,MAEH,gBAAAG;AAAA,QAACO;AAAA,QAAA;AAAA,UACC,SAAS,MAAMhB,EAAU,SAAS,IAAI;AAAA,UACtC,UAAUG,KAAS;AAAA,QAAA;AAAA,MAAA;AAAA,IACrB,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"select_field_filter.js","sources":["../../../../src/components/table_filter_panel/field_filters/select_field_filter.tsx"],"sourcesContent":["import { useSignalValue } from '@tcn/state';\nimport { Option, Select } from '@tcn/ui/inputs';\nimport { FieldFilterProps } from './field_filter_props.js';\nimport { SelectFieldFilterPresenter } from './select_field_filter_presenter.js';\nimport { useFieldFilterStrategy } from './use_field_filter_strategy.js';\nimport { ClearableField } from './clearable_field.js';\n\nexport type SelectFieldFilterProps = FieldFilterProps & {\n options: { label: string; value: string | boolean | number }[];\n};\n\nexport function SelectFieldFilter({ fieldName, label, options }: SelectFieldFilterProps) {\n const presenter = useFieldFilterStrategy(SelectFieldFilterPresenter, fieldName);\n\n const value = useSignalValue(presenter.broadcasts.value);\n const valueLabel = options.find(option => option.value === value)?.label ?? '';\n\n return (\n <ClearableField\n label={label}\n onClear={() => presenter.setValue(null)}\n isClearable={value == null}\n >\n <Select\n value={valueLabel}\n onChange={value => {\n const realValue = options.find(option => option.label === value)?.value;\n presenter.setValue(realValue ?? null);\n }}\n width=\"flex\"\n >\n {options.map(option => (\n <Option key={option.value.toString()} value={option.label} label={option.label}>\n {option.label}\n </Option>\n ))}\n </Select>\n </ClearableField>\n );\n}\n"],"names":["SelectFieldFilter","fieldName","label","options","presenter","useFieldFilterStrategy","SelectFieldFilterPresenter","value","useSignalValue","valueLabel","option","jsx","ClearableField","Select","realValue","Option"],"mappings":";;;;;;AAWO,SAASA,EAAkB,EAAE,WAAAC,GAAW,OAAAC,GAAO,SAAAC,KAAmC;AACvF,QAAMC,IAAYC,EAAuBC,GAA4BL,CAAS,GAExEM,IAAQC,EAAeJ,EAAU,WAAW,KAAK,GACjDK,IAAaN,EAAQ,KAAK,CAAAO,MAAUA,EAAO,UAAUH,CAAK,GAAG,SAAS;AAE5E,SACE,gBAAAI;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,OAAAV;AAAA,MACA,SAAS,MAAME,EAAU,SAAS,IAAI;AAAA,MACtC,aAAaG,KAAS;AAAA,MAEtB,UAAA,gBAAAI;AAAA,QAACE;AAAA,QAAA;AAAA,UACC,OAAOJ;AAAA,UACP,UAAU,CAAAF,MAAS;AACjB,kBAAMO,IAAYX,EAAQ,KAAK,OAAUO,EAAO,UAAUH,CAAK,GAAG;AAClE,YAAAH,EAAU,SAASU,KAAa,IAAI;AAAA,UACtC;AAAA,UACA,OAAM;AAAA,UAEL,YAAQ,IAAI,CAAAJ,MACX,gBAAAC,EAACI,GAAA,EAAqC,OAAOL,EAAO,OAAO,OAAOA,EAAO,OACtE,UAAAA,EAAO,MAAA,GADGA,EAAO,MAAM,UAE1B,CACD;AAAA,QAAA;AAAA,MAAA;AAAA,IACH;AAAA,EAAA;AAGN;"}
@@ -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}._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 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.0",
3
+ "version": "2.4.2",
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/state": "1.3.3",
42
43
  "@tcn/aip-160": "1.2.5",
43
44
  "@tcn/icons": "2.3.0",
44
- "@tcn/resource-store": "2.5.6",
45
- "@tcn/state": "1.3.2",
46
- "@tcn/ui": "0.13.0"
45
+ "@tcn/resource-store": "2.5.7",
46
+ "@tcn/ui": "0.14.0"
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',
@@ -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 { Footer, Header, Rail, Side, Body } from '@tcn/ui/layouts';
14
+ import { Checkbox } from '@tcn/ui/inputs';
15
+ import { Footer, Header, Rail, Side, Scaffold } 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';
@@ -18,12 +20,14 @@ import { GlobalSearch } from '../components/global_search.js';
18
20
  import { Table } from '../components/table/table.js';
19
21
  import { TableColumn } from '../components/table/table_column.js';
20
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';
21
24
  import { NumberFieldFilter } from '../components/table_filter_panel/field_filters/number_field_filter.js';
22
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';
23
27
  import { StringFieldFilter } from '../components/table_filter_panel/field_filters/string_field_filter.js';
24
28
  import { TableFilterPanel } from '../components/table_filter_panel/table_filter_panel.js';
25
29
  import { TablePager } from '../components/table_pager.js';
26
- import { DataItem, items, stickyItems } from './sample_data.js';
30
+ import { DataItem, items, OCCUPATIONS, stickyItems } from './sample_data.js';
27
31
  import styles from './table.module.css';
28
32
 
29
33
  const meta: Meta = {
@@ -308,7 +312,7 @@ export function WithTableHeaderAndTableFooter() {
308
312
  <Spacer />
309
313
  <GlobalSearch dataSource={source} />
310
314
  </Header>
311
- <Body height="flex">
315
+ <Scaffold>
312
316
  <Table
313
317
  dataSource={source}
314
318
  onRowClick={item => {
@@ -323,7 +327,7 @@ export function WithTableHeaderAndTableFooter() {
323
327
  <TableColumn heading="Occupation" fieldName="occupation" />
324
328
  <TableColumn heading="Active" fieldName="isActive" />
325
329
  </Table>
326
- </Body>
330
+ </Scaffold>
327
331
  <Footer>
328
332
  <TablePager dataSource={source} />
329
333
  </Footer>
@@ -411,40 +415,49 @@ export function WithFilterPanel() {
411
415
  <StoryWrapper>
412
416
  <Panel height="100%">
413
417
  <Header>The Table</Header>
414
- <Body>
415
- <Rail>
416
- {/* TODO: Refactor to remove duplicate column logic (outer Side and inner Aside - in TableFilterPanel ) */}
417
- <Side padding="0px">
418
- <Box minWidth="300px" enableResizeOnEnd height="100%" maxHeight="100%">
419
- <TableFilterPanel
420
- dataSource={source}
421
- onClose={() => window.alert('onClose callback called.')}
422
- >
423
- <StringFieldFilter fieldName="name" label="Name (string)" />
424
- <NumberFieldFilter fieldName="age" label="Age (number)" />
425
- <DateFieldFilter fieldName="birthdate" label="Birthdate (date range)" />
426
- <NumberRangeFieldFilter fieldName="age" label="Age (number range)" />
427
- </TableFilterPanel>
428
- </Box>
429
- </Side>
430
- <Body>
431
- <Table dataSource={source} height="100%">
432
- <TableColumn heading="Name" fieldName="name" sticky="start" />
433
- <TableColumn heading="Age" fieldName="age" width={150} canSort />
434
- <TableColumn heading="Email" fieldName="email" width={300} />
435
- <TableColumn heading="City" fieldName="city" width={300} />
436
- <TableColumn
437
- heading="Birthdate"
438
- fieldName="birthdate"
439
- width={300}
440
- 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 }))}
441
442
  />
442
- <TableColumn heading="Occupation" fieldName="occupation" width={200} />
443
- <TableColumn heading="Active" fieldName="isActive" />
444
- </Table>
445
- </Body>
446
- </Rail>
447
- </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>
448
461
  <Footer>
449
462
  <TablePager dataSource={source} />
450
463
  </Footer>
@@ -453,4 +466,148 @@ export function WithFilterPanel() {
453
466
  );
454
467
  }
455
468
 
469
+ // Selection Table
470
+
471
+ type SelectionStatus = 'none' | 'some' | 'all';
472
+
473
+ class SelectionPresenter<T extends { id: string }> {
474
+ private _selected = new Signal<Set<string>>(new Set());
475
+ private _status = new Signal<SelectionStatus>('none');
476
+ private _dataSource: DataSource<T>;
477
+
478
+ constructor(dataSource: DataSource<T>) {
479
+ this._dataSource = dataSource;
480
+ }
481
+
482
+ get broadcasts() {
483
+ return {
484
+ selected: this._selected.broadcast,
485
+ status: this._status.broadcast,
486
+ };
487
+ }
488
+
489
+ private getTotalRowCount(): number {
490
+ return this._dataSource.broadcasts.currentResults.get().length;
491
+ }
492
+
493
+ private getSelectedCount(): number {
494
+ return this._selected.get().size;
495
+ }
496
+
497
+ private getNextStatus() {
498
+ const selectedCount = this.getSelectedCount();
499
+ const totalCount = this.getTotalRowCount();
500
+ if (selectedCount === totalCount) return 'all';
501
+ if (selectedCount === 0) return 'none';
502
+ if (selectedCount < totalCount) return 'some';
503
+ return 'none';
504
+ }
505
+
506
+ private setStatus() {
507
+ const status = this.getNextStatus();
508
+ this._status.set(status);
509
+ }
510
+
511
+ toggleRow(row: T) {
512
+ this._selected.transform(selected => {
513
+ if (selected.has(row.id)) selected.delete(row.id);
514
+ else selected.add(row.id);
515
+ return selected;
516
+ });
517
+ this.setStatus();
518
+ }
519
+
520
+ private getAllIds(): string[] {
521
+ return this._dataSource.broadcasts.currentResults.get().map((r: T) => r.id);
522
+ }
523
+
524
+ toggleAllRows() {
525
+ const status = this._status.get();
526
+ if (status === 'all') {
527
+ this._selected.set(new Set());
528
+ } else {
529
+ this._selected.set(new Set(this.getAllIds()));
530
+ }
531
+ this.setStatus();
532
+ }
533
+ }
534
+
535
+ const RowCheck = ({
536
+ item,
537
+ presenter,
538
+ }: {
539
+ item: DataItem;
540
+ presenter: SelectionPresenter<DataItem>;
541
+ }) => {
542
+ const selected = useSignalValue(presenter.broadcasts.selected);
543
+ const status = useSignalValue(presenter.broadcasts.status);
544
+ const isChecked = selected.has(item.id) || status === 'all';
545
+ return <Checkbox checked={isChecked} onChange={() => presenter.toggleRow(item)} />;
546
+ };
547
+
548
+ const ColumnCheck = ({ presenter }: { presenter: SelectionPresenter<DataItem> }) => {
549
+ const status = useSignalValue(presenter.broadcasts.status);
550
+ const isChecked = status === 'all';
551
+
552
+ return (
553
+ <Checkbox
554
+ checked={isChecked}
555
+ data-indeterminate={status === 'some'}
556
+ onChange={() => presenter.toggleAllRows()}
557
+ onClick={e => e.stopPropagation()}
558
+ />
559
+ );
560
+ };
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
+ */
569
+ export function SelectionTable() {
570
+ const [source] = useState(() => {
571
+ return new StaticDataSource<DataItem>(items, [
572
+ new StaticStringField('id', i => i.id),
573
+ new StaticStringField('name', i => i.name),
574
+ new StaticNumberField('age', i => i.age),
575
+ new StaticStringField('email', i => i.email),
576
+ new StaticStringField('city', i => i.city),
577
+ new StaticStringField('country', i => i.country),
578
+ new StaticStringField('occupation', i => i.occupation),
579
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
580
+ ]);
581
+ });
582
+
583
+ const [presenter] = useState(() => new SelectionPresenter<DataItem>(source));
584
+
585
+ return (
586
+ <StoryWrapper>
587
+ <Panel>
588
+ <Header>Selection Table</Header>
589
+ <Scaffold>
590
+ <Table dataSource={source}>
591
+ {/* canResize={false} recommended for fixed-width utility columns — resize handle overlaps narrow content */}
592
+ <TableColumn
593
+ heading={<ColumnCheck presenter={presenter} />}
594
+ sticky="start"
595
+ width={48}
596
+ canResize={false}
597
+ render={(item: DataItem) => <RowCheck item={item} presenter={presenter} />}
598
+ />
599
+ <TableColumn heading="Name" fieldName="name" />
600
+ <TableColumn heading="Age" fieldName="age" />
601
+ <TableColumn heading="Email" fieldName="email" />
602
+ <TableColumn heading="City" fieldName="city" />
603
+ <TableColumn heading="Country" fieldName="country" />
604
+ <TableColumn heading="Occupation" fieldName="occupation" />
605
+ <TableColumn heading="Active" fieldName="isActive" />
606
+ </Table>
607
+ </Scaffold>
608
+ </Panel>
609
+ </StoryWrapper>
610
+ );
611
+ }
612
+
456
613
  export default meta;
@@ -34,17 +34,18 @@ 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"
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
  >
@@ -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"],
@@ -59,6 +49,26 @@
59
49
  z-index: 2;
60
50
  }
61
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
+
62
72
  .table-body th > div {
63
73
  display: flex;
64
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
+ }