@indico-data/design-system 2.48.0 → 2.50.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 (41) hide show
  1. package/lib/components/table/Table.stories.d.ts +1 -0
  2. package/lib/components/table/components/HorizontalStickyHeader.d.ts +10 -0
  3. package/lib/components/table/components/__tests__/HorizontalStickyHeader.test.d.ts +1 -0
  4. package/lib/components/table/components/helpers.d.ts +6 -0
  5. package/lib/components/table/hooks/usePinnedColumnsManager.d.ts +8 -0
  6. package/lib/components/table/sampleData.d.ts +4 -0
  7. package/lib/components/table/types.d.ts +11 -1
  8. package/lib/components/table/utils/processColumns.d.ts +2 -0
  9. package/lib/index.css +188 -89
  10. package/lib/index.d.ts +12 -1
  11. package/lib/index.esm.css +188 -89
  12. package/lib/index.esm.js +238 -2
  13. package/lib/index.esm.js.map +1 -1
  14. package/lib/index.js +238 -2
  15. package/lib/index.js.map +1 -1
  16. package/lib/stylesAndAnimations/utilityClasses/UtilityClassesData.d.ts +7 -0
  17. package/lib/stylesAndAnimations/utilityClasses/UtilityClassesTable.d.ts +1 -0
  18. package/lib/stylesAndAnimations/utilityClasses/UtilityClassesTable.stories.d.ts +6 -0
  19. package/lib/utils/getPreviousHeadersWidth.d.ts +1 -0
  20. package/package.json +1 -1
  21. package/src/components/table/Table.mdx +134 -0
  22. package/src/components/table/Table.stories.tsx +71 -2
  23. package/src/components/table/Table.tsx +16 -1
  24. package/src/components/table/components/HorizontalStickyHeader.tsx +57 -0
  25. package/src/components/table/components/__tests__/HorizontalStickyHeader.test.tsx +104 -0
  26. package/src/components/table/components/helpers.ts +90 -0
  27. package/src/components/table/hooks/usePinnedColumnsManager.ts +146 -0
  28. package/src/components/table/{sampleData.ts → sampleData.tsx} +156 -1
  29. package/src/components/table/styles/Table.scss +32 -15
  30. package/src/components/table/styles/_variables.scss +2 -0
  31. package/src/components/table/types.ts +13 -1
  32. package/src/components/table/utils/processColumns.tsx +35 -0
  33. package/src/setup/setupTests.ts +8 -0
  34. package/src/storybookDocs/Permafrost.mdx +22 -11
  35. package/src/styles/_borders.scss +2 -1
  36. package/src/stylesAndAnimations/borders/BorderColor.tsx +14 -6
  37. package/src/stylesAndAnimations/utilityClasses/UtilityClasses.mdx +24 -0
  38. package/src/stylesAndAnimations/utilityClasses/UtilityClassesData.ts +230 -0
  39. package/src/stylesAndAnimations/utilityClasses/UtilityClassesTable.stories.tsx +13 -0
  40. package/src/stylesAndAnimations/utilityClasses/UtilityClassesTable.tsx +146 -0
  41. package/src/utils/getPreviousHeadersWidth.ts +12 -0
@@ -0,0 +1,146 @@
1
+ import { useEffect, useCallback, useMemo } from 'react';
2
+ import { TableColumn } from '../types';
3
+ import { sortPinnedColumns, getPreviousHeadersWidth } from '../components/helpers';
4
+ import { processColumns } from '../utils/processColumns';
5
+
6
+ /**
7
+ * Hook to manage pinned columns in a table
8
+ * Handles initialization, toggling, positioning and resizing of pinned columns
9
+ */
10
+ export const usePinnedColumnsManager = <T>(
11
+ columns: TableColumn<T>[],
12
+ canPinColumns: boolean,
13
+ onPinnedColumnsChange?: (pinnedColumnIds: string[]) => void,
14
+ ) => {
15
+ const pinnedColumnIds = columns.filter((column) => column.isPinned).map((column) => column.id);
16
+
17
+ // `dataColumnIds` is the list of IDs used as `data-column-id` attributes on the table headers and cells
18
+ const dataColumnIds = useMemo(() => {
19
+ const ids = columns
20
+ .map((column, index) => (column.isPinned ? `sticky-column-${index}` : null))
21
+ .filter((id): id is string => id !== null);
22
+
23
+ return ids.length > 0 ? ['checkbox-column', ...ids] : ids;
24
+ }, [columns]);
25
+
26
+ // Toggle individual column pin state
27
+ const togglePinnedColumn = useCallback(
28
+ (columnId: string) => {
29
+ const prevPinnedColumns = pinnedColumnIds;
30
+
31
+ // Handle unpinning
32
+ if (prevPinnedColumns.some((id) => id === columnId)) {
33
+ onPinnedColumnsChange?.(prevPinnedColumns.filter((id) => id !== columnId));
34
+ } else {
35
+ onPinnedColumnsChange?.(prevPinnedColumns.concat(columnId));
36
+ }
37
+ },
38
+ [pinnedColumnIds, onPinnedColumnsChange],
39
+ );
40
+
41
+ // Handle resize events and recalculate pinned column positions
42
+ useEffect(() => {
43
+ if (!canPinColumns) return;
44
+
45
+ const recalculatePositions = () => {
46
+ // Reset all column styles and remove last-pinned-column class
47
+ const allCells = document.querySelectorAll('.rdt_TableCol, .rdt_TableCell');
48
+ allCells.forEach((cell) => {
49
+ (cell as HTMLElement).style.position = '';
50
+ (cell as HTMLElement).style.left = '';
51
+ (cell as HTMLElement).style.zIndex = '';
52
+ (cell as HTMLElement).style.backgroundColor = '';
53
+ (cell as HTMLElement).style.borderRight = '';
54
+ });
55
+
56
+ // Apply styles to pinned columns
57
+ dataColumnIds.forEach((column, index) => {
58
+ const isLastPinnedColumn = index === dataColumnIds.length - 1;
59
+
60
+ if (column === 'checkbox-column') {
61
+ // Handle header checkbox
62
+ const headerCheckbox = document.querySelector('.rdt_TableCol:not([data-column-id])');
63
+ if (headerCheckbox) {
64
+ (headerCheckbox as HTMLElement).style.position = 'sticky';
65
+ (headerCheckbox as HTMLElement).style.left = '0';
66
+ (headerCheckbox as HTMLElement).style.zIndex = '4';
67
+ (headerCheckbox as HTMLElement).style.backgroundColor =
68
+ 'var(--pf-table-background-color)';
69
+ }
70
+
71
+ // Handle cell checkboxes
72
+ const cellCheckboxes = document.querySelectorAll('.rdt_TableCell:first-child');
73
+ cellCheckboxes.forEach((cell) => {
74
+ (cell as HTMLElement).style.position = 'sticky';
75
+ (cell as HTMLElement).style.left = '0';
76
+ (cell as HTMLElement).style.zIndex = '2';
77
+ (cell as HTMLElement).style.backgroundColor =
78
+ 'var(--pf-table-pinned-column-background-color)';
79
+ if (isLastPinnedColumn) {
80
+ (cell as HTMLElement).style.borderRight =
81
+ `2px solid var(--pf-table-pinned-column-border-color)`;
82
+ }
83
+ });
84
+ } else {
85
+ const columnIndex = parseInt(column.split('-')[2]);
86
+ const left = getPreviousHeadersWidth(columnIndex, dataColumnIds);
87
+
88
+ // Headers
89
+ const headers = document.querySelectorAll(
90
+ `.rdt_TableCol[data-column-id="sticky-column-${columnIndex}"]`,
91
+ );
92
+ headers.forEach((header) => {
93
+ (header as HTMLElement).style.position = 'sticky';
94
+ (header as HTMLElement).style.left = `${left}px`;
95
+ (header as HTMLElement).style.zIndex = '2';
96
+ (header as HTMLElement).style.backgroundColor =
97
+ 'var(--pf-table-pinned-column-background-color)';
98
+ if (isLastPinnedColumn) {
99
+ (header as HTMLElement).style.borderRight =
100
+ `2px solid var(--pf-table-pinned-column-border-color)`;
101
+ }
102
+ });
103
+
104
+ // Cells
105
+ const cells = document.querySelectorAll(
106
+ `.rdt_TableCell[data-column-id="sticky-column-${columnIndex}"]`,
107
+ );
108
+ cells.forEach((cell) => {
109
+ (cell as HTMLElement).style.position = 'sticky';
110
+ (cell as HTMLElement).style.left = `${left}px`;
111
+ (cell as HTMLElement).style.zIndex = '2';
112
+ (cell as HTMLElement).style.backgroundColor =
113
+ 'var(--pf-table-pinned-column-background-color)';
114
+ if (isLastPinnedColumn) {
115
+ (cell as HTMLElement).style.borderRight =
116
+ `2px solid var(--pf-table-pinned-column-border-color)`;
117
+ }
118
+ });
119
+ }
120
+ });
121
+ };
122
+
123
+ // Set up resize observers
124
+ const table = document.querySelector('.rdt_Table');
125
+ const resizeObserver = new ResizeObserver(recalculatePositions);
126
+
127
+ if (table) {
128
+ resizeObserver.observe(table);
129
+ }
130
+ window.addEventListener('resize', recalculatePositions);
131
+
132
+ return () => {
133
+ resizeObserver.disconnect();
134
+ window.removeEventListener('resize', recalculatePositions);
135
+ };
136
+ }, [canPinColumns, dataColumnIds]);
137
+
138
+ // Process columns for rendering with pin state
139
+ const columnsWithPinning = canPinColumns
140
+ ? sortPinnedColumns(processColumns(columns, dataColumnIds, togglePinnedColumn), dataColumnIds)
141
+ : columns;
142
+
143
+ return {
144
+ columnsWithPinning, // Columns with pin state and handlers applied
145
+ };
146
+ };
@@ -1,4 +1,6 @@
1
1
  import { TableColumn } from './types';
2
+ import { useState } from 'react';
3
+ import { Table } from './Table';
2
4
 
3
5
  export interface SampleDataRow {
4
6
  name: string;
@@ -7,6 +9,10 @@ export interface SampleDataRow {
7
9
  weapon: string;
8
10
  backstory: string;
9
11
  favoriteMeal: string;
12
+ homeland: string;
13
+ alignment: string;
14
+ specialAbility: string;
15
+ test: string;
10
16
  }
11
17
 
12
18
  export const sampleData: SampleDataRow[] = [
@@ -17,6 +23,10 @@ export const sampleData: SampleDataRow[] = [
17
23
  weapon: 'Longbow',
18
24
  backstory: 'Raised by wolves in the deep forests.',
19
25
  favoriteMeal: 'Venison stew',
26
+ homeland: 'Silverleaf Forest',
27
+ alignment: 'Neutral Good',
28
+ specialAbility: 'Beast Speech',
29
+ test: 'test',
20
30
  },
21
31
  {
22
32
  name: 'Brom',
@@ -25,6 +35,10 @@ export const sampleData: SampleDataRow[] = [
25
35
  weapon: 'Greatsword',
26
36
  backstory: 'A former soldier seeking redemption.',
27
37
  favoriteMeal: 'Roasted boar',
38
+ homeland: 'Kingdom of Valorhaven',
39
+ alignment: 'Lawful Good',
40
+ specialAbility: 'Battle Master',
41
+ test: 'test',
28
42
  },
29
43
  {
30
44
  name: 'Elysia',
@@ -33,6 +47,10 @@ export const sampleData: SampleDataRow[] = [
33
47
  weapon: 'Mace',
34
48
  backstory: 'A devoted follower of the goddess of life.',
35
49
  favoriteMeal: 'Vegetable soup',
50
+ homeland: 'Temple of Dawn',
51
+ alignment: 'Lawful Good',
52
+ specialAbility: 'Divine Healing',
53
+ test: 'test',
36
54
  },
37
55
  {
38
56
  name: 'Faelar',
@@ -41,6 +59,10 @@ export const sampleData: SampleDataRow[] = [
41
59
  weapon: 'Dagger',
42
60
  backstory: 'Grew up on the streets of a bustling city.',
43
61
  favoriteMeal: 'Bread and cheese',
62
+ homeland: 'Shadowport City',
63
+ alignment: 'Chaotic Neutral',
64
+ specialAbility: 'Shadow Step',
65
+ test: 'test',
44
66
  },
45
67
  {
46
68
  name: 'Maelis',
@@ -49,6 +71,10 @@ export const sampleData: SampleDataRow[] = [
49
71
  weapon: 'Staff',
50
72
  backstory: 'A scholar obsessed with ancient magic.',
51
73
  favoriteMeal: 'Mushroom stew',
74
+ homeland: 'Crystal Spire Academy',
75
+ alignment: 'Neutral',
76
+ specialAbility: 'Time Manipulation',
77
+ test: 'test',
52
78
  },
53
79
  {
54
80
  name: 'Grimm',
@@ -57,6 +83,10 @@ export const sampleData: SampleDataRow[] = [
57
83
  weapon: 'Battleaxe',
58
84
  backstory: 'Hails from a tribe in the frozen north.',
59
85
  favoriteMeal: 'Grilled salmon',
86
+ homeland: 'Frostpeak Mountains',
87
+ alignment: 'Chaotic Good',
88
+ specialAbility: 'Berserker Rage',
89
+ test: 'test',
60
90
  },
61
91
  {
62
92
  name: 'Seraphina',
@@ -65,6 +95,10 @@ export const sampleData: SampleDataRow[] = [
65
95
  weapon: 'Rapier',
66
96
  backstory: 'A wandering minstrel with a mysterious past.',
67
97
  favoriteMeal: 'Apple pie',
98
+ homeland: 'Wandering Roads',
99
+ alignment: 'Chaotic Good',
100
+ specialAbility: 'Enchanting Voice',
101
+ test: 'test',
68
102
  },
69
103
  {
70
104
  name: 'Kael',
@@ -73,6 +107,10 @@ export const sampleData: SampleDataRow[] = [
73
107
  weapon: 'Scimitar',
74
108
  backstory: 'A guardian of the natural world.',
75
109
  favoriteMeal: 'Berry tart',
110
+ homeland: 'Emerald Grove',
111
+ alignment: 'True Neutral',
112
+ specialAbility: 'Wild Shape',
113
+ test: 'test',
76
114
  },
77
115
  {
78
116
  name: 'Aria',
@@ -81,6 +119,10 @@ export const sampleData: SampleDataRow[] = [
81
119
  weapon: 'Longsword',
82
120
  backstory: 'A knight on a divine mission.',
83
121
  favoriteMeal: 'Roasted chicken',
122
+ homeland: 'Radiant Citadel',
123
+ alignment: 'Lawful Good',
124
+ specialAbility: 'Holy Smite',
125
+ test: 'test',
84
126
  },
85
127
  {
86
128
  name: 'Xan',
@@ -89,6 +131,10 @@ export const sampleData: SampleDataRow[] = [
89
131
  weapon: 'Pact Blade',
90
132
  backstory: 'Made a pact with a powerful entity for magic.',
91
133
  favoriteMeal: 'Stuffed peppers',
134
+ homeland: 'Shadowfell Gate',
135
+ alignment: 'Lawful Evil',
136
+ specialAbility: 'Eldritch Blast',
137
+ test: 'test',
92
138
  },
93
139
  {
94
140
  name: 'Lyra',
@@ -97,6 +143,10 @@ export const sampleData: SampleDataRow[] = [
97
143
  weapon: 'Dagger',
98
144
  backstory: 'Born with innate magical abilities.',
99
145
  favoriteMeal: 'Pancakes with syrup',
146
+ homeland: 'Storm Peaks',
147
+ alignment: 'Chaotic Neutral',
148
+ specialAbility: 'Wild Magic',
149
+ test: 'test',
100
150
  },
101
151
  {
102
152
  name: 'Thorn',
@@ -105,6 +155,10 @@ export const sampleData: SampleDataRow[] = [
105
155
  weapon: 'Quarterstaff',
106
156
  backstory: 'A hermit who seeks enlightenment.',
107
157
  favoriteMeal: 'Rice and vegetables',
158
+ homeland: 'Mountain Sanctuary',
159
+ alignment: 'Lawful Neutral',
160
+ specialAbility: 'Ki Mastery',
161
+ test: 'test',
108
162
  },
109
163
  {
110
164
  name: 'Cassia',
@@ -113,6 +167,10 @@ export const sampleData: SampleDataRow[] = [
113
167
  weapon: 'Shortbow',
114
168
  backstory: 'A hunter with a keen eye.',
115
169
  favoriteMeal: 'Roasted pheasant',
170
+ homeland: 'Misty Vale',
171
+ alignment: 'Neutral Good',
172
+ specialAbility: 'Eagle Eye',
173
+ test: 'test',
116
174
  },
117
175
  {
118
176
  name: 'Darius',
@@ -121,6 +179,10 @@ export const sampleData: SampleDataRow[] = [
121
179
  weapon: 'Longsword',
122
180
  backstory: 'A mercenary with a heart of gold.',
123
181
  favoriteMeal: 'Beef stew',
182
+ homeland: 'Free Cities',
183
+ alignment: 'Neutral Good',
184
+ specialAbility: 'Second Wind',
185
+ test: 'test',
124
186
  },
125
187
  {
126
188
  name: 'Iris',
@@ -129,6 +191,10 @@ export const sampleData: SampleDataRow[] = [
129
191
  weapon: 'Warhammer',
130
192
  backstory: 'A healer devoted to the god of light.',
131
193
  favoriteMeal: 'Fresh bread and soup',
194
+ homeland: 'Sun Temple',
195
+ alignment: 'Lawful Good',
196
+ specialAbility: 'Turn Undead',
197
+ test: 'test',
132
198
  },
133
199
  {
134
200
  name: 'Fenrir',
@@ -137,6 +203,10 @@ export const sampleData: SampleDataRow[] = [
137
203
  weapon: 'Greataxe',
138
204
  backstory: 'A warrior from a distant land.',
139
205
  favoriteMeal: 'Grilled steak',
206
+ homeland: 'Northern Wastes',
207
+ alignment: 'Chaotic Neutral',
208
+ specialAbility: 'Primal Strike',
209
+ test: 'test',
140
210
  },
141
211
  {
142
212
  name: 'Luna',
@@ -145,6 +215,10 @@ export const sampleData: SampleDataRow[] = [
145
215
  weapon: 'Sickle',
146
216
  backstory: 'A protector of the natural world.',
147
217
  favoriteMeal: 'Fruit salad',
218
+ homeland: 'Moongrove Forest',
219
+ alignment: 'Neutral Good',
220
+ specialAbility: 'Natures Blessing',
221
+ test: 'test',
148
222
  },
149
223
  {
150
224
  name: 'Orion',
@@ -153,6 +227,10 @@ export const sampleData: SampleDataRow[] = [
153
227
  weapon: 'Halberd',
154
228
  backstory: 'A knight on a holy quest.',
155
229
  favoriteMeal: 'Roasted lamb',
230
+ homeland: 'Golden Spires',
231
+ alignment: 'Lawful Good',
232
+ specialAbility: 'Divine Shield',
233
+ test: 'test',
156
234
  },
157
235
  {
158
236
  name: 'Astra',
@@ -161,6 +239,10 @@ export const sampleData: SampleDataRow[] = [
161
239
  weapon: 'Lute',
162
240
  backstory: 'A musician with a magical voice.',
163
241
  favoriteMeal: 'Honey cake',
242
+ homeland: 'Singing Valleys',
243
+ alignment: 'Chaotic Good',
244
+ specialAbility: 'Sonic Wave',
245
+ test: 'test',
164
246
  },
165
247
  {
166
248
  name: 'Zephyr',
@@ -169,6 +251,10 @@ export const sampleData: SampleDataRow[] = [
169
251
  weapon: 'Shortsword',
170
252
  backstory: 'A thief with a quick hand.',
171
253
  favoriteMeal: 'Fish and chips',
254
+ homeland: 'Port Haven',
255
+ alignment: 'Chaotic Neutral',
256
+ specialAbility: 'Quick Strike',
257
+ test: 'test',
172
258
  },
173
259
  {
174
260
  name: 'Thalia',
@@ -177,6 +263,10 @@ export const sampleData: SampleDataRow[] = [
177
263
  weapon: 'Crystal Staff',
178
264
  backstory: 'An archivist of forbidden knowledge.',
179
265
  favoriteMeal: 'Spiced wine and cheese',
266
+ homeland: 'Arcane Archives',
267
+ alignment: 'Neutral',
268
+ specialAbility: 'Spell Mastery',
269
+ test: 'test',
180
270
  },
181
271
  {
182
272
  name: 'Ragnar',
@@ -185,6 +275,10 @@ export const sampleData: SampleDataRow[] = [
185
275
  weapon: 'War Hammer',
186
276
  backstory: 'A dwarven smith turned adventurer.',
187
277
  favoriteMeal: 'Mead and roasted mutton',
278
+ homeland: 'Iron Mountains',
279
+ alignment: 'Lawful Neutral',
280
+ specialAbility: 'Master Smith',
281
+ test: 'test',
188
282
  },
189
283
  {
190
284
  name: 'Sylvana',
@@ -193,6 +287,10 @@ export const sampleData: SampleDataRow[] = [
193
287
  weapon: 'Elven Bow',
194
288
  backstory: 'Guardian of the enchanted forest.',
195
289
  favoriteMeal: 'Elvish waybread',
290
+ homeland: 'Eternal Woods',
291
+ alignment: 'Neutral Good',
292
+ specialAbility: 'Forest Walker',
293
+ test: 'test',
196
294
  },
197
295
  {
198
296
  name: 'Magnus',
@@ -201,6 +299,10 @@ export const sampleData: SampleDataRow[] = [
201
299
  weapon: 'Cursed Dagger',
202
300
  backstory: 'Made a deal with a demon for revenge.',
203
301
  favoriteMeal: 'Blood oranges',
302
+ homeland: 'Cursed Lands',
303
+ alignment: 'Neutral Evil',
304
+ specialAbility: 'Dark Pact',
305
+ test: 'test',
204
306
  },
205
307
  {
206
308
  name: 'Echo',
@@ -209,6 +311,10 @@ export const sampleData: SampleDataRow[] = [
209
311
  weapon: 'Enchanted Violin',
210
312
  backstory: 'Can mimic any sound perfectly.',
211
313
  favoriteMeal: 'Sweet rolls',
314
+ homeland: 'Harmony Hall',
315
+ alignment: 'Chaotic Good',
316
+ specialAbility: 'Perfect Pitch',
317
+ test: 'test',
212
318
  },
213
319
  {
214
320
  name: 'Korg',
@@ -217,6 +323,10 @@ export const sampleData: SampleDataRow[] = [
217
323
  weapon: 'Stone Maul',
218
324
  backstory: 'Last survivor of a petrified tribe.',
219
325
  favoriteMeal: 'Raw meat',
326
+ homeland: 'Stone Peaks',
327
+ alignment: 'Neutral',
328
+ specialAbility: 'Stone Skin',
329
+ test: 'test',
220
330
  },
221
331
  {
222
332
  name: 'Celeste',
@@ -225,6 +335,10 @@ export const sampleData: SampleDataRow[] = [
225
335
  weapon: 'Starlight Wand',
226
336
  backstory: 'Born during a celestial convergence.',
227
337
  favoriteMeal: 'Moon cakes',
338
+ homeland: 'Star Tower',
339
+ alignment: 'Chaotic Good',
340
+ specialAbility: 'Celestial Power',
341
+ test: 'test',
228
342
  },
229
343
  {
230
344
  name: 'Raven',
@@ -233,6 +347,10 @@ export const sampleData: SampleDataRow[] = [
233
347
  weapon: 'Shadow Blade',
234
348
  backstory: 'Professional assassin seeking redemption.',
235
349
  favoriteMeal: 'Whatever their mark is having',
350
+ homeland: 'Night City',
351
+ alignment: 'True Neutral',
352
+ specialAbility: 'Death Strike',
353
+ test: 'test',
236
354
  },
237
355
  {
238
356
  name: 'Terra',
@@ -241,6 +359,10 @@ export const sampleData: SampleDataRow[] = [
241
359
  weapon: 'Living Wood Staff',
242
360
  backstory: 'Speaks with ancient trees.',
243
361
  favoriteMeal: 'Wild mushrooms',
362
+ homeland: 'Ancient Grove',
363
+ alignment: 'Neutral Good',
364
+ specialAbility: 'Tree Speech',
365
+ test: 'test',
244
366
  },
245
367
  {
246
368
  name: 'Ash',
@@ -249,6 +371,10 @@ export const sampleData: SampleDataRow[] = [
249
371
  weapon: 'Flame Fists',
250
372
  backstory: 'Raised by phoenix monks in a volcano.',
251
373
  favoriteMeal: 'Spicy noodles',
374
+ homeland: 'Phoenix Monastery',
375
+ alignment: 'Lawful Neutral',
376
+ specialAbility: 'Fire Fist',
377
+ test: 'test',
252
378
  },
253
379
  ];
254
380
 
@@ -256,26 +382,55 @@ export const columns: TableColumn<SampleDataRow>[] = [
256
382
  {
257
383
  name: 'Name',
258
384
  selector: (row: SampleDataRow) => row.name,
385
+ id: 'name',
386
+ width: '150px',
259
387
  },
260
388
  {
261
389
  name: 'Class',
262
390
  selector: (row) => row.class,
391
+ id: 'class',
392
+ width: '150px',
263
393
  },
264
394
  {
265
395
  name: 'Age',
266
396
  selector: (row) => row.age,
267
- sortable: true,
397
+ id: 'age',
398
+ width: '150px',
268
399
  },
269
400
  {
270
401
  name: 'Weapon',
271
402
  selector: (row) => row.weapon,
403
+ id: 'weapon',
404
+ width: '150px',
272
405
  },
273
406
  {
274
407
  name: 'Backstory',
275
408
  selector: (row) => row.backstory,
409
+ id: 'backstory',
410
+ width: '150px',
276
411
  },
277
412
  {
278
413
  name: 'Favorite Meal',
279
414
  selector: (row) => row.favoriteMeal,
415
+ id: 'favoriteMeal',
416
+ width: '200px',
417
+ },
418
+ {
419
+ name: 'Homeland',
420
+ selector: (row) => row.homeland,
421
+ id: 'homeland',
422
+ width: '200px',
423
+ },
424
+ {
425
+ name: 'Alignment',
426
+ selector: (row) => row.alignment,
427
+ id: 'alignment',
428
+ width: '200px',
429
+ },
430
+ {
431
+ name: 'Special Ability',
432
+ selector: (row) => row.specialAbility,
433
+ id: 'specialAbility',
434
+ width: '200px',
280
435
  },
281
436
  ];
@@ -53,8 +53,28 @@
53
53
  // Firefox scrollbar styles
54
54
  scrollbar-width: thin;
55
55
  scrollbar-color: var(--pf-table-border-color) var(--pf-table-background-color);
56
+ } // body
57
+
58
+ // Pinned Columns
59
+ .table__column__pin-action {
60
+ padding: 0;
61
+ padding-right: var(--pf-padding-1);
62
+ }
63
+ .table__column--is-pinned {
64
+ opacity: 1;
65
+ }
66
+
67
+ .table__column--is-not-pinned {
68
+ opacity: 0.3;
69
+ }
70
+
71
+ .table__header-cell {
72
+ display: flex;
73
+ align-items: center;
56
74
  }
57
75
 
76
+ // End Pinned Columns
77
+
58
78
  & > *:nth-child(3) {
59
79
  margin-top: auto;
60
80
  background-color: transparent;
@@ -87,33 +107,26 @@
87
107
  }
88
108
  }
89
109
 
110
+ .rdt_TableHead {
111
+ z-index: 3;
112
+ }
113
+
90
114
  .rdt_TableHeader {
91
115
  border-radius: var(--pf-rounded) 0;
92
116
  border: var(--pf-border-sm) solid var(--pf-table-border-color);
93
117
  border-bottom: none;
94
118
  }
95
119
 
96
- .rdt_TableHeadRow,
97
- .rdt_TableRow {
98
- & > :first-child {
99
- padding-left: var(--pf-padding-4);
100
- min-width: 60px;
101
- justify-content: left;
102
- }
103
- }
104
-
105
120
  .rdt_TableRow {
106
121
  border-top: var(--pf-border-sm) solid var(--pf-table-border-color);
107
122
  border-bottom: var(--pf-border-sm) solid var(--pf-table-border-color);
108
123
  &:hover {
109
- .rdt_TableCell {
110
- background-color: var(--pf-table-hover-color) !important;
111
- }
112
- }
124
+ border-top: var(--pf-border-sm) solid var(--pf-table-border-color);
125
+ border-bottom: var(--pf-border-sm) solid var(--pf-table-border-color);
126
+ outline-color: var(--pf-table-border-color);
113
127
 
114
- &:first-child {
115
128
  .rdt_TableCell {
116
- border-top: var(--pf-border-sm) solid var(--pf-table-border-color);
129
+ background-color: var(--pf-table-hover-color) !important;
117
130
  }
118
131
  }
119
132
  }
@@ -125,6 +138,10 @@
125
138
  &:not(:first-child) {
126
139
  border-left: var(--pf-border-sm) solid var(--pf-table-border-color);
127
140
  }
141
+
142
+ &:not(:last-of-type) {
143
+ border-bottom-width: 0;
144
+ }
128
145
  }
129
146
 
130
147
  /* Striped: alternating background */
@@ -28,4 +28,6 @@
28
28
  --pf-table-highlighted-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4), 0 8px 16px rgba(0, 0, 0, 0.3);
29
29
  --pf-table-font-size: var(--pf-font-size-body2);
30
30
  --pf-table-pagination-background-color: var(--pf-primary-color-700);
31
+ --pf-table-pinned-column-border-color: var(--pf-primary-color-100);
32
+ --pf-table-pinned-column-background-color: var(--pf-table-background-color);
31
33
  }
@@ -5,17 +5,29 @@ import {
5
5
  TableColumn as RDTTableColumn,
6
6
  IDataTableProps,
7
7
  } from 'react-data-table-component';
8
+ import { CSSObject } from 'styled-components';
8
9
 
9
10
  export type Direction = `${RDTDirection}`;
10
11
  export type Alignment = `${RDTAlignment}`;
11
- export type TableColumn<T> = RDTTableColumn<T>;
12
+
13
+ export interface PinnableColumn<T> extends RDTTableColumn<T> {
14
+ id: string;
15
+ isPinned?: boolean;
16
+ style?: CSSObject;
17
+ position?: number;
18
+ }
19
+
20
+ export type TableColumn<T> = PinnableColumn<T>;
12
21
 
13
22
  export interface TableProps<T>
14
23
  extends Omit<IDataTableProps<T>, 'paginationComponent' | 'direction' | 'subHeaderAlign'> {
24
+ columns: TableColumn<T>[];
15
25
  isDisabled?: boolean;
16
26
  isLoading?: boolean;
17
27
  direction?: Direction;
18
28
  subHeaderAlign?: 'left' | 'right' | 'center';
19
29
  isFullHeight?: boolean;
20
30
  totalEntriesText?: string;
31
+ canPinColumns?: boolean;
32
+ onPinnedColumnsChange?: (pinnedColumnIds: any[]) => void;
21
33
  }
@@ -0,0 +1,35 @@
1
+ import { TableColumn } from '../types';
2
+ import HorizontalStickyHeader from '../components/HorizontalStickyHeader';
3
+ import { getPinnedColumnStyles } from '../components/helpers';
4
+
5
+ export const processColumns = <T,>(
6
+ columns: TableColumn<T>[],
7
+ pinnedColumnIds: string[],
8
+ togglePinnedColumn: (id: string) => void,
9
+ ): TableColumn<T>[] => {
10
+ return columns.map((column, index) => {
11
+ const dataColumnId = `sticky-column-${index}`;
12
+ const isPinned = pinnedColumnIds.includes(dataColumnId);
13
+
14
+ const headerContent =
15
+ column.isPinned !== undefined ? (
16
+ <HorizontalStickyHeader
17
+ position={index}
18
+ isPinned={isPinned}
19
+ onPinColumn={() => togglePinnedColumn(column.id)}
20
+ pinnedColumnIds={pinnedColumnIds}
21
+ >
22
+ {column.name}
23
+ </HorizontalStickyHeader>
24
+ ) : (
25
+ <>{column.name}</>
26
+ );
27
+
28
+ return {
29
+ ...column,
30
+ name: headerContent,
31
+ id: dataColumnId,
32
+ style: getPinnedColumnStyles(isPinned, index, pinnedColumnIds),
33
+ };
34
+ });
35
+ };