@astryxdesign/core 0.1.1 → 0.1.2-canary.2581eb4
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/CHANGELOG.md +105 -0
- package/README.md +119 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Banner/Banner.d.ts +7 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +9 -2
- package/dist/Button/Button.d.ts +12 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +51 -13
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +6 -1
- package/dist/Calendar/index.d.ts +1 -1
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +7 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +5 -4
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +7 -1
- package/dist/HoverCard/HoverCard.d.ts +2 -2
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +18 -6
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +1 -1
- package/dist/Layer/anchorName.d.ts +20 -0
- package/dist/Layer/anchorName.d.ts.map +1 -0
- package/dist/Layer/anchorName.js +36 -0
- package/dist/Layer/useLayer.d.ts +19 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +15 -6
- package/dist/Link/Link.d.ts +1 -1
- package/dist/Link/Link.js +2 -2
- package/dist/Link/LinkProvider.d.ts +1 -0
- package/dist/Link/LinkProvider.d.ts.map +1 -1
- package/dist/Link/LinkProvider.js +1 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +13 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +13 -0
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +31 -27
- package/dist/Slider/Slider.js +2 -2
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +27 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +30 -7
- package/dist/Table/TableRow.d.ts +6 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +15 -7
- package/dist/Table/index.d.ts +3 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/index.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
- package/dist/Table/types.d.ts +95 -5
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +1 -1
- package/dist/Text/text.stylex.d.ts +2 -2
- package/dist/Text/text.stylex.js +2 -2
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +28 -3
- package/dist/ToggleButton/ToggleButton.d.ts +7 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +34 -57
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +1 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +1 -0
- package/dist/astryx.css +30 -1
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- package/dist/theme/generateThemeRules.js +1 -1
- package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
- package/dist/theme/syntax/presets.d.ts +1 -1
- package/dist/theme/syntax/presets.d.ts.map +1 -1
- package/dist/theme/syntax/presets.js +1 -1
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.js +1 -1
- package/dist/theme/types.d.ts +1 -1
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/package.json +6 -2
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Button/Button.doc.mjs +6 -0
- package/src/Button/Button.test.tsx +105 -11
- package/src/Button/Button.tsx +73 -5
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +21 -26
- package/src/Calendar/Calendar.tsx +21 -5
- package/src/Calendar/index.ts +1 -0
- package/src/Chat/ChatComposer.doc.mjs +3 -0
- package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +8 -0
- package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +11 -3
- package/src/DateTimeInput/DateTimeInput.test.tsx +23 -3
- package/src/DateTimeInput/DateTimeInput.tsx +13 -10
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +13 -10
- package/src/Icon/Icon.doc.mjs +8 -0
- package/src/Kbd/Kbd.tsx +2 -9
- package/src/Layer/anchorName.test.ts +146 -0
- package/src/Layer/anchorName.ts +46 -0
- package/src/Layer/useLayer.doc.mjs +7 -2
- package/src/Layer/useLayer.tsx +33 -9
- package/src/Layout/Layout.doc.mjs +1 -1
- package/src/Lightbox/Lightbox.doc.mjs +0 -2
- package/src/Link/Link.test.tsx +2 -2
- package/src/Link/Link.tsx +3 -3
- package/src/Link/LinkProvider.tsx +1 -0
- package/src/List/List.doc.mjs +7 -0
- package/src/Markdown/Markdown.doc.mjs +2 -0
- package/src/Markdown/Markdown.test.tsx +17 -26
- package/src/Markdown/Markdown.tsx +16 -6
- package/src/MobileNav/MobileNav.doc.mjs +8 -8
- package/src/MobileNav/MobileNav.tsx +13 -0
- package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
- package/src/OverflowList/OverflowList.doc.mjs +17 -0
- package/src/Pagination/Pagination.test.tsx +137 -13
- package/src/Pagination/Pagination.tsx +33 -28
- package/src/ProgressBar/ProgressBar.test.tsx +13 -3
- package/src/Resizable/Resizable.doc.mjs +1 -1
- package/src/Section/Section.doc.mjs +11 -0
- package/src/SelectableCard/SelectableCard.doc.mjs +15 -0
- package/src/Selector/Selector.doc.mjs +4 -0
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Slider/Slider.doc.mjs +4 -0
- package/src/Slider/Slider.test.tsx +3 -16
- package/src/Slider/Slider.tsx +1 -1
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/Table/BaseTable.tsx +51 -24
- package/src/Table/Table.doc.mjs +12 -0
- package/src/Table/Table.test.tsx +29 -30
- package/src/Table/Table.tsx +22 -1
- package/src/Table/TableRow.tsx +37 -7
- package/src/Table/index.ts +3 -0
- package/src/Table/plugins/stickyColumns/index.ts +4 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
- package/src/Table/types.ts +101 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- package/src/Table/useTableColumnResize.doc.mjs +96 -0
- package/src/Table/useTableStickyColumns.doc.mjs +59 -0
- package/src/Text/Heading.doc.mjs +15 -2
- package/src/Text/Text.doc.mjs +7 -1
- package/src/Text/text.stylex.ts +2 -2
- package/src/Timestamp/Timestamp.doc.mjs +9 -0
- package/src/Timestamp/Timestamp.test.tsx +44 -18
- package/src/Timestamp/Timestamp.tsx +30 -10
- package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
- package/src/ToggleButton/ToggleButton.test.tsx +39 -18
- package/src/ToggleButton/ToggleButton.tsx +46 -83
- package/src/Tokenizer/Tokenizer.tsx +4 -1
- package/src/Toolbar/Toolbar.doc.mjs +65 -5
- package/src/Tooltip/Tooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +2 -3
- package/src/hooks/useImageMode.test.ts +133 -0
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +7 -8
- package/src/theme/generateThemeRules.ts +1 -1
- package/src/theme/syntax/SyntaxTheme.tsx +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
- package/src/theme/syntax/presets.ts +5 -2
- package/src/theme/syntax/tokens.ts +1 -1
- package/src/theme/types.ts +1 -1
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
package/src/Table/BaseTable.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
HeaderCellRenderProps,
|
|
27
27
|
BodyRowRenderProps,
|
|
28
28
|
BodyCellRenderProps,
|
|
29
|
+
ScrollWrapperRenderProps,
|
|
29
30
|
TableRowComponentProps,
|
|
30
31
|
TableCellComponentProps,
|
|
31
32
|
TableHeaderCellComponentProps,
|
|
@@ -152,22 +153,27 @@ function TableRowInner<T extends Record<string, unknown>>({
|
|
|
152
153
|
CellComponent,
|
|
153
154
|
}: TableRowProps<T>): ReactElement {
|
|
154
155
|
// Build cells first
|
|
155
|
-
const cells = columns.map(col => {
|
|
156
|
+
const cells = columns.map((col, columnIndex) => {
|
|
156
157
|
// Apply column alignment to body cells
|
|
157
158
|
const initialCellHtmlProps: Record<string, unknown> = {};
|
|
158
159
|
if (col.align) {
|
|
159
160
|
initialCellHtmlProps.style = {textAlign: col.align};
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
const initialBodyCellRenderProps: BodyCellRenderProps = {
|
|
164
|
+
htmlProps: initialCellHtmlProps,
|
|
165
|
+
styles: [],
|
|
166
|
+
columnIndex,
|
|
167
|
+
columns: columns as ReadonlyArray<TableColumn<Record<string, unknown>>>,
|
|
168
|
+
};
|
|
162
169
|
const cellRenderProps = applyPlugins(
|
|
163
170
|
plugins,
|
|
164
171
|
p => p.transformBodyCell,
|
|
165
|
-
|
|
166
|
-
htmlProps: initialCellHtmlProps,
|
|
167
|
-
styles: [],
|
|
168
|
-
} satisfies BodyCellRenderProps,
|
|
172
|
+
initialBodyCellRenderProps,
|
|
169
173
|
col,
|
|
170
174
|
item,
|
|
175
|
+
columnIndex,
|
|
176
|
+
columns,
|
|
171
177
|
);
|
|
172
178
|
|
|
173
179
|
const isDefaultRenderer = !col.renderCell;
|
|
@@ -313,8 +319,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
313
319
|
// Use stable empty array when no plugins provided
|
|
314
320
|
const plugins = pluginsProp ?? (EMPTY_PLUGINS as TablePlugin<T>[]);
|
|
315
321
|
|
|
316
|
-
const RowComponent =
|
|
317
|
-
TableRow as React.ComponentType<TableRowComponentProps>;
|
|
322
|
+
const RowComponent = TableRow as React.ComponentType<TableRowComponentProps>;
|
|
318
323
|
const CellComponent =
|
|
319
324
|
TableCell as React.ComponentType<TableCellComponentProps>;
|
|
320
325
|
const HeaderCellComponent =
|
|
@@ -354,7 +359,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
354
359
|
} satisfies TableRenderProps);
|
|
355
360
|
|
|
356
361
|
// --- Plugin pipeline: header cells ---
|
|
357
|
-
const headerCells = resolvedColumns.map(col => {
|
|
362
|
+
const headerCells = resolvedColumns.map((col, columnIndex) => {
|
|
358
363
|
const headerContent = col.header ?? col.key;
|
|
359
364
|
|
|
360
365
|
// Build initial htmlProps with column alignment if specified
|
|
@@ -365,15 +370,22 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
365
370
|
initialHeaderHtmlProps.style = {textAlign: col.align};
|
|
366
371
|
}
|
|
367
372
|
|
|
373
|
+
const initialHeaderRenderProps: HeaderCellRenderProps = {
|
|
374
|
+
htmlProps: initialHeaderHtmlProps,
|
|
375
|
+
styles: [],
|
|
376
|
+
content: headerContent,
|
|
377
|
+
columnIndex,
|
|
378
|
+
columns: resolvedColumns as ReadonlyArray<
|
|
379
|
+
TableColumn<Record<string, unknown>>
|
|
380
|
+
>,
|
|
381
|
+
};
|
|
368
382
|
const cellRenderProps = applyPlugins(
|
|
369
383
|
plugins,
|
|
370
384
|
p => p.transformHeaderCell,
|
|
371
|
-
|
|
372
|
-
htmlProps: initialHeaderHtmlProps,
|
|
373
|
-
styles: [],
|
|
374
|
-
content: headerContent,
|
|
375
|
-
} satisfies HeaderCellRenderProps,
|
|
385
|
+
initialHeaderRenderProps,
|
|
376
386
|
col,
|
|
387
|
+
columnIndex,
|
|
388
|
+
resolvedColumns,
|
|
377
389
|
);
|
|
378
390
|
|
|
379
391
|
// Apply pre-computed column width styles on the <th>.
|
|
@@ -465,6 +477,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
465
477
|
<TableHeader>
|
|
466
478
|
<RowComponent
|
|
467
479
|
{...headerRowRenderProps.htmlProps}
|
|
480
|
+
isHeaderRow
|
|
468
481
|
xstyle={headerRowRenderProps.styles}>
|
|
469
482
|
{headerRowRenderProps.children}
|
|
470
483
|
</RowComponent>
|
|
@@ -497,9 +510,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
497
510
|
emptyState !== false && (
|
|
498
511
|
<tr>
|
|
499
512
|
<td colSpan={resolvedColumns.length}>
|
|
500
|
-
{emptyState ??
|
|
501
|
-
<EmptyState title="No data" isCompact />
|
|
502
|
-
)}
|
|
513
|
+
{emptyState ?? <EmptyState title="No data" isCompact />}
|
|
503
514
|
</td>
|
|
504
515
|
</tr>
|
|
505
516
|
)}
|
|
@@ -513,8 +524,29 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
513
524
|
// when columns exceed the container width. This wrapper sits between
|
|
514
525
|
// the <table> and transformTableContext, so plugin chrome (pagination,
|
|
515
526
|
// toolbars) renders outside the scroll area.
|
|
527
|
+
//
|
|
528
|
+
// Before rendering the wrapper, run the plugin `transformScrollWrapper`
|
|
529
|
+
// pipeline so plugins can attach a ref to the scroll container (scroll-aware
|
|
530
|
+
// sticky shadows, virtualization) and inject before/after chrome.
|
|
516
531
|
if (ScrollWrapper) {
|
|
517
|
-
|
|
532
|
+
const scrollWrapperRenderProps = applyPlugins(
|
|
533
|
+
plugins,
|
|
534
|
+
p => p.transformScrollWrapper,
|
|
535
|
+
{
|
|
536
|
+
htmlProps: {},
|
|
537
|
+
styles: [],
|
|
538
|
+
} satisfies ScrollWrapperRenderProps,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
tableElement = (
|
|
542
|
+
<ScrollWrapper
|
|
543
|
+
htmlProps={scrollWrapperRenderProps.htmlProps}
|
|
544
|
+
styles={scrollWrapperRenderProps.styles}
|
|
545
|
+
beforeTable={scrollWrapperRenderProps.beforeTable}
|
|
546
|
+
afterTable={scrollWrapperRenderProps.afterTable}>
|
|
547
|
+
{tableElement}
|
|
548
|
+
</ScrollWrapper>
|
|
549
|
+
);
|
|
518
550
|
}
|
|
519
551
|
|
|
520
552
|
// Apply transformTableContext from each plugin.
|
|
@@ -527,10 +559,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
527
559
|
try {
|
|
528
560
|
tableElement = plugin.transformTableContext(tableElement);
|
|
529
561
|
} catch (error) {
|
|
530
|
-
console.error(
|
|
531
|
-
'[Table] Plugin threw in transformTableContext:',
|
|
532
|
-
error,
|
|
533
|
-
);
|
|
562
|
+
console.error('[Table] Plugin threw in transformTableContext:', error);
|
|
534
563
|
}
|
|
535
564
|
}
|
|
536
565
|
}
|
|
@@ -556,9 +585,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
556
585
|
* />
|
|
557
586
|
* ```
|
|
558
587
|
*/
|
|
559
|
-
export const BaseTable = BaseTableInner as <
|
|
560
|
-
T extends Record<string, unknown>,
|
|
561
|
-
>(
|
|
588
|
+
export const BaseTable = BaseTableInner as <T extends Record<string, unknown>>(
|
|
562
589
|
props: BaseTableProps<T> & {ref?: Ref<HTMLTableElement>},
|
|
563
590
|
) => ReactElement;
|
|
564
591
|
|
package/src/Table/Table.doc.mjs
CHANGED
|
@@ -71,6 +71,18 @@ export const docs = {
|
|
|
71
71
|
description: 'Applies a hover highlight background to rows on pointer devices.',
|
|
72
72
|
default: 'false',
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: 'verticalAlign',
|
|
76
|
+
type: "'middle' | 'top' | 'bottom'",
|
|
77
|
+
description: 'Vertical alignment for body row cells. Controls `vertical-align` on the `<td>` elements.',
|
|
78
|
+
default: "'middle'",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'textOverflow',
|
|
82
|
+
type: "'wrap' | 'truncate'",
|
|
83
|
+
description: "How body cell text behaves when it exceeds the column width. 'wrap' lets text wrap and the row grow taller; 'truncate' clips with an ellipsis (default-rendered cells show a tooltip on hover when truncated). Header cells always truncate.",
|
|
84
|
+
default: "'wrap'",
|
|
85
|
+
},
|
|
74
86
|
{
|
|
75
87
|
name: 'plugins',
|
|
76
88
|
type: 'Record<string, TablePlugin<T>>',
|
package/src/Table/Table.test.tsx
CHANGED
|
@@ -245,6 +245,27 @@ describe('BaseTable', () => {
|
|
|
245
245
|
expect(screen.getAllByRole('row')).toHaveLength(4);
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
+
it('does not apply hover styling to the header row when hasHover is set', () => {
|
|
249
|
+
// Use the public Table (provides TableContext); BaseTable alone has no
|
|
250
|
+
// context, so rows wouldn't pick up hover styling at all.
|
|
251
|
+
const {container} = render(
|
|
252
|
+
<Table data={users} columns={columns} hasHover />,
|
|
253
|
+
);
|
|
254
|
+
const headerRow = container.querySelector('thead tr');
|
|
255
|
+
const bodyRow = container.querySelector('tbody tr');
|
|
256
|
+
expect(headerRow).not.toBeNull();
|
|
257
|
+
expect(bodyRow).not.toBeNull();
|
|
258
|
+
|
|
259
|
+
const headerClasses = new Set(
|
|
260
|
+
(headerRow?.className ?? '').split(/\s+/).filter(Boolean),
|
|
261
|
+
);
|
|
262
|
+
// The body row carries hover-styling class(es) that the header row does not.
|
|
263
|
+
const bodyOnlyClasses = (bodyRow?.className ?? '')
|
|
264
|
+
.split(/\s+/)
|
|
265
|
+
.filter(c => c && !headerClasses.has(c));
|
|
266
|
+
expect(bodyOnlyClasses.length).toBeGreaterThan(0);
|
|
267
|
+
});
|
|
268
|
+
|
|
248
269
|
it('auto-generates columns from data keys when columns omitted', () => {
|
|
249
270
|
render(<BaseTable data={users} />);
|
|
250
271
|
const headers = screen.getAllByRole('columnheader');
|
|
@@ -276,11 +297,7 @@ describe('BaseTable', () => {
|
|
|
276
297
|
|
|
277
298
|
it('uses idKey function to key rows', () => {
|
|
278
299
|
render(
|
|
279
|
-
<BaseTable
|
|
280
|
-
data={users}
|
|
281
|
-
columns={columns}
|
|
282
|
-
idKey={item => item.email}
|
|
283
|
-
/>,
|
|
300
|
+
<BaseTable data={users} columns={columns} idKey={item => item.email} />,
|
|
284
301
|
);
|
|
285
302
|
expect(screen.getAllByRole('row')).toHaveLength(4);
|
|
286
303
|
});
|
|
@@ -374,9 +391,7 @@ describe('BaseTable', () => {
|
|
|
374
391
|
htmlProps: {...props.htmlProps, 'data-testid': 'plugin-table'},
|
|
375
392
|
}),
|
|
376
393
|
};
|
|
377
|
-
render(
|
|
378
|
-
<BaseTable data={users} columns={columns} plugins={[plugin]} />,
|
|
379
|
-
);
|
|
394
|
+
render(<BaseTable data={users} columns={columns} plugins={[plugin]} />);
|
|
380
395
|
expect(screen.getByTestId('plugin-table')).toBeInTheDocument();
|
|
381
396
|
});
|
|
382
397
|
|
|
@@ -387,9 +402,7 @@ describe('BaseTable', () => {
|
|
|
387
402
|
htmlProps: {...props.htmlProps, 'data-testid': 'plugin-header-row'},
|
|
388
403
|
}),
|
|
389
404
|
};
|
|
390
|
-
render(
|
|
391
|
-
<BaseTable data={users} columns={columns} plugins={[plugin]} />,
|
|
392
|
-
);
|
|
405
|
+
render(<BaseTable data={users} columns={columns} plugins={[plugin]} />);
|
|
393
406
|
expect(screen.getByTestId('plugin-header-row')).toBeInTheDocument();
|
|
394
407
|
});
|
|
395
408
|
|
|
@@ -404,9 +417,7 @@ describe('BaseTable', () => {
|
|
|
404
417
|
};
|
|
405
418
|
},
|
|
406
419
|
};
|
|
407
|
-
render(
|
|
408
|
-
<BaseTable data={users} columns={columns} plugins={[plugin]} />,
|
|
409
|
-
);
|
|
420
|
+
render(<BaseTable data={users} columns={columns} plugins={[plugin]} />);
|
|
410
421
|
expect(receivedKeys).toEqual(['name', 'age', 'email']);
|
|
411
422
|
const headers = screen.getAllByRole('columnheader');
|
|
412
423
|
expect(headers[0]).toHaveAttribute('data-column', 'name');
|
|
@@ -423,9 +434,7 @@ describe('BaseTable', () => {
|
|
|
423
434
|
};
|
|
424
435
|
},
|
|
425
436
|
};
|
|
426
|
-
render(
|
|
427
|
-
<BaseTable data={users} columns={columns} plugins={[plugin]} />,
|
|
428
|
-
);
|
|
437
|
+
render(<BaseTable data={users} columns={columns} plugins={[plugin]} />);
|
|
429
438
|
expect(receivedItems).toEqual(['Alice', 'Bob', 'Charlie']);
|
|
430
439
|
});
|
|
431
440
|
|
|
@@ -437,9 +446,7 @@ describe('BaseTable', () => {
|
|
|
437
446
|
return props;
|
|
438
447
|
},
|
|
439
448
|
};
|
|
440
|
-
render(
|
|
441
|
-
<BaseTable data={users} columns={columns} plugins={[plugin]} />,
|
|
442
|
-
);
|
|
449
|
+
render(<BaseTable data={users} columns={columns} plugins={[plugin]} />);
|
|
443
450
|
// 3 rows * 3 columns = 9 calls
|
|
444
451
|
expect(calls).toHaveLength(9);
|
|
445
452
|
expect(calls[0]).toEqual({col: 'name', name: 'Alice'});
|
|
@@ -746,11 +753,7 @@ describe('Table', () => {
|
|
|
746
753
|
}),
|
|
747
754
|
};
|
|
748
755
|
render(
|
|
749
|
-
<Table
|
|
750
|
-
data={users}
|
|
751
|
-
columns={columns}
|
|
752
|
-
plugins={{custom: userPlugin}}
|
|
753
|
-
/>,
|
|
756
|
+
<Table data={users} columns={columns} plugins={{custom: userPlugin}} />,
|
|
754
757
|
);
|
|
755
758
|
expect(screen.getByTestId('custom-plugin')).toBeInTheDocument();
|
|
756
759
|
});
|
|
@@ -767,11 +770,7 @@ describe('Table', () => {
|
|
|
767
770
|
},
|
|
768
771
|
};
|
|
769
772
|
render(
|
|
770
|
-
<Table
|
|
771
|
-
data={users}
|
|
772
|
-
columns={columns}
|
|
773
|
-
plugins={{custom: userPlugin}}
|
|
774
|
-
/>,
|
|
773
|
+
<Table data={users} columns={columns} plugins={{custom: userPlugin}} />,
|
|
775
774
|
);
|
|
776
775
|
expect(screen.getByTestId('after-xds')).toBeInTheDocument();
|
|
777
776
|
});
|
package/src/Table/Table.tsx
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
TablePlugin,
|
|
31
31
|
TableRenderProps,
|
|
32
32
|
} from './types';
|
|
33
|
+
import type {StyleXStyles} from '../theme/types';
|
|
33
34
|
|
|
34
35
|
// =============================================================================
|
|
35
36
|
// Table Types
|
|
@@ -131,17 +132,37 @@ const scrollWrapperStyles = stylex.create({
|
|
|
131
132
|
},
|
|
132
133
|
});
|
|
133
134
|
|
|
134
|
-
function TableScrollWrapper({
|
|
135
|
+
function TableScrollWrapper({
|
|
136
|
+
children,
|
|
137
|
+
htmlProps,
|
|
138
|
+
styles: pluginStyles,
|
|
139
|
+
beforeTable,
|
|
140
|
+
afterTable,
|
|
141
|
+
}: {
|
|
142
|
+
children: React.ReactNode;
|
|
143
|
+
htmlProps?: React.HTMLAttributes<HTMLDivElement> & {
|
|
144
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
145
|
+
};
|
|
146
|
+
styles?: StyleXStyles[];
|
|
147
|
+
beforeTable?: React.ReactNode;
|
|
148
|
+
afterTable?: React.ReactNode;
|
|
149
|
+
}) {
|
|
150
|
+
const {ref, ...restHtmlProps} = htmlProps ?? {};
|
|
135
151
|
return (
|
|
136
152
|
<div
|
|
153
|
+
ref={ref}
|
|
154
|
+
{...restHtmlProps}
|
|
137
155
|
{...mergeProps(
|
|
138
156
|
themeProps('table-scroll-wrapper'),
|
|
139
157
|
stylex.props(
|
|
140
158
|
scrollWrapperStyles.base,
|
|
141
159
|
scrollWrapperStyles.containerBleed,
|
|
160
|
+
...(pluginStyles ?? []),
|
|
142
161
|
),
|
|
143
162
|
)}>
|
|
163
|
+
{beforeTable}
|
|
144
164
|
{children}
|
|
165
|
+
{afterTable}
|
|
145
166
|
</div>
|
|
146
167
|
);
|
|
147
168
|
}
|
package/src/Table/TableRow.tsx
CHANGED
|
@@ -34,6 +34,11 @@ export interface TableRowProps extends BaseProps<HTMLTableRowElement> {
|
|
|
34
34
|
* Must be a `stylex.create()` value — not an inline style object.
|
|
35
35
|
*/
|
|
36
36
|
xstyle?: StyleXStyles[];
|
|
37
|
+
/**
|
|
38
|
+
* Whether this row is the header row. Header rows skip the striped/hover
|
|
39
|
+
* row styling (which is only meant for body rows).
|
|
40
|
+
*/
|
|
41
|
+
isHeaderRow?: boolean;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
const stripedRowStyles = stylex.create({
|
|
@@ -42,6 +47,13 @@ const stripedRowStyles = stylex.create({
|
|
|
42
47
|
default: null,
|
|
43
48
|
':nth-child(even)': colorVars['--color-background-muted'],
|
|
44
49
|
},
|
|
50
|
+
// Publish the row's current overlay color as an inheritable variable so
|
|
51
|
+
// pinned/sticky cells (which paint an opaque background over the otherwise
|
|
52
|
+
// transparent row) can replay the exact same striping. Unset on odd rows.
|
|
53
|
+
'--table-row-overlay': {
|
|
54
|
+
default: null,
|
|
55
|
+
':nth-child(even)': colorVars['--color-background-muted'],
|
|
56
|
+
},
|
|
45
57
|
},
|
|
46
58
|
});
|
|
47
59
|
|
|
@@ -53,6 +65,12 @@ const hoverRowStyles = stylex.create({
|
|
|
53
65
|
'@media (hover: hover)': colorVars['--color-overlay-hover'],
|
|
54
66
|
},
|
|
55
67
|
},
|
|
68
|
+
'--table-row-overlay': {
|
|
69
|
+
default: null,
|
|
70
|
+
':hover': {
|
|
71
|
+
'@media (hover: hover)': colorVars['--color-overlay-hover'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
56
74
|
transitionProperty: 'background-color',
|
|
57
75
|
transitionDuration: durationVars['--duration-fast'],
|
|
58
76
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
@@ -68,6 +86,13 @@ const stripedHoverRowStyles = stylex.create({
|
|
|
68
86
|
'@media (hover: hover)': colorVars['--color-overlay-hover'],
|
|
69
87
|
},
|
|
70
88
|
},
|
|
89
|
+
'--table-row-overlay': {
|
|
90
|
+
default: null,
|
|
91
|
+
':nth-child(even)': colorVars['--color-background-muted'],
|
|
92
|
+
':hover': {
|
|
93
|
+
'@media (hover: hover)': colorVars['--color-overlay-hover'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
71
96
|
transitionProperty: 'background-color',
|
|
72
97
|
transitionDuration: durationVars['--duration-fast'],
|
|
73
98
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
@@ -94,6 +119,7 @@ export function TableRow({
|
|
|
94
119
|
children,
|
|
95
120
|
xstyle,
|
|
96
121
|
ref,
|
|
122
|
+
isHeaderRow = false,
|
|
97
123
|
...props
|
|
98
124
|
}: TableRowProps) {
|
|
99
125
|
const ctx = use(TableContext);
|
|
@@ -114,13 +140,17 @@ export function TableRow({
|
|
|
114
140
|
|
|
115
141
|
const rowStyles: StyleXStyles[] = [];
|
|
116
142
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
// Striped + hover styling is for body rows only — the header row must not
|
|
144
|
+
// pick up the hover highlight (or striping) even when hasHover/isStriped.
|
|
145
|
+
if (!isHeaderRow) {
|
|
146
|
+
// Handle striped + hover combination to avoid backgroundColor conflicts
|
|
147
|
+
if (ctx.isStriped && ctx.hasHover) {
|
|
148
|
+
rowStyles.push(stripedHoverRowStyles.row);
|
|
149
|
+
} else if (ctx.isStriped) {
|
|
150
|
+
rowStyles.push(stripedRowStyles.row);
|
|
151
|
+
} else if (ctx.hasHover) {
|
|
152
|
+
rowStyles.push(hoverRowStyles.row);
|
|
153
|
+
}
|
|
124
154
|
}
|
|
125
155
|
|
|
126
156
|
if (ctx.dividers === 'rows' || ctx.dividers === 'grid') {
|
package/src/Table/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export {useTablePagination, paginateData} from './plugins/pagination';
|
|
|
27
27
|
export {useTableColumnSettings} from './plugins/columnSettings';
|
|
28
28
|
export {useTableColumnSettingsState} from './plugins/columnSettings';
|
|
29
29
|
export {useTableColumnResize} from './plugins/columnResize';
|
|
30
|
+
export {useTableStickyColumns} from './plugins/stickyColumns';
|
|
30
31
|
export {
|
|
31
32
|
useTableFiltering,
|
|
32
33
|
useTableFilterState,
|
|
@@ -53,6 +54,7 @@ export type {
|
|
|
53
54
|
HeaderCellRenderProps,
|
|
54
55
|
BodyRowRenderProps,
|
|
55
56
|
BodyCellRenderProps,
|
|
57
|
+
ScrollWrapperRenderProps,
|
|
56
58
|
BaseTableProps,
|
|
57
59
|
} from './types';
|
|
58
60
|
export type {
|
|
@@ -93,6 +95,7 @@ export type {
|
|
|
93
95
|
UseTableColumnSettingsStateReturn,
|
|
94
96
|
} from './plugins/columnSettings';
|
|
95
97
|
export type {UseTableColumnResizeConfig} from './plugins/columnResize';
|
|
98
|
+
export type {UseTableStickyColumnsConfig} from './plugins/stickyColumns';
|
|
96
99
|
export type {
|
|
97
100
|
UseTableFilteringConfig,
|
|
98
101
|
TableFilterState,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useTableStickyColumns.test.tsx
|
|
5
|
+
* @input useTableStickyColumns, Table, React testing utilities
|
|
6
|
+
* @output Functional tests for the sticky-columns plugin
|
|
7
|
+
* @position Test file; validates pinning, cumulative offsets, edges, no-op
|
|
8
|
+
*
|
|
9
|
+
* Note: `position: sticky` and the z-index/background are applied via StyleX
|
|
10
|
+
* (compiled to classNames), which jsdom does not resolve to `element.style`.
|
|
11
|
+
* The plugin sets the per-column *offset* (`insetInlineStart`/`insetInlineEnd`)
|
|
12
|
+
* as an inline style, so these tests assert on those inline offsets — the
|
|
13
|
+
* jsdom-visible, plugin-owned signal that a column is pinned and at what offset.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {describe, it, expect} from 'vitest';
|
|
17
|
+
import {render, screen} from '@testing-library/react';
|
|
18
|
+
import {Table} from '../../Table';
|
|
19
|
+
import {useTableStickyColumns} from './useTableStickyColumns';
|
|
20
|
+
import {pixel} from '../../columnUtils';
|
|
21
|
+
import type {TableColumn} from '../../types';
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Test Data
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
interface Row extends Record<string, unknown> {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
email: string;
|
|
31
|
+
team: string;
|
|
32
|
+
status: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const data: Row[] = [
|
|
36
|
+
{id: '1', name: 'Alice', email: 'a@x.com', team: 'DS', status: 'Active'},
|
|
37
|
+
{id: '2', name: 'Bob', email: 'b@x.com', team: 'Plat', status: 'Away'},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const columns: TableColumn<Row>[] = [
|
|
41
|
+
{key: 'name', header: 'Name', width: pixel(180)},
|
|
42
|
+
{key: 'email', header: 'Email', width: pixel(220)},
|
|
43
|
+
{key: 'team', header: 'Team', width: pixel(160)},
|
|
44
|
+
{key: 'status', header: 'Status', width: pixel(140)},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function getHeader(name: string): HTMLElement {
|
|
48
|
+
return screen.getByRole('columnheader', {name});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Tests
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
describe('useTableStickyColumns', () => {
|
|
56
|
+
it('pins a start column at inset-inline-start: 0', () => {
|
|
57
|
+
function Harness() {
|
|
58
|
+
const sticky = useTableStickyColumns<Row>({startKeys: ['name']});
|
|
59
|
+
return (
|
|
60
|
+
<Table
|
|
61
|
+
data={data}
|
|
62
|
+
columns={columns}
|
|
63
|
+
idKey="id"
|
|
64
|
+
plugins={{stickyColumns: sticky}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
render(<Harness />);
|
|
69
|
+
expect(getHeader('Name').style.insetInlineStart).toBe('0px');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('computes cumulative start offsets for contiguous pinned columns', () => {
|
|
73
|
+
function Harness() {
|
|
74
|
+
const sticky = useTableStickyColumns<Row>({
|
|
75
|
+
startKeys: ['name', 'email'],
|
|
76
|
+
});
|
|
77
|
+
return (
|
|
78
|
+
<Table
|
|
79
|
+
data={data}
|
|
80
|
+
columns={columns}
|
|
81
|
+
idKey="id"
|
|
82
|
+
plugins={{stickyColumns: sticky}}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
render(<Harness />);
|
|
87
|
+
// name is first → offset 0; email follows → offset = name width (180px)
|
|
88
|
+
expect(getHeader('Name').style.insetInlineStart).toBe('0px');
|
|
89
|
+
expect(getHeader('Email').style.insetInlineStart).toBe('180px');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('pins an end column at inset-inline-end: 0', () => {
|
|
93
|
+
function Harness() {
|
|
94
|
+
const sticky = useTableStickyColumns<Row>({endKeys: ['status']});
|
|
95
|
+
return (
|
|
96
|
+
<Table
|
|
97
|
+
data={data}
|
|
98
|
+
columns={columns}
|
|
99
|
+
idKey="id"
|
|
100
|
+
plugins={{stickyColumns: sticky}}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
render(<Harness />);
|
|
105
|
+
expect(getHeader('Status').style.insetInlineEnd).toBe('0px');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('pins body cells, not just headers', () => {
|
|
109
|
+
function Harness() {
|
|
110
|
+
const sticky = useTableStickyColumns<Row>({startKeys: ['name']});
|
|
111
|
+
return (
|
|
112
|
+
<Table
|
|
113
|
+
data={data}
|
|
114
|
+
columns={columns}
|
|
115
|
+
idKey="id"
|
|
116
|
+
plugins={{stickyColumns: sticky}}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
render(<Harness />);
|
|
121
|
+
const firstBodyCell = screen.getByText('Alice').closest('td');
|
|
122
|
+
expect(firstBodyCell).not.toBeNull();
|
|
123
|
+
expect(firstBodyCell!.style.insetInlineStart).toBe('0px');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('is a no-op with an empty config — no cell gets an offset', () => {
|
|
127
|
+
function Harness() {
|
|
128
|
+
const sticky = useTableStickyColumns<Row>({});
|
|
129
|
+
return (
|
|
130
|
+
<Table
|
|
131
|
+
data={data}
|
|
132
|
+
columns={columns}
|
|
133
|
+
idKey="id"
|
|
134
|
+
plugins={{stickyColumns: sticky}}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
render(<Harness />);
|
|
139
|
+
for (const header of ['Name', 'Email', 'Team', 'Status']) {
|
|
140
|
+
const th = getHeader(header);
|
|
141
|
+
expect(th.style.insetInlineStart).toBe('');
|
|
142
|
+
expect(th.style.insetInlineEnd).toBe('');
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('only pins configured columns, leaving others unset', () => {
|
|
147
|
+
function Harness() {
|
|
148
|
+
const sticky = useTableStickyColumns<Row>({startKeys: ['name']});
|
|
149
|
+
return (
|
|
150
|
+
<Table
|
|
151
|
+
data={data}
|
|
152
|
+
columns={columns}
|
|
153
|
+
idKey="id"
|
|
154
|
+
plugins={{stickyColumns: sticky}}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
render(<Harness />);
|
|
159
|
+
expect(getHeader('Name').style.insetInlineStart).toBe('0px');
|
|
160
|
+
expect(getHeader('Team').style.insetInlineStart).toBe('');
|
|
161
|
+
expect(getHeader('Team').style.insetInlineEnd).toBe('');
|
|
162
|
+
});
|
|
163
|
+
});
|