@idealyst/components 1.2.128 → 1.2.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/Table/Table.native.tsx +91 -67
- package/src/Table/Table.web.tsx +53 -44
- package/src/Table/types.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/components",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.130",
|
|
4
4
|
"description": "Shared component library for React and React Native",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"publish:npm": "npm publish"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@idealyst/theme": "^1.2.
|
|
59
|
+
"@idealyst/theme": "^1.2.130",
|
|
60
60
|
"@mdi/js": ">=7.0.0",
|
|
61
61
|
"@mdi/react": ">=1.0.0",
|
|
62
62
|
"@react-native-vector-icons/common": ">=12.0.0",
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
"@mdi/react": {
|
|
82
82
|
"optional": true
|
|
83
83
|
},
|
|
84
|
-
"@react-
|
|
84
|
+
"@react-native/normalize-colors": {
|
|
85
85
|
"optional": true
|
|
86
86
|
},
|
|
87
|
-
"@react-
|
|
87
|
+
"@react-navigation/bottom-tabs": {
|
|
88
88
|
"optional": true
|
|
89
89
|
},
|
|
90
90
|
"react-native": {
|
|
@@ -111,8 +111,8 @@
|
|
|
111
111
|
},
|
|
112
112
|
"devDependencies": {
|
|
113
113
|
"@idealyst/blur": "^1.2.40",
|
|
114
|
-
"@idealyst/theme": "^1.2.
|
|
115
|
-
"@idealyst/tooling": "^1.2.
|
|
114
|
+
"@idealyst/theme": "^1.2.130",
|
|
115
|
+
"@idealyst/tooling": "^1.2.130",
|
|
116
116
|
"@mdi/react": "^1.6.1",
|
|
117
117
|
"@types/react": "^19.1.0",
|
|
118
118
|
"react": "^19.1.0",
|
|
@@ -256,6 +256,96 @@ function TableInner<T = any>({
|
|
|
256
256
|
return column.footer;
|
|
257
257
|
};
|
|
258
258
|
|
|
259
|
+
// Split columns into sticky left, scrollable, and sticky right
|
|
260
|
+
const leftCols = useMemo(() => columns.filter((c) => c.sticky === true || c.sticky === 'left'), [columns]);
|
|
261
|
+
const rightCols = useMemo(() => columns.filter((c) => c.sticky === 'right'), [columns]);
|
|
262
|
+
const scrollCols = useMemo(() => columns.filter((c) => !c.sticky), [columns]);
|
|
263
|
+
const hasStickyColumns = leftCols.length > 0 || rightCols.length > 0;
|
|
264
|
+
|
|
265
|
+
// Renders a column group (header + body + footer) for a set of columns
|
|
266
|
+
const renderColumnGroup = (cols: TableColumn<T>[]) => (
|
|
267
|
+
<View>
|
|
268
|
+
{/* Header */}
|
|
269
|
+
<View style={theadStyle}>
|
|
270
|
+
<View style={{ flexDirection: 'row' }}>
|
|
271
|
+
{cols.map((column) => (
|
|
272
|
+
<TH key={column.key} size={size} type={type} align={column.align} width={column.width}>
|
|
273
|
+
{column.title}
|
|
274
|
+
</TH>
|
|
275
|
+
))}
|
|
276
|
+
</View>
|
|
277
|
+
</View>
|
|
278
|
+
|
|
279
|
+
{/* Body */}
|
|
280
|
+
<View style={tbodyStyle}>
|
|
281
|
+
{data.length === 0 && emptyState ? (
|
|
282
|
+
<View style={{ alignItems: 'center', padding: 16 }}>
|
|
283
|
+
{emptyState}
|
|
284
|
+
</View>
|
|
285
|
+
) : (
|
|
286
|
+
data.map((row, rowIndex) => (
|
|
287
|
+
<TR
|
|
288
|
+
key={rowIndex}
|
|
289
|
+
size={size}
|
|
290
|
+
type={type}
|
|
291
|
+
clickable={isClickable}
|
|
292
|
+
onPress={() => onRowPress?.(row, rowIndex)}
|
|
293
|
+
testID={testID ? `${testID}-row-${rowIndex}` : undefined}
|
|
294
|
+
>
|
|
295
|
+
{cols.map((column) => (
|
|
296
|
+
<TD key={column.key} size={size} type={type} align={column.align} width={column.width}>
|
|
297
|
+
{getCellValue(column, row, rowIndex)}
|
|
298
|
+
</TD>
|
|
299
|
+
))}
|
|
300
|
+
</TR>
|
|
301
|
+
))
|
|
302
|
+
)}
|
|
303
|
+
</View>
|
|
304
|
+
|
|
305
|
+
{/* Footer */}
|
|
306
|
+
{hasFooter && (
|
|
307
|
+
<View style={(tableStyles.tfoot as any)({})}>
|
|
308
|
+
<View style={{ flexDirection: 'row' }}>
|
|
309
|
+
{cols.map((column) => (
|
|
310
|
+
<TF key={column.key} size={size} type={type} align={column.align} width={column.width}>
|
|
311
|
+
{getFooterContent(column) ?? null}
|
|
312
|
+
</TF>
|
|
313
|
+
))}
|
|
314
|
+
</View>
|
|
315
|
+
</View>
|
|
316
|
+
)}
|
|
317
|
+
</View>
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// When there are sticky columns, render as: [left sticky] [scrollable] [right sticky]
|
|
321
|
+
if (hasStickyColumns) {
|
|
322
|
+
return (
|
|
323
|
+
<View
|
|
324
|
+
nativeID={id}
|
|
325
|
+
style={[containerStyle, { flexDirection: 'row' }, style]}
|
|
326
|
+
testID={testID}
|
|
327
|
+
{...nativeA11yProps}
|
|
328
|
+
>
|
|
329
|
+
{leftCols.length > 0 && (
|
|
330
|
+
<View style={{ zIndex: 1, backgroundColor: theadStyle.backgroundColor || containerStyle.backgroundColor }}>
|
|
331
|
+
{renderColumnGroup(leftCols)}
|
|
332
|
+
</View>
|
|
333
|
+
)}
|
|
334
|
+
<ScrollView ref={ref} horizontal style={{ flex: 1 }}>
|
|
335
|
+
<View style={tableStyle}>
|
|
336
|
+
{renderColumnGroup(scrollCols)}
|
|
337
|
+
</View>
|
|
338
|
+
</ScrollView>
|
|
339
|
+
{rightCols.length > 0 && (
|
|
340
|
+
<View style={{ zIndex: 1, backgroundColor: theadStyle.backgroundColor || containerStyle.backgroundColor }}>
|
|
341
|
+
{renderColumnGroup(rightCols)}
|
|
342
|
+
</View>
|
|
343
|
+
)}
|
|
344
|
+
</View>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// No sticky columns — original simple layout
|
|
259
349
|
return (
|
|
260
350
|
<ScrollView
|
|
261
351
|
ref={ref}
|
|
@@ -266,73 +356,7 @@ function TableInner<T = any>({
|
|
|
266
356
|
{...nativeA11yProps}
|
|
267
357
|
>
|
|
268
358
|
<View style={tableStyle}>
|
|
269
|
-
{
|
|
270
|
-
<View style={theadStyle}>
|
|
271
|
-
<View style={{ flexDirection: 'row' }}>
|
|
272
|
-
{columns.map((column) => (
|
|
273
|
-
<TH
|
|
274
|
-
key={column.key}
|
|
275
|
-
size={size}
|
|
276
|
-
type={type}
|
|
277
|
-
align={column.align}
|
|
278
|
-
width={column.width}
|
|
279
|
-
>
|
|
280
|
-
{column.title}
|
|
281
|
-
</TH>
|
|
282
|
-
))}
|
|
283
|
-
</View>
|
|
284
|
-
</View>
|
|
285
|
-
|
|
286
|
-
{/* Body */}
|
|
287
|
-
<View style={tbodyStyle}>
|
|
288
|
-
{data.length === 0 && emptyState ? (
|
|
289
|
-
<View style={{ alignItems: 'center', padding: 16 }}>
|
|
290
|
-
{emptyState}
|
|
291
|
-
</View>
|
|
292
|
-
) : (
|
|
293
|
-
data.map((row, rowIndex) => (
|
|
294
|
-
<TR
|
|
295
|
-
key={rowIndex}
|
|
296
|
-
size={size}
|
|
297
|
-
type={type}
|
|
298
|
-
clickable={isClickable}
|
|
299
|
-
onPress={() => onRowPress?.(row, rowIndex)}
|
|
300
|
-
testID={testID ? `${testID}-row-${rowIndex}` : undefined}
|
|
301
|
-
>
|
|
302
|
-
{columns.map((column) => (
|
|
303
|
-
<TD
|
|
304
|
-
key={column.key}
|
|
305
|
-
size={size}
|
|
306
|
-
type={type}
|
|
307
|
-
align={column.align}
|
|
308
|
-
width={column.width}
|
|
309
|
-
>
|
|
310
|
-
{getCellValue(column, row, rowIndex)}
|
|
311
|
-
</TD>
|
|
312
|
-
))}
|
|
313
|
-
</TR>
|
|
314
|
-
))
|
|
315
|
-
)}
|
|
316
|
-
</View>
|
|
317
|
-
|
|
318
|
-
{/* Footer */}
|
|
319
|
-
{hasFooter && (
|
|
320
|
-
<View style={(tableStyles.tfoot as any)({})}>
|
|
321
|
-
<View style={{ flexDirection: 'row' }}>
|
|
322
|
-
{columns.map((column) => (
|
|
323
|
-
<TF
|
|
324
|
-
key={column.key}
|
|
325
|
-
size={size}
|
|
326
|
-
type={type}
|
|
327
|
-
align={column.align}
|
|
328
|
-
width={column.width}
|
|
329
|
-
>
|
|
330
|
-
{getFooterContent(column) ?? null}
|
|
331
|
-
</TF>
|
|
332
|
-
))}
|
|
333
|
-
</View>
|
|
334
|
-
</View>
|
|
335
|
-
)}
|
|
359
|
+
{renderColumnGroup(columns)}
|
|
336
360
|
</View>
|
|
337
361
|
</ScrollView>
|
|
338
362
|
);
|
package/src/Table/Table.web.tsx
CHANGED
|
@@ -4,6 +4,25 @@ import { tableStyles } from './Table.styles';
|
|
|
4
4
|
import type { TableProps, TableColumn, TableType, TableSizeVariant, TableAlignVariant } from './types';
|
|
5
5
|
import { getWebAriaProps } from '../utils/accessibility';
|
|
6
6
|
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Helpers
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
function getStickyStyle(
|
|
12
|
+
sticky: boolean | 'left' | 'right' | undefined,
|
|
13
|
+
offset: number | string | undefined,
|
|
14
|
+
zIndex: number,
|
|
15
|
+
): React.CSSProperties | undefined {
|
|
16
|
+
if (!sticky) return undefined;
|
|
17
|
+
const side = sticky === 'right' ? 'right' : 'left';
|
|
18
|
+
return {
|
|
19
|
+
position: 'sticky',
|
|
20
|
+
[side]: offset ?? 0,
|
|
21
|
+
zIndex,
|
|
22
|
+
backgroundColor: 'inherit',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
// ============================================================================
|
|
8
27
|
// Sub-component Props
|
|
9
28
|
// ============================================================================
|
|
@@ -23,8 +42,8 @@ interface THProps {
|
|
|
23
42
|
type?: TableType;
|
|
24
43
|
align?: TableAlignVariant;
|
|
25
44
|
width?: number | string;
|
|
26
|
-
sticky?: boolean;
|
|
27
|
-
|
|
45
|
+
sticky?: boolean | 'left' | 'right';
|
|
46
|
+
stickyOffset?: number | string;
|
|
28
47
|
resizable?: boolean;
|
|
29
48
|
onResize?: (width: number) => void;
|
|
30
49
|
minWidth?: number;
|
|
@@ -37,8 +56,8 @@ interface TDProps {
|
|
|
37
56
|
type?: TableType;
|
|
38
57
|
align?: TableAlignVariant;
|
|
39
58
|
width?: number | string;
|
|
40
|
-
sticky?: boolean;
|
|
41
|
-
|
|
59
|
+
sticky?: boolean | 'left' | 'right';
|
|
60
|
+
stickyOffset?: number | string;
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
// ============================================================================
|
|
@@ -83,7 +102,7 @@ function TH({
|
|
|
83
102
|
align = 'left',
|
|
84
103
|
width,
|
|
85
104
|
sticky,
|
|
86
|
-
|
|
105
|
+
stickyOffset,
|
|
87
106
|
resizable,
|
|
88
107
|
onResize,
|
|
89
108
|
minWidth = 50,
|
|
@@ -129,20 +148,13 @@ function TH({
|
|
|
129
148
|
document.addEventListener('pointerup', handlePointerUp);
|
|
130
149
|
}, [minWidth, onResize]);
|
|
131
150
|
|
|
132
|
-
const stickyStyle: React.CSSProperties | undefined = sticky ? {
|
|
133
|
-
position: 'sticky',
|
|
134
|
-
left: stickyLeft ?? 0,
|
|
135
|
-
zIndex: 11,
|
|
136
|
-
backgroundColor: 'inherit',
|
|
137
|
-
} : undefined;
|
|
138
|
-
|
|
139
151
|
return (
|
|
140
152
|
<th
|
|
141
153
|
{...headerCellProps}
|
|
142
154
|
ref={thRef}
|
|
143
155
|
scope="col"
|
|
144
156
|
aria-sort={accessibilitySort}
|
|
145
|
-
style={{ width, ...
|
|
157
|
+
style={{ width, ...getStickyStyle(sticky, stickyOffset, 11) }}
|
|
146
158
|
>
|
|
147
159
|
{children}
|
|
148
160
|
{resizable && (
|
|
@@ -174,7 +186,7 @@ function TD({
|
|
|
174
186
|
align = 'left',
|
|
175
187
|
width,
|
|
176
188
|
sticky,
|
|
177
|
-
|
|
189
|
+
stickyOffset,
|
|
178
190
|
}: TDProps) {
|
|
179
191
|
tableStyles.useVariants({
|
|
180
192
|
size,
|
|
@@ -184,17 +196,10 @@ function TD({
|
|
|
184
196
|
|
|
185
197
|
const cellProps = getWebProps([(tableStyles.cell as any)({})]);
|
|
186
198
|
|
|
187
|
-
const stickyStyle: React.CSSProperties | undefined = sticky ? {
|
|
188
|
-
position: 'sticky',
|
|
189
|
-
left: stickyLeft ?? 0,
|
|
190
|
-
zIndex: 1,
|
|
191
|
-
backgroundColor: 'inherit',
|
|
192
|
-
} : undefined;
|
|
193
|
-
|
|
194
199
|
return (
|
|
195
200
|
<td
|
|
196
201
|
{...cellProps}
|
|
197
|
-
style={{ width, ...
|
|
202
|
+
style={{ width, ...getStickyStyle(sticky, stickyOffset, 1) }}
|
|
198
203
|
>
|
|
199
204
|
{children}
|
|
200
205
|
</td>
|
|
@@ -211,8 +216,8 @@ interface TFProps {
|
|
|
211
216
|
type?: TableType;
|
|
212
217
|
align?: TableAlignVariant;
|
|
213
218
|
width?: number | string;
|
|
214
|
-
sticky?: boolean;
|
|
215
|
-
|
|
219
|
+
sticky?: boolean | 'left' | 'right';
|
|
220
|
+
stickyOffset?: number | string;
|
|
216
221
|
}
|
|
217
222
|
|
|
218
223
|
function TF({
|
|
@@ -222,7 +227,7 @@ function TF({
|
|
|
222
227
|
align = 'left',
|
|
223
228
|
width,
|
|
224
229
|
sticky,
|
|
225
|
-
|
|
230
|
+
stickyOffset,
|
|
226
231
|
}: TFProps) {
|
|
227
232
|
tableStyles.useVariants({
|
|
228
233
|
size,
|
|
@@ -232,17 +237,10 @@ function TF({
|
|
|
232
237
|
|
|
233
238
|
const footerCellProps = getWebProps([(tableStyles.footerCell as any)({})]);
|
|
234
239
|
|
|
235
|
-
const stickyStyle: React.CSSProperties | undefined = sticky ? {
|
|
236
|
-
position: 'sticky',
|
|
237
|
-
left: stickyLeft ?? 0,
|
|
238
|
-
zIndex: 1,
|
|
239
|
-
backgroundColor: 'inherit',
|
|
240
|
-
} : undefined;
|
|
241
|
-
|
|
242
240
|
return (
|
|
243
241
|
<td
|
|
244
242
|
{...footerCellProps}
|
|
245
|
-
style={{ width, ...
|
|
243
|
+
style={{ width, ...getStickyStyle(sticky, stickyOffset, 1) }}
|
|
246
244
|
>
|
|
247
245
|
{children}
|
|
248
246
|
</td>
|
|
@@ -329,21 +327,32 @@ function Table<T = any>({
|
|
|
329
327
|
return column.footer;
|
|
330
328
|
};
|
|
331
329
|
|
|
332
|
-
// Compute cumulative
|
|
333
|
-
const
|
|
334
|
-
const map = new Map<string, number
|
|
330
|
+
// Compute cumulative offsets for sticky columns (left and right independently)
|
|
331
|
+
const stickyOffsetMap = useMemo(() => {
|
|
332
|
+
const map = new Map<string, number>();
|
|
333
|
+
|
|
334
|
+
// Left sticky: accumulate left-to-right
|
|
335
335
|
let cumulativeLeft = 0;
|
|
336
336
|
for (const col of columns) {
|
|
337
|
-
|
|
337
|
+
const side = col.sticky === 'right' ? 'right' : col.sticky ? 'left' : null;
|
|
338
|
+
if (side !== 'left') continue;
|
|
338
339
|
map.set(col.key, cumulativeLeft);
|
|
339
340
|
if (typeof col.width === 'number') {
|
|
340
341
|
cumulativeLeft += col.width;
|
|
341
|
-
} else if (typeof col.width === 'string') {
|
|
342
|
-
// For string widths (e.g. '200px'), use as-is for the first, then can't accumulate
|
|
343
|
-
// Only numeric widths support proper multi-column stacking
|
|
344
|
-
map.set(col.key, cumulativeLeft > 0 ? cumulativeLeft : 0);
|
|
345
342
|
}
|
|
346
343
|
}
|
|
344
|
+
|
|
345
|
+
// Right sticky: accumulate right-to-left
|
|
346
|
+
let cumulativeRight = 0;
|
|
347
|
+
for (let i = columns.length - 1; i >= 0; i--) {
|
|
348
|
+
const col = columns[i];
|
|
349
|
+
if (col.sticky !== 'right') continue;
|
|
350
|
+
map.set(col.key, cumulativeRight);
|
|
351
|
+
if (typeof col.width === 'number') {
|
|
352
|
+
cumulativeRight += col.width;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
347
356
|
return map;
|
|
348
357
|
}, [columns]);
|
|
349
358
|
|
|
@@ -360,7 +369,7 @@ function Table<T = any>({
|
|
|
360
369
|
align={column.align}
|
|
361
370
|
width={column.width}
|
|
362
371
|
sticky={column.sticky}
|
|
363
|
-
|
|
372
|
+
stickyOffset={stickyOffsetMap.get(column.key)}
|
|
364
373
|
resizable={column.resizable}
|
|
365
374
|
minWidth={column.minWidth}
|
|
366
375
|
onResize={onColumnResize ? (w) => onColumnResize(column.key, w) : undefined}
|
|
@@ -396,7 +405,7 @@ function Table<T = any>({
|
|
|
396
405
|
align={column.align}
|
|
397
406
|
width={column.width}
|
|
398
407
|
sticky={column.sticky}
|
|
399
|
-
|
|
408
|
+
stickyOffset={stickyOffsetMap.get(column.key)}
|
|
400
409
|
>
|
|
401
410
|
{getCellValue(column, row, rowIndex)}
|
|
402
411
|
</TD>
|
|
@@ -416,7 +425,7 @@ function Table<T = any>({
|
|
|
416
425
|
align={column.align}
|
|
417
426
|
width={column.width}
|
|
418
427
|
sticky={column.sticky}
|
|
419
|
-
|
|
428
|
+
stickyOffset={stickyOffsetMap.get(column.key)}
|
|
420
429
|
>
|
|
421
430
|
{getFooterContent(column) ?? null}
|
|
422
431
|
</TF>
|
package/src/Table/types.ts
CHANGED
|
@@ -19,9 +19,10 @@ export interface TableColumn<T = any> extends SortableAccessibilityProps {
|
|
|
19
19
|
align?: TableAlignVariant;
|
|
20
20
|
/**
|
|
21
21
|
* Makes this column sticky (pinned) when scrolling horizontally.
|
|
22
|
-
*
|
|
22
|
+
* `true` or `'left'` pins to the left, `'right'` pins to the right.
|
|
23
|
+
* On web uses CSS `position: sticky`, on native renders outside the ScrollView.
|
|
23
24
|
*/
|
|
24
|
-
sticky?: boolean;
|
|
25
|
+
sticky?: boolean | 'left' | 'right';
|
|
25
26
|
/**
|
|
26
27
|
* Allows the column to be resized by dragging the right edge of the header.
|
|
27
28
|
* Web only.
|