@astryxdesign/core 0.1.4-canary.39ffd21 → 0.1.4-canary.3d64ef2
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/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +8 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +31 -11
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Card/Card.d.ts +4 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +15 -7
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +1 -0
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +8 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +26 -3
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +23 -5
- package/dist/Collapsible/Collapsible.d.ts +9 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +101 -13
- package/dist/Collapsible/CollapsibleGroup.d.ts +39 -7
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +70 -9
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +31 -0
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +33 -3
- package/dist/Collapsible/index.d.ts +1 -0
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +2 -0
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +2 -0
- package/dist/Grid/Grid.d.ts +4 -3
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +26 -9
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +5 -1
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/Item/Item.js +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +26 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +32 -16
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +87 -36
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +39 -21
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +2 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +10 -2
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +6 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +34 -7
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +14 -3
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +5 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +5 -1
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
- package/dist/Thumbnail/Thumbnail.d.ts +3 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +11 -3
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +12 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -2
- package/dist/astryx.css +7 -0
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts +3 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +38 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +10 -3
- package/dist/theme/hct.d.ts +0 -15
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/tokens.d.ts +10 -2
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +220 -4
- package/dist/utils/color.d.ts +42 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +150 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +8 -5
- package/package.json +1 -1
- package/src/Avatar/Avatar.doc.mjs +11 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +38 -0
- package/src/Banner/Banner.tsx +8 -1
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +112 -4
- package/src/Calendar/Calendar.tsx +26 -5
- package/src/Calendar/styles.ts +12 -2
- package/src/Card/Card.tsx +16 -6
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +13 -0
- package/src/Carousel/Carousel.tsx +1 -0
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +55 -0
- package/src/Chat/ChatToolCalls.tsx +10 -12
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +71 -4
- package/src/CheckboxInput/CheckboxInput.tsx +2 -0
- package/src/Citation/Citation.doc.mjs +5 -0
- package/src/ClickableCard/ClickableCard.tsx +45 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
- package/src/CodeBlock/CodeBlock.test.tsx +87 -3
- package/src/CodeBlock/CodeBlock.tsx +43 -3
- package/src/Collapsible/Collapsible.doc.mjs +8 -4
- package/src/Collapsible/Collapsible.tsx +86 -10
- package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
- package/src/Collapsible/CollapsibleGroup.test.tsx +288 -0
- package/src/Collapsible/CollapsibleGroup.tsx +107 -9
- package/src/Collapsible/CollapsibleGroupContext.tsx +42 -2
- package/src/Collapsible/index.ts +4 -0
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
- package/src/ContextMenu/ContextMenu.test.tsx +15 -0
- package/src/ContextMenu/ContextMenu.tsx +2 -0
- package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +70 -0
- package/src/DateTimeInput/DateTimeInput.tsx +2 -0
- package/src/Dialog/DialogHeader.doc.mjs +45 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.test.tsx +43 -18
- package/src/Grid/Grid.tsx +26 -9
- package/src/Icon/Icon.test.tsx +41 -29
- package/src/Icon/Icon.tsx +6 -2
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/Item/Item.tsx +1 -1
- package/src/Layout/LayoutContent.doc.mjs +12 -0
- package/src/Layout/LayoutPanel.doc.mjs +40 -0
- package/src/Lightbox/Lightbox.doc.mjs +5 -0
- package/src/Lightbox/Lightbox.test.tsx +123 -2
- package/src/Lightbox/Lightbox.tsx +29 -1
- package/src/NavMenu/NavMenu.doc.mjs +15 -0
- package/src/NumberInput/NumberInput.test.tsx +48 -3
- package/src/NumberInput/NumberInput.tsx +32 -15
- package/src/Overlay/Overlay.doc.mjs +6 -0
- package/src/Pagination/Pagination.test.tsx +143 -0
- package/src/Pagination/Pagination.tsx +75 -23
- package/src/Popover/Popover.test.tsx +65 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +109 -1
- package/src/PowerSearch/PowerSearch.tsx +37 -16
- package/src/ProgressBar/ProgressBar.test.tsx +10 -0
- package/src/ProgressBar/ProgressBar.tsx +2 -1
- package/src/Resizable/ResizeHandle.test.tsx +180 -0
- package/src/Resizable/ResizeHandle.tsx +10 -2
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/SideNav/SideNav.doc.mjs +7 -1
- package/src/SideNav/SideNav.test.tsx +20 -0
- package/src/SideNav/SideNav.tsx +6 -2
- package/src/TabList/TabList.test.tsx +185 -1
- package/src/TabList/TabMenu.tsx +45 -9
- package/src/Table/BaseTable.tsx +13 -2
- package/src/Table/Table.test.tsx +51 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
- package/src/Table/plugins/pagination/paginateData.ts +5 -1
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
- package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
- package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +36 -2
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.test.tsx +37 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Toast/Toast.tsx +11 -3
- package/src/Toast/ToastViewport.test.tsx +64 -0
- package/src/Toast/ToastViewport.tsx +12 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +45 -1
- package/src/__tests__/TestIcon.tsx +26 -0
- package/src/docPropReferences.test.ts +178 -0
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/useFocusTrap.doc.mjs +4 -2
- package/src/hooks/useFocusTrap.test.tsx +149 -1
- package/src/hooks/useFocusTrap.ts +50 -1
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +4 -13
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +29 -0
- package/src/theme/expandColorScale.ts +11 -6
- package/src/theme/hct.ts +9 -17
- package/src/theme/tokens.test.ts +182 -9
- package/src/theme/tokens.ts +239 -4
- package/src/theme/useTheme.test.tsx +20 -0
- package/src/utils/color.test.ts +133 -0
- package/src/utils/color.ts +148 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/timeParser.test.ts +13 -0
- package/src/utils/timeParser.ts +8 -5
|
@@ -77,6 +77,22 @@ describe('Collapsible', () => {
|
|
|
77
77
|
expect(screen.getByText('Hidden content')).not.toBeVisible();
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it('links the trigger to its content region via aria-controls', () => {
|
|
81
|
+
render(
|
|
82
|
+
<Collapsible trigger="Details">
|
|
83
|
+
<p>Region content</p>
|
|
84
|
+
</Collapsible>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const trigger = screen.getByRole('button', {name: /Details/});
|
|
88
|
+
const controlsId = trigger.getAttribute('aria-controls');
|
|
89
|
+
// aria-controls must be present and point at the real content region.
|
|
90
|
+
expect(controlsId).toBeTruthy();
|
|
91
|
+
const region = document.getElementById(controlsId as string);
|
|
92
|
+
expect(region).not.toBeNull();
|
|
93
|
+
expect(region).toContainElement(screen.getByText('Region content'));
|
|
94
|
+
});
|
|
95
|
+
|
|
80
96
|
it('respects controlled isOpen/onOpenChange', async () => {
|
|
81
97
|
const onOpenChange = vi.fn();
|
|
82
98
|
const user = userEvent.setup();
|
|
@@ -397,4 +413,276 @@ describe('CollapsibleGroup', () => {
|
|
|
397
413
|
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
398
414
|
});
|
|
399
415
|
});
|
|
416
|
+
|
|
417
|
+
describe('dividers', () => {
|
|
418
|
+
function getItem(name: RegExp): HTMLElement {
|
|
419
|
+
const root = screen
|
|
420
|
+
.getByRole('button', {name})
|
|
421
|
+
.closest('.astryx-collapsible');
|
|
422
|
+
expect(root).not.toBeNull();
|
|
423
|
+
return root as HTMLElement;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
it('renders no wrapper DOM by default and with dividers="none"', () => {
|
|
427
|
+
const {container, rerender} = render(
|
|
428
|
+
<CollapsibleGroup>
|
|
429
|
+
<Collapsible trigger="Item A" value="a">
|
|
430
|
+
<p>Content A</p>
|
|
431
|
+
</Collapsible>
|
|
432
|
+
</CollapsibleGroup>,
|
|
433
|
+
);
|
|
434
|
+
expect(container.querySelector('.astryx-collapsible-group')).toBeNull();
|
|
435
|
+
|
|
436
|
+
rerender(
|
|
437
|
+
<CollapsibleGroup dividers="none">
|
|
438
|
+
<Collapsible trigger="Item A" value="a">
|
|
439
|
+
<p>Content A</p>
|
|
440
|
+
</Collapsible>
|
|
441
|
+
</CollapsibleGroup>,
|
|
442
|
+
);
|
|
443
|
+
expect(container.querySelector('.astryx-collapsible-group')).toBeNull();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('renders a wrapper with data attributes when dividers are enabled', () => {
|
|
447
|
+
const {container} = render(
|
|
448
|
+
<CollapsibleGroup dividers="between">
|
|
449
|
+
<Collapsible trigger="Item A" value="a">
|
|
450
|
+
<p>Content A</p>
|
|
451
|
+
</Collapsible>
|
|
452
|
+
<Collapsible trigger="Item B" value="b">
|
|
453
|
+
<p>Content B</p>
|
|
454
|
+
</Collapsible>
|
|
455
|
+
</CollapsibleGroup>,
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const wrapper = container.querySelector('.astryx-collapsible-group');
|
|
459
|
+
expect(wrapper).not.toBeNull();
|
|
460
|
+
expect(wrapper).toHaveAttribute('data-dividers', 'between');
|
|
461
|
+
// Divided groups default to balanced density
|
|
462
|
+
expect(wrapper).toHaveAttribute('data-density', 'balanced');
|
|
463
|
+
expect(wrapper).toContainElement(getItem(/Item A/));
|
|
464
|
+
expect(wrapper).toContainElement(getItem(/Item B/));
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('reflects dividers and density on each item', () => {
|
|
468
|
+
render(
|
|
469
|
+
<CollapsibleGroup dividers="all" density="compact">
|
|
470
|
+
<Collapsible trigger="Item A" value="a">
|
|
471
|
+
<p>Content A</p>
|
|
472
|
+
</Collapsible>
|
|
473
|
+
</CollapsibleGroup>,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const item = getItem(/Item A/);
|
|
477
|
+
expect(item).toHaveAttribute('data-dividers', 'all');
|
|
478
|
+
expect(item).toHaveAttribute('data-density', 'compact');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('does not reflect divider chrome on items without dividers', () => {
|
|
482
|
+
render(
|
|
483
|
+
<CollapsibleGroup>
|
|
484
|
+
<Collapsible trigger="Item A" value="a">
|
|
485
|
+
<p>Content A</p>
|
|
486
|
+
</Collapsible>
|
|
487
|
+
</CollapsibleGroup>,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const item = getItem(/Item A/);
|
|
491
|
+
expect(item).not.toHaveAttribute('data-dividers');
|
|
492
|
+
expect(item).not.toHaveAttribute('data-density');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('applies density without dividers (and without a wrapper)', () => {
|
|
496
|
+
const {container} = render(
|
|
497
|
+
<CollapsibleGroup density="spacious">
|
|
498
|
+
<Collapsible trigger="Item A" value="a">
|
|
499
|
+
<p>Content A</p>
|
|
500
|
+
</Collapsible>
|
|
501
|
+
</CollapsibleGroup>,
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
expect(container.querySelector('.astryx-collapsible-group')).toBeNull();
|
|
505
|
+
expect(getItem(/Item A/)).toHaveAttribute('data-density', 'spacious');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('does not leak divider chrome into nested collapsibles', () => {
|
|
509
|
+
render(
|
|
510
|
+
<CollapsibleGroup dividers="between" defaultValue="outer">
|
|
511
|
+
<Collapsible trigger="Outer" value="outer">
|
|
512
|
+
<Collapsible trigger="Nested">
|
|
513
|
+
<p>Nested content</p>
|
|
514
|
+
</Collapsible>
|
|
515
|
+
</Collapsible>
|
|
516
|
+
</CollapsibleGroup>,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
expect(getItem(/Outer/)).toHaveAttribute('data-dividers', 'between');
|
|
520
|
+
expect(getItem(/Nested/)).not.toHaveAttribute('data-dividers');
|
|
521
|
+
expect(getItem(/Nested/)).not.toHaveAttribute('data-density');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('keeps group coordination working with dividers enabled', async () => {
|
|
525
|
+
const user = userEvent.setup();
|
|
526
|
+
render(
|
|
527
|
+
<CollapsibleGroup type="single" dividers="between" defaultValue="a">
|
|
528
|
+
<Collapsible trigger="Item A" value="a">
|
|
529
|
+
<p>Content A</p>
|
|
530
|
+
</Collapsible>
|
|
531
|
+
<Collapsible trigger="Item B" value="b">
|
|
532
|
+
<p>Content B</p>
|
|
533
|
+
</Collapsible>
|
|
534
|
+
</CollapsibleGroup>,
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
expect(screen.getByText('Content A')).toBeVisible();
|
|
538
|
+
await user.click(screen.getByRole('button', {name: /Item B/}));
|
|
539
|
+
expect(screen.getByText('Content A')).not.toBeVisible();
|
|
540
|
+
expect(screen.getByText('Content B')).toBeVisible();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('applies xstyle/className/style to the wrapper in divider mode', () => {
|
|
544
|
+
const {container} = render(
|
|
545
|
+
<CollapsibleGroup
|
|
546
|
+
dividers="between"
|
|
547
|
+
className="custom-class"
|
|
548
|
+
data-testid="group">
|
|
549
|
+
<Collapsible trigger="Item A" value="a">
|
|
550
|
+
<p>Content A</p>
|
|
551
|
+
</Collapsible>
|
|
552
|
+
</CollapsibleGroup>,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
const wrapper = container.querySelector('.astryx-collapsible-group');
|
|
556
|
+
expect(wrapper).toHaveClass('custom-class');
|
|
557
|
+
expect(wrapper).toHaveAttribute('data-testid', 'group');
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('forwards ref to the wrapper in divider mode', () => {
|
|
561
|
+
const ref = {current: null as HTMLElement | null};
|
|
562
|
+
const {container} = render(
|
|
563
|
+
<CollapsibleGroup dividers="between" ref={ref}>
|
|
564
|
+
<Collapsible trigger="Item A" value="a">
|
|
565
|
+
<p>Content A</p>
|
|
566
|
+
</Collapsible>
|
|
567
|
+
</CollapsibleGroup>,
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
expect(ref.current).toBe(
|
|
571
|
+
container.querySelector('.astryx-collapsible-group'),
|
|
572
|
+
);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('explicit density overrides the divider default', () => {
|
|
576
|
+
const {container} = render(
|
|
577
|
+
<CollapsibleGroup dividers="between" density="spacious">
|
|
578
|
+
<Collapsible trigger="Item A" value="a">
|
|
579
|
+
<p>Content A</p>
|
|
580
|
+
</Collapsible>
|
|
581
|
+
</CollapsibleGroup>,
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const wrapper = container.querySelector('.astryx-collapsible-group');
|
|
585
|
+
expect(wrapper).toHaveAttribute('data-density', 'spacious');
|
|
586
|
+
expect(getItem(/Item A/)).toHaveAttribute('data-density', 'spacious');
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('tolerates interleaved non-Collapsible children', async () => {
|
|
590
|
+
const user = userEvent.setup();
|
|
591
|
+
render(
|
|
592
|
+
<CollapsibleGroup type="single" dividers="between" defaultValue="a">
|
|
593
|
+
<Collapsible trigger="Item A" value="a">
|
|
594
|
+
<p>Content A</p>
|
|
595
|
+
</Collapsible>
|
|
596
|
+
<hr data-testid="separator" />
|
|
597
|
+
<Collapsible trigger="Item B" value="b">
|
|
598
|
+
<p>Content B</p>
|
|
599
|
+
</Collapsible>
|
|
600
|
+
</CollapsibleGroup>,
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
expect(screen.getByTestId('separator')).toBeInTheDocument();
|
|
604
|
+
expect(getItem(/Item A/)).toHaveAttribute('data-dividers', 'between');
|
|
605
|
+
expect(getItem(/Item B/)).toHaveAttribute('data-dividers', 'between');
|
|
606
|
+
await user.click(screen.getByRole('button', {name: /Item B/}));
|
|
607
|
+
expect(screen.getByText('Content A')).not.toBeVisible();
|
|
608
|
+
expect(screen.getByText('Content B')).toBeVisible();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('lets a nested group define its own chrome instead of inheriting', () => {
|
|
612
|
+
render(
|
|
613
|
+
<CollapsibleGroup dividers="between" defaultValue="outer">
|
|
614
|
+
<Collapsible trigger="Outer" value="outer">
|
|
615
|
+
<CollapsibleGroup type="multiple" dividers="all">
|
|
616
|
+
<Collapsible trigger="Inner divided" value="in-a">
|
|
617
|
+
<p>Inner content</p>
|
|
618
|
+
</Collapsible>
|
|
619
|
+
</CollapsibleGroup>
|
|
620
|
+
<CollapsibleGroup type="multiple">
|
|
621
|
+
<Collapsible trigger="Inner plain" value="in-b">
|
|
622
|
+
<p>Inner content</p>
|
|
623
|
+
</Collapsible>
|
|
624
|
+
</CollapsibleGroup>
|
|
625
|
+
</Collapsible>
|
|
626
|
+
</CollapsibleGroup>,
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
expect(getItem(/Outer/)).toHaveAttribute('data-dividers', 'between');
|
|
630
|
+
expect(getItem(/Inner divided/)).toHaveAttribute('data-dividers', 'all');
|
|
631
|
+
expect(getItem(/Inner plain/)).not.toHaveAttribute('data-dividers');
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it('keeps controlled coordination and onChange shape with dividers', async () => {
|
|
635
|
+
const user = userEvent.setup();
|
|
636
|
+
const onChange = vi.fn();
|
|
637
|
+
const {rerender} = render(
|
|
638
|
+
<CollapsibleGroup
|
|
639
|
+
type="single"
|
|
640
|
+
dividers="all"
|
|
641
|
+
value="a"
|
|
642
|
+
onChange={onChange}>
|
|
643
|
+
<Collapsible trigger="Item A" value="a">
|
|
644
|
+
<p>Content A</p>
|
|
645
|
+
</Collapsible>
|
|
646
|
+
<Collapsible trigger="Item B" value="b">
|
|
647
|
+
<p>Content B</p>
|
|
648
|
+
</Collapsible>
|
|
649
|
+
</CollapsibleGroup>,
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
await user.click(screen.getByRole('button', {name: /Item B/}));
|
|
653
|
+
expect(onChange).toHaveBeenCalledWith('b');
|
|
654
|
+
// Controlled: nothing opens until the parent passes the new value
|
|
655
|
+
expect(screen.getByText('Content B')).not.toBeVisible();
|
|
656
|
+
|
|
657
|
+
rerender(
|
|
658
|
+
<CollapsibleGroup
|
|
659
|
+
type="single"
|
|
660
|
+
dividers="all"
|
|
661
|
+
value="b"
|
|
662
|
+
onChange={onChange}>
|
|
663
|
+
<Collapsible trigger="Item A" value="a">
|
|
664
|
+
<p>Content A</p>
|
|
665
|
+
</Collapsible>
|
|
666
|
+
<Collapsible trigger="Item B" value="b">
|
|
667
|
+
<p>Content B</p>
|
|
668
|
+
</Collapsible>
|
|
669
|
+
</CollapsibleGroup>,
|
|
670
|
+
);
|
|
671
|
+
expect(screen.getByText('Content A')).not.toBeVisible();
|
|
672
|
+
expect(screen.getByText('Content B')).toBeVisible();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('renders standalone Collapsible without any group chrome', () => {
|
|
676
|
+
render(
|
|
677
|
+
<Collapsible trigger="Alone">
|
|
678
|
+
<p>Standalone content</p>
|
|
679
|
+
</Collapsible>,
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
const item = getItem(/Alone/);
|
|
683
|
+
expect(item).not.toHaveAttribute('data-dividers');
|
|
684
|
+
expect(item).not.toHaveAttribute('data-density');
|
|
685
|
+
expect(item.querySelector('.astryx-collapsible-group')).toBeNull();
|
|
686
|
+
});
|
|
687
|
+
});
|
|
400
688
|
});
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CollapsibleGroup.tsx
|
|
7
|
-
* @input Uses React useState, useCallback, CollapsibleGroupContext
|
|
7
|
+
* @input Uses React useState, useCallback, CollapsibleGroupContext, StyleX, theme tokens
|
|
8
8
|
* @output Exports CollapsibleGroup component and CollapsibleGroupProps
|
|
9
|
-
* @position Core collapsible group coordination provider — renders no wrapper
|
|
9
|
+
* @position Core collapsible group coordination provider — renders no wrapper
|
|
10
|
+
* DOM unless `dividers` is enabled
|
|
10
11
|
*
|
|
11
12
|
* CollapsibleGroup groups collapsible components (Card, etc.) with
|
|
12
|
-
* coordinated open/close behavior.
|
|
13
|
-
* DOM element.
|
|
13
|
+
* coordinated open/close behavior. By default it renders only `{children}` —
|
|
14
|
+
* no wrapper DOM element. When `dividers` is 'between' or 'all' it renders a
|
|
15
|
+
* wrapper div that anchors the divider chrome (outer borders, reliable
|
|
16
|
+
* :first-child suppression) and provides dividers/density to items via
|
|
17
|
+
* CollapsibleGroupPresentationContext — each Collapsible draws its own
|
|
18
|
+
* borders since StyleX has no child selectors.
|
|
14
19
|
*
|
|
15
20
|
* In "single" mode (default), only one item can be open at a time.
|
|
16
21
|
* In "multiple" mode, any number of items can be open simultaneously.
|
|
@@ -24,10 +29,35 @@
|
|
|
24
29
|
*/
|
|
25
30
|
|
|
26
31
|
import React, {useCallback, useMemo, useState, type ReactNode} from 'react';
|
|
27
|
-
import
|
|
28
|
-
import
|
|
32
|
+
import * as stylex from '@stylexjs/stylex';
|
|
33
|
+
import {borderVars, colorVars} from '../theme/tokens.stylex';
|
|
34
|
+
import {
|
|
35
|
+
CollapsibleGroupContext,
|
|
36
|
+
CollapsibleGroupPresentationContext,
|
|
37
|
+
} from './CollapsibleGroupContext';
|
|
38
|
+
import type {
|
|
39
|
+
CollapsibleGroupContextValue,
|
|
40
|
+
CollapsibleGroupDensity,
|
|
41
|
+
CollapsibleGroupDividers,
|
|
42
|
+
CollapsibleGroupPresentationValue,
|
|
43
|
+
} from './CollapsibleGroupContext';
|
|
44
|
+
import {mergeProps} from '../utils';
|
|
45
|
+
import {themeProps} from '../utils/themeProps';
|
|
29
46
|
import type {BaseProps} from '../BaseProps';
|
|
30
47
|
|
|
48
|
+
const styles = stylex.create({
|
|
49
|
+
// 'all' draws the group's outer top/bottom edges; between-item lines are
|
|
50
|
+
// drawn by each Collapsible (borderBlockStart, suppressed on :first-child)
|
|
51
|
+
wrapperAll: {
|
|
52
|
+
borderBlockStartWidth: borderVars['--border-width'],
|
|
53
|
+
borderBlockStartStyle: 'solid',
|
|
54
|
+
borderBlockStartColor: colorVars['--color-border'],
|
|
55
|
+
borderBlockEndWidth: borderVars['--border-width'],
|
|
56
|
+
borderBlockEndStyle: 'solid',
|
|
57
|
+
borderBlockEndColor: colorVars['--color-border'],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
31
61
|
export interface CollapsibleGroupProps extends Omit<
|
|
32
62
|
BaseProps<HTMLElement>,
|
|
33
63
|
'onChange'
|
|
@@ -56,6 +86,24 @@ export interface CollapsibleGroupProps extends Omit<
|
|
|
56
86
|
*/
|
|
57
87
|
onChange?: (value: string | string[]) => void;
|
|
58
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Divider style rendered around the group's items — the accordion row
|
|
91
|
+
* chrome. 'between' draws hairlines between adjacent items; 'all' adds the
|
|
92
|
+
* group's top and bottom edges. When enabled, the group renders a wrapper
|
|
93
|
+
* div (it otherwise renders no DOM) and items get 'balanced' density unless
|
|
94
|
+
* `density` says otherwise. Pair with bare Collapsible children; Card-wrapped
|
|
95
|
+
* items provide their own separation.
|
|
96
|
+
* @default "none"
|
|
97
|
+
*/
|
|
98
|
+
dividers?: CollapsibleGroupDividers;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Row density controlling trigger and content block padding on the group's
|
|
102
|
+
* items. Defaults to 'balanced' when dividers are shown; otherwise items
|
|
103
|
+
* keep their default unpadded look.
|
|
104
|
+
*/
|
|
105
|
+
density?: CollapsibleGroupDensity;
|
|
106
|
+
|
|
59
107
|
/**
|
|
60
108
|
* Children — any components that support isCollapsible + value.
|
|
61
109
|
*
|
|
@@ -92,16 +140,26 @@ function normalizeToArray(value: string | string[] | undefined): string[] {
|
|
|
92
140
|
|
|
93
141
|
/**
|
|
94
142
|
* Groups collapsible components with coordinated open/close behavior.
|
|
95
|
-
* Renders no wrapper DOM.
|
|
143
|
+
* Renders no wrapper DOM unless `dividers` is enabled.
|
|
96
144
|
*
|
|
97
145
|
* In "single" mode (default), opening one item closes the others.
|
|
98
146
|
* In "multiple" mode, items toggle independently.
|
|
99
147
|
*
|
|
100
148
|
* @compositionHint Wrap Collapsible instances to coordinate their open/close state.
|
|
101
|
-
* Each Collapsible needs a `value` prop to participate.
|
|
149
|
+
* Each Collapsible needs a `value` prop to participate. For FAQ-style lists,
|
|
150
|
+
* use `dividers` with bare Collapsible children instead of wrapping each item
|
|
151
|
+
* in Card.
|
|
102
152
|
*
|
|
103
153
|
* @example
|
|
104
154
|
* ```
|
|
155
|
+
* <CollapsibleGroup type="single" dividers="between" defaultValue="faq1">
|
|
156
|
+
* <Collapsible trigger="What is Astryx?" value="faq1">
|
|
157
|
+
* Astryx is a design system for building internal tools.
|
|
158
|
+
* </Collapsible>
|
|
159
|
+
* <Collapsible trigger="How do I start?" value="faq2">
|
|
160
|
+
* Install the package and import components.
|
|
161
|
+
* </Collapsible>
|
|
162
|
+
* </CollapsibleGroup>
|
|
105
163
|
* <CollapsibleGroup type="single" defaultValue="faq1">
|
|
106
164
|
* <VStack gap={2}>
|
|
107
165
|
* <Card>
|
|
@@ -123,7 +181,14 @@ export function CollapsibleGroup({
|
|
|
123
181
|
defaultValue,
|
|
124
182
|
value: controlledValue,
|
|
125
183
|
onChange,
|
|
184
|
+
dividers = 'none',
|
|
185
|
+
density,
|
|
126
186
|
children,
|
|
187
|
+
ref,
|
|
188
|
+
xstyle,
|
|
189
|
+
className,
|
|
190
|
+
style,
|
|
191
|
+
...props
|
|
127
192
|
}: CollapsibleGroupProps) {
|
|
128
193
|
const isControlled = controlledValue !== undefined;
|
|
129
194
|
const [internalValue, setInternalValue] = useState<string[]>(() =>
|
|
@@ -174,9 +239,42 @@ export function CollapsibleGroup({
|
|
|
174
239
|
[isOpen, toggle],
|
|
175
240
|
);
|
|
176
241
|
|
|
242
|
+
const hasDividers = dividers !== 'none';
|
|
243
|
+
const resolvedDensity = density ?? (hasDividers ? 'balanced' : null);
|
|
244
|
+
|
|
245
|
+
const presentationValue = useMemo<CollapsibleGroupPresentationValue>(
|
|
246
|
+
() => ({dividers, density: resolvedDensity}),
|
|
247
|
+
[dividers, resolvedDensity],
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// The wrapper anchors divider chrome: 'all' outer borders live here, and it
|
|
251
|
+
// makes the items' :first-child suppression independent of surrounding
|
|
252
|
+
// siblings. Without dividers the group stays DOM-less (documented contract),
|
|
253
|
+
// so ref/xstyle/className/style only take effect in wrapper mode.
|
|
254
|
+
const content = hasDividers ? (
|
|
255
|
+
<div
|
|
256
|
+
ref={ref as React.Ref<HTMLDivElement>}
|
|
257
|
+
{...mergeProps(
|
|
258
|
+
themeProps('collapsible-group', {
|
|
259
|
+
dividers,
|
|
260
|
+
density: resolvedDensity ?? undefined,
|
|
261
|
+
}),
|
|
262
|
+
stylex.props(dividers === 'all' && styles.wrapperAll, xstyle),
|
|
263
|
+
className,
|
|
264
|
+
style,
|
|
265
|
+
)}
|
|
266
|
+
{...props}>
|
|
267
|
+
{children}
|
|
268
|
+
</div>
|
|
269
|
+
) : (
|
|
270
|
+
children
|
|
271
|
+
);
|
|
272
|
+
|
|
177
273
|
return (
|
|
178
274
|
<CollapsibleGroupContext value={contextValue}>
|
|
179
|
-
{
|
|
275
|
+
<CollapsibleGroupPresentationContext value={presentationValue}>
|
|
276
|
+
{content}
|
|
277
|
+
</CollapsibleGroupPresentationContext>
|
|
180
278
|
</CollapsibleGroupContext>
|
|
181
279
|
);
|
|
182
280
|
}
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CollapsibleGroupContext.tsx
|
|
7
7
|
* @input Uses React createContext
|
|
8
|
-
* @output Exports CollapsibleGroupContext
|
|
9
|
-
*
|
|
8
|
+
* @output Exports CollapsibleGroupContext, CollapsibleGroupContextValue,
|
|
9
|
+
* CollapsibleGroupPresentationContext, CollapsibleGroupPresentationValue,
|
|
10
|
+
* CollapsibleGroupDividers, and CollapsibleGroupDensity types
|
|
11
|
+
* @position Context definitions for collapsible group coordination and presentation
|
|
10
12
|
*
|
|
11
13
|
* SYNC: When modified, update these files to stay in sync:
|
|
12
14
|
* - /packages/core/src/Collapsible/CollapsibleGroup.tsx (provider)
|
|
@@ -33,3 +35,41 @@ export interface CollapsibleGroupContextValue {
|
|
|
33
35
|
export const CollapsibleGroupContext =
|
|
34
36
|
createContext<CollapsibleGroupContextValue | null>(null);
|
|
35
37
|
CollapsibleGroupContext.displayName = 'CollapsibleGroupContext';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Divider style rendered around the items of a CollapsibleGroup.
|
|
41
|
+
* - 'between': hairlines between adjacent items only.
|
|
42
|
+
* - 'all': hairlines between items plus the group's top and bottom edges.
|
|
43
|
+
* - 'none': no dividers (default).
|
|
44
|
+
*/
|
|
45
|
+
export type CollapsibleGroupDividers = 'between' | 'all' | 'none';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Row density for the items of a CollapsibleGroup, controlling trigger and
|
|
49
|
+
* content block padding. Shares the repo-wide density vocabulary
|
|
50
|
+
* (Table, List, Item).
|
|
51
|
+
*/
|
|
52
|
+
export type CollapsibleGroupDensity = 'compact' | 'balanced' | 'spacious';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Presentation value provided by CollapsibleGroup so each Collapsible can
|
|
56
|
+
* draw its own group chrome (StyleX has no child selectors, so the group
|
|
57
|
+
* cannot style items from the outside).
|
|
58
|
+
*/
|
|
59
|
+
export interface CollapsibleGroupPresentationValue {
|
|
60
|
+
/** Resolved divider style for the group's items. */
|
|
61
|
+
dividers: CollapsibleGroupDividers;
|
|
62
|
+
/** Resolved row density, or null to keep the default (unpadded) look. */
|
|
63
|
+
density: CollapsibleGroupDensity | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Context for collapsible group presentation (dividers, density).
|
|
68
|
+
* Kept separate from CollapsibleGroupContext so the public useCollapsible
|
|
69
|
+
* state API is unaffected. Collapsible resets this context around its
|
|
70
|
+
* children so nested collapsibles never inherit row chrome.
|
|
71
|
+
*/
|
|
72
|
+
export const CollapsibleGroupPresentationContext =
|
|
73
|
+
createContext<CollapsibleGroupPresentationValue | null>(null);
|
|
74
|
+
CollapsibleGroupPresentationContext.displayName =
|
|
75
|
+
'CollapsibleGroupPresentationContext';
|
package/src/Collapsible/index.ts
CHANGED
|
@@ -16,6 +16,10 @@ export type {CollapsibleProps} from './Collapsible';
|
|
|
16
16
|
|
|
17
17
|
export {CollapsibleGroup} from './CollapsibleGroup';
|
|
18
18
|
export type {CollapsibleGroupProps} from './CollapsibleGroup';
|
|
19
|
+
export type {
|
|
20
|
+
CollapsibleGroupDividers,
|
|
21
|
+
CollapsibleGroupDensity,
|
|
22
|
+
} from './CollapsibleGroupContext';
|
|
19
23
|
|
|
20
24
|
export {useCollapsible} from './useCollapsible';
|
|
21
25
|
export type {
|
|
@@ -27,6 +27,16 @@ export const docs = {
|
|
|
27
27
|
description: 'StyleX styles for layout customization. Must be a stylex.create() value.',
|
|
28
28
|
},
|
|
29
29
|
],
|
|
30
|
+
playground: {
|
|
31
|
+
defaults: {
|
|
32
|
+
heading: 'Navigation',
|
|
33
|
+
children: [
|
|
34
|
+
{__element: 'CommandPaletteItem', props: {value: 'home', onSelect: undefined}, children: 'Go Home'},
|
|
35
|
+
{__element: 'CommandPaletteItem', props: {value: 'settings', onSelect: undefined}, children: 'Open Settings'},
|
|
36
|
+
{__element: 'CommandPaletteItem', props: {value: 'profile', onSelect: undefined}, children: 'View Profile'},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
30
40
|
};
|
|
31
41
|
|
|
32
42
|
export const docsZh = {
|
|
@@ -57,6 +57,12 @@ export const docs = {
|
|
|
57
57
|
description: 'StyleX styles for layout customization. Must be a stylex.create() value.',
|
|
58
58
|
},
|
|
59
59
|
],
|
|
60
|
+
playground: {
|
|
61
|
+
defaults: {
|
|
62
|
+
placeholder: 'Search commands, files, or actions...',
|
|
63
|
+
hasAutoFocus: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
export const docsZh = {
|
|
@@ -50,6 +50,12 @@ export const docs = {
|
|
|
50
50
|
description: 'StyleX styles for layout customization. Must be a stylex.create() value.',
|
|
51
51
|
},
|
|
52
52
|
],
|
|
53
|
+
playground: {
|
|
54
|
+
defaults: {
|
|
55
|
+
value: 'go-home',
|
|
56
|
+
children: 'Go to Dashboard',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
export const docsZh = {
|
|
@@ -27,6 +27,15 @@ export const docs = {
|
|
|
27
27
|
description: 'StyleX styles for layout customization. Must be a stylex.create() value.',
|
|
28
28
|
},
|
|
29
29
|
],
|
|
30
|
+
playground: {
|
|
31
|
+
defaults: {
|
|
32
|
+
children: [
|
|
33
|
+
{__element: 'CommandPaletteItem', props: {value: 'home', onSelect: undefined}, children: 'Go Home'},
|
|
34
|
+
{__element: 'CommandPaletteItem', props: {value: 'settings', onSelect: undefined}, children: 'Open Settings'},
|
|
35
|
+
{__element: 'CommandPaletteItem', props: {value: 'profile', onSelect: undefined}, children: 'View Profile'},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
30
39
|
};
|
|
31
40
|
|
|
32
41
|
export const docsZh = {
|
|
@@ -78,6 +78,21 @@ export const docs = {
|
|
|
78
78
|
{ guidance: false, description: 'Place more than 10–12 items in a single menu without grouping them into sections.' },
|
|
79
79
|
],
|
|
80
80
|
},
|
|
81
|
+
playground: {
|
|
82
|
+
defaults: {
|
|
83
|
+
children: {
|
|
84
|
+
__element: 'Card',
|
|
85
|
+
props: {padding: 6},
|
|
86
|
+
children: {__element: 'Text', props: {color: 'secondary'}, children: 'Right-click this area'},
|
|
87
|
+
},
|
|
88
|
+
items: [
|
|
89
|
+
{label: 'Edit'},
|
|
90
|
+
{label: 'Duplicate'},
|
|
91
|
+
{type: 'divider'},
|
|
92
|
+
{label: 'Delete'},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
81
96
|
};
|
|
82
97
|
|
|
83
98
|
/** @type {import('../docs-types').TranslationDoc} */
|
|
@@ -172,6 +172,21 @@ describe('ContextMenu', () => {
|
|
|
172
172
|
expect(preventDefault).toHaveBeenCalled();
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
it('prevents default context menu on the opened menu container', () => {
|
|
176
|
+
render(
|
|
177
|
+
<ContextMenu items={[{label: 'Item 1'}]}>
|
|
178
|
+
<div>Right-click me</div>
|
|
179
|
+
</ContextMenu>,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
fireEvent.contextMenu(screen.getByText('Right-click me'));
|
|
183
|
+
const menu = screen.getByRole('menu', {hidden: true});
|
|
184
|
+
const event = new MouseEvent('contextmenu', {bubbles: true});
|
|
185
|
+
const preventDefault = vi.spyOn(event, 'preventDefault');
|
|
186
|
+
menu.dispatchEvent(event);
|
|
187
|
+
expect(preventDefault).toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
|
|
175
190
|
it('does not open when isDisabled is true', () => {
|
|
176
191
|
render(
|
|
177
192
|
<ContextMenu items={[{label: 'Item 1'}]} isDisabled>
|
|
@@ -87,6 +87,7 @@ const styles = stylex.create({
|
|
|
87
87
|
transitionProperty: 'opacity',
|
|
88
88
|
transitionDuration: durationVars['--duration-fast'],
|
|
89
89
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
90
|
+
userSelect: 'none',
|
|
90
91
|
},
|
|
91
92
|
popover: {
|
|
92
93
|
minWidth: '160px',
|
|
@@ -409,6 +410,7 @@ export function ContextMenu({
|
|
|
409
410
|
role="menu"
|
|
410
411
|
aria-label={label}
|
|
411
412
|
onKeyDown={listKeyDown}
|
|
413
|
+
onContextMenu={e => e.preventDefault()}
|
|
412
414
|
{...mergeProps(
|
|
413
415
|
themeProps('context-menu'),
|
|
414
416
|
stylex.props(styles.menu, xstyle),
|