@tcn/ui-table 2.2.0 → 2.3.0

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 (217) hide show
  1. package/README.md +1 -1
  2. package/dist/cell.css +1 -0
  3. package/dist/cell.module-WpHnQBVu.js +5 -0
  4. package/dist/cell.module-WpHnQBVu.js.map +1 -0
  5. package/dist/components/cells/data_cell.d.ts +3 -2
  6. package/dist/components/cells/data_cell.d.ts.map +1 -0
  7. package/dist/components/cells/data_cell.js +18 -10
  8. package/dist/components/cells/data_cell.js.map +1 -1
  9. package/dist/components/cells/footer_cell.d.ts +3 -2
  10. package/dist/components/cells/footer_cell.d.ts.map +1 -0
  11. package/dist/components/cells/footer_cell.js +18 -10
  12. package/dist/components/cells/footer_cell.js.map +1 -1
  13. package/dist/components/cells/header_cell.d.ts +3 -2
  14. package/dist/components/cells/header_cell.d.ts.map +1 -0
  15. package/dist/components/cells/header_cell.js +52 -18
  16. package/dist/components/cells/header_cell.js.map +1 -1
  17. package/dist/components/cells/sticky_row_data_cell.d.ts +3 -2
  18. package/dist/components/cells/sticky_row_data_cell.d.ts.map +1 -0
  19. package/dist/components/cells/sticky_row_data_cell.js +26 -11
  20. package/dist/components/cells/sticky_row_data_cell.js.map +1 -1
  21. package/dist/components/cells/sticky_row_fill_cell.d.ts +2 -2
  22. package/dist/components/cells/sticky_row_fill_cell.d.ts.map +1 -0
  23. package/dist/components/cells/sticky_row_fill_cell.js +15 -5
  24. package/dist/components/cells/sticky_row_fill_cell.js.map +1 -1
  25. package/dist/components/global_search.d.ts +2 -2
  26. package/dist/components/global_search.d.ts.map +1 -0
  27. package/dist/components/global_search.js +26 -9
  28. package/dist/components/global_search.js.map +1 -1
  29. package/dist/components/global_search_presenter.d.ts +2 -1
  30. package/dist/components/global_search_presenter.d.ts.map +1 -0
  31. package/dist/components/global_search_presenter.js +20 -18
  32. package/dist/components/global_search_presenter.js.map +1 -1
  33. package/dist/components/table/table.d.ts +3 -2
  34. package/dist/components/table/table.d.ts.map +1 -0
  35. package/dist/components/table/table.js +140 -77
  36. package/dist/components/table/table.js.map +1 -1
  37. package/dist/components/table/table_column.d.ts +1 -1
  38. package/dist/components/table/table_column.d.ts.map +1 -0
  39. package/dist/components/table/table_column.js +6 -5
  40. package/dist/components/table/table_column.js.map +1 -1
  41. package/dist/components/table/table_presenter.d.ts +3 -2
  42. package/dist/components/table/table_presenter.d.ts.map +1 -0
  43. package/dist/components/table/table_presenter.js +45 -62
  44. package/dist/components/table/table_presenter.js.map +1 -1
  45. package/dist/components/table_filter_panel/field_filters/date_field_filter.d.ts +2 -2
  46. package/dist/components/table_filter_panel/field_filters/date_field_filter.d.ts.map +1 -0
  47. package/dist/components/table_filter_panel/field_filters/date_field_filter.js +59 -33
  48. package/dist/components/table_filter_panel/field_filters/date_field_filter.js.map +1 -1
  49. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.d.ts +4 -3
  50. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.d.ts.map +1 -0
  51. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.js +57 -91
  52. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.js.map +1 -1
  53. package/dist/components/table_filter_panel/field_filters/field_filter_props.d.ts +1 -0
  54. package/dist/components/table_filter_panel/field_filters/field_filter_props.d.ts.map +1 -0
  55. package/dist/components/table_filter_panel/field_filters/field_filter_strategy.d.ts +1 -0
  56. package/dist/components/table_filter_panel/field_filters/field_filter_strategy.d.ts.map +1 -0
  57. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.d.ts +3 -3
  58. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.d.ts.map +1 -0
  59. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.js +52 -29
  60. package/dist/components/table_filter_panel/field_filters/mulit_select_field_filter.js.map +1 -1
  61. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.d.ts +3 -2
  62. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.d.ts.map +1 -0
  63. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.js +53 -70
  64. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.js.map +1 -1
  65. package/dist/components/table_filter_panel/field_filters/number_field_filter.d.ts +3 -3
  66. package/dist/components/table_filter_panel/field_filters/number_field_filter.d.ts.map +1 -0
  67. package/dist/components/table_filter_panel/field_filters/number_field_filter.js +47 -23
  68. package/dist/components/table_filter_panel/field_filters/number_field_filter.js.map +1 -1
  69. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.d.ts +5 -4
  70. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.d.ts.map +1 -0
  71. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.js +53 -58
  72. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.js.map +1 -1
  73. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.d.ts +2 -2
  74. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.d.ts.map +1 -0
  75. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.js +61 -31
  76. package/dist/components/table_filter_panel/field_filters/number_range_field_filter.js.map +1 -1
  77. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.d.ts +4 -3
  78. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.d.ts.map +1 -0
  79. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.js +57 -91
  80. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.js.map +1 -1
  81. package/dist/components/table_filter_panel/field_filters/select_field_filter.d.ts +3 -3
  82. package/dist/components/table_filter_panel/field_filters/select_field_filter.d.ts.map +1 -0
  83. package/dist/components/table_filter_panel/field_filters/select_field_filter.js +49 -24
  84. package/dist/components/table_filter_panel/field_filters/select_field_filter.js.map +1 -1
  85. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.d.ts +3 -2
  86. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.d.ts.map +1 -0
  87. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.js +49 -53
  88. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.js.map +1 -1
  89. package/dist/components/table_filter_panel/field_filters/string_field_filter.d.ts +3 -3
  90. package/dist/components/table_filter_panel/field_filters/string_field_filter.d.ts.map +1 -0
  91. package/dist/components/table_filter_panel/field_filters/string_field_filter.js +62 -33
  92. package/dist/components/table_filter_panel/field_filters/string_field_filter.js.map +1 -1
  93. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.d.ts +5 -4
  94. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.d.ts.map +1 -0
  95. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.js +54 -59
  96. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.js.map +1 -1
  97. package/dist/components/table_filter_panel/field_filters/use_field_filter_strategy.d.ts +2 -1
  98. package/dist/components/table_filter_panel/field_filters/use_field_filter_strategy.d.ts.map +1 -0
  99. package/dist/components/table_filter_panel/field_filters/use_field_filter_strategy.js +13 -19
  100. package/dist/components/table_filter_panel/field_filters/use_field_filter_strategy.js.map +1 -1
  101. package/dist/components/table_filter_panel/table_filter_panel.d.ts +5 -4
  102. package/dist/components/table_filter_panel/table_filter_panel.d.ts.map +1 -0
  103. package/dist/components/table_filter_panel/table_filter_panel.js +15 -11
  104. package/dist/components/table_filter_panel/table_filter_panel.js.map +1 -1
  105. package/dist/components/table_filter_panel/table_filter_panel_presenter.d.ts +2 -2
  106. package/dist/components/table_filter_panel/table_filter_panel_presenter.d.ts.map +1 -0
  107. package/dist/components/table_filter_panel/table_filter_panel_presenter.js +45 -62
  108. package/dist/components/table_filter_panel/table_filter_panel_presenter.js.map +1 -1
  109. package/dist/components/table_filter_panel/types.d.ts +1 -0
  110. package/dist/components/table_filter_panel/types.d.ts.map +1 -0
  111. package/dist/components/table_filter_panel/types.js +5 -2
  112. package/dist/components/table_filter_panel/types.js.map +1 -1
  113. package/dist/components/table_pager.d.ts +2 -2
  114. package/dist/components/table_pager.d.ts.map +1 -0
  115. package/dist/components/table_pager.js +22 -20
  116. package/dist/components/table_pager.js.map +1 -1
  117. package/dist/index.d.ts +1 -0
  118. package/dist/index.d.ts.map +1 -0
  119. package/dist/index.js +27 -13
  120. package/dist/index.js.map +1 -1
  121. package/dist/table.css +1 -0
  122. package/dist/table_pager.css +1 -0
  123. package/package.json +61 -61
  124. package/src/__stories__/aip_table.stories.tsx +190 -0
  125. package/src/__stories__/auth_provider.tsx +14 -0
  126. package/src/__stories__/demo.stories.tsx +137 -0
  127. package/src/__stories__/sample_data.ts +1398 -0
  128. package/src/__stories__/table.stories.tsx +423 -0
  129. package/src/__tests__/sanity.test.ts +7 -0
  130. package/src/components/cells/data_cell.tsx +25 -0
  131. package/src/components/cells/footer_cell.tsx +25 -0
  132. package/src/components/cells/header_cell.tsx +77 -0
  133. package/src/components/cells/sticky_row_data_cell.tsx +31 -0
  134. package/src/components/cells/sticky_row_fill_cell.tsx +16 -0
  135. package/src/components/global_search.tsx +33 -0
  136. package/src/components/global_search_presenter.ts +24 -0
  137. package/{dist → src}/components/table/table.module.css +3 -2
  138. package/src/components/table/table.tsx +183 -0
  139. package/src/components/table/table_column.tsx +27 -0
  140. package/src/components/table/table_presenter.test.ts +161 -0
  141. package/src/components/table/table_presenter.ts +103 -0
  142. package/src/components/table_filter_panel/field_filters/date_field_filter.tsx +70 -0
  143. package/src/components/table_filter_panel/field_filters/date_field_filter_presenter.test.ts +583 -0
  144. package/src/components/table_filter_panel/field_filters/date_field_filter_presenter.ts +110 -0
  145. package/src/components/table_filter_panel/field_filters/field_filter_props.ts +5 -0
  146. package/src/components/table_filter_panel/field_filters/field_filter_strategy.ts +14 -0
  147. package/src/components/table_filter_panel/field_filters/mulit_select_field_filter.tsx +68 -0
  148. package/src/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.test.ts +444 -0
  149. package/src/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.ts +90 -0
  150. package/src/components/table_filter_panel/field_filters/number_field_filter.tsx +53 -0
  151. package/src/components/table_filter_panel/field_filters/number_field_filter_presenter.test.ts +431 -0
  152. package/src/components/table_filter_panel/field_filters/number_field_filter_presenter.ts +80 -0
  153. package/src/components/table_filter_panel/field_filters/number_range_field_filter.tsx +68 -0
  154. package/src/components/table_filter_panel/field_filters/number_range_field_filter_presenter.test.ts +582 -0
  155. package/src/components/table_filter_panel/field_filters/number_range_field_filter_presenter.ts +110 -0
  156. package/src/components/table_filter_panel/field_filters/select_field_filter.tsx +57 -0
  157. package/src/components/table_filter_panel/field_filters/select_field_filter_presenter.test.ts +365 -0
  158. package/src/components/table_filter_panel/field_filters/select_field_filter_presenter.ts +74 -0
  159. package/src/components/table_filter_panel/field_filters/string_field_filter.tsx +70 -0
  160. package/src/components/table_filter_panel/field_filters/string_field_filter_presenter.test.ts +296 -0
  161. package/src/components/table_filter_panel/field_filters/string_field_filter_presenter.ts +81 -0
  162. package/src/components/table_filter_panel/field_filters/use_field_filter_strategy.tsx +30 -0
  163. package/src/components/table_filter_panel/table_filter_panel.stories.tsx +46 -0
  164. package/src/components/table_filter_panel/table_filter_panel.tsx +26 -0
  165. package/src/components/table_filter_panel/table_filter_panel_presenter.ts +77 -0
  166. package/src/components/table_filter_panel/types.ts +3 -0
  167. package/src/components/table_pager.tsx +39 -0
  168. package/src/index.ts +16 -0
  169. package/tsconfig.json +36 -0
  170. package/types/file_types.d.ts +54 -0
  171. package/types/react_color.d.ts +61 -0
  172. package/dist/__stories__/aip_table.stories.d.ts +0 -5
  173. package/dist/__stories__/aip_table.stories.js +0 -96
  174. package/dist/__stories__/aip_table.stories.js.map +0 -1
  175. package/dist/__stories__/auth_provider.d.ts +0 -4
  176. package/dist/__stories__/auth_provider.js +0 -10
  177. package/dist/__stories__/auth_provider.js.map +0 -1
  178. package/dist/__stories__/demo.stories.d.ts +0 -6
  179. package/dist/__stories__/demo.stories.js +0 -94
  180. package/dist/__stories__/demo.stories.js.map +0 -1
  181. package/dist/__stories__/sample_data.d.ts +0 -36
  182. package/dist/__stories__/sample_data.js +0 -1385
  183. package/dist/__stories__/sample_data.js.map +0 -1
  184. package/dist/__stories__/table.stories.d.ts +0 -12
  185. package/dist/__stories__/table.stories.js +0 -272
  186. package/dist/__stories__/table.stories.js.map +0 -1
  187. package/dist/components/table/table_presenter.test.d.ts +0 -1
  188. package/dist/components/table/table_presenter.test.js +0 -125
  189. package/dist/components/table/table_presenter.test.js.map +0 -1
  190. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.test.d.ts +0 -1
  191. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.test.js +0 -434
  192. package/dist/components/table_filter_panel/field_filters/date_field_filter_presenter.test.js.map +0 -1
  193. package/dist/components/table_filter_panel/field_filters/field_filter_props.js +0 -2
  194. package/dist/components/table_filter_panel/field_filters/field_filter_props.js.map +0 -1
  195. package/dist/components/table_filter_panel/field_filters/field_filter_strategy.js +0 -2
  196. package/dist/components/table_filter_panel/field_filters/field_filter_strategy.js.map +0 -1
  197. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.test.d.ts +0 -1
  198. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.test.js +0 -332
  199. package/dist/components/table_filter_panel/field_filters/multi_select_field_filter_presenter.test.js.map +0 -1
  200. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.test.d.ts +0 -1
  201. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.test.js +0 -347
  202. package/dist/components/table_filter_panel/field_filters/number_field_filter_presenter.test.js.map +0 -1
  203. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.test.d.ts +0 -1
  204. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.test.js +0 -452
  205. package/dist/components/table_filter_panel/field_filters/number_range_field_filter_presenter.test.js.map +0 -1
  206. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.test.d.ts +0 -1
  207. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.test.js +0 -285
  208. package/dist/components/table_filter_panel/field_filters/select_field_filter_presenter.test.js.map +0 -1
  209. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.test.d.ts +0 -1
  210. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.test.js +0 -232
  211. package/dist/components/table_filter_panel/field_filters/string_field_filter_presenter.test.js.map +0 -1
  212. package/dist/components/table_filter_panel/table_filter_panel.stories.d.ts +0 -6
  213. package/dist/components/table_filter_panel/table_filter_panel.stories.js +0 -25
  214. package/dist/components/table_filter_panel/table_filter_panel.stories.js.map +0 -1
  215. /package/{dist → src}/__stories__/table.module.css +0 -0
  216. /package/{dist → src}/components/cells/cell.module.css +0 -0
  217. /package/{dist → src}/components/table_pager.module.css +0 -0
@@ -0,0 +1,423 @@
1
+ import { Meta } from '@storybook/react-vite';
2
+ import React, { useCallback, useState } from 'react';
3
+
4
+ import {
5
+ StaticDataSource,
6
+ StaticDateField,
7
+ StaticNumberField,
8
+ StaticStringField,
9
+ } from '@tcn/resource-store';
10
+ import { useSignalValue } from '@tcn/state';
11
+ import { Button } from '@tcn/ui/actions';
12
+ import { Box, HStack, Spacer, VStack } from '@tcn/ui/stacks';
13
+ import { VPanel } from '@tcn/ui/surfaces';
14
+ import { Title } from '@tcn/ui/typography';
15
+ import { GlobalSearch } from '../components/global_search.js';
16
+ import { Table } from '../components/table/table.js';
17
+ import { TableColumn } from '../components/table/table_column.js';
18
+ import { DateFieldFilter } from '../components/table_filter_panel/field_filters/date_field_filter.js';
19
+ import { NumberFieldFilter } from '../components/table_filter_panel/field_filters/number_field_filter.js';
20
+ import { NumberRangeFieldFilter } from '../components/table_filter_panel/field_filters/number_range_field_filter.js';
21
+ import { StringFieldFilter } from '../components/table_filter_panel/field_filters/string_field_filter.js';
22
+ import { TableFilterPanel } from '../components/table_filter_panel/table_filter_panel.js';
23
+ import { TablePager } from '../components/table_pager.js';
24
+ import { DataItem, items, stickyItems } from './sample_data.js';
25
+ import styles from './table.module.css';
26
+
27
+ const meta: Meta = {
28
+ title: 'Table',
29
+ };
30
+
31
+ export function Basic() {
32
+ const [source] = useState(() => {
33
+ return new StaticDataSource<DataItem>(items, [
34
+ new StaticStringField('name', i => i.name),
35
+ new StaticNumberField('age', i => i.age),
36
+ new StaticStringField('email', i => i.email),
37
+ new StaticStringField('city', i => i.city),
38
+ new StaticStringField('country', i => i.country),
39
+ new StaticStringField('occupation', i => i.occupation),
40
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
41
+ ]);
42
+ });
43
+
44
+ return (
45
+ <Table
46
+ dataSource={source}
47
+ onRowClick={item => {
48
+ alert(`Clicked ${item.name}`);
49
+ }}
50
+ >
51
+ <TableColumn heading="Name" fieldName="name" />
52
+ <TableColumn heading="Age" fieldName="age" />
53
+ <TableColumn heading="Email" fieldName="email" />
54
+ <TableColumn heading="City" fieldName="city" />
55
+ <TableColumn heading="Country" fieldName="country" />
56
+ <TableColumn heading="Occupation" fieldName="occupation" />
57
+ <TableColumn heading="Active" fieldName="isActive" />
58
+ <div>Other kinds of children don't render</div>
59
+ </Table>
60
+ );
61
+ }
62
+
63
+ export function WithSort() {
64
+ const [source] = useState(() => {
65
+ return new StaticDataSource<DataItem>(items, [
66
+ new StaticStringField('name', i => i.name),
67
+ new StaticNumberField('age', i => i.age),
68
+ new StaticStringField('email', i => i.email),
69
+ new StaticStringField('city', i => i.city),
70
+ new StaticStringField('country', i => i.country),
71
+ new StaticStringField('occupation', i => i.occupation),
72
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
73
+ ]);
74
+ });
75
+
76
+ const currentResults = useSignalValue(source.broadcasts.currentResults);
77
+
78
+ return (
79
+ <Table
80
+ dataSource={source}
81
+ onRowClick={item => {
82
+ alert(`Clicked ${item.name}`);
83
+ }}
84
+ >
85
+ <TableColumn heading="Name" footer="Some Names" fieldName="name" canSort />
86
+ <TableColumn
87
+ heading="Age"
88
+ footer={`Average Age: ${Math.round(currentResults.reduce((sum, i) => sum + i.age, 0) / currentResults.length)}`}
89
+ fieldName="age"
90
+ canSort
91
+ width={120}
92
+ />
93
+ <TableColumn heading="Email" fieldName="email" canSort />
94
+ <TableColumn heading="City" fieldName="city" canSort />
95
+ <TableColumn heading="Country" fieldName="country" canSort />
96
+ <TableColumn heading="Occupation" fieldName="occupation" canSort />
97
+ <TableColumn heading="Active" fieldName="isActive" canSort />
98
+ </Table>
99
+ );
100
+ }
101
+
102
+ export function WithRowHighlighting() {
103
+ const [source] = useState(() => {
104
+ return new StaticDataSource<DataItem>(items, [
105
+ new StaticStringField('name', i => i.name),
106
+ new StaticNumberField('age', i => i.age),
107
+ new StaticStringField('id', i => i.id),
108
+ new StaticStringField('email', i => i.email),
109
+ new StaticStringField('city', i => i.city),
110
+ new StaticStringField('country', i => i.country),
111
+ new StaticStringField('occupation', i => i.occupation),
112
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
113
+ ]);
114
+ });
115
+
116
+ const [selectedRow, setSelectedRow] = useState<string | null>(null);
117
+
118
+ return (
119
+ <div>
120
+ <Table
121
+ dataSource={source}
122
+ isRowHighlighted={row => {
123
+ return row.id === selectedRow;
124
+ }}
125
+ onRowClick={item => setSelectedRow(item.id)}
126
+ >
127
+ <TableColumn heading="Name" fieldName="name" />
128
+ <TableColumn heading="Age" fieldName="age" />
129
+ <TableColumn heading="Email" fieldName="email" />
130
+ <TableColumn heading="City" fieldName="city" />
131
+ <TableColumn heading="Country" fieldName="country" />
132
+ <TableColumn heading="Occupation" fieldName="occupation" />
133
+ <TableColumn heading="Active" fieldName="isActive" />
134
+ </Table>
135
+ </div>
136
+ );
137
+ }
138
+
139
+ export function ComplexHeadersAndFooters() {
140
+ const [source] = useState(() => {
141
+ return new StaticDataSource<DataItem>(items, [
142
+ new StaticStringField('name', i => i.name),
143
+ new StaticNumberField('age', i => i.age),
144
+ new StaticStringField('email', i => i.email),
145
+ new StaticStringField('city', i => i.city),
146
+ new StaticStringField('country', i => i.country),
147
+ new StaticStringField('occupation', i => i.occupation),
148
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
149
+ ]);
150
+ });
151
+
152
+ return (
153
+ <Table dataSource={source}>
154
+ <TableColumn
155
+ heading={
156
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
157
+ <span>👤</span>
158
+ <span>Name</span>
159
+ </div>
160
+ }
161
+ footer={
162
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
163
+ <span>Total:</span>
164
+ <span>{items.length}</span>
165
+ </div>
166
+ }
167
+ fieldName="name"
168
+ render={(i: DataItem) => i.name}
169
+ />
170
+ <TableColumn
171
+ heading={
172
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
173
+ <span>🎂</span>
174
+ <span>Age</span>
175
+ </div>
176
+ }
177
+ footer={
178
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
179
+ <span>Average:</span>
180
+ <span>
181
+ {Math.round(items.reduce((sum, i) => sum + i.age, 0) / items.length)}
182
+ </span>
183
+ </div>
184
+ }
185
+ fieldName="age"
186
+ render={(i: DataItem) => i.age}
187
+ />
188
+ <TableColumn heading="Email" fieldName="email" render={(i: DataItem) => i.email} />
189
+ <TableColumn heading="City" fieldName="city" render={(i: DataItem) => i.city} />
190
+ <TableColumn
191
+ heading="Country"
192
+ fieldName="country"
193
+ render={(i: DataItem) => i.country}
194
+ />
195
+ <TableColumn
196
+ heading="Occupation"
197
+ fieldName="occupation"
198
+ render={(i: DataItem) => i.occupation}
199
+ />
200
+ <TableColumn
201
+ heading="Active"
202
+ fieldName="isActive"
203
+ render={(i: DataItem) => (i.isActive ? 'Yes' : 'No')}
204
+ />
205
+ </Table>
206
+ );
207
+ }
208
+
209
+ export function WithStickyActionsColumn() {
210
+ const [source] = useState(() => {
211
+ return new StaticDataSource<DataItem>(items, [
212
+ new StaticStringField('name', i => i.name),
213
+ new StaticNumberField('age', i => i.age),
214
+ new StaticStringField('email', i => i.email),
215
+ new StaticStringField('city', i => i.city),
216
+ new StaticStringField('country', i => i.country),
217
+ new StaticStringField('occupation', i => i.occupation),
218
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
219
+ ]);
220
+ });
221
+
222
+ return (
223
+ <div style={{ width: '600px', height: '400px' }}>
224
+ <Table
225
+ dataSource={source}
226
+ onRowClick={item => {
227
+ alert(`Clicked ${item.name}`);
228
+ }}
229
+ >
230
+ <TableColumn heading="Age" fieldName="age" sticky="start" />
231
+ <TableColumn heading="Name" fieldName="name" />
232
+ <TableColumn heading="Email" fieldName="email" />
233
+ <TableColumn heading="City" fieldName="city" />
234
+ <TableColumn heading="Country" fieldName="country" />
235
+ <TableColumn heading="Occupation" fieldName="occupation" />
236
+ <TableColumn heading="Active" fieldName="isActive" />
237
+ <TableColumn
238
+ heading="Actions"
239
+ sticky="end"
240
+ width={120}
241
+ render={(item: DataItem) => (
242
+ <div style={{ display: 'flex', gap: '4px', justifyContent: 'center' }}>
243
+ <button
244
+ onClick={e => {
245
+ e.stopPropagation();
246
+ alert(`Edit ${item.name}`);
247
+ }}
248
+ style={{
249
+ padding: '4px 8px',
250
+ fontSize: '12px',
251
+ border: '1px solid #ccc',
252
+ borderRadius: '4px',
253
+ background: '#f0f0f0',
254
+ cursor: 'pointer',
255
+ }}
256
+ >
257
+ Edit
258
+ </button>
259
+ <button
260
+ onClick={e => {
261
+ e.stopPropagation();
262
+ alert(`Delete ${item.name}`);
263
+ }}
264
+ style={{
265
+ padding: '4px 8px',
266
+ fontSize: '12px',
267
+ border: '1px solid #ff6b6b',
268
+ borderRadius: '4px',
269
+ background: '#ff6b6b',
270
+ color: 'white',
271
+ cursor: 'pointer',
272
+ }}
273
+ >
274
+ Delete
275
+ </button>
276
+ </div>
277
+ )}
278
+ />
279
+ </Table>
280
+ </div>
281
+ );
282
+ }
283
+
284
+ export function WithTableHeaderAndTableFooter() {
285
+ const [source] = useState(() => {
286
+ return new StaticDataSource<DataItem>(items, [
287
+ new StaticStringField('name', i => i.name),
288
+ new StaticNumberField('age', i => i.age),
289
+ new StaticStringField('email', i => i.email),
290
+ new StaticStringField('city', i => i.city),
291
+ new StaticStringField('country', i => i.country),
292
+ new StaticStringField('occupation', i => i.occupation),
293
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
294
+ ]);
295
+ });
296
+
297
+ return (
298
+ <VPanel>
299
+ <HStack className={styles.header} padding="8px" vAlign="center">
300
+ <Title>The Table</Title>
301
+ <Spacer />
302
+ <GlobalSearch dataSource={source} />
303
+ </HStack>
304
+ <VStack height="flex">
305
+ <Table
306
+ dataSource={source}
307
+ onRowClick={item => {
308
+ alert(`Clicked ${item.name}`);
309
+ }}
310
+ >
311
+ <TableColumn heading="Name" fieldName="name" />
312
+ <TableColumn heading="Age" fieldName="age" />
313
+ <TableColumn heading="Email" fieldName="email" />
314
+ <TableColumn heading="City" fieldName="city" />
315
+ <TableColumn heading="Country" fieldName="country" />
316
+ <TableColumn heading="Occupation" fieldName="occupation" />
317
+ <TableColumn heading="Active" fieldName="isActive" />
318
+ </Table>
319
+ </VStack>
320
+ <HStack padding="8px">
321
+ <TablePager dataSource={source} />
322
+ </HStack>
323
+ </VPanel>
324
+ );
325
+ }
326
+
327
+ export function WithStickyItems() {
328
+ const [stickyThings, setStickyThings] = useState([...stickyItems]);
329
+
330
+ const [source] = useState(() => {
331
+ const source = new StaticDataSource<DataItem>(items, [
332
+ new StaticStringField('name', i => i.name),
333
+ new StaticNumberField('age', i => i.age),
334
+ new StaticStringField('email', i => i.email),
335
+ new StaticStringField('city', i => i.city),
336
+ new StaticStringField('country', i => i.country),
337
+ new StaticStringField('occupation', i => i.occupation),
338
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
339
+ ]);
340
+ source.setPageSize(80);
341
+ return source;
342
+ });
343
+
344
+ const dismissStickyThing = useCallback((item: DataItem) => {
345
+ setStickyThings(currentStickyThings =>
346
+ currentStickyThings.filter(i => i.id !== item.id)
347
+ );
348
+ }, []);
349
+
350
+ return (
351
+ <VStack height="100%">
352
+ <Table dataSource={source} stickyItems={stickyThings}>
353
+ <TableColumn heading="Name" fieldName="name" sticky="start" />
354
+ <TableColumn heading="Age" fieldName="age" width={150} />
355
+ <TableColumn heading="Email" fieldName="email" width={300} />
356
+ <TableColumn heading="City" fieldName="city" width={300} />
357
+ <TableColumn heading="Country" fieldName="country" width={200} />
358
+ <TableColumn heading="Occupation" fieldName="occupation" width={200} />
359
+ <TableColumn heading="Active" fieldName="isActive" />
360
+ <TableColumn
361
+ heading="Actions"
362
+ sticky="end"
363
+ width={120}
364
+ render={(item: DataItem) => {
365
+ if (stickyThings.includes(item)) {
366
+ return <Button onClick={() => dismissStickyThing(item)}>x</Button>;
367
+ }
368
+ return null;
369
+ }}
370
+ />
371
+ </Table>
372
+ <TablePager dataSource={source} />
373
+ </VStack>
374
+ );
375
+ }
376
+
377
+ export function WithFilterPanel() {
378
+ const [source] = useState(() => {
379
+ const source = new StaticDataSource<DataItem>(items, [
380
+ new StaticStringField('name', i => i.name),
381
+ new StaticNumberField('age', i => i.age),
382
+ new StaticStringField('email', i => i.email),
383
+ new StaticStringField('city', i => i.city),
384
+ new StaticDateField('birthdate', i => i.birthdate),
385
+ new StaticStringField('occupation', i => i.occupation),
386
+ new StaticStringField('isActive', i => (i.isActive ? 'Yes' : 'No')),
387
+ ]);
388
+ source.setPageSize(80);
389
+ return source;
390
+ });
391
+
392
+ return (
393
+ <VStack height="100%">
394
+ <HStack height="100%" vAlign="start" gap="8px">
395
+ <Box width="300px" height="100%" enableResizeOnEnd>
396
+ <TableFilterPanel dataSource={source}>
397
+ <StringFieldFilter fieldName="name" label="Name (string)" />
398
+ <NumberFieldFilter fieldName="age" label="Age (number)" />
399
+ <DateFieldFilter fieldName="birthdate" label="Birthdate (date range)" />
400
+ <NumberRangeFieldFilter fieldName="age" label="Age (number range)" />
401
+ </TableFilterPanel>
402
+ </Box>
403
+ <Table dataSource={source} height="100%" width="flex">
404
+ <TableColumn heading="Name" fieldName="name" sticky="start" />
405
+ <TableColumn heading="Age" fieldName="age" width={150} canSort />
406
+ <TableColumn heading="Email" fieldName="email" width={300} />
407
+ <TableColumn heading="City" fieldName="city" width={300} />
408
+ <TableColumn
409
+ heading="Birthdate"
410
+ fieldName="birthdate"
411
+ width={300}
412
+ render={(i: DataItem) => i.birthdate.toLocaleDateString()}
413
+ />
414
+ <TableColumn heading="Occupation" fieldName="occupation" width={200} />
415
+ <TableColumn heading="Active" fieldName="isActive" />
416
+ </Table>
417
+ </HStack>
418
+ <TablePager dataSource={source} />
419
+ </VStack>
420
+ );
421
+ }
422
+
423
+ export default meta;
@@ -0,0 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('sanity', () => {
4
+ it('works', () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+
3
+ import cellStyles from './cell.module.css';
4
+
5
+ export type DataCellProps = {
6
+ content: React.ReactNode;
7
+ sticky?: 'start' | 'end';
8
+ width?: number;
9
+ };
10
+
11
+ export function DataCell({ content, sticky, width }: DataCellProps) {
12
+ return (
13
+ <td className={cellStyles['table-cell']} data-stick-to={sticky}>
14
+ <div
15
+ style={{
16
+ width: `${width}px`,
17
+ minWidth: `${width}px`,
18
+ maxWidth: `${width}px`,
19
+ }}
20
+ >
21
+ {content}
22
+ </div>
23
+ </td>
24
+ );
25
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+
3
+ import cellStyles from './cell.module.css';
4
+
5
+ export type FooterCellProps = {
6
+ content: React.ReactNode;
7
+ sticky?: 'start' | 'end';
8
+ width?: number;
9
+ };
10
+
11
+ export function FooterCell({ content, sticky, width }: FooterCellProps) {
12
+ return (
13
+ <td className={cellStyles['table-cell']} data-stick-to={sticky}>
14
+ <div
15
+ style={{
16
+ width: `${width}px`,
17
+ minWidth: `${width}px`,
18
+ maxWidth: `${width}px`,
19
+ }}
20
+ >
21
+ {content}
22
+ </div>
23
+ </td>
24
+ );
25
+ }
@@ -0,0 +1,77 @@
1
+ import React, { useCallback } from 'react';
2
+
3
+ import { Box, HStack } from '@tcn/ui/stacks';
4
+
5
+ import { TwoClassSortDownIcon } from '@tcn/icons/two_class_sort_down_icon.js';
6
+ import { TwoClassSortFilledIcon } from '@tcn/icons/two_class_sort_filled_icon.js';
7
+ import { TwoClassSortUpIcon } from '@tcn/icons/two_class_sort_up_icon.js';
8
+ import { Button } from '@tcn/ui/actions';
9
+ import cellStyles from './cell.module.css';
10
+
11
+ export type HeaderCellProps = {
12
+ heading: React.ReactNode;
13
+ index: number;
14
+ sticky?: 'start' | 'end';
15
+ onResize?: (width: number) => void;
16
+ width?: number;
17
+ sortMode?: 'none' | 'asc' | 'desc';
18
+ onSortModeChange?: () => void;
19
+ canSort?: boolean;
20
+ };
21
+
22
+ export function HeaderCell({
23
+ heading,
24
+ sticky,
25
+ onResize,
26
+ width,
27
+ sortMode,
28
+ onSortModeChange,
29
+ canSort,
30
+ }: HeaderCellProps) {
31
+ const zIndex = sticky != null ? 2 : 1;
32
+
33
+ const handleResize = useCallback(
34
+ (newSize: number) => {
35
+ onResize?.(Math.max(newSize, 20));
36
+ },
37
+ [onResize]
38
+ );
39
+
40
+ return (
41
+ <th
42
+ className={cellStyles['table-cell']}
43
+ data-stick-to={sticky}
44
+ style={{ width: `${width}px`, zIndex }}
45
+ >
46
+ <Box
47
+ padding="4px 6px"
48
+ overflow="hidden"
49
+ minWidth="20px"
50
+ enableResizeOnEnd
51
+ onWidthResize={handleResize}
52
+ style={{ width: `${width}px`, minWidth: '20px' }}
53
+ onClick={e => e.stopPropagation()}
54
+ height="30px"
55
+ >
56
+ <HStack>
57
+ <Box className="ellipsis" style={{ alignItems: 'center', display: 'flex' }}>
58
+ {heading}
59
+ </Box>
60
+ <Box width="auto" height="auto">
61
+ {canSort && (
62
+ <Button onClick={() => onSortModeChange?.()} hierarchy="tertiary">
63
+ {sortMode === 'asc' ? (
64
+ <TwoClassSortDownIcon />
65
+ ) : sortMode === 'desc' ? (
66
+ <TwoClassSortUpIcon />
67
+ ) : (
68
+ <TwoClassSortFilledIcon />
69
+ )}
70
+ </Button>
71
+ )}
72
+ </Box>
73
+ </HStack>
74
+ </Box>
75
+ </th>
76
+ );
77
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import cellStyles from './cell.module.css';
3
+
4
+ export type StickyRowDataCellProps = {
5
+ content: React.ReactNode;
6
+ sticky?: 'start' | 'end';
7
+ width?: number;
8
+ top?: number;
9
+ };
10
+
11
+ export function StickyRowDataCell({
12
+ content,
13
+ sticky,
14
+ width,
15
+ top,
16
+ }: StickyRowDataCellProps) {
17
+ return (
18
+ <td
19
+ className={cellStyles['sticky-row-table-cell']}
20
+ data-stick-to={sticky}
21
+ style={{
22
+ width: `${width}px`,
23
+ minWidth: `${width}px`,
24
+ maxWidth: `${width}px`,
25
+ top: `${top}px`,
26
+ }}
27
+ >
28
+ <div>{content}</div>
29
+ </td>
30
+ );
31
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import cellStyles from './cell.module.css';
3
+
4
+ export type StyckyRowFillCellProps = {
5
+ top?: number;
6
+ };
7
+
8
+ export function StickyRowFillCell({ top }: StyckyRowFillCellProps) {
9
+ return (
10
+ <td
11
+ className={cellStyles['sticky-row-table-cell']}
12
+ data-stick-to="start"
13
+ style={{ top: `${top}px` }}
14
+ ></td>
15
+ );
16
+ }
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { DataSource } from '@tcn/resource-store';
4
+ import { useSignalValue } from '@tcn/state';
5
+ import { Input, InputProps } from '@tcn/ui/inputs';
6
+ import { GlobalSearchPresenter } from './global_search_presenter.js';
7
+
8
+ export interface GlobalSearchOwnProps<T> {
9
+ dataSource: DataSource<T>;
10
+ }
11
+
12
+ export type GlobalSearchProps<T> = GlobalSearchOwnProps<T> &
13
+ Omit<InputProps, 'value' | 'onChange'>;
14
+
15
+ export function GlobalSearch<T = any>({
16
+ dataSource,
17
+ width = 'auto',
18
+ placeholder = 'Search',
19
+ ...rest
20
+ }: GlobalSearchProps<T>) {
21
+ const [presenter] = useState(() => new GlobalSearchPresenter<T>(dataSource));
22
+ const value = useSignalValue(presenter.broadcasts.value);
23
+
24
+ return (
25
+ <Input
26
+ width={width}
27
+ placeholder={placeholder}
28
+ value={value ?? ''}
29
+ onChange={e => presenter.setValue(e)}
30
+ {...rest}
31
+ />
32
+ );
33
+ }
@@ -0,0 +1,24 @@
1
+ import { DataSource } from '@tcn/resource-store';
2
+ import { Signal } from '@tcn/state';
3
+
4
+ export class GlobalSearchPresenter<T = unknown> {
5
+ private _dataSource: DataSource<T>;
6
+ private _value = new Signal<string | null>(null);
7
+
8
+ private _broadcasts = {
9
+ value: this._value.broadcast,
10
+ };
11
+
12
+ constructor(dataSource: DataSource<T>) {
13
+ this._dataSource = dataSource;
14
+ }
15
+
16
+ get broadcasts() {
17
+ return this._broadcasts;
18
+ }
19
+
20
+ async setValue(value: string | null) {
21
+ this._dataSource.setGlobalSearch(value ?? '');
22
+ this._value.set(value ?? '');
23
+ }
24
+ }
@@ -1,7 +1,8 @@
1
1
  .table-body-wrapper {
2
2
  box-shadow: var(--table-box-shadow, inset 0px 1px 2px 0px rgba(0, 0, 0, 0.5));
3
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
4
- Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
3
+ font-family:
4
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
5
+ "Open Sans", "Helvetica Neue", sans-serif;
5
6
  color: #333;
6
7
  }
7
8