@astryxdesign/core 0.1.0 → 0.1.1-canary.129bf0e
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 +66 -0
- package/README.md +68 -0
- 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.map +1 -1
- package/dist/Button/Button.js +2 -0
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +5 -1
- package/dist/ContextMenu/ContextMenu.js +2 -2
- package/dist/DropdownMenu/DropdownMenu.js +2 -2
- package/dist/DropdownMenu/{renderXDSDropdownItems.d.ts → renderDropdownItems.d.ts} +3 -3
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -0
- package/dist/DropdownMenu/{renderXDSDropdownItems.js → renderDropdownItems.js} +2 -2
- 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 +6 -3
- package/dist/Layer/useLayer.d.ts +13 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +7 -2
- package/dist/Layout/Layout.d.ts +10 -1
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +5 -1
- 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/Outline/Outline.d.ts +3 -2
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +23 -4
- package/dist/Outline/useScrollSpy.d.ts +14 -1
- package/dist/Outline/useScrollSpy.d.ts.map +1 -1
- package/dist/Outline/useScrollSpy.js +161 -50
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +31 -27
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +1 -5
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +26 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +30 -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 +376 -0
- package/dist/Table/types.d.ts +90 -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/ToggleButton/ToggleButton.d.ts +10 -3
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +64 -18
- package/dist/astryx.css +11 -0
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- package/dist/theme/Theme.js +1 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +1 -1
- package/dist/theme/tokens.d.ts +1 -1
- package/dist/theme/tokens.js +4 -4
- package/dist/theme/useTheme.d.ts +2 -2
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +15 -2
- package/package.json +7 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Button/Button.test.tsx +26 -11
- package/src/Button/Button.tsx +2 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +7 -1
- package/src/Collapsible/useCollapsible.doc.mjs +2 -2
- package/src/ContextMenu/ContextMenu.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +68 -20
- package/src/Divider/Divider.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/DropdownMenu/{renderXDSDropdownItems.tsx → renderDropdownItems.tsx} +2 -2
- package/src/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- package/src/FormLayout/FormLayout.doc.mjs +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +178 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +12 -10
- package/src/Icon/Icon.doc.mjs +4 -4
- package/src/Item/Item.doc.mjs +2 -2
- package/src/Layer/useLayer.doc.mjs +7 -2
- package/src/Layer/useLayer.tsx +19 -2
- package/src/Layout/Layout.doc.mjs +2 -1
- package/src/Layout/Layout.tsx +15 -1
- package/src/Layout/__tests__/childrenAsContent.test.tsx +59 -0
- package/src/Lightbox/Lightbox.doc.mjs +0 -2
- package/src/Link/Link.doc.mjs +3 -3
- package/src/Link/LinkProvider.doc.mjs +3 -3
- package/src/Markdown/Markdown.doc.mjs +6 -4
- 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/Outline/Outline.doc.mjs +1 -1
- package/src/Outline/Outline.test.tsx +76 -38
- package/src/Outline/Outline.tsx +23 -4
- package/src/Outline/useScrollSpy.ts +196 -63
- package/src/Pagination/Pagination.test.tsx +137 -13
- package/src/Pagination/Pagination.tsx +33 -28
- package/src/Resizable/Resizable.doc.mjs +3 -3
- package/src/Resizable/useResizable.ts +1 -7
- package/src/Selector/Selector.doc.mjs +4 -0
- package/src/Selector/Selector.tsx +5 -6
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Table/BaseTable.tsx +50 -24
- package/src/Table/Table.doc.mjs +3 -3
- package/src/Table/Table.tsx +22 -1
- 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 +414 -0
- package/src/Table/types.ts +96 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +2 -2
- package/src/ToggleButton/ToggleButton.test.tsx +148 -6
- package/src/ToggleButton/ToggleButton.tsx +83 -20
- package/src/Toolbar/Toolbar.doc.mjs +1 -1
- package/src/hooks/useEntryAnimation.doc.mjs +3 -3
- package/src/hooks/useMediaQuery.doc.mjs +2 -2
- package/src/hooks/useStreamingText.doc.mjs +3 -3
- package/src/theme/Theme.doc.mjs +2 -2
- package/src/theme/Theme.tsx +1 -1
- package/src/theme/defineTheme.ts +1 -1
- package/src/theme/index.ts +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +1 -1
- package/src/theme/tokens.ts +4 -4
- package/src/theme/useTheme.ts +2 -2
- package/src/utils/dateParser.test.ts +26 -0
- package/src/utils/dateParser.ts +16 -2
- package/dist/DropdownMenu/renderXDSDropdownItems.d.ts.map +0 -1
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* label, data-testid, xstyle
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import {useTransition} from 'react';
|
|
22
|
+
import {useOptimistic, useTransition} from 'react';
|
|
23
23
|
import * as stylex from '@stylexjs/stylex';
|
|
24
24
|
import {
|
|
25
25
|
colorVars,
|
|
@@ -349,16 +349,22 @@ export function Pagination({
|
|
|
349
349
|
style,
|
|
350
350
|
ref,
|
|
351
351
|
}: PaginationProps) {
|
|
352
|
-
const [
|
|
352
|
+
const [, startTransition] = useTransition();
|
|
353
|
+
|
|
354
|
+
// Track the page optimistically so rapid prev/next clicks advance from the
|
|
355
|
+
// in-flight target instead of stalling on the last committed page.
|
|
356
|
+
const [optimisticPage, setOptimisticPage] = useOptimistic(page);
|
|
353
357
|
|
|
354
358
|
// Compute pagination state
|
|
355
359
|
const computedTotalPages =
|
|
356
360
|
totalPagesProp ??
|
|
357
361
|
(totalItems != null ? Math.ceil(totalItems / pageSize) : undefined);
|
|
358
362
|
|
|
359
|
-
const hasPrevious =
|
|
363
|
+
const hasPrevious = optimisticPage > 1;
|
|
360
364
|
const hasNext =
|
|
361
|
-
computedTotalPages != null
|
|
365
|
+
computedTotalPages != null
|
|
366
|
+
? optimisticPage < computedTotalPages
|
|
367
|
+
: (hasMore ?? false);
|
|
362
368
|
|
|
363
369
|
// Return null for empty state
|
|
364
370
|
if (totalItems != null && totalItems <= 0) {
|
|
@@ -368,48 +374,47 @@ export function Pagination({
|
|
|
368
374
|
return null;
|
|
369
375
|
}
|
|
370
376
|
|
|
377
|
+
// Interruptible: re-clicking before the transition settles starts a fresh one
|
|
378
|
+
// with the next optimistic page rather than being dropped, so there is no
|
|
379
|
+
// re-entry guard.
|
|
371
380
|
const handlePageChange = (newPage: number) => {
|
|
372
|
-
if (isDisabled
|
|
381
|
+
if (isDisabled) {
|
|
373
382
|
return;
|
|
374
383
|
}
|
|
384
|
+
// Keep onChange urgent so controlled page state updates in the same commit
|
|
385
|
+
// as the click; only the optimistic indicator and changeAction defer.
|
|
375
386
|
onChange(newPage);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
387
|
+
startTransition(async () => {
|
|
388
|
+
setOptimisticPage(newPage);
|
|
389
|
+
await changeAction?.(newPage);
|
|
390
|
+
});
|
|
381
391
|
};
|
|
382
392
|
|
|
383
393
|
const handlePrevious = () => {
|
|
384
394
|
if (hasPrevious) {
|
|
385
|
-
handlePageChange(
|
|
395
|
+
handlePageChange(optimisticPage - 1);
|
|
386
396
|
}
|
|
387
397
|
};
|
|
388
398
|
|
|
389
399
|
const handleNext = () => {
|
|
390
400
|
if (hasNext) {
|
|
391
|
-
handlePageChange(
|
|
401
|
+
handlePageChange(optimisticPage + 1);
|
|
392
402
|
}
|
|
393
403
|
};
|
|
394
404
|
|
|
395
405
|
const handlePageSizeChange = (value: string) => {
|
|
396
406
|
const newSize = Number(value);
|
|
397
407
|
onPageSizeChange?.(newSize);
|
|
398
|
-
// Reset to page 1 when page size changes
|
|
399
|
-
|
|
400
|
-
if (changeAction) {
|
|
401
|
-
startTransition(async () => {
|
|
402
|
-
await changeAction(1);
|
|
403
|
-
});
|
|
404
|
-
}
|
|
408
|
+
// Reset to page 1 when page size changes.
|
|
409
|
+
handlePageChange(1);
|
|
405
410
|
};
|
|
406
411
|
|
|
407
412
|
// Item range for count display
|
|
408
|
-
const rangeStart = (
|
|
413
|
+
const rangeStart = (optimisticPage - 1) * pageSize + 1;
|
|
409
414
|
const rangeEnd =
|
|
410
415
|
totalItems != null
|
|
411
|
-
? Math.min(
|
|
412
|
-
:
|
|
416
|
+
? Math.min(optimisticPage * pageSize, totalItems)
|
|
417
|
+
: optimisticPage * pageSize;
|
|
413
418
|
|
|
414
419
|
const buttonSize = size === 'sm' ? 'sm' : 'md';
|
|
415
420
|
const isSm = size === 'sm';
|
|
@@ -421,7 +426,7 @@ export function Pagination({
|
|
|
421
426
|
return null;
|
|
422
427
|
}
|
|
423
428
|
const pageRange = generatePageRange(
|
|
424
|
-
|
|
429
|
+
optimisticPage,
|
|
425
430
|
computedTotalPages,
|
|
426
431
|
siblingCount,
|
|
427
432
|
);
|
|
@@ -443,7 +448,7 @@ export function Pagination({
|
|
|
443
448
|
</span>
|
|
444
449
|
);
|
|
445
450
|
}
|
|
446
|
-
const isActive = item ===
|
|
451
|
+
const isActive = item === optimisticPage;
|
|
447
452
|
return (
|
|
448
453
|
<Button
|
|
449
454
|
key={item}
|
|
@@ -483,7 +488,7 @@ export function Pagination({
|
|
|
483
488
|
return (
|
|
484
489
|
<span {...stylex.props(styles.infoText)}>
|
|
485
490
|
<Text type="body" size="sm" color="secondary">
|
|
486
|
-
{`Page ${
|
|
491
|
+
{`Page ${optimisticPage} of ${computedTotalPages}`}
|
|
487
492
|
</Text>
|
|
488
493
|
</span>
|
|
489
494
|
);
|
|
@@ -503,18 +508,18 @@ export function Pagination({
|
|
|
503
508
|
key={i + 1}
|
|
504
509
|
type="button"
|
|
505
510
|
aria-label={`Go to page ${i + 1}`}
|
|
506
|
-
aria-current={i + 1 ===
|
|
511
|
+
aria-current={i + 1 === optimisticPage ? 'page' : undefined}
|
|
507
512
|
onClick={() => handlePageChange(i + 1)}
|
|
508
513
|
disabled={isDisabled}
|
|
509
514
|
{...mergeProps(
|
|
510
515
|
themeProps('pagination-dot', {
|
|
511
|
-
active: i + 1 ===
|
|
516
|
+
active: i + 1 === optimisticPage ? 'active' : null,
|
|
512
517
|
size,
|
|
513
518
|
}),
|
|
514
519
|
stylex.props(
|
|
515
520
|
styles.dot,
|
|
516
521
|
isSm && styles.dotSm,
|
|
517
|
-
i + 1 ===
|
|
522
|
+
i + 1 === optimisticPage && styles.dotActive,
|
|
518
523
|
isDisabled && styles.dotDisabled,
|
|
519
524
|
),
|
|
520
525
|
)}
|
|
@@ -27,7 +27,7 @@ export const docs = {
|
|
|
27
27
|
{
|
|
28
28
|
guidance: true,
|
|
29
29
|
description:
|
|
30
|
-
'Use useResizable() with existing
|
|
30
|
+
'Use useResizable() with existing Astryx layout components. ' +
|
|
31
31
|
'Pass the returned props to the resizable prop on LayoutPanel or SideNav.',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -147,7 +147,7 @@ export const docs = {
|
|
|
147
147
|
type: 'boolean',
|
|
148
148
|
description:
|
|
149
149
|
'Show the pill grip at rest instead of only on hover. Use when discoverability is important.',
|
|
150
|
-
default: '
|
|
150
|
+
default: 'true',
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
name: 'pillPlacement',
|
|
@@ -195,7 +195,7 @@ export const docsDense = {
|
|
|
195
195
|
description:
|
|
196
196
|
'Hook-based resizable panel system. useResizable() manages size state; ResizeHandle provides interactive pill-grip separator. Pass resize props to existing layout components via their resizable prop.',
|
|
197
197
|
bestPractices: [
|
|
198
|
-
{guidance: true, description: 'Use useResizable() w/ existing
|
|
198
|
+
{guidance: true, description: 'Use useResizable() w/ existing Astryx layout components. Pass returned props to resizable prop on LayoutPanel or SideNav.'},
|
|
199
199
|
{guidance: true, description: 'Provide accessible label on each ResizeHandle when multiple handles exist (e.g. "Resize sidebar", "Resize terminal").'},
|
|
200
200
|
{guidance: false, description: 'Wrap panels in extra container components for resize. Hook-first architecture avoids extra DOM; use it directly on existing components.'},
|
|
201
201
|
],
|
|
@@ -107,10 +107,6 @@ export interface ResizableProps {
|
|
|
107
107
|
const DEFAULT_MIN = 50;
|
|
108
108
|
const DEFAULT_COLLAPSED_SIZE = 40;
|
|
109
109
|
const STORAGE_PREFIX = 'astryx-resizable:';
|
|
110
|
-
// Legacy key prefix read during the compat window so persisted panel sizes
|
|
111
|
-
// survive the xds -> astryx rename. Read-only fallback; we always write the
|
|
112
|
-
// new prefix. Removed at final cutover.
|
|
113
|
-
const LEGACY_STORAGE_PREFIX = 'xds-resizable:';
|
|
114
110
|
|
|
115
111
|
// =============================================================================
|
|
116
112
|
// Helpers
|
|
@@ -147,9 +143,7 @@ function loadPersistedSize(key: string): number | null {
|
|
|
147
143
|
return null;
|
|
148
144
|
}
|
|
149
145
|
try {
|
|
150
|
-
const raw =
|
|
151
|
-
localStorage.getItem(STORAGE_PREFIX + key) ??
|
|
152
|
-
localStorage.getItem(LEGACY_STORAGE_PREFIX + key);
|
|
146
|
+
const raw = localStorage.getItem(STORAGE_PREFIX + key);
|
|
153
147
|
if (raw != null) {
|
|
154
148
|
const parsed = JSON.parse(raw);
|
|
155
149
|
if (typeof parsed === 'number') {
|
|
@@ -84,11 +84,13 @@ export const docs = {
|
|
|
84
84
|
name: 'isDisabled',
|
|
85
85
|
type: 'boolean',
|
|
86
86
|
description: 'Disables the selector.',
|
|
87
|
+
default: 'false',
|
|
87
88
|
},
|
|
88
89
|
{
|
|
89
90
|
name: 'isLabelHidden',
|
|
90
91
|
type: 'boolean',
|
|
91
92
|
description: 'Visually hides the label while keeping it accessible.',
|
|
93
|
+
default: 'false',
|
|
92
94
|
},
|
|
93
95
|
{
|
|
94
96
|
name: 'description',
|
|
@@ -99,11 +101,13 @@ export const docs = {
|
|
|
99
101
|
name: 'isOptional',
|
|
100
102
|
type: 'boolean',
|
|
101
103
|
description: 'Marks the field as optional.',
|
|
104
|
+
default: 'false',
|
|
102
105
|
},
|
|
103
106
|
{
|
|
104
107
|
name: 'isRequired',
|
|
105
108
|
type: 'boolean',
|
|
106
109
|
description: 'Marks the field as required.',
|
|
110
|
+
default: 'false',
|
|
107
111
|
},
|
|
108
112
|
{
|
|
109
113
|
name: 'status',
|
|
@@ -107,12 +107,11 @@ const styles = stylex.create({
|
|
|
107
107
|
lineHeight: 'inherit',
|
|
108
108
|
color: 'inherit',
|
|
109
109
|
cursor: 'pointer',
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
borderRadius: radiusVars['--radius-element'],
|
|
110
|
+
// The wrapper (inputWrapperStyles.base) renders the focus ring via
|
|
111
|
+
// :focus-within when this button is focused, matching TextInput/NumberInput.
|
|
112
|
+
// The button must not draw its own :focus-visible outline or the two stack
|
|
113
|
+
// into a doubled ring over the trigger.
|
|
114
|
+
outline: 'none',
|
|
116
115
|
},
|
|
117
116
|
triggerPlaceholder: {
|
|
118
117
|
color: colorVars['--color-text-secondary'],
|
|
@@ -33,7 +33,17 @@ export const docs = {
|
|
|
33
33
|
'Index for staggered animation timing. For element at index n, animation starts at DELAY_TIME + (STAGGER_TIME × n).',
|
|
34
34
|
default: '0',
|
|
35
35
|
},
|
|
36
|
-
],
|
|
36
|
+
],
|
|
37
|
+
playground: {
|
|
38
|
+
// Skeleton width/height default to '100%', which collapses to a zero-size
|
|
39
|
+
// (invisible) element in the properties-tab preview. Give the example
|
|
40
|
+
// explicit pixel dimensions so the shimmer placeholder is visible.
|
|
41
|
+
defaults: {
|
|
42
|
+
width: 320,
|
|
43
|
+
height: 80,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
theming: {
|
|
37
47
|
targets: [
|
|
38
48
|
{className: 'astryx-skeleton'},
|
|
39
49
|
],
|
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>.
|
|
@@ -497,9 +509,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
497
509
|
emptyState !== false && (
|
|
498
510
|
<tr>
|
|
499
511
|
<td colSpan={resolvedColumns.length}>
|
|
500
|
-
{emptyState ??
|
|
501
|
-
<EmptyState title="No data" isCompact />
|
|
502
|
-
)}
|
|
512
|
+
{emptyState ?? <EmptyState title="No data" isCompact />}
|
|
503
513
|
</td>
|
|
504
514
|
</tr>
|
|
505
515
|
)}
|
|
@@ -513,8 +523,29 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
513
523
|
// when columns exceed the container width. This wrapper sits between
|
|
514
524
|
// the <table> and transformTableContext, so plugin chrome (pagination,
|
|
515
525
|
// toolbars) renders outside the scroll area.
|
|
526
|
+
//
|
|
527
|
+
// Before rendering the wrapper, run the plugin `transformScrollWrapper`
|
|
528
|
+
// pipeline so plugins can attach a ref to the scroll container (scroll-aware
|
|
529
|
+
// sticky shadows, virtualization) and inject before/after chrome.
|
|
516
530
|
if (ScrollWrapper) {
|
|
517
|
-
|
|
531
|
+
const scrollWrapperRenderProps = applyPlugins(
|
|
532
|
+
plugins,
|
|
533
|
+
p => p.transformScrollWrapper,
|
|
534
|
+
{
|
|
535
|
+
htmlProps: {},
|
|
536
|
+
styles: [],
|
|
537
|
+
} satisfies ScrollWrapperRenderProps,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
tableElement = (
|
|
541
|
+
<ScrollWrapper
|
|
542
|
+
htmlProps={scrollWrapperRenderProps.htmlProps}
|
|
543
|
+
styles={scrollWrapperRenderProps.styles}
|
|
544
|
+
beforeTable={scrollWrapperRenderProps.beforeTable}
|
|
545
|
+
afterTable={scrollWrapperRenderProps.afterTable}>
|
|
546
|
+
{tableElement}
|
|
547
|
+
</ScrollWrapper>
|
|
548
|
+
);
|
|
518
549
|
}
|
|
519
550
|
|
|
520
551
|
// Apply transformTableContext from each plugin.
|
|
@@ -527,10 +558,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
527
558
|
try {
|
|
528
559
|
tableElement = plugin.transformTableContext(tableElement);
|
|
529
560
|
} catch (error) {
|
|
530
|
-
console.error(
|
|
531
|
-
'[Table] Plugin threw in transformTableContext:',
|
|
532
|
-
error,
|
|
533
|
-
);
|
|
561
|
+
console.error('[Table] Plugin threw in transformTableContext:', error);
|
|
534
562
|
}
|
|
535
563
|
}
|
|
536
564
|
}
|
|
@@ -556,9 +584,7 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
556
584
|
* />
|
|
557
585
|
* ```
|
|
558
586
|
*/
|
|
559
|
-
export const BaseTable = BaseTableInner as <
|
|
560
|
-
T extends Record<string, unknown>,
|
|
561
|
-
>(
|
|
587
|
+
export const BaseTable = BaseTableInner as <T extends Record<string, unknown>>(
|
|
562
588
|
props: BaseTableProps<T> & {ref?: Ref<HTMLTableElement>},
|
|
563
589
|
) => ReactElement;
|
|
564
590
|
|
package/src/Table/Table.doc.mjs
CHANGED
|
@@ -104,7 +104,7 @@ export const docs = {
|
|
|
104
104
|
'Table displays structured data in rows and columns with consistent dimensionality. It supports rich cell content, sorting, selection, pagination, and column management through a composable plugin system. Use Table for data sets with uniform structure; for simpler or inconsistent data, consider a list or card layout instead.',
|
|
105
105
|
bestPractices: [
|
|
106
106
|
{ guidance: true, description: 'Use density and divider variants to match the information density and scanning needs of your data.' },
|
|
107
|
-
{ guidance: true, description: 'Compose rich cell content with
|
|
107
|
+
{ guidance: true, description: 'Compose rich cell content with Astryx components like Badge, StatusDot, and Avatar via renderCell.' },
|
|
108
108
|
{ guidance: true, description: 'Set explicit width on every column using proportional() or pixel(). proportional(1) gives equal flex distribution with a 120px minimum that prevents columns from collapsing on narrow viewports. Omitting width skips the minimum.' },
|
|
109
109
|
{ guidance: false, description: 'Use a table for data without consistent columns. Use a list or card layout for heterogeneous content.' },
|
|
110
110
|
{ guidance: false, description: 'Enable every plugin at once. Add only the features your use case requires to keep the interface focused.' },
|
|
@@ -128,7 +128,7 @@ export const docsZh = {
|
|
|
128
128
|
'Table displays structured data in rows and columns with consistent dimensionality. It supports rich cell content, sorting, selection, pagination, and column management through a composable plugin system. Use Table for data sets with uniform structure; for simpler or inconsistent data, consider a list or card layout instead.',
|
|
129
129
|
bestPractices: [
|
|
130
130
|
{ guidance: true, description: 'Use density and divider variants to match the information density and scanning needs of your data.' },
|
|
131
|
-
{ guidance: true, description: 'Compose rich cell content with
|
|
131
|
+
{ guidance: true, description: 'Compose rich cell content with Astryx components like Badge, StatusDot, and Avatar via renderCell.' },
|
|
132
132
|
{ guidance: false, description: 'Use a table for data without consistent columns. Use a list or card layout for heterogeneous content.' },
|
|
133
133
|
{ guidance: false, description: 'Enable every plugin at once. Add only the features your use case requires to keep the interface focused.' },
|
|
134
134
|
],
|
|
@@ -151,7 +151,7 @@ export const docsDense = {
|
|
|
151
151
|
'Table displays structured data in rows and columns with consistent dimensionality. It supports rich cell content, sorting, selection, pagination, and column management through a composable plugin system. Use Table for data sets with uniform structure; for simpler or inconsistent data, consider a list or card layout instead.',
|
|
152
152
|
bestPractices: [
|
|
153
153
|
{ guidance: true, description: 'Use density and divider variants to match the information density and scanning needs of your data.' },
|
|
154
|
-
{ guidance: true, description: 'Compose rich cell content with
|
|
154
|
+
{ guidance: true, description: 'Compose rich cell content with Astryx components like Badge, StatusDot, and Avatar via renderCell.' },
|
|
155
155
|
{ guidance: true, description: 'Set explicit width on every column via proportional() or pixel(). proportional(1) = equal flex w/ 120px min preventing collapse on narrow viewports. Omitting width skips the minimum.' },
|
|
156
156
|
{ guidance: false, description: 'Use a table for data without consistent columns. Use a list or card layout for heterogeneous content.' },
|
|
157
157
|
{ guidance: false, description: 'Enable every plugin at once. Add only the features your use case requires to keep the interface focused.' },
|
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/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,
|