diff --git a/.all-contributorsrc b/.all-contributorsrc index 13b30ca113d6..7a699c1bc6b1 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1398,6 +1398,7 @@ "code" ] }, + { "login": "mattborghi", "name": "Matias Borghi", "avatar_url": "https://avatars.githubusercontent.com/u/11933424?v=4", @@ -1451,6 +1452,15 @@ "contributions": [ "code" ] + }, + { + "login": "cuppajoey", + "name": "Joseph Schultz", + "avatar_url": "https://avatars.githubusercontent.com/u/14837881?v=4", + "profile": "https://cuppajoey.com/", + "contributions": [ + "code" + ] } ], "commitConvention": "none" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76c9d9ba7804..03312410d8db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 with: node-version: '20.x' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 if: github.event_name != 'merge_group' id: cache with: @@ -88,7 +88,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 with: node-version: '20.x' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 if: github.event_name != 'merge_group' id: cache with: @@ -128,7 +128,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 with: node-version: '20.x' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 if: github.event_name != 'merge_group' id: cache with: @@ -191,7 +191,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 with: node-version: '20.x' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 if: github.event_name != 'merge_group' id: cache with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d83cc5dc6a2d..1cbc896b47f7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,9 +24,9 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@3ab4101902695724f9365a384f86c1074d94e18c #v3.24.7 + uses: github/codeql-action/init@05963f47d870e2cb19a537396c1f668a348c7d8f #v3.24.8 with: languages: javascript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3ab4101902695724f9365a384f86c1074d94e18c #v3.24.7 + uses: github/codeql-action/analyze@05963f47d870e2cb19a537396c1f668a348c7d8f #v3.24.8 diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml index d448d2f608d2..63e6d5e0493b 100644 --- a/.github/workflows/dco.yml +++ b/.github/workflows/dco.yml @@ -16,7 +16,7 @@ jobs: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the DCO document and I hereby sign the DCO.') || github.event_name == 'pull_request_target' - uses: cla-assistant/github-action@a895a435fcce79ecf28fbce61a4ef0f0dabc9853 #v2.3.1 + uses: cla-assistant/github-action@dbc1c64d82d3aad5072007a41fff2828ae6d23ec #v2.3.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} diff --git a/.github/workflows/v10-ci.yml b/.github/workflows/v10-ci.yml index 0648362aac9c..0cb4a504693b 100644 --- a/.github/workflows/v10-ci.yml +++ b/.github/workflows/v10-ci.yml @@ -64,7 +64,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 with: node-version: '20.x' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 id: cache with: path: | @@ -92,7 +92,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 #v4.0.2 with: node-version: '20.x' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 id: cache with: path: | diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 876175922d70..69a9b8806c25 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -36,7 +36,7 @@ jobs: with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2 id: cache with: path: | diff --git a/.yarn/cache/@adobe-css-tools-npm-4.2.0-26da6de88a-d6d2b48963.zip b/.yarn/cache/@adobe-css-tools-npm-4.2.0-26da6de88a-d6d2b48963.zip new file mode 100644 index 000000000000..f6b77ad30598 Binary files /dev/null and b/.yarn/cache/@adobe-css-tools-npm-4.2.0-26da6de88a-d6d2b48963.zip differ diff --git a/.yarn/cache/@adobe-css-tools-npm-4.3.2-779f6c743d-973dcb7ba5.zip b/.yarn/cache/@adobe-css-tools-npm-4.3.2-779f6c743d-973dcb7ba5.zip deleted file mode 100644 index e8f3c25c1c1d..000000000000 Binary files a/.yarn/cache/@adobe-css-tools-npm-4.3.2-779f6c743d-973dcb7ba5.zip and /dev/null differ diff --git a/.yarn/cache/@babel-generator-npm-7.22.9-d9fccf9328-1ee43f9951.zip b/.yarn/cache/@babel-generator-npm-7.22.9-d9fccf9328-1ee43f9951.zip new file mode 100644 index 000000000000..a83cc3329ed5 Binary files /dev/null and b/.yarn/cache/@babel-generator-npm-7.22.9-d9fccf9328-1ee43f9951.zip differ diff --git a/.yarn/cache/@babel-generator-npm-7.23.0-08841c5369-bd1598bd35.zip b/.yarn/cache/@babel-generator-npm-7.23.0-08841c5369-bd1598bd35.zip deleted file mode 100644 index 805b8094df58..000000000000 Binary files a/.yarn/cache/@babel-generator-npm-7.23.0-08841c5369-bd1598bd35.zip and /dev/null differ diff --git a/.yarn/cache/@babel-helper-function-name-npm-7.22.5-8a1a69b63d-6d02e304a4.zip b/.yarn/cache/@babel-helper-function-name-npm-7.22.5-8a1a69b63d-6d02e304a4.zip new file mode 100644 index 000000000000..10d57c0f7caa Binary files /dev/null and b/.yarn/cache/@babel-helper-function-name-npm-7.22.5-8a1a69b63d-6d02e304a4.zip differ diff --git a/.yarn/cache/@babel-helper-function-name-npm-7.23.0-ce38271242-7b2ae024cd.zip b/.yarn/cache/@babel-helper-function-name-npm-7.23.0-ce38271242-7b2ae024cd.zip deleted file mode 100644 index b6a4704442ed..000000000000 Binary files a/.yarn/cache/@babel-helper-function-name-npm-7.23.0-ce38271242-7b2ae024cd.zip and /dev/null differ diff --git a/.yarn/cache/@babel-parser-npm-7.22.7-7fbdf28552-f420f89ea8.zip b/.yarn/cache/@babel-parser-npm-7.22.7-7fbdf28552-f420f89ea8.zip new file mode 100644 index 000000000000..2836e57d67e5 Binary files /dev/null and b/.yarn/cache/@babel-parser-npm-7.22.7-7fbdf28552-f420f89ea8.zip differ diff --git a/.yarn/cache/@babel-parser-npm-7.23.0-8a7b151672-201641e068.zip b/.yarn/cache/@babel-parser-npm-7.23.0-8a7b151672-201641e068.zip deleted file mode 100644 index 3c97c6dce7a3..000000000000 Binary files a/.yarn/cache/@babel-parser-npm-7.23.0-8a7b151672-201641e068.zip and /dev/null differ diff --git a/.yarn/cache/@babel-template-npm-7.22.15-0b464facb4-21e768e4ee.zip b/.yarn/cache/@babel-template-npm-7.22.15-0b464facb4-21e768e4ee.zip deleted file mode 100644 index 68979e397b58..000000000000 Binary files a/.yarn/cache/@babel-template-npm-7.22.15-0b464facb4-21e768e4ee.zip and /dev/null differ diff --git a/.yarn/cache/@babel-template-npm-7.22.5-358c44dc9d-460634b1c5.zip b/.yarn/cache/@babel-template-npm-7.22.5-358c44dc9d-460634b1c5.zip new file mode 100644 index 000000000000..e634e6c465d7 Binary files /dev/null and b/.yarn/cache/@babel-template-npm-7.22.5-358c44dc9d-460634b1c5.zip differ diff --git a/.yarn/cache/@floating-ui-core-npm-1.4.2-81f30e7a48-b0673b9fe5.zip b/.yarn/cache/@floating-ui-core-npm-1.4.2-81f30e7a48-b0673b9fe5.zip new file mode 100644 index 000000000000..f80e7a6a5379 Binary files /dev/null and b/.yarn/cache/@floating-ui-core-npm-1.4.2-81f30e7a48-b0673b9fe5.zip differ diff --git a/.yarn/cache/@floating-ui-core-npm-1.5.0-3cdde0a88b-3182715a30.zip b/.yarn/cache/@floating-ui-core-npm-1.5.0-3cdde0a88b-3182715a30.zip deleted file mode 100644 index dc92eb9b7b0d..000000000000 Binary files a/.yarn/cache/@floating-ui-core-npm-1.5.0-3cdde0a88b-3182715a30.zip and /dev/null differ diff --git a/.yarn/cache/@floating-ui-react-npm-0.25.4-335f23f037-9790e2ee5c.zip b/.yarn/cache/@floating-ui-react-npm-0.25.4-335f23f037-9790e2ee5c.zip new file mode 100644 index 000000000000..636a97427367 Binary files /dev/null and b/.yarn/cache/@floating-ui-react-npm-0.25.4-335f23f037-9790e2ee5c.zip differ diff --git a/README.md b/README.md index dc92d12fd721..a510686f560e 100644 --- a/README.md +++ b/README.md @@ -271,14 +271,16 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
SamChinellato

πŸ’»
stevenpatrick009

πŸ’»
HunterXalc

πŸ’» +
Onur Γ–zkardeş

πŸ’»
Matias Borghi

πŸ’»
Alexandr Ovchinnikov

πŸ’» -
J Thomas

πŸ’» +
J Thomas

πŸ’»
Garrett Dawson

πŸ’» πŸ“–
Daniel Adebonojo

πŸ“–
Anjana M R

πŸ’» +
Joseph Schultz

πŸ’» diff --git a/e2e/components/CodeSnippet/CodeSnippet-test.avt.e2e.js b/e2e/components/CodeSnippet/CodeSnippet-test.avt.e2e.js index b3cf8cefffc0..1957ffa6b882 100644 --- a/e2e/components/CodeSnippet/CodeSnippet-test.avt.e2e.js +++ b/e2e/components/CodeSnippet/CodeSnippet-test.avt.e2e.js @@ -127,7 +127,7 @@ test.describe('@avt CodeSnippet', () => { await page.keyboard.press('Tab'); await page.keyboard.press('Enter'); await expect(page.getByText('Show less')).toBeVisible(); - await expect(page.getByText('Show more')).not.toBeVisible(); + await expect(page.getByText('Show more')).toBeHidden(); }); test('@avt-keyboard-nav singleline', async ({ page }) => { diff --git a/e2e/components/ComboBox/ComboBox-test.avt.e2e.js b/e2e/components/ComboBox/ComboBox-test.avt.e2e.js index dcb32c07d176..ae3fba30cc19 100644 --- a/e2e/components/ComboBox/ComboBox-test.avt.e2e.js +++ b/e2e/components/ComboBox/ComboBox-test.avt.e2e.js @@ -38,7 +38,7 @@ test.describe('@avt ComboBox', () => { await page.keyboard.press('Tab'); await expect(combobox).toBeFocused(); await page.keyboard.press('Enter'); - await expect(page.getByRole('combobox', { expanded: true })).toBeVisible; + await expect(page.getByRole('combobox', { expanded: true })).toBeVisible(); await expect(combobox).toBeFocused(); await expect(page).toHaveNoACViolations('ComboBox-open'); @@ -65,7 +65,7 @@ test.describe('@avt ComboBox', () => { }); await expect(combobox).toBeVisible(); - await expect(clearButton).not.toBeVisible(); + await expect(clearButton).toBeHidden(); // Tab and open the ComboBox with Arrow Down await page.keyboard.press('Tab'); await expect(combobox).toBeFocused(); @@ -73,14 +73,14 @@ test.describe('@avt ComboBox', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Spacebar await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(combobox).toBeFocused(); await page.keyboard.press('Space'); await expect(menu).toBeVisible(); // Close and clear with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(combobox).toBeFocused(); await page.keyboard.press('Enter'); await expect(menu).toBeVisible(); @@ -99,11 +99,11 @@ test.describe('@avt ComboBox', () => { ); // focus comes back to the toggle button after selecting await expect(combobox).toBeFocused(); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(clearButton).toBeVisible(); // should only clear selection when escape is pressed when the menu is closed await page.keyboard.press('Escape'); - await expect(clearButton).not.toBeVisible(); + await expect(clearButton).toBeHidden(); await expect(combobox).toHaveValue(''); // should highlight menu items based on text input await page.keyboard.press('2'); diff --git a/e2e/components/ComboButton/ComboButton-test.avt.e2e.js b/e2e/components/ComboButton/ComboButton-test.avt.e2e.js index d5a2a74eca87..0e61a8dc4bbb 100644 --- a/e2e/components/ComboButton/ComboButton-test.avt.e2e.js +++ b/e2e/components/ComboButton/ComboButton-test.avt.e2e.js @@ -58,7 +58,7 @@ test.describe('@avt ComboButton', () => { await page.keyboard.press('ArrowDown'); await expect(page.getByRole('menuitem').nth(1)).toBeFocused(); await page.keyboard.press('Escape'); - await expect(page.getByRole('menuitem').first()).not.toBeVisible(); + await expect(page.getByRole('menuitem').first()).toBeHidden(); }); test('@avt-keyboard-nav ComboButton with danger', async ({ page }) => { @@ -93,6 +93,6 @@ test.describe('@avt ComboButton', () => { ); // Selecting the item to close the menu await page.keyboard.press('Enter'); - await expect(page.getByRole('menuitem').first()).not.toBeVisible(); + await expect(page.getByRole('menuitem').first()).toBeHidden(); }); }); diff --git a/e2e/components/ComposedModal/ComposedModal-test.avt.e2e.js b/e2e/components/ComposedModal/ComposedModal-test.avt.e2e.js index d43a66b6a0c7..6ee5a80af78c 100644 --- a/e2e/components/ComposedModal/ComposedModal-test.avt.e2e.js +++ b/e2e/components/ComposedModal/ComposedModal-test.avt.e2e.js @@ -79,7 +79,7 @@ test.describe('@avt ComposedModal', () => { await expect(page.getByRole('button', { name: 'Close' })).toBeFocused(); await page.keyboard.press('Enter'); // Make sure modal was closed - await expect(page.getByText('Account resource')).not.toBeVisible(); + await expect(page.getByText('Account resource')).toBeHidden(); }); test('@avt-keyboard-nav Full width', async ({ page }) => { @@ -102,7 +102,7 @@ test.describe('@avt ComposedModal', () => { await expect(page.getByRole('button', { name: 'Cancel' })).toBeFocused(); await page.keyboard.press('Enter'); // Make sure modal was closed - await expect(page.getByText('Full Width Modal')).not.toBeVisible(); + await expect(page.getByText('Full Width Modal')).toBeHidden(); }); test('@avt-keyboard-nav Passive modal', async ({ page }) => { @@ -122,7 +122,7 @@ test.describe('@avt ComposedModal', () => { // Make sure modal was closed await expect( page.getByText('You have been successfully signed out') - ).not.toBeVisible(); + ).toBeHidden(); }); test('@avt-keyboard-nav With state manager', async ({ page }) => { @@ -157,7 +157,7 @@ test.describe('@avt ComposedModal', () => { await expect(page.getByRole('button', { name: 'Close' })).toBeFocused(); await page.keyboard.press('Enter'); // Make sure modal was closed and button gets focused - await expect(page.getByText('Account resource')).not.toBeVisible(); + await expect(page.getByText('Account resource')).toBeHidden(); await expect(page.getByRole('button')).toBeFocused(); await expect( page.getByRole('button', { name: 'Launch composed modal' }) diff --git a/e2e/components/ContainedList/ContainedList-test.avt.e2e.js b/e2e/components/ContainedList/ContainedList-test.avt.e2e.js index 8883b96285c0..a421d1e53f79 100644 --- a/e2e/components/ContainedList/ContainedList-test.avt.e2e.js +++ b/e2e/components/ContainedList/ContainedList-test.avt.e2e.js @@ -172,7 +172,7 @@ test.describe('@avt ContainedList', () => { await page.keyboard.press('ArrowDown'); await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); - await expect(page.getByText('Remove')).not.toBeVisible(); + await expect(page.getByText('Remove')).toBeHidden(); }); test('@avt-keyboard-nav With actions', async ({ page }) => { @@ -192,7 +192,7 @@ test.describe('@avt ContainedList', () => { ).toBeFocused(); await expect(page.getByText('Dismiss').first()).toBeVisible(); await page.keyboard.press('Enter'); - await expect(page.getByText('Dismiss').first()).not.toBeVisible(); + await expect(page.getByText('Dismiss').first()).toBeHidden(); //navigating to the last element await page.keyboard.press('Tab'); @@ -220,7 +220,7 @@ test.describe('@avt ContainedList', () => { await expect(page.getByRole('search')).toHaveClass(/cds--search--expanded/); page.getByRole('searchbox').fill('List item 3'); await expect(page.getByText('List item 3')).toBeVisible(); - await expect(page.getByText('List item 1')).not.toBeVisible(); + await expect(page.getByText('List item 1')).toBeHidden(); // Close search await page.keyboard.press('Escape'); @@ -299,7 +299,7 @@ test.describe('@avt ContainedList', () => { await expect(page.getByRole('searchbox')).toBeFocused(); page.getByRole('searchbox').fill('List item 3'); await expect(page.getByText('List item 3')).toBeVisible(); - await expect(page.getByText('List item 1')).not.toBeVisible(); + await expect(page.getByText('List item 1')).toBeHidden(); // Close search await page.keyboard.press('Escape'); diff --git a/e2e/components/DataTable/DataTable-test.avt.e2e.js b/e2e/components/DataTable/DataTable-test.avt.e2e.js index ee60607ca366..5aee29d2dab7 100644 --- a/e2e/components/DataTable/DataTable-test.avt.e2e.js +++ b/e2e/components/DataTable/DataTable-test.avt.e2e.js @@ -167,7 +167,7 @@ test.describe('@avt DataTable', () => { await expect(page.getByRole('button', { name: 'Cancel' })).toBeFocused(); // Invoke the cancel button await page.keyboard.press('Space'); - await expect(page.getByText('1 item selected')).not.toBeVisible(); + await expect(page.getByText('1 item selected')).toBeHidden(); // Every checkbox should no longer be checked for (const checkbox of await page.getByRole('checkbox').all()) { await expect(checkbox).not.toBeChecked(); diff --git a/e2e/components/Dropdown/Dropdown-test.avt.e2e.js b/e2e/components/Dropdown/Dropdown-test.avt.e2e.js index 914608b6a757..70c0a24231a8 100644 --- a/e2e/components/Dropdown/Dropdown-test.avt.e2e.js +++ b/e2e/components/Dropdown/Dropdown-test.avt.e2e.js @@ -39,7 +39,7 @@ test.describe('@avt Dropdown', () => { const menu = page.getByRole('listbox'); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); - await expect(page.getByRole('combobox', { expanded: true })).toBeVisible; + await expect(page.getByRole('combobox', { expanded: true })).toBeVisible(); await expect(menu).toBeFocused(); await expect(page).toHaveNoACViolations('Dropdown-open'); @@ -68,7 +68,7 @@ test.describe('@avt Dropdown', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); // Should focus on selected item by default diff --git a/e2e/components/ErrorBoundary/ErrorBoundary-test.avt.e2e.js b/e2e/components/ErrorBoundary/ErrorBoundary-test.avt.e2e.js index b701eb9eb8c4..cba42e7b13ed 100644 --- a/e2e/components/ErrorBoundary/ErrorBoundary-test.avt.e2e.js +++ b/e2e/components/ErrorBoundary/ErrorBoundary-test.avt.e2e.js @@ -35,7 +35,7 @@ test.describe('@avt ErrorBoundary', () => { ); }); - test('@avt-keyboard-state', async ({ page }) => { + test('@avt-keyboard-nav', async ({ page }) => { await visitStory(page, { component: 'ErrorBoundary', id: 'components-errorboundary--default', diff --git a/e2e/components/FileUploader/FileUploader-test.avt.e2e.js b/e2e/components/FileUploader/FileUploader-test.avt.e2e.js index 1a6e4fa2f6c2..5f2a02ad9b0a 100644 --- a/e2e/components/FileUploader/FileUploader-test.avt.e2e.js +++ b/e2e/components/FileUploader/FileUploader-test.avt.e2e.js @@ -120,7 +120,7 @@ test.describe('@avt FileUploader', () => { // Delete the file await page.keyboard.press('Tab'); await page.keyboard.press('Enter'); - await expect(page.getByText('test-file-for-uploading')).not.toBeVisible(); + await expect(page.getByText('test-file-for-uploading')).toBeHidden(); }); test.slow( @@ -164,7 +164,7 @@ test.describe('@avt FileUploader', () => { await page.keyboard.press('Enter'); await expect( page.locator('#test-upload-file-long-text-for-tooltip-to-show-up.jpg') - ).not.toBeVisible(); + ).toBeHidden(); } ); @@ -207,6 +207,6 @@ test.describe('@avt FileUploader', () => { await page.keyboard.press('Enter'); await expect( page.locator('#test-upload-file-long-text-for-tooltip-to-show-up.jpg') - ).not.toBeVisible(); + ).toBeHidden(); }); }); diff --git a/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js b/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js index 25ed8f0703b4..8612988a4af6 100644 --- a/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js +++ b/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js @@ -38,7 +38,7 @@ test.describe('@avt FluidComboBox', () => { await page.keyboard.press('Tab'); await expect(combobox).toBeFocused(); await page.keyboard.press('Enter'); - await expect(page.getByRole('combobox', { expanded: true })).toBeVisible; + await expect(page.getByRole('combobox', { expanded: true })).toBeVisible(); await expect(combobox).toBeFocused(); await expect(page).toHaveNoACViolations('FluidComboBox-open'); @@ -65,7 +65,7 @@ test.describe('@avt FluidComboBox', () => { }); await expect(combobox).toBeVisible(); - await expect(clearButton).not.toBeVisible(); + await expect(clearButton).toBeHidden(); // Tab and open the ComboBox with Arrow Down await page.keyboard.press('Tab'); await expect(combobox).toBeFocused(); @@ -73,14 +73,14 @@ test.describe('@avt FluidComboBox', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Spacebar await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(combobox).toBeFocused(); await page.keyboard.press('Space'); await expect(menu).toBeVisible(); // Close and clear with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(combobox).toBeFocused(); await page.keyboard.press('Enter'); await expect(menu).toBeVisible(); @@ -99,11 +99,11 @@ test.describe('@avt FluidComboBox', () => { ); // focus comes back to the toggle button after selecting await expect(combobox).toBeFocused(); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(clearButton).toBeVisible(); // should only clear selection when escape is pressed when the menu is closed await page.keyboard.press('Escape'); - await expect(clearButton).not.toBeVisible(); + await expect(clearButton).toBeHidden(); await expect(combobox).toHaveValue(''); // should highlight menu items based on text input await page.keyboard.press('2'); diff --git a/e2e/components/FluidDropdown/FluidDropdown-test.avt.e2e.js b/e2e/components/FluidDropdown/FluidDropdown-test.avt.e2e.js index c360013e90f8..7d39d5159e03 100644 --- a/e2e/components/FluidDropdown/FluidDropdown-test.avt.e2e.js +++ b/e2e/components/FluidDropdown/FluidDropdown-test.avt.e2e.js @@ -67,7 +67,7 @@ test.describe('@avt FluidDropdown', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); // Should focus on selected item by default diff --git a/e2e/components/FluidMultiSelect/FluidMultiSelect-test.avt.e2e.js b/e2e/components/FluidMultiSelect/FluidMultiSelect-test.avt.e2e.js index 5327284e23af..fea74909bcaa 100644 --- a/e2e/components/FluidMultiSelect/FluidMultiSelect-test.avt.e2e.js +++ b/e2e/components/FluidMultiSelect/FluidMultiSelect-test.avt.e2e.js @@ -63,7 +63,7 @@ test.describe('@avt FluidMultiSelect', () => { const menu = page.getByRole('listbox'); await expect(toggleButton).toBeVisible(); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); // Tab and open the MultiSelect with Arrow Down await page.keyboard.press('Tab'); await expect(toggleButton).toBeFocused(); @@ -71,13 +71,13 @@ test.describe('@avt FluidMultiSelect', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Spacebar await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Space'); await expect(menu).toBeVisible(); @@ -134,11 +134,11 @@ test.describe('@avt FluidMultiSelect', () => { await page.keyboard.press('Escape'); await expect(toggleButton).toBeFocused(); // should show count of selected items when closed - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(selection).toBeVisible(); // should only clear selection when escape is pressed when the menu is closed await page.keyboard.press('Escape'); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); }); test('@avt-keyboard-nav FluidMultiSelect condensed', async ({ page }) => { @@ -158,7 +158,7 @@ test.describe('@avt FluidMultiSelect', () => { const menu = page.getByRole('listbox'); await expect(toggleButton).toBeVisible(); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); // Tab and open the MultiSelect with Arrow Down await page.keyboard.press('Tab'); await expect(toggleButton).toBeFocused(); @@ -166,13 +166,13 @@ test.describe('@avt FluidMultiSelect', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Spacebar await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Space'); await expect(menu).toBeVisible(); @@ -229,10 +229,10 @@ test.describe('@avt FluidMultiSelect', () => { await page.keyboard.press('Escape'); await expect(toggleButton).toBeFocused(); // should show count of selected items when closed - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(selection).toBeVisible(); // should only clear selection when escape is pressed when the menu is closed await page.keyboard.press('Escape'); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); }); }); diff --git a/e2e/components/FluidNumberInput/FluidNumberInput-test.avt.e2e.js b/e2e/components/FluidNumberInput/FluidNumberInput-test.avt.e2e.js index bee5798a0ee5..0c084d4e7f52 100644 --- a/e2e/components/FluidNumberInput/FluidNumberInput-test.avt.e2e.js +++ b/e2e/components/FluidNumberInput/FluidNumberInput-test.avt.e2e.js @@ -10,7 +10,7 @@ const { expect, test } = require('@playwright/test'); const { visitStory } = require('../../test-utils/storybook'); -test.describe('F@avt luidNumberInput', () => { +test.describe('@avt FluidNumberInput', () => { test('@avt-default-state', async ({ page }) => { await visitStory(page, { component: 'FluidNumberInput', diff --git a/e2e/components/FluidSearch/FluidSearch-test.avt.e2e.js b/e2e/components/FluidSearch/FluidSearch-test.avt.e2e.js index 93859a2884e0..cf0e0103bf1b 100644 --- a/e2e/components/FluidSearch/FluidSearch-test.avt.e2e.js +++ b/e2e/components/FluidSearch/FluidSearch-test.avt.e2e.js @@ -46,7 +46,7 @@ test.describe('@avt FluidSearch', () => { name: 'Clear search input', }); await expect(search).toBeVisible(); - await expect(clearButton).not.toBeVisible(); + await expect(clearButton).toBeHidden(); // Tab to the Search await page.keyboard.press('Tab'); diff --git a/e2e/components/FormLabel/FormLabel-test.avt.e2e.js b/e2e/components/FormLabel/FormLabel-test.avt.e2e.js index a1a1504190bd..458009fc1378 100644 --- a/e2e/components/FormLabel/FormLabel-test.avt.e2e.js +++ b/e2e/components/FormLabel/FormLabel-test.avt.e2e.js @@ -67,6 +67,6 @@ test.describe('@avt FormLabel', () => { 'This can be used to provide more information about a field.' ) .last() - ).not.toBeVisible(); + ).toBeHidden(); }); }); diff --git a/e2e/components/Menu/Menu-test.avt.e2e.js b/e2e/components/Menu/Menu-test.avt.e2e.js index 45b758bb562c..8facaf04c427 100644 --- a/e2e/components/Menu/Menu-test.avt.e2e.js +++ b/e2e/components/Menu/Menu-test.avt.e2e.js @@ -42,7 +42,7 @@ test.describe('@avt Menu', () => { await expect(firstItem).toBeVisible(); await expect(LastItem).toBeVisible(); - await expect(nestedMenu).not.toBeVisible(); + await expect(nestedMenu).toBeHidden(); await expect(firstItem).toBeFocused(); // Should go to last item when focused on the first item and arrow up is pressed @@ -64,6 +64,6 @@ test.describe('@avt Menu', () => { // Should close menu with ArrowLeft await page.keyboard.press('ArrowLeft'); - await expect(nestedMenu).not.toBeVisible(); + await expect(nestedMenu).toBeHidden(); }); }); diff --git a/e2e/components/Modal/Modal-test.avt.e2e.js b/e2e/components/Modal/Modal-test.avt.e2e.js index d6d129dbf325..2842ab3e3d89 100644 --- a/e2e/components/Modal/Modal-test.avt.e2e.js +++ b/e2e/components/Modal/Modal-test.avt.e2e.js @@ -66,7 +66,7 @@ test.describe('@avt Modal', () => { await page.keyboard.press('Enter'); // The modal should no longer be open/visisble - await expect(page.getByRole('dialog')).not.toBeVisible(); + await expect(page.getByRole('dialog')).toBeHidden(); // Focus moves to the button that opened the Modal await expect(button).toBeFocused(); }); diff --git a/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js b/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js index 8f8047ef3ae0..76e12a8f58c0 100644 --- a/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js +++ b/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js @@ -51,7 +51,7 @@ test.describe('@avt MultiSelect', () => { await page.keyboard.press('Tab'); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); - await expect(page.getByRole('combobox', { expanded: true })).toBeVisible; + await expect(page.getByRole('combobox', { expanded: true })).toBeVisible(); await expect(page).toHaveNoACViolations('MultiSelect-open'); }); @@ -72,7 +72,7 @@ test.describe('@avt MultiSelect', () => { await page.keyboard.press('Tab'); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); - await expect(page.getByRole('combobox', { expanded: true })).toBeVisible; + await expect(page.getByRole('combobox', { expanded: true })).toBeVisible(); await expect(page).toHaveNoACViolations('MultiSelect-open'); }); @@ -94,7 +94,7 @@ test.describe('@avt MultiSelect', () => { const menu = page.getByRole('listbox'); await expect(toggleButton).toBeVisible(); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); // Tab and open the MultiSelect with Arrow Down await page.keyboard.press('Tab'); await expect(toggleButton).toBeFocused(); @@ -102,13 +102,13 @@ test.describe('@avt MultiSelect', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Spacebar await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Space'); await expect(menu).toBeVisible(); @@ -165,11 +165,11 @@ test.describe('@avt MultiSelect', () => { await page.keyboard.press('Escape'); await expect(toggleButton).toBeFocused(); // should show count of selected items when closed - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(selection).toBeVisible(); // should only clear selection when escape is pressed when the menu is closed await page.keyboard.press('Escape'); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); }); test.slow('@avt-keyboard-nav filterable multiselect', async ({ page }) => { @@ -187,7 +187,7 @@ test.describe('@avt MultiSelect', () => { const menu = page.getByRole('listbox'); await expect(toggleButton).toBeVisible(); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); // Tab and open the MultiSelect with Arrow Down await page.keyboard.press('Tab'); await expect(toggleButton).toBeFocused(); @@ -195,13 +195,13 @@ test.describe('@avt MultiSelect', () => { await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Enter await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Enter'); await expect(menu).toBeVisible(); // Close with Escape, retain focus, and open with Spacebar await page.keyboard.press('Escape'); - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(toggleButton).toBeFocused(); await page.keyboard.press('Space'); await expect(menu).toBeVisible(); @@ -244,11 +244,11 @@ test.describe('@avt MultiSelect', () => { await page.keyboard.press('Escape'); await expect(toggleButton).toBeFocused(); // should show count of selected items when closed - await expect(menu).not.toBeVisible(); + await expect(menu).toBeHidden(); await expect(selection).toBeVisible(); // should only clear selection when escape is pressed when the menu is closed await page.keyboard.press('Escape'); - await expect(selection).not.toBeVisible(); + await expect(selection).toBeHidden(); // should filter menu items based on text input await page.keyboard.press('2'); @@ -264,6 +264,6 @@ test.describe('@avt MultiSelect', () => { name: 'Option 1', selected: false, }) - ).not.toBeVisible(); + ).toBeHidden(); }); }); diff --git a/e2e/components/OverflowMenu/OverflowMenu-test.avt.e2e.js b/e2e/components/OverflowMenu/OverflowMenu-test.avt.e2e.js index 90b66cc05172..638a99515178 100644 --- a/e2e/components/OverflowMenu/OverflowMenu-test.avt.e2e.js +++ b/e2e/components/OverflowMenu/OverflowMenu-test.avt.e2e.js @@ -99,6 +99,6 @@ test.describe('@avt OverflowMenu', () => { ).toBeFocused(); await page.keyboard.press('Enter'); // Once closed menu is no longer visibile - await expect(page.getByRole('menu')).not.toBeVisible(); + await expect(page.getByRole('menu')).toBeHidden(); }); }); diff --git a/e2e/components/Pagination/Pagination-test.avt.e2e.js b/e2e/components/Pagination/Pagination-test.avt.e2e.js index 8fd29ee6e16f..c52ff4854589 100644 --- a/e2e/components/Pagination/Pagination-test.avt.e2e.js +++ b/e2e/components/Pagination/Pagination-test.avt.e2e.js @@ -60,7 +60,7 @@ test.describe('@avt Pagination', () => { await expect(itemsPerPage).toBeVisible(); await expect(pageSelector).toBeVisible(); - await expect(updatedPageSelector).not.toBeVisible(); + await expect(updatedPageSelector).toBeHidden(); // Tab to first items per page await page.keyboard.press('Tab'); @@ -69,7 +69,7 @@ test.describe('@avt Pagination', () => { await itemsPerPage.selectOption('50'); await expect(itemsPerPage).toHaveValue('50'); // Should update the pages text - await expect(pageSelector).not.toBeVisible(); + await expect(pageSelector).toBeHidden(); await expect(updatedPageSelector).toBeVisible(); // Should still be on page one await expect(updatedPageSelector).toHaveValue('1'); @@ -83,7 +83,7 @@ test.describe('@avt Pagination', () => { await page.keyboard.press('Enter'); await expect(updatedPageSelector).toHaveValue('2'); // Tab to previous page - await expect(prevPageButton).not.toBeDisabled(); + await expect(prevPageButton).toBeEnabled(); await page.keyboard.press('Shift+Tab'); await page.keyboard.press('Enter'); await expect(updatedPageSelector).toHaveValue('1'); diff --git a/e2e/components/Popover/Popover-test.avt.e2e.js b/e2e/components/Popover/Popover-test.avt.e2e.js index 0ae35489ab57..2054e04e1b67 100644 --- a/e2e/components/Popover/Popover-test.avt.e2e.js +++ b/e2e/components/Popover/Popover-test.avt.e2e.js @@ -14,7 +14,7 @@ test.describe('@avt Popover', () => { test('@avt-advanced-states auto align', async ({ page }) => { await visitStory(page, { component: 'Popover', - id: 'components-popover--auto-align', + id: 'components-popover--experimental-auto-align', globals: { theme: 'white', }, @@ -52,19 +52,19 @@ test.describe('@avt Popover', () => { // Checking popover state and interaction with Enter key await expect(page.locator('.cds--popover--open')).toBeVisible(); await page.keyboard.press('Enter'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); // Checking popover state and interaction with Space key await page.keyboard.press('Space'); await expect(page.locator('.cds--popover--open')).toBeVisible(); await page.keyboard.press('Space'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); // Checking popover state and interaction with Escape key await page.keyboard.press('Space'); await expect(page.locator('.cds--popover--open')).toBeVisible(); await page.keyboard.press('Escape'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); // Testing right popover await page.keyboard.press('Tab'); @@ -74,13 +74,13 @@ test.describe('@avt Popover', () => { await page.keyboard.press('Enter'); await expect(page.locator('.cds--popover--open')).toBeVisible(); await page.keyboard.press('Enter'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); // Checking popover state and interaction with Space key await page.keyboard.press('Space'); await expect(page.locator('.cds--popover--open')).toBeVisible(); await page.keyboard.press('Space'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); // Checking popover state and interaction with Escape key (it should not close) await page.keyboard.press('Space'); diff --git a/e2e/components/Popover/Popover-test.e2e.js b/e2e/components/Popover/Popover-test.e2e.js index e65d7c460884..d28fe03f5609 100644 --- a/e2e/components/Popover/Popover-test.e2e.js +++ b/e2e/components/Popover/Popover-test.e2e.js @@ -14,10 +14,10 @@ const { snapshotStory } = require('../../test-utils/storybook'); test.describe('Popover', () => { themes.forEach((theme) => { test.describe(theme, () => { - test('Popover - auto align @vrt', async ({ page }) => { + test('Popover @vrt', async ({ page }) => { await snapshotStory(page, { component: 'Popover', - id: 'components-popover--auto-align', + id: 'components-popover--playground', theme, }); }); diff --git a/e2e/components/Search/Search-test.avt.e2e.js b/e2e/components/Search/Search-test.avt.e2e.js index c30d6b9967c2..aff6f7e5bd9a 100644 --- a/e2e/components/Search/Search-test.avt.e2e.js +++ b/e2e/components/Search/Search-test.avt.e2e.js @@ -46,7 +46,7 @@ test.describe('@avt Search', () => { name: 'Clear search input', }); await expect(search).toBeVisible(); - await expect(clearButton).not.toBeVisible(); + await expect(clearButton).toBeHidden(); // Tab to the Search await page.keyboard.press('Tab'); @@ -82,15 +82,15 @@ test.describe('@avt Search', () => { name: 'Clear search input', }); const searchButton = page.getByRole('button'); - await expect(search).not.toBeVisible(); - await expect(clearButton).not.toBeVisible(); + await expect(search).toBeHidden(); + await expect(clearButton).toBeHidden(); await expect(searchButton).toBeVisible(); await expect(searchButton).not.toHaveAttribute('aria-expanded', 'true'); // Tab to the ExpandableSearch, should only open on Enter await page.keyboard.press('Tab'); await expect(searchButton).toBeFocused(); - await expect(search).not.toBeVisible(); + await expect(search).toBeHidden(); // Enter search value await page.keyboard.press('Enter'); await expect(search).toBeVisible(); @@ -113,6 +113,6 @@ test.describe('@avt Search', () => { // Close ExpandableSearch with ESC await page.keyboard.press('Escape'); await expect(searchButton).not.toHaveAttribute('aria-expanded', 'true'); - await expect(search).not.toBeVisible(); + await expect(search).toBeHidden(); }); }); diff --git a/e2e/components/Tabs/Tabs-test.avt.e2e.js b/e2e/components/Tabs/Tabs-test.avt.e2e.js index 075bf4879725..422366f2e23a 100644 --- a/e2e/components/Tabs/Tabs-test.avt.e2e.js +++ b/e2e/components/Tabs/Tabs-test.avt.e2e.js @@ -175,8 +175,6 @@ test.describe('@avt Tabs', () => { // Pressing delete should delete the dismissable tab await page.keyboard.press('Delete'); - await expect( - page.getByRole('tab', { name: 'Tab label 4' }) - ).not.toBeVisible(); + await expect(page.getByRole('tab', { name: 'Tab label 4' })).toBeHidden(); }); }); diff --git a/e2e/components/Toggle/Toggle-test.avt.e2e.js b/e2e/components/Toggle/Toggle-test.avt.e2e.js index 4f5d7328dbb3..ac05457be70a 100644 --- a/e2e/components/Toggle/Toggle-test.avt.e2e.js +++ b/e2e/components/Toggle/Toggle-test.avt.e2e.js @@ -66,8 +66,8 @@ test.describe('@avt Toggle', () => { await page.keyboard.press('Tab'); await expect(page.getByRole('switch')).toBeVisible(); await page.keyboard.press('Space'); - await page.getByText('Off'); + page.getByText('Off'); await page.keyboard.press('Space'); - await page.getByText('On'); + page.getByText('On'); }); }); diff --git a/e2e/components/Toggletip/Toggletip-test.avt.e2e.js b/e2e/components/Toggletip/Toggletip-test.avt.e2e.js index 758cba620afa..69b984f68a2d 100644 --- a/e2e/components/Toggletip/Toggletip-test.avt.e2e.js +++ b/e2e/components/Toggletip/Toggletip-test.avt.e2e.js @@ -45,13 +45,13 @@ test.describe('@avt Toggletip', () => { await page.keyboard.press('Tab'); await expect(page.getByRole('button', { name: 'Button' })).toBeFocused(); await page.keyboard.press('Tab'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); // Checking second Toggletip interaction and close on Escape key await expect(page.getByLabel('Show information').last()).toBeFocused(); await page.keyboard.press('Enter'); await expect(page.locator('.cds--popover--open')).toBeVisible(); await page.keyboard.press('Escape'); - await expect(page.locator('.cds--popover--open')).not.toBeVisible(); + await expect(page.locator('.cds--popover--open')).toBeHidden(); }); }); diff --git a/e2e/components/Tooltip/Tooltip-test.avt.e2e.js b/e2e/components/Tooltip/Tooltip-test.avt.e2e.js index 2beec600f83c..1fa765bcd6c9 100644 --- a/e2e/components/Tooltip/Tooltip-test.avt.e2e.js +++ b/e2e/components/Tooltip/Tooltip-test.avt.e2e.js @@ -45,7 +45,7 @@ test.describe('@avt Tooltip', () => { }); // Prevent timeout - test.slow('tooltip default - @avt-keyboard-nav', async ({ page }) => { + test.slow('@avt-keyboard-nav - tooltip default', async ({ page }) => { await visitStory(page, { component: 'Tooltip', id: 'components-tooltip--default', diff --git a/e2e/components/UIShell/UIShell-test.avt.e2e.js b/e2e/components/UIShell/UIShell-test.avt.e2e.js index 3877599985d4..0b3c2986a995 100644 --- a/e2e/components/UIShell/UIShell-test.avt.e2e.js +++ b/e2e/components/UIShell/UIShell-test.avt.e2e.js @@ -113,9 +113,7 @@ test.describe('@avt UIShell', () => { await expect(page.getByRole('link', { name: 'Sub-link 3' })).toBeFocused(); // tab once more and the sublinks menu should close await page.keyboard.press('Tab'); - await expect( - page.getByRole('link', { name: 'Sub-link 1' }) - ).not.toBeVisible(); + await expect(page.getByRole('link', { name: 'Sub-link 1' })).toBeHidden(); // tab through to open the sidenav await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); diff --git a/packages/icons-vue/examples/vue-cli/yarn.lock b/packages/icons-vue/examples/vue-cli/yarn.lock index 37b8592aa2aa..24dcb5f31bf9 100644 --- a/packages/icons-vue/examples/vue-cli/yarn.lock +++ b/packages/icons-vue/examples/vue-cli/yarn.lock @@ -3495,9 +3495,9 @@ flush-write-stream@^1.0.0: readable-stream "^2.0.4" follow-redirects@^1.0.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-in@^1.0.2: version "1.0.2" diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 1da006e3a326..617d0777ef85 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -1403,10 +1403,14 @@ Map { Array [ "top", "top-left", + "top-start", "top-right", + "top-end", "bottom", "bottom-left", + "bottom-start", "bottom-right", + "bottom-end", "left", "right", ], @@ -4083,10 +4087,14 @@ Map { Array [ "top", "top-left", + "top-start", "top-right", + "top-end", "bottom", "bottom-left", + "bottom-start", "bottom-right", + "bottom-end", "left", "right", ], @@ -6069,6 +6077,14 @@ Map { "right", "right-bottom", "right-top", + "top-start", + "top-end", + "bottom-start", + "bottom-end", + "left-end", + "left-start", + "right-end", + "right-start", ], ], "type": "oneOf", @@ -9058,6 +9074,14 @@ Map { "right", "right-bottom", "right-top", + "top-start", + "top-end", + "bottom-start", + "bottom-end", + "left-end", + "left-start", + "right-end", + "right-start", ], ], "type": "oneOf", @@ -9090,6 +9114,7 @@ Map { }, }, "ToggletipButton" => Object { + "$$typeof": Symbol(react.forward_ref), "propTypes": Object { "children": Object { "type": "node", @@ -9101,6 +9126,7 @@ Map { "type": "string", }, }, + "render": [Function], }, "ToggletipContent" => Object { "propTypes": Object { @@ -9142,6 +9168,14 @@ Map { "right", "right-bottom", "right-top", + "top-start", + "top-end", + "bottom-start", + "bottom-end", + "left-end", + "left-start", + "right-end", + "right-start", ], ], "type": "oneOf", @@ -10491,6 +10525,14 @@ Map { "right", "right-bottom", "right-top", + "top-start", + "top-end", + "bottom-start", + "bottom-end", + "left-end", + "left-start", + "right-end", + "right-start", ], ], "type": "oneOf", diff --git a/packages/react/package.json b/packages/react/package.json index 813fdbe40b1e..d489f535a2cf 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -52,6 +52,7 @@ "@carbon/icons-react": "^11.38.0", "@carbon/layout": "^11.21.0", "@carbon/styles": "^1.53.0", + "@floating-ui/react": "^0.25.4", "@ibm/telemetry-js": "^1.2.1", "classnames": "2.5.1", "copy-to-clipboard": "^3.3.1", diff --git a/packages/react/src/components/ComboButton/ComboButton-test.js b/packages/react/src/components/ComboButton/ComboButton-test.js index 653889445daf..098324681552 100644 --- a/packages/react/src/components/ComboButton/ComboButton-test.js +++ b/packages/react/src/components/ComboButton/ComboButton-test.js @@ -90,11 +90,11 @@ describe('ComboButton', () => { describe('supports props.tooltipAlignment', () => { const alignments = [ 'top', - 'top-left', - 'top-right', + 'top-start', + 'top-end', 'bottom', - 'bottom-left', - 'bottom-right', + 'bottom-start', + 'bottom-end', 'left', 'right', ]; diff --git a/packages/react/src/components/ComboButton/index.js b/packages/react/src/components/ComboButton/index.js index 6e719ae4b01b..d6b2577bfcff 100644 --- a/packages/react/src/components/ComboButton/index.js +++ b/packages/react/src/components/ComboButton/index.js @@ -194,10 +194,14 @@ ComboButton.propTypes = { tooltipAlignment: PropTypes.oneOf([ 'top', 'top-left', + 'top-start', 'top-right', + 'top-end', 'bottom', 'bottom-left', + 'bottom-start', 'bottom-right', + 'bottom-end', 'left', 'right', ]), diff --git a/packages/react/src/components/DataTable/__tests__/__snapshots__/DataTable-test.js.snap b/packages/react/src/components/DataTable/__tests__/__snapshots__/DataTable-test.js.snap index 75bace0e4870..29ff9c44e44c 100644 --- a/packages/react/src/components/DataTable/__tests__/__snapshots__/DataTable-test.js.snap +++ b/packages/react/src/components/DataTable/__tests__/__snapshots__/DataTable-test.js.snap @@ -10,20 +10,20 @@ exports[`DataTable behaves as expected selection -- radio buttons should not hav >

DataTable with selection

@@ -182,20 +182,20 @@ exports[`DataTable behaves as expected selection -- radio buttons should render >

DataTable with selection

@@ -354,13 +354,13 @@ exports[`DataTable behaves as expected selection should render and match snapsho >

DataTable with selection

@@ -800,13 +800,13 @@ exports[`DataTable renders as expected - Component API should render and match s >

DataTable with toolbar

diff --git a/packages/react/src/components/IconButton/index.tsx b/packages/react/src/components/IconButton/index.tsx index f67a459fd6b8..104c15f73bb3 100644 --- a/packages/react/src/components/IconButton/index.tsx +++ b/packages/react/src/components/IconButton/index.tsx @@ -29,10 +29,14 @@ interface IconButtonProps align?: | 'top' | 'top-left' + | 'top-start' | 'top-right' + | 'top-end' | 'bottom' | 'bottom-left' + | 'bottom-start' | 'bottom-right' + | 'bottom-end' | 'left' | 'right'; @@ -161,10 +165,14 @@ IconButton.propTypes = { align: PropTypes.oneOf([ 'top', 'top-left', + 'top-start', 'top-right', + 'top-end', 'bottom', 'bottom-left', + 'bottom-start', 'bottom-right', + 'bottom-end', 'left', 'right', ]), diff --git a/packages/react/src/components/Popover/Popover.stories.js b/packages/react/src/components/Popover/Popover.stories.js index 3575ed19a0be..9e9530a043b2 100644 --- a/packages/react/src/components/Popover/Popover.stories.js +++ b/packages/react/src/components/Popover/Popover.stories.js @@ -7,7 +7,7 @@ import './story.scss'; import { Checkbox as CheckboxIcon } from '@carbon/icons-react'; -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Popover, PopoverContent } from '../Popover'; import RadioButton from '../RadioButton'; import RadioButtonGroup from '../RadioButtonGroup'; @@ -178,20 +178,20 @@ Playground.argTypes = { align: { options: [ 'top', - 'top-left', - 'top-right', + 'top-start', + 'top-end', 'bottom', - 'bottom-left', - 'bottom-right', + 'bottom-start', + 'bottom-end', 'left', - 'left-bottom', - 'left-top', + 'left-end', + 'left-start', 'right', - 'right-bottom', - 'right-top', + 'right-end', + 'right-start', ], control: { type: 'select', @@ -225,19 +225,23 @@ Playground.story = { ], }; -export const AutoAlign = () => { - const [open, setOpen] = useState(false); +export const ExperimentalAutoAlign = () => { + const [open, setOpen] = useState(true); + const ref = useRef(); + + useEffect(() => { + ref?.current?.scrollIntoView({ block: 'center', inline: 'center' }); + }); + return ( -
+
- +
{ @@ -246,81 +250,58 @@ export const AutoAlign = () => { />
-

Available storage

-

- This server has 150 GB of block storage remaining. -

-
-
-
- -
- -
- -

Available storage

-

- This server has 150 GB of block storage remaining. -

-
-
-
- -
- -
- -

Available storage

-

- This server has 350 GB of block storage remaining. -

-
-
-
-
- -
- -
- -

Available storage

-

- This server has 150 GB of block storage remaining. -

+
+

This popover uses autoAlign

+

+ Scroll the container up, down, left or right to observe how the + popover will automatically change its position in attempt to + stay within the viewport. This works on initial render in + addition to on scroll. +

+
+
+ ); +}; + +export const TabTipExperimentalAutoAlign = () => { + const [open, setOpen] = useState(true); + const ref = useRef(); + + useEffect(() => { + ref?.current?.scrollIntoView({ block: 'center', inline: 'center' }); + }); + + return ( +
- +
- + { + setOpen(!open); + }} + />
-

Available storage

-

- This server has 150 GB of block storage remaining. -

+
+

+ This popover uses autoAlign with isTabTip +

+

+ Scroll the container up, down, left or right to observe how the + popover will automatically change its position in attempt to + stay within the viewport. This works on initial render in + addition to on scroll. +

+
diff --git a/packages/react/src/components/Popover/__tests__/Popover-test.js b/packages/react/src/components/Popover/__tests__/Popover-test.js index 2108e49f78f3..0ce92993bbd6 100644 --- a/packages/react/src/components/Popover/__tests__/Popover-test.js +++ b/packages/react/src/components/Popover/__tests__/Popover-test.js @@ -90,19 +90,131 @@ describe('Popover', () => { expect(container.firstChild).toHaveClass(`${prefix}--popover--tab-tip`); }); - it('should not allow other alignments than bottom-left or bottom-right when isTabTip is present', () => { + it('should not allow other alignments than bottom-start or bottom-end when isTabTip is present', () => { const { container } = render( - + test ); expect(container.firstChild).not.toHaveClass( - `${prefix}--popover--top-left` + `${prefix}--popover--top-start` ); expect(container.firstChild).toHaveClass( + `${prefix}--popover--bottom-start` + ); + }); + + it('should shim legacy align prop top-left to top-start', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--top-left` + ); + expect(container.firstChild).toHaveClass(`${prefix}--popover--top-start`); + }); + + it('should shim legacy align prop top-right to top-end', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--top-right` + ); + expect(container.firstChild).toHaveClass(`${prefix}--popover--top-end`); + }); + + it('should shim legacy align prop bottom-left to bottom-start', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( `${prefix}--popover--bottom-left` ); + expect(container.firstChild).toHaveClass( + `${prefix}--popover--bottom-start` + ); + }); + + it('should shim legacy align prop bottom-right to bottom-end', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--bottom-right` + ); + expect(container.firstChild).toHaveClass( + `${prefix}--popover--bottom-end` + ); + }); + + it('should shim legacy align prop left-bottom to left-end', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--left-bottom` + ); + expect(container.firstChild).toHaveClass(`${prefix}--popover--left-end`); + }); + + it('should shim legacy align prop left-top to left-start', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--left-top` + ); + expect(container.firstChild).toHaveClass( + `${prefix}--popover--left-start` + ); + }); + + it('should shim legacy align prop right-bottom to right-end', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--right-bottom` + ); + expect(container.firstChild).toHaveClass(`${prefix}--popover--right-end`); + }); + + it('should shim legacy align prop right-top to right-start', () => { + const { container } = render( + + + Test + + ); + expect(container.firstChild).not.toHaveClass( + `${prefix}--popover--right-top` + ); + expect(container.firstChild).toHaveClass( + `${prefix}--popover--right-start` + ); }); it('should call onRequestClose when click happens outside the popover', async () => { diff --git a/packages/react/src/components/Popover/docs/overview.mdx b/packages/react/src/components/Popover/docs/overview.mdx index b929b426015c..02a096d3c795 100644 --- a/packages/react/src/components/Popover/docs/overview.mdx +++ b/packages/react/src/components/Popover/docs/overview.mdx @@ -6,7 +6,7 @@ variants={[ { label: 'Default', - variant: 'components-popover--auto-align' - } + variant: 'components-popover--experimental-auto-align', + }, ]} -/> \ No newline at end of file +/> diff --git a/packages/react/src/components/Popover/index.tsx b/packages/react/src/components/Popover/index.tsx index 08f07da43672..dd87dfb58117 100644 --- a/packages/react/src/components/Popover/index.tsx +++ b/packages/react/src/components/Popover/index.tsx @@ -9,8 +9,8 @@ import cx from 'classnames'; import PropTypes from 'prop-types'; import React, { useRef, - useState, useMemo, + useEffect, type ForwardedRef, type WeakValidationMap, type ElementType, @@ -20,30 +20,53 @@ import { useMergedRefs } from '../../internal/useMergedRefs'; import { usePrefix } from '../../internal/usePrefix'; import { type PolymorphicProps } from '../../types/common'; import { useWindowEvent } from '../../internal/useEvent'; +import { mapPopoverAlignProp } from '../../tools/createPropAdapter'; +import { + useFloating, + flip, + autoUpdate, + arrow, + offset, +} from '@floating-ui/react'; interface PopoverContext { - floating: React.Ref; + setFloating: React.Ref; + caretRef: React.Ref; + autoAlign: boolean | null; } const PopoverContext = React.createContext({ - floating: { + setFloating: { current: null, }, + caretRef: { + current: null, + }, + autoAlign: null, }); export type PopoverAlignment = | 'top' - | 'top-left' - | 'top-right' + | 'top-left' // deprecated + | 'top-right' // deprecated | 'bottom' - | 'bottom-left' - | 'bottom-right' + | 'bottom-left' // deprecated + | 'bottom-right' // deprecated | 'left' - | 'left-bottom' - | 'left-top' + | 'left-bottom' // deprecated + | 'left-top' // deprecated | 'right' - | 'right-bottom' - | 'right-top'; + | 'right-bottom' // deprecated + | 'right-top' // deprecated + // new values to match floating-ui + | 'top-start' + | 'top-end' + | 'bottom-start' + | 'bottom-end' + | 'left-end' + | 'left-start' + | 'right-end' + | 'right-start'; interface PopoverBaseProps { /** @@ -116,7 +139,7 @@ export const Popover: PopoverComponent = React.forwardRef( function PopoverRenderFunction( { isTabTip, - align = isTabTip ? 'bottom-left' : 'bottom', + align: initialAlign = isTabTip ? 'bottom-start' : 'bottom', as: BaseComponent = 'span' as E, autoAlign = false, caret = isTabTip ? false : true, @@ -132,7 +155,9 @@ export const Popover: PopoverComponent = React.forwardRef( ) { const prefix = usePrefix(); const floating = useRef(null); + const caretRef = useRef(null); const popover = useRef(null); + let align = mapPopoverAlignProp(initialAlign); // If the `Popover` is the last focusable item in the tab order, it should also close when the browser window loses focus (#12922) useWindowEvent('blur', () => { @@ -147,26 +172,144 @@ export const Popover: PopoverComponent = React.forwardRef( } }); + // Slug styling places a border around the popover content so the caret + // needs to be placed 1px further outside the popover content. To do so, + // we look to see if any of the children has a className containing "slug" + const initialCaretHeight = React.Children.toArray(children).some((x) => { + return (x as any)?.props?.className?.includes('slug'); + }) + ? 7 + : 6; + // These defaults match the defaults defined in packages/styles/scss/components/popover/_popover.scss + const popoverDimensions = useRef({ + offset: 10, + caretHeight: initialCaretHeight, + }); + + useIsomorphicEffect(() => { + // The popover is only offset when a caret is present. Technically, the custom properties + // accessed below can be set by a user even if caret=false, but doing so does not follow + // the design specification for Popover. + if (caret && popover.current) { + // Gather the dimensions of the caret and prefer the values set via custom properties. + // If a value is not set via a custom property, provide a default value that matches the + // default values defined in the sass style file + const getStyle = window.getComputedStyle(popover.current, null); + const offsetProperty = getStyle.getPropertyValue( + '--cds-popover-offset' + ); + const caretProperty = getStyle.getPropertyValue( + '--cds-popover-caret-height' + ); + + // Handle if the property values are in px or rem. + // We want to store just the base number value without a unit suffix + if (offsetProperty) { + popoverDimensions.current.offset = offsetProperty.includes('px') + ? Number(offsetProperty.split('px', 1)[0]) * 1 + : Number(offsetProperty.split('rem', 1)[0]) * 16; + } + + if (caretProperty) { + popoverDimensions.current.caretHeight = caretProperty.includes('px') + ? Number(caretProperty.split('px', 1)[0]) * 1 + : Number(caretProperty.split('rem', 1)[0]) * 16; + } + } + }); + + const { refs, floatingStyles, placement, middlewareData } = useFloating( + autoAlign + ? { + placement: align, + + // The floating element is positioned relative to its nearest + // containing block (usually the viewport). It will in many cases also + // β€œbreak” the floating element out of a clipping ancestor. + // https://floating-ui.com/docs/misc#clipping + strategy: 'fixed', + + // Middleware order matters, arrow should be last + middleware: [ + offset(!isTabTip ? popoverDimensions?.current?.offset : 0), + flip({ fallbackAxisSideDirection: 'start' }), + arrow({ + element: caretRef, + }), + ], + whileElementsMounted: autoUpdate, + } + : {} // When autoAlign is turned off, floating-ui will not be used + ); + const value = useMemo(() => { return { floating, + setFloating: refs.setFloating, + caretRef, + autoAlign: autoAlign, }; - }, []); + }, [refs.setFloating, autoAlign]); if (isTabTip) { const tabTipAlignments: PopoverAlignment[] = [ - 'bottom-left', - 'bottom-right', + 'bottom-start', + 'bottom-end', ]; if (!tabTipAlignments.includes(align)) { - align = 'bottom-left'; + align = 'bottom-start'; } } + useEffect(() => { + if (autoAlign) { + Object.keys(floatingStyles).forEach((style) => { + if (refs.floating.current) { + refs.floating.current.style[style] = floatingStyles[style]; + } + }); + + if ( + caret && + middlewareData && + middlewareData.arrow && + caretRef?.current + ) { + const { x, y } = middlewareData.arrow; + + const staticSide = { + top: 'bottom', + right: 'left', + bottom: 'top', + left: 'right', + }[placement.split('-')[0]]; + + caretRef.current.style.left = x != null ? `${x}px` : ''; + caretRef.current.style.top = y != null ? `${y}px` : ''; + + // Ensure the static side gets unset when flipping to other placements' axes. + caretRef.current.style.right = ''; + caretRef.current.style.bottom = ''; + + if (staticSide) { + caretRef.current.style[staticSide] = `${-popoverDimensions?.current + ?.caretHeight}px`; + } + } + } + }, [ + floatingStyles, + refs.floating, + autoAlign, + middlewareData, + placement, + caret, + ]); + const ref = useMergedRefs([forwardRef, popover]); - const [autoAligned, setAutoAligned] = useState(false); - const [autoAlignment, setAutoAlignment] = useState(align); + const currentAlignment = + autoAlign && placement !== align ? placement : align; const className = cx( { [`${prefix}--popover-container`]: true, @@ -174,150 +317,64 @@ export const Popover: PopoverComponent = React.forwardRef( [`${prefix}--popover--drop-shadow`]: dropShadow, [`${prefix}--popover--high-contrast`]: highContrast, [`${prefix}--popover--open`]: open, - [`${prefix}--popover--${autoAlignment}`]: autoAligned && !isTabTip, - [`${prefix}--popover--${align}`]: !autoAligned, + [`${prefix}--popover--auto-align`]: autoAlign, + [`${prefix}--popover--${currentAlignment}`]: true, [`${prefix}--popover--tab-tip`]: isTabTip, }, customClassName ); - useIsomorphicEffect(() => { - if (!open) { - return; - } - - if (!autoAlign || isTabTip) { - setAutoAligned(false); - return; - } - - if (!floating.current) { - return; - } - - if (autoAligned === true) { - return; - } - - const rect = floating.current.getBoundingClientRect(); - - // The conditions, per side, of when the popover is not visible, excluding the popover's internal padding(16) - const conditions = { - left: rect.x < -16, - top: rect.y < -16, - right: - rect.x + (rect.width - 16) > document.documentElement.clientWidth, - bottom: - rect.y + (rect.height - 16) > document.documentElement.clientHeight, - }; - - if ( - !conditions.left && - !conditions.top && - !conditions.right && - !conditions.bottom - ) { - setAutoAligned(false); - return; - } - - const alignments: PopoverAlignment[] = [ - 'top', - 'top-left', - 'right-bottom', - 'right', - 'right-top', - 'bottom-left', - 'bottom', - 'bottom-right', - 'left-top', - 'left', - 'left-bottom', - 'top-right', - ]; - - // Creates the prioritized list of options depending on ideal alignment coming from `align` - const options = [align]; - let option = - alignments[(alignments.indexOf(align) + 1) % alignments.length]; - - while (option) { - if (options.includes(option)) { - break; - } - options.push(option); - option = - alignments[(alignments.indexOf(option) + 1) % alignments.length]; - } - - function isVisible(alignment: PopoverAlignment) { - if (!popover.current || !floating.current) { - return false; - } - - popover.current.classList.add(`${prefix}--popover--${alignment}`); - - const rect = floating.current.getBoundingClientRect(); - - // Check if popover is not visible to the left of the screen - if (rect.x < -16) { - popover.current.classList.remove(`${prefix}--popover--${alignment}`); - return false; - } - - // Check if popover is not visible at the top of the screen - if (rect.y < -16) { - popover.current.classList.remove(`${prefix}--popover--${alignment}`); - return false; - } - - // Check if popover is not visible to right of screen - if (rect.x + (rect.width - 16) > document.documentElement.clientWidth) { - popover.current.classList.remove(`${prefix}--popover--${alignment}`); - return false; - } - - // Check if popover is not visible to bottom of screen - if ( - rect.y + (rect.height - 16) > - document.documentElement.clientHeight - ) { - popover.current.classList.remove(`${prefix}--popover--${alignment}`); - return false; - } - - popover.current.classList.remove(`${prefix}--popover--${alignment}`); - return true; - } - - let alignment: PopoverAlignment | null = null; - - for (let i = 0; i < options.length; i++) { - const option = options[i]; - - if (isVisible(option)) { - alignment = option; - break; - } - } - - if (alignment) { - setAutoAligned(true); - setAutoAlignment(alignment); - } - }, [autoAligned, align, autoAlign, prefix, open, isTabTip]); - const mappedChildren = React.Children.map(children, (child) => { const item = child as any; - if (item?.type === 'button') { - const { className } = item.props; + if ( + (item?.type === 'button' || + (autoAlign && item?.type?.displayName !== 'PopoverContent') || + (autoAlign && item?.type?.displayName === 'ToggletipButton')) && + React.isValidElement(item) + ) { + const className = (item?.props as any)?.className; + const ref = (item?.props as any).ref; const tabTipClasses = cx( `${prefix}--popover--tab-tip__button`, className ); - return React.cloneElement(item, { - className: tabTipClasses, + + return React.cloneElement(item as any, { + className: + isTabTip && item?.type === 'button' + ? tabTipClasses + : className || '', + + // With cloneElement, if you pass a `ref`, it overrides the original ref. + // https://react.dev/reference/react/cloneElement#parameters + // The block below works around this and ensures that the original ref is still + // called while allowing the floating-ui reference element to be set as well. + // `useMergedRefs` can't be used here because hooks can't be called from within a callback. + // More here: https://github.com/facebook/react/issues/8873#issuecomment-489579878 + ref: (node) => { + // For a popover, there isn't an explicit trigger component, it's just the first child that's + // passed in which should *not* be PopoverContent. + // For a toggletip there is a specific trigger component, ToggletipButton. + // In either of these caes we want to set this as the reference node for floating-ui autoAlign + // positioning. + if ( + (autoAlign && + (item?.type as any)?.displayName !== 'PopoverContent') || + (autoAlign && + (item?.type as any)?.displayName === 'ToggletipButton') + ) { + // Set the reference element for floating-ui + refs.setReference(node); + } + + // Call the original ref, if any + if (typeof ref === 'function') { + ref(node); + } else if (ref !== null && ref !== undefined) { + ref.current = node; + } + }, }); } else { return item; @@ -329,7 +386,7 @@ export const Popover: PopoverComponent = React.forwardRef( return ( - {isTabTip ? mappedChildren : children} + {autoAlign || isTabTip ? mappedChildren : children} ); @@ -348,20 +405,30 @@ Popover.propTypes = { */ align: PropTypes.oneOf([ 'top', - 'top-left', - 'top-right', + 'top-left', // deprecated use top-start instead + 'top-right', // deprecated use top-end instead 'bottom', - 'bottom-left', - 'bottom-right', + 'bottom-left', // deprecated use bottom-start instead + 'bottom-right', // deprecated use bottom-end instead 'left', - 'left-bottom', - 'left-top', + 'left-bottom', // deprecated use left-end instead + 'left-top', // deprecated use left-start instead 'right', - 'right-bottom', - 'right-top', + 'right-bottom', // deprecated use right-end instead + 'right-top', // deprecated use right-start instead + + // new values to match floating-ui + 'top-start', + 'top-end', + 'bottom-start', + 'bottom-end', + 'left-end', + 'left-start', + 'right-end', + 'right-start', ]), /** @@ -426,19 +493,37 @@ function PopoverContentRenderFunction( forwardRef: React.ForwardedRef ) { const prefix = usePrefix(); - const { floating } = React.useContext(PopoverContext); - const ref = useMergedRefs([floating, forwardRef]); + const { setFloating, caretRef, autoAlign } = React.useContext(PopoverContext); + const ref = useMergedRefs([setFloating, forwardRef]); + return ( {children} + {autoAlign && ( + + )} - + {!autoAlign && ( + + )} ); } export const PopoverContent = React.forwardRef(PopoverContentRenderFunction); +PopoverContent.displayName = 'PopoverContent'; PopoverContent.propTypes = { /** diff --git a/packages/react/src/components/RadioTile/RadioTile.js b/packages/react/src/components/RadioTile/RadioTile.tsx similarity index 53% rename from packages/react/src/components/RadioTile/RadioTile.js rename to packages/react/src/components/RadioTile/RadioTile.tsx index 3d6cf803d90b..36dbd92f6bd6 100644 --- a/packages/react/src/components/RadioTile/RadioTile.js +++ b/packages/react/src/components/RadioTile/RadioTile.tsx @@ -21,12 +21,73 @@ import { noopFn } from '../../internal/noopFn'; import { Text } from '../Text'; import { useFeatureFlag } from '../FeatureFlags'; +export interface RadioTileProps { + /** + * Specify whether the `RadioTile` should be checked. + */ + checked?: boolean; + + /** + * The `RadioTile` content. + */ + children?: React.ReactNode; + + /** + * Provide an optional `className` to be applied to the underlying `