@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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen, within} from '@testing-library/react';
|
|
13
|
+
import {render, screen, within, fireEvent, act} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {Pagination, generatePageRange} from './Pagination';
|
|
16
16
|
|
|
@@ -346,6 +346,140 @@ describe('Pagination', () => {
|
|
|
346
346
|
});
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// changeAction (interruptible, optimistic)
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
describe('changeAction', () => {
|
|
354
|
+
it('fires onChange then changeAction with the new page', async () => {
|
|
355
|
+
const user = userEvent.setup();
|
|
356
|
+
const order: string[] = [];
|
|
357
|
+
const onChange = vi.fn(() => order.push('onChange'));
|
|
358
|
+
const changeAction = vi.fn(() => {
|
|
359
|
+
order.push('changeAction');
|
|
360
|
+
});
|
|
361
|
+
render(
|
|
362
|
+
<Pagination
|
|
363
|
+
page={1}
|
|
364
|
+
onChange={onChange}
|
|
365
|
+
changeAction={changeAction}
|
|
366
|
+
totalPages={5}
|
|
367
|
+
/>,
|
|
368
|
+
);
|
|
369
|
+
await user.click(screen.getByRole('button', {name: 'Go to next page'}));
|
|
370
|
+
expect(onChange).toHaveBeenCalledWith(2);
|
|
371
|
+
expect(changeAction).toHaveBeenCalledWith(2);
|
|
372
|
+
expect(order).toEqual(['onChange', 'changeAction']);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('shows the optimistic page while changeAction is pending', async () => {
|
|
376
|
+
const user = userEvent.setup();
|
|
377
|
+
let resolveAction: (() => void) | undefined;
|
|
378
|
+
const changeAction = vi.fn(
|
|
379
|
+
async () =>
|
|
380
|
+
new Promise<void>(resolve => {
|
|
381
|
+
resolveAction = resolve;
|
|
382
|
+
}),
|
|
383
|
+
);
|
|
384
|
+
render(
|
|
385
|
+
<Pagination
|
|
386
|
+
page={1}
|
|
387
|
+
onChange={() => {}}
|
|
388
|
+
changeAction={changeAction}
|
|
389
|
+
totalPages={5}
|
|
390
|
+
variant="compact"
|
|
391
|
+
/>,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
// The committed `page` prop stays at 1, but the indicator optimistically
|
|
395
|
+
// reflects the page being navigated to.
|
|
396
|
+
await user.click(screen.getByRole('button', {name: 'Go to next page'}));
|
|
397
|
+
expect(changeAction).toHaveBeenCalledWith(2);
|
|
398
|
+
expect(screen.getByText('Page 2 of 5')).toBeInTheDocument();
|
|
399
|
+
|
|
400
|
+
await act(async () => {
|
|
401
|
+
resolveAction?.();
|
|
402
|
+
await Promise.resolve();
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('interrupts an in-flight action on rapid next clicks', async () => {
|
|
407
|
+
// Each click derives its target from the optimistic page, so clicking
|
|
408
|
+
// next twice before the action settles advances 1 -> 2 -> 3 instead of
|
|
409
|
+
// being dropped by a re-entry guard.
|
|
410
|
+
const resolvers: (() => void)[] = [];
|
|
411
|
+
const changeAction = vi.fn(
|
|
412
|
+
async () =>
|
|
413
|
+
new Promise<void>(resolve => {
|
|
414
|
+
resolvers.push(resolve);
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
417
|
+
render(
|
|
418
|
+
<Pagination
|
|
419
|
+
page={1}
|
|
420
|
+
onChange={() => {}}
|
|
421
|
+
changeAction={changeAction}
|
|
422
|
+
totalPages={5}
|
|
423
|
+
variant="compact"
|
|
424
|
+
/>,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const next = screen.getByRole('button', {name: 'Go to next page'});
|
|
428
|
+
await act(async () => {
|
|
429
|
+
fireEvent.click(next);
|
|
430
|
+
});
|
|
431
|
+
expect(screen.getByText('Page 2 of 5')).toBeInTheDocument();
|
|
432
|
+
await act(async () => {
|
|
433
|
+
fireEvent.click(next);
|
|
434
|
+
});
|
|
435
|
+
expect(screen.getByText('Page 3 of 5')).toBeInTheDocument();
|
|
436
|
+
|
|
437
|
+
expect(changeAction).toHaveBeenCalledTimes(2);
|
|
438
|
+
expect(changeAction).toHaveBeenNthCalledWith(1, 2);
|
|
439
|
+
expect(changeAction).toHaveBeenNthCalledWith(2, 3);
|
|
440
|
+
|
|
441
|
+
await act(async () => {
|
|
442
|
+
resolvers.forEach(resolve => resolve());
|
|
443
|
+
await Promise.resolve();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('supports a synchronous changeAction', async () => {
|
|
448
|
+
const user = userEvent.setup();
|
|
449
|
+
const changeAction = vi.fn((_page: number) => {});
|
|
450
|
+
const onChange = vi.fn();
|
|
451
|
+
render(
|
|
452
|
+
<Pagination
|
|
453
|
+
page={2}
|
|
454
|
+
onChange={onChange}
|
|
455
|
+
changeAction={changeAction}
|
|
456
|
+
totalPages={5}
|
|
457
|
+
/>,
|
|
458
|
+
);
|
|
459
|
+
await user.click(
|
|
460
|
+
screen.getByRole('button', {name: 'Go to previous page'}),
|
|
461
|
+
);
|
|
462
|
+
expect(onChange).toHaveBeenCalledWith(1);
|
|
463
|
+
expect(changeAction).toHaveBeenCalledWith(1);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('does not fire changeAction when disabled', async () => {
|
|
467
|
+
const user = userEvent.setup();
|
|
468
|
+
const changeAction = vi.fn();
|
|
469
|
+
render(
|
|
470
|
+
<Pagination
|
|
471
|
+
page={1}
|
|
472
|
+
onChange={() => {}}
|
|
473
|
+
changeAction={changeAction}
|
|
474
|
+
totalPages={5}
|
|
475
|
+
isDisabled
|
|
476
|
+
/>,
|
|
477
|
+
);
|
|
478
|
+
await user.click(screen.getByRole('button', {name: 'Go to next page'}));
|
|
479
|
+
expect(changeAction).not.toHaveBeenCalled();
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
349
483
|
// ---------------------------------------------------------------------------
|
|
350
484
|
// Boundary states
|
|
351
485
|
// ---------------------------------------------------------------------------
|
|
@@ -459,12 +593,7 @@ describe('Pagination', () => {
|
|
|
459
593
|
describe('disabled state', () => {
|
|
460
594
|
it('disables all page buttons when isDisabled', () => {
|
|
461
595
|
render(
|
|
462
|
-
<Pagination
|
|
463
|
-
page={3}
|
|
464
|
-
onChange={() => {}}
|
|
465
|
-
totalPages={5}
|
|
466
|
-
isDisabled
|
|
467
|
-
/>,
|
|
596
|
+
<Pagination page={3} onChange={() => {}} totalPages={5} isDisabled />,
|
|
468
597
|
);
|
|
469
598
|
expect(
|
|
470
599
|
screen.getByRole('button', {name: 'Go to previous page'}),
|
|
@@ -480,12 +609,7 @@ describe('Pagination', () => {
|
|
|
480
609
|
const user = userEvent.setup();
|
|
481
610
|
const onChange = vi.fn();
|
|
482
611
|
render(
|
|
483
|
-
<Pagination
|
|
484
|
-
page={3}
|
|
485
|
-
onChange={onChange}
|
|
486
|
-
totalPages={5}
|
|
487
|
-
isDisabled
|
|
488
|
-
/>,
|
|
612
|
+
<Pagination page={3} onChange={onChange} totalPages={5} isDisabled />,
|
|
489
613
|
);
|
|
490
614
|
// Disabled buttons can't be clicked
|
|
491
615
|
await user.click(screen.getByRole('button', {name: 'Go to page 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
|
)}
|
|
@@ -79,9 +79,7 @@ describe('ProgressBar', () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
it('passes data-testid', () => {
|
|
82
|
-
render(
|
|
83
|
-
<ProgressBar value={50} label="Test" data-testid="my-progress" />,
|
|
84
|
-
);
|
|
82
|
+
render(<ProgressBar value={50} label="Test" data-testid="my-progress" />);
|
|
85
83
|
expect(screen.getByTestId('my-progress')).toBeInTheDocument();
|
|
86
84
|
});
|
|
87
85
|
|
|
@@ -115,6 +113,18 @@ describe('ProgressBar', () => {
|
|
|
115
113
|
expect(screen.getByText('Hidden')).toBeInTheDocument();
|
|
116
114
|
});
|
|
117
115
|
|
|
116
|
+
it('renders no visible value label when isLabelHidden without hasValueLabel', () => {
|
|
117
|
+
// Mirrors the intended "accessible label only" composition: the text
|
|
118
|
+
// label is kept for assistive tech (visually hidden) while no extra
|
|
119
|
+
// visible value label is surfaced.
|
|
120
|
+
render(<ProgressBar value={42} label="Context usage" isLabelHidden />);
|
|
121
|
+
expect(screen.queryByText('42%')).not.toBeInTheDocument();
|
|
122
|
+
const label = screen.getByText('Context usage');
|
|
123
|
+
expect(label).toBeInTheDocument();
|
|
124
|
+
const meter = screen.getByRole('meter');
|
|
125
|
+
expect(meter).toHaveAttribute('aria-labelledby', label.id);
|
|
126
|
+
});
|
|
127
|
+
|
|
118
128
|
it('handles zero max gracefully', () => {
|
|
119
129
|
render(<ProgressBar value={0} max={0} label="Empty" />);
|
|
120
130
|
const meter = screen.getByRole('meter');
|
|
@@ -53,6 +53,11 @@ export const docs = {
|
|
|
53
53
|
description: 'Internal padding using the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10). Use padding={0} for edge-to-edge content.',
|
|
54
54
|
default: '4',
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
name: 'paddingBlock',
|
|
58
|
+
type: 'SpacingStep',
|
|
59
|
+
description: 'Block (vertical) padding override. Overrides only the block-axis padding while preserving inline padding from `padding` or the container theme default. Accepts the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10).',
|
|
60
|
+
},
|
|
56
61
|
{
|
|
57
62
|
name: 'xstyle',
|
|
58
63
|
type: 'StyleXStyles',
|
|
@@ -130,6 +135,11 @@ export const docsZh = {
|
|
|
130
135
|
description: '使用间距比例的内部内边距(0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)。使用 padding={0} 实现全宽内容。',
|
|
131
136
|
default: '4',
|
|
132
137
|
},
|
|
138
|
+
{
|
|
139
|
+
name: 'paddingBlock',
|
|
140
|
+
type: 'SpacingStep',
|
|
141
|
+
description: '块(垂直)方向内边距覆盖。仅覆盖块轴内边距,同时保留来自 padding 或容器主题默认值的行内内边距。使用间距比例(0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)。',
|
|
142
|
+
},
|
|
133
143
|
{
|
|
134
144
|
name: 'xstyle',
|
|
135
145
|
type: 'StyleXStyles',
|
|
@@ -182,6 +192,7 @@ export const docsDense = {
|
|
|
182
192
|
children: 'Content rendered inside section.',
|
|
183
193
|
dividers: 'Which sides of section have divider borders.',
|
|
184
194
|
padding: 'Internal padding via spacing scale; 0 for edge-to-edge content.',
|
|
195
|
+
paddingBlock: 'Block-axis padding override; preserves inline padding from padding/theme.',
|
|
185
196
|
xstyle: 'StyleX styles for layout customization; must be stylex.create() value.',
|
|
186
197
|
},
|
|
187
198
|
};
|
|
@@ -36,6 +36,21 @@ export const docs = {
|
|
|
36
36
|
container: true,
|
|
37
37
|
targets: [{className: 'astryx-selectable-card', visualProps: ['selected']}],
|
|
38
38
|
},
|
|
39
|
+
playground: {
|
|
40
|
+
defaults: {
|
|
41
|
+
label: 'Pro plan',
|
|
42
|
+
isSelected: true,
|
|
43
|
+
padding: 4,
|
|
44
|
+
children: {
|
|
45
|
+
__element: 'XDSVStack',
|
|
46
|
+
props: {gap: 1},
|
|
47
|
+
children: [
|
|
48
|
+
{__element: 'XDSHeading', props: {level: 3}, children: 'Pro plan'},
|
|
49
|
+
{__element: 'XDSText', props: {type: 'body'}, children: '$29/month — unlimited projects and priority support.'},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
39
54
|
};
|
|
40
55
|
|
|
41
56
|
/** @type {import('../docs-types').TranslationDoc} */
|
|
@@ -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',
|
|
@@ -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
|
],
|
|
@@ -11,6 +11,10 @@ export const docs = {
|
|
|
11
11
|
defaults: {
|
|
12
12
|
label: 'Volume',
|
|
13
13
|
value: 50,
|
|
14
|
+
// Give the properties-tab preview a defined width so the track has room
|
|
15
|
+
// to render and stays draggable/interactive (the slider track grows to
|
|
16
|
+
// fill its container, which can collapse without an explicit width).
|
|
17
|
+
width: 300,
|
|
14
18
|
},
|
|
15
19
|
},
|
|
16
20
|
props: [
|
|
@@ -24,9 +24,7 @@ describe('Slider', () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('range thumbs have correct aria-labels', () => {
|
|
27
|
-
render(
|
|
28
|
-
<Slider label="Price range" value={[20, 80] as [number, number]} />,
|
|
29
|
-
);
|
|
27
|
+
render(<Slider label="Price range" value={[20, 80] as [number, number]} />);
|
|
30
28
|
const sliders = screen.getAllByRole('slider');
|
|
31
29
|
expect(sliders[0]).toHaveAttribute(
|
|
32
30
|
'aria-label',
|
|
@@ -172,12 +170,7 @@ describe('Slider', () => {
|
|
|
172
170
|
it('does not fire onChange on keyboard when disabled', () => {
|
|
173
171
|
const handleChange = vi.fn();
|
|
174
172
|
render(
|
|
175
|
-
<Slider
|
|
176
|
-
label="Volume"
|
|
177
|
-
value={50}
|
|
178
|
-
onChange={handleChange}
|
|
179
|
-
isDisabled
|
|
180
|
-
/>,
|
|
173
|
+
<Slider label="Volume" value={50} onChange={handleChange} isDisabled />,
|
|
181
174
|
);
|
|
182
175
|
const slider = screen.getByRole('slider');
|
|
183
176
|
fireEvent.keyDown(slider, {key: 'ArrowRight'});
|
|
@@ -297,13 +290,7 @@ describe('Slider', () => {
|
|
|
297
290
|
|
|
298
291
|
it('focuses closest thumb on track click', () => {
|
|
299
292
|
render(
|
|
300
|
-
<Slider
|
|
301
|
-
label="Volume"
|
|
302
|
-
value={50}
|
|
303
|
-
min={0}
|
|
304
|
-
max={100}
|
|
305
|
-
onChange={vi.fn()}
|
|
306
|
-
/>,
|
|
293
|
+
<Slider label="Volume" value={50} min={0} max={100} onChange={vi.fn()} />,
|
|
307
294
|
);
|
|
308
295
|
const slider = screen.getByRole('slider');
|
|
309
296
|
const trackContainer = slider.parentElement!;
|
package/src/Slider/Slider.tsx
CHANGED
|
@@ -159,7 +159,7 @@ const styles = stylex.create({
|
|
|
159
159
|
},
|
|
160
160
|
track: {
|
|
161
161
|
position: 'absolute',
|
|
162
|
-
backgroundColor: colorVars['--color-
|
|
162
|
+
backgroundColor: colorVars['--color-track'],
|
|
163
163
|
borderRadius: radiusVars['--radius-full'],
|
|
164
164
|
},
|
|
165
165
|
trackHorizontal: {
|
|
@@ -43,6 +43,12 @@ export const docs = {
|
|
|
43
43
|
description: 'Size variant applied to all child tabs.',
|
|
44
44
|
default: "'md'",
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: 'layout',
|
|
48
|
+
type: "'hug' | 'fill'",
|
|
49
|
+
description: "Layout mode for tab sizing. 'hug': each tab hugs its content width. 'fill': tabs stretch equally to fill the container width.",
|
|
50
|
+
default: "'hug'",
|
|
51
|
+
},
|
|
46
52
|
{
|
|
47
53
|
name: 'hasDivider',
|
|
48
54
|
type: 'boolean',
|