diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca430ab0d1..51aa7c1f42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,28 +182,6 @@ jobs: name: playwright-avt-report path: .playwright - vrt-runner: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: '20.x' - cache: yarn - - name: Install - run: yarn - - name: Install browsers - run: yarn playwright install --with-deps - - name: Build project - run: yarn build - - name: Run VRT - working-directory: packages/core - env: - PERCY_TOKEN: web_d04495b0b413d61c2ea6b9118d1748b43f4fdd58d17ebe453ef8e0016b5766e4 - run: yarn percy storybook storybook-static - avt: if: ${{ always() }} runs-on: ubuntu-latest diff --git a/.github/workflows/process-pr-review-data.yml b/.github/workflows/process-pr-review-data.yml index 9ea2098416..63fa16f6d6 100644 --- a/.github/workflows/process-pr-review-data.yml +++ b/.github/workflows/process-pr-review-data.yml @@ -17,11 +17,6 @@ jobs: github.event.workflow_run.conclusion == 'success' steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: '20.x' - cache: yarn - uses: ./actions/add-review-labels with: APP_ID: ${{ secrets.APP_ID }} diff --git a/config/jest-config-ibm-cloud-cognitive/CHANGELOG.md b/config/jest-config-ibm-cloud-cognitive/CHANGELOG.md index a092695914..be70211d70 100644 --- a/config/jest-config-ibm-cloud-cognitive/CHANGELOG.md +++ b/config/jest-config-ibm-cloud-cognitive/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.15.0-rc.0](https://github.com/carbon-design-system/ibm-products/compare/jest-config-ibm-cloud-cognitive@1.13.0-rc.0...jest-config-ibm-cloud-cognitive@1.15.0-rc.0) (2024-12-09) + + +### Bug Fixes + +* update accessibility-checker version ([#6525](https://github.com/carbon-design-system/ibm-products/issues/6525)) ([d8c7051](https://github.com/carbon-design-system/ibm-products/commit/d8c70518087e7e41fdf1aa45cbbf692389058d56)) + + + + + # [1.14.0](https://github.com/carbon-design-system/ibm-products/compare/jest-config-ibm-cloud-cognitive@1.14.0-rc.0...jest-config-ibm-cloud-cognitive@1.14.0) (2024-12-04) **Note:** Version bump only for package jest-config-ibm-cloud-cognitive diff --git a/config/jest-config-ibm-cloud-cognitive/package.json b/config/jest-config-ibm-cloud-cognitive/package.json index b8b6f2dc21..563a8d3b17 100644 --- a/config/jest-config-ibm-cloud-cognitive/package.json +++ b/config/jest-config-ibm-cloud-cognitive/package.json @@ -1,7 +1,7 @@ { "name": "jest-config-ibm-cloud-cognitive", "private": true, - "version": "1.14.0", + "version": "1.15.0-rc.0", "license": "Apache-2.0", "main": "index.js", "repository": { diff --git a/config/storybook-addon-carbon-theme/CHANGELOG.md b/config/storybook-addon-carbon-theme/CHANGELOG.md index 5420c578cb..3c0de70710 100644 --- a/config/storybook-addon-carbon-theme/CHANGELOG.md +++ b/config/storybook-addon-carbon-theme/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0-rc.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/storybook-addon-theme@2.3.0-rc.0...@carbon/storybook-addon-theme@2.5.0-rc.0) (2024-12-09) + +**Note:** Version bump only for package @carbon/storybook-addon-theme + + + + + # [2.4.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/storybook-addon-theme@2.4.0-rc.0...@carbon/storybook-addon-theme@2.4.0) (2024-12-04) **Note:** Version bump only for package @carbon/storybook-addon-theme diff --git a/config/storybook-addon-carbon-theme/package.json b/config/storybook-addon-carbon-theme/package.json index 72b2b16c73..2aa67ca576 100644 --- a/config/storybook-addon-carbon-theme/package.json +++ b/config/storybook-addon-carbon-theme/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/storybook-addon-theme", "description": "Carbon theme switcher for Storybook", - "version": "2.4.0", + "version": "2.5.0-rc.0", "license": "Apache-2.0", "main": "dist/react.js", "repository": { diff --git a/e2e/components/ProductiveCard/ProductiveCard-test.avt.e2e.js b/e2e/components/ProductiveCard/ProductiveCard-test.avt.e2e.js index 3eb31ac10e..b477b8467e 100644 --- a/e2e/components/ProductiveCard/ProductiveCard-test.avt.e2e.js +++ b/e2e/components/ProductiveCard/ProductiveCard-test.avt.e2e.js @@ -9,6 +9,7 @@ import { expect, test } from '@playwright/test'; import { visitStory } from '../../test-utils/storybook'; +import { carbon, pkg } from '../../../packages/ibm-products/src/settings'; test.describe('ProductiveCard @avt', () => { test('@avt-default-state', async ({ page }) => { @@ -23,4 +24,253 @@ test.describe('ProductiveCard @avt', () => { 'ProductiveCard @avt-default-state' ); }); + + test('@avt-with-caption', async ({ page }) => { + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--with-caption', + globals: { + carbonTheme: 'white', + }, + }); + await expect(page).toHaveNoACViolations('ProductiveCard @avt-with-caption'); + }); + + // Disabled state test + test('@avt-disabled: validates disabled button state', async ({ page }) => { + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--with-action-ghost-button', + }); + + await expect(page).toHaveNoACViolations('ProductiveCard @avt-disabled'); + const editButton = page.getByRole('button', { name: 'Edit' }); + const deleteButton = page.getByRole('button', { name: 'Delete' }); + const disabledButton = page.getByRole('button', { name: 'Read more' }); + expect(disabledButton.getAttribute('disabled')).not.toBeNull(); + + await page.keyboard.press('Tab'); + expect(editButton).toBeFocused(); + + await page.keyboard.press('Tab'); + expect(deleteButton).toBeFocused(); + // disabled button + await page.keyboard.press('Tab'); + expect( + await disabledButton.evaluate((btn) => document.activeElement !== btn) + ).toBe(true); + }); + + // Overflow menu open/close states test + test('@avt-overflow-menu: validates overflow menu interactions', async ({ + page, + }) => { + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--with-overflow', + }); + + const menuButton = page.getByRole('button', { label: 'Option' }); + const menu = page.getByRole('menu'); + + // Check initial state + expect(await menuButton.getAttribute('aria-expanded')).toBe('false'); + + // Open the menu + await menuButton.click(); + + // Wait for menu to be visible + await expect(menu).toBeVisible(); + + expect(await menuButton.getAttribute('aria-expanded')).toBe('true'); + await expect(page).toHaveNoACViolations('ProductiveCard @menu-open'); + + // Close the menu with Escape + await page.keyboard.press('Escape'); + await expect(menu).not.toBeVisible(); + + expect(await menuButton.getAttribute('aria-expanded')).toBe('false'); + await expect(page).toHaveNoACViolations('ProductiveCard @menu-closed'); + + // Reopen the menu via keyboard + await page.keyboard.press('Tab'); + expect( + await menuButton.evaluate((btn) => document.activeElement === btn) + ).toBe(true); + + await page.keyboard.press('Enter'); + await expect(menu).toBeVisible(); + + // Check menu item count and focus + const menuItems = page.locator(`li.${carbon.prefix}--menu-item`); + expect(await menuItems.count()).toBeGreaterThan(0); + expect( + await menuItems.first().evaluate((btn) => document.activeElement === btn) + ).toBe(true); + expect(await menuButton.getAttribute('aria-expanded')).toBe('true'); + + // Ensure the menu is closed when pressing Escape + await page.keyboard.press('Escape'); + // Focus returns to menu button + expect( + await menuButton.evaluate((btn) => document.activeElement === btn) + ).toBe(true); + + // Check final state + await expect(menu).not.toBeVisible(); + }); + + test('@avt-keyboard: validates keyboard navigation for all interactive elements', async ({ + page, + }) => { + // Navigate to the "Supplemental Bottom Bar" story for ProductiveCard, that has all interactive elements + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--supplemental-bottom-bar', + }); + + // Ensure no accessibility violations for the story + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - Supplemental Bottom Bar' + ); + + // Move focus to the Edit button and validate + await page.keyboard.press('Tab'); + const editButton = page.getByLabel('Edit'); + await expect(editButton).toBeVisible(); + await expect(editButton).toBeFocused(); + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - Edit Button' + ); + + // Move focus to the Delete button and validate + await page.keyboard.press('Tab'); + const deleteButton = page.getByLabel('Delete'); + await expect(deleteButton).toBeVisible(); + await expect(deleteButton).toBeFocused(); + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - Delete Button' + ); + + // Move focus to the Read more button and validate + await page.keyboard.press('Tab'); + const readMoreButton1 = page.getByText('Read more'); + await expect(readMoreButton1).toBeVisible(); + await expect(readMoreButton1).toBeFocused(); + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - Read more Button' + ); + + // Tab Navigation in "Clickable Card" story for ProductiveCard, (zone one is default, whole card recieves focus) + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--clickable', + }); + + // Ensure no accessibility violations for the story + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - Clickable Card' + ); + + // Move focus to the card element and validate + await page.keyboard.press('Tab'); + const zone1 = page.locator(`.${pkg.prefix}--card__clickable`); + await expect(zone1).toBeFocused(); + await expect(zone1).toHaveAttribute('role', 'button'); + + // Move focus to the Read more button and validate + await page.keyboard.press('Tab'); + const readMoreButton2 = page.getByText('Read more'); + await expect(readMoreButton2).toBeVisible(); + await expect(readMoreButton2).toBeFocused(); + + // Validate zone two focus + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--clickable&args=clickZone:two', + }); + await page.keyboard.press('Tab'); + + const zone2 = page.locator(`.${pkg.prefix}--card__header-body-container`); + await expect(zone2).toBeFocused(); + await expect(zone2).toHaveAttribute('role', 'button'); + + // Move focus to the Read more button and validate + await page.keyboard.press('Tab'); + const readMoreButton3 = page.getByText('Read more'); + await expect(readMoreButton3).toBeVisible(); + await expect(readMoreButton3).toBeFocused(); + + // Validate zone three focus + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--clickable&args=clickZone:three', + }); + await page.keyboard.press('Tab'); + const zone3 = page.locator(`.${pkg.prefix}--card__body`); + await expect(zone3).toBeFocused(); + await expect(zone3).toHaveAttribute('role', 'button'); + + // Move focus to the Read more button and validate + await page.keyboard.press('Tab'); + const readMoreButton4 = page.getByText('Read more'); + await expect(readMoreButton4).toBeVisible(); + await expect(readMoreButton4).toBeFocused(); + + // Navigate to the "button with href" story for ProductiveCard + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--with-button-href', + }); + + // Ensure no accessibility violations for the story + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - button with href' + ); + + // Move focus to the href button and validate + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + const hrefButton = page.getByText('Read more'); + await expect(hrefButton).toHaveAttribute('href', '#'); + await expect(hrefButton).toBeVisible(); + await expect(hrefButton).toBeFocused(); + await expect(page).toHaveNoACViolations( + 'ProductiveCard @keyboard-navigation - href Button' + ); + }); + + // hover states + test('@avt-hover: validates hover states', async ({ page }) => { + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--with-overflow', + }); + const menuButton = page.getByRole('button', { label: 'Overflow menu' }); + const tooltip = page.getByRole('tooltip', { name: 'Overflow menu' }); + + await menuButton.hover(); + await expect(page).toHaveNoACViolations( + 'ProductiveCard @hover - with overflow' + ); + await expect(tooltip).toBeVisible(); + + await visitStory(page, { + component: 'ProductiveCard', + id: 'ibm-products-components-cards-productivecard--default', + }); + const editButton = page.getByLabel('Edit'); + const editTooltip = page.getByRole('tooltip', { name: 'Edit' }); + const deleteButton = page.getByLabel('Delete'); + const deleteTooltip = page.getByRole('tooltip', { name: 'Delete' }); + + await editButton.hover(); + await expect(page).toHaveNoACViolations('ProductiveCard @hover - default'); + await expect(editTooltip).toBeVisible(); + + await deleteButton.hover(); + await expect(page).toHaveNoACViolations('ProductiveCard @hover - default'); + await expect(deleteTooltip).toBeVisible(); + }); }); diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index bced659372..231c862b1b 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.32.0-rc.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-cloud-cognitive-core@2.30.0-rc.0...@carbon/ibm-cloud-cognitive-core@2.32.0-rc.0) (2024-12-09) + + +### Bug Fixes + +* update to Carbon 11 compatible versions to latest ([#6437](https://github.com/carbon-design-system/ibm-products/issues/6437)) ([48d5c34](https://github.com/carbon-design-system/ibm-products/commit/48d5c34dca79a4b00fc69391a513431fa21295ee)) + + +### Features + +* add utils section to storybook ([#6394](https://github.com/carbon-design-system/ibm-products/issues/6394)) ([711eb72](https://github.com/carbon-design-system/ibm-products/commit/711eb72ef00d1f1935fd0fcec9e0c0383dff53dc)) + + + + + # [2.31.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-cloud-cognitive-core@2.31.0-rc.0...@carbon/ibm-cloud-cognitive-core@2.31.0) (2024-12-04) **Note:** Version bump only for package @carbon/ibm-cloud-cognitive-core diff --git a/packages/core/package.json b/packages/core/package.json index 228249352c..1776e8f14e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/ibm-cloud-cognitive-core", "private": true, - "version": "2.31.0", + "version": "2.32.0-rc.0", "license": "Apache-2.0", "main": "scripts/build.js", "repository": { @@ -29,7 +29,7 @@ }, "devDependencies": { "@carbon/grid": "^11.29.0", - "@carbon/ibm-products-styles": "^2.51.0", + "@carbon/ibm-products-styles": "^2.52.0-rc.0", "@carbon/layout": "^11.28.0", "@carbon/motion": "^11.24.0", "@carbon/react": "^1.71.1", diff --git a/packages/ibm-products-styles/CHANGELOG.md b/packages/ibm-products-styles/CHANGELOG.md index 136af44d9e..8c1e11f757 100644 --- a/packages/ibm-products-styles/CHANGELOG.md +++ b/packages/ibm-products-styles/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.52.0-rc.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-products-styles@2.50.0-rc.0...@carbon/ibm-products-styles@2.52.0-rc.0) (2024-12-09) + + +### Bug Fixes + +* **pageHeader:** menu button overflow issue on small screens ([#6502](https://github.com/carbon-design-system/ibm-products/issues/6502)) ([0072e42](https://github.com/carbon-design-system/ibm-products/commit/0072e424fc1f25fbf58be67f7d4d792643f26c30)) + + +### Features + +* **BreadcrumbWithOverflow:** adopt overflowMenuV12 and floating ui ([#6411](https://github.com/carbon-design-system/ibm-products/issues/6411)) ([f716852](https://github.com/carbon-design-system/ibm-products/commit/f716852c979a0b9556e853fad7e7a7774b4c6579)) + + + + + # [2.51.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-products-styles@2.51.0-rc.0...@carbon/ibm-products-styles@2.51.0) (2024-12-04) **Note:** Version bump only for package @carbon/ibm-products-styles diff --git a/packages/ibm-products-styles/package.json b/packages/ibm-products-styles/package.json index 487789f0ac..62c59ec9f1 100644 --- a/packages/ibm-products-styles/package.json +++ b/packages/ibm-products-styles/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/ibm-products-styles", "description": "Carbon for IBM Products styles", - "version": "2.51.0", + "version": "2.52.0-rc.0", "license": "Apache-2.0", "installConfig": { "hoistingLimits": "none" @@ -52,7 +52,7 @@ "cross-env": "^7.0.3", "glob": "^10.3.10", "jest": "^29.7.0", - "jest-config-ibm-cloud-cognitive": "^1.14.0", + "jest-config-ibm-cloud-cognitive": "^1.15.0-rc.0", "jest-environment-jsdom": "^29.7.0", "npm-check-updates": "^16.14.12", "npm-run-all": "^4.1.5", diff --git a/packages/ibm-products-styles/src/__tests__/__snapshots__/styles.test.js.snap b/packages/ibm-products-styles/src/__tests__/__snapshots__/styles.test.js.snap index 58081841e0..da590c0057 100644 --- a/packages/ibm-products-styles/src/__tests__/__snapshots__/styles.test.js.snap +++ b/packages/ibm-products-styles/src/__tests__/__snapshots__/styles.test.js.snap @@ -129,6 +129,10 @@ p.c4p--about-modal__copyright-text:first-child { fill: var(--cds-button-danger-primary, #da1e28); } +.c4p--apikey-modal__checkmark-icon { + fill: var(--cds-button-primary, #0f62fe); +} + .c4p--action-set { align-items: stretch; justify-content: flex-end; diff --git a/packages/ibm-products-styles/src/components/APIKeyModal/_api-key-modal.scss b/packages/ibm-products-styles/src/components/APIKeyModal/_api-key-modal.scss index f5b19278a5..8b0cf5202d 100644 --- a/packages/ibm-products-styles/src/components/APIKeyModal/_api-key-modal.scss +++ b/packages/ibm-products-styles/src/components/APIKeyModal/_api-key-modal.scss @@ -50,3 +50,7 @@ $block-class: #{c4p-settings.$pkg-prefix}--apikey-modal; .#{$block-class}__error-icon svg { fill: $button-danger-primary; } + +.#{$block-class}__checkmark-icon { + fill: $button-primary; +} diff --git a/packages/ibm-products-styles/src/components/PageHeader/_page-header.scss b/packages/ibm-products-styles/src/components/PageHeader/_page-header.scss index fa1adf8ebe..e2878df4b0 100644 --- a/packages/ibm-products-styles/src/components/PageHeader/_page-header.scss +++ b/packages/ibm-products-styles/src/components/PageHeader/_page-header.scss @@ -538,14 +538,8 @@ $right-section-alt-width: 100% - $left-section-alt-width; } .#{$block-class}__subtitle-row { - display: -webkit-box; - overflow: hidden; - max-width: 100%; margin-top: $spacing-03; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - @include breakpoint-up('md') { max-width: $left-section-std-width; } @@ -559,6 +553,24 @@ $right-section-alt-width: 100% - $left-section-alt-width; @include type.type-style('body-01'); } + .#{$block-class}__subtitle-tooltip .#{$carbon-prefix}--definition-term { + border-bottom: 0; + letter-spacing: inherit; + } + + // overwrites the existing styles to make the popover bigger because in some cases the narrow space can be too constricting for the header + .#{$block-class}__subtitle-tooltip + .#{$carbon-prefix}--popover-content.#{$carbon-prefix}--definition-tooltip { + max-inline-size: fit-content; + } + + .#{$block-class}__subtitle-text { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } + .#{$block-class}__available-row { @include type.type-style('body-01'); diff --git a/packages/ibm-products-styles/src/components/SidePanel/_side-panel.scss b/packages/ibm-products-styles/src/components/SidePanel/_side-panel.scss index ea8f7818f7..6f53924328 100644 --- a/packages/ibm-products-styles/src/components/SidePanel/_side-panel.scss +++ b/packages/ibm-products-styles/src/components/SidePanel/_side-panel.scss @@ -112,7 +112,8 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set; border-right: 1px solid $border-subtle-02; } &.#{$block-class}.#{$block-class}--has-slug, - &.#{$block-class}.#{$block-class}--has-ai-label { + &.#{$block-class}.#{$block-class}--has-ai-label, + &.#{$block-class}.#{$block-class}--has-decorator { border-color: transparent; box-shadow: inset 0 -80px 70px -65px $ai-inner-shadow, 0 4px 10px 2px $ai-drop-shadow; @@ -199,14 +200,16 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set; &.#{$block-class}:has(.#{$block-class}__action-toolbar), &.#{$block-class}--has-action-toolbar, &.#{$block-class}--has-slug, - &.#{$block-class}--has-ai-label { + &.#{$block-class}--has-ai-label, + &.#{$block-class}--has-decorator { --#{$block-class}--title-padding-right: #{$spacing-10}; } &.#{$block-class}:has(.#{$block-class}__action-toolbar), &.#{$block-class}--has-action-toolbar { &.#{$block-class}--has-slug, - &.#{$block-class}--has-ai-label { + &.#{$block-class}--has-ai-label, + &.#{$block-class}--has-decorator { --#{$block-class}--title-padding-right: #{$spacing-11}; } } @@ -313,7 +316,8 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set; } &.#{$block-class}--has-slug .#{$block-class}--scrolls, - &.#{$block-class}--has-ai-label .#{$block-class}--scrolls { + &.#{$block-class}--has-ai-label .#{$block-class}--scrolls, + &.#{$block-class}--has-decorator .#{$block-class}--scrolls { @include utilities.ai-popover-gradient('default', 0, 'layer'); box-shadow: inset 0 -80px 70px -65px $ai-inner-shadow, @@ -368,7 +372,8 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set; } .#{$block-class}__slug-and-close, - .#{$block-class}__ai-label-and-close { + .#{$block-class}__ai-label-and-close, + .#{$block-class}__decorator-and-close { position: absolute; z-index: 10; /* must be higher than title container border bottom */ top: 0; @@ -470,7 +475,9 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set; inset: 0; } +/* stylelint-disable-next-line carbon/theme-token-use */ .#{$block-class}--has-slug + .#{$block-class}__overlay, -.#{$block-class}--has-ai-label + .#{$block-class}__overlay { +.#{$block-class}--has-ai-label + .#{$block-class}__overlay, +.#{$block-class}--has-decorator + .#{$block-class}__overlay { background-color: $ai-overlay; } diff --git a/packages/ibm-products-web-components/CHANGELOG.md b/packages/ibm-products-web-components/CHANGELOG.md index 2833b0b306..05e249daa5 100644 --- a/packages/ibm-products-web-components/CHANGELOG.md +++ b/packages/ibm-products-web-components/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.5.0-rc.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-products-web-components@0.3.0-rc.0...@carbon/ibm-products-web-components@0.5.0-rc.0) (2024-12-09) + + +### Bug Fixes + +* **Side Panel Web components:** storybook controls issue ([#6500](https://github.com/carbon-design-system/ibm-products/issues/6500)) ([19a7536](https://github.com/carbon-design-system/ibm-products/commit/19a75363d0683ed6765e0d713c3f68878eaeaef2)) +* **Tearsheet in Web Components:** cancel button not working as expected ([#6458](https://github.com/carbon-design-system/ibm-products/issues/6458)) ([2461f81](https://github.com/carbon-design-system/ibm-products/commit/2461f8120431c125ceb826ccb5d139a82d49f39c)) +* **Tearsheet web components:** control changes doesn't reflect in stories ([#6413](https://github.com/carbon-design-system/ibm-products/issues/6413)) ([84f6d14](https://github.com/carbon-design-system/ibm-products/commit/84f6d140d0476868a13b18ea886515d856bb0835)) +* update to Carbon 11 compatible versions to latest ([#6437](https://github.com/carbon-design-system/ibm-products/issues/6437)) ([48d5c34](https://github.com/carbon-design-system/ibm-products/commit/48d5c34dca79a4b00fc69391a513431fa21295ee)) + + + + + # [0.4.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-products-web-components@0.4.0-rc.0...@carbon/ibm-products-web-components@0.4.0) (2024-12-04) **Note:** Version bump only for package @carbon/ibm-products-web-components diff --git a/packages/ibm-products-web-components/package.json b/packages/ibm-products-web-components/package.json index e20a8d0e1a..26436b2aa1 100644 --- a/packages/ibm-products-web-components/package.json +++ b/packages/ibm-products-web-components/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/ibm-products-web-components", "description": "Carbon for IBM Products Web Components", - "version": "0.4.0", + "version": "0.5.0-rc.0", "license": "Apache-2.0", "main": "es/index.js", "module": "es/index.js", @@ -49,7 +49,7 @@ "wca": "web-component-analyzer analyze src --outFile custom-elements.json" }, "dependencies": { - "@carbon/ibm-products-styles": "^2.51.0", + "@carbon/ibm-products-styles": "^2.52.0-rc.0", "@carbon/styles": "1.70.0", "@carbon/web-components": "2.18.0", "lit": "^3.1.0" diff --git a/packages/ibm-products/CHANGELOG.md b/packages/ibm-products/CHANGELOG.md index f201427248..30046bbfed 100644 --- a/packages/ibm-products/CHANGELOG.md +++ b/packages/ibm-products/CHANGELOG.md @@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.56.0-rc.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-products@2.54.0-rc.0...@carbon/ibm-products@2.56.0-rc.0) (2024-12-09) + + +### Bug Fixes + +* **APIKeyModal:** implement focus return to generate button ([#6440](https://github.com/carbon-design-system/ibm-products/issues/6440)) ([535e871](https://github.com/carbon-design-system/ibm-products/commit/535e87142413695c530952f7b314201c8a35becc)) +* **APIKeyModal:** implement useFocus hook ([#6290](https://github.com/carbon-design-system/ibm-products/issues/6290)) ([4a92770](https://github.com/carbon-design-system/ibm-products/commit/4a92770d9179f6b59bfe18e53c998fdb51bd794a)) +* **conditionBuilder:** issue fix and data correction ([#6421](https://github.com/carbon-design-system/ibm-products/issues/6421)) ([1f5b63e](https://github.com/carbon-design-system/ibm-products/commit/1f5b63ec428ddd2a2b2ee7c4153c83c2fff6a61c)) +* **CreateTearsheet:** Dynamically disable/hide the experimentalSecondarySubmit button in CreateTearsheet ([#6412](https://github.com/carbon-design-system/ibm-products/issues/6412)) ([2eab4db](https://github.com/carbon-design-system/ibm-products/commit/2eab4db31164aed9f84c003466fce0bc74467ce3)) +* **EmptyState:** empty states throws hydration errors in next js due to dynamic id ([#6508](https://github.com/carbon-design-system/ibm-products/issues/6508)) ([d5767e8](https://github.com/carbon-design-system/ibm-products/commit/d5767e8e9e5bc078d7324fbda71268a4d42d0bee)) +* **FullPageError:** accessibility violations ([#6503](https://github.com/carbon-design-system/ibm-products/issues/6503)) ([3f45528](https://github.com/carbon-design-system/ibm-products/commit/3f45528cd037c0232c2699c18d8bf63a5da8891b)) +* **side panel in react:** slideIn not working as expected ([#6501](https://github.com/carbon-design-system/ibm-products/issues/6501)) ([9d07dab](https://github.com/carbon-design-system/ibm-products/commit/9d07dab82cefd4046f65f8cac25d1d829f6d7c1a)) +* **tearsheet:** address portalTarget type ([#6400](https://github.com/carbon-design-system/ibm-products/issues/6400)) ([a11d036](https://github.com/carbon-design-system/ibm-products/commit/a11d0364a5beaa3d018db559656c3d7806e1b484)) +* update accessibility-checker version ([#6525](https://github.com/carbon-design-system/ibm-products/issues/6525)) ([d8c7051](https://github.com/carbon-design-system/ibm-products/commit/d8c70518087e7e41fdf1aa45cbbf692389058d56)) +* update to Carbon 11 compatible versions to latest ([#6437](https://github.com/carbon-design-system/ibm-products/issues/6437)) ([48d5c34](https://github.com/carbon-design-system/ibm-products/commit/48d5c34dca79a4b00fc69391a513431fa21295ee)) +* **useravatar:** accessibility issue and add avt complex state ([#6399](https://github.com/carbon-design-system/ibm-products/issues/6399)) ([4a70821](https://github.com/carbon-design-system/ibm-products/commit/4a70821b85688730a9d4484340da2517ee079db1)) + + +### Features + +* add utils section to storybook ([#6394](https://github.com/carbon-design-system/ibm-products/issues/6394)) ([711eb72](https://github.com/carbon-design-system/ibm-products/commit/711eb72ef00d1f1935fd0fcec9e0c0383dff53dc)) +* **BreadcrumbWithOverflow:** adopt overflowMenuV12 and floating ui ([#6411](https://github.com/carbon-design-system/ibm-products/issues/6411)) ([f716852](https://github.com/carbon-design-system/ibm-products/commit/f716852c979a0b9556e853fad7e7a7774b4c6579)) +* **coachmark:** Add default opening for not-stacked coachmarks ([#6516](https://github.com/carbon-design-system/ibm-products/issues/6516)) ([73ee693](https://github.com/carbon-design-system/ibm-products/commit/73ee693dda68bbbc0ccc6ed802b0efe8f528950e)) +* Decouple lottie-web dependency from our repo ([#6477](https://github.com/carbon-design-system/ibm-products/issues/6477)) ([6b0a75b](https://github.com/carbon-design-system/ibm-products/commit/6b0a75ba4d15d7b2e640b4bb6992635ec3ab770c)) +* **productive card:** floating ui for overflow menu ([#6395](https://github.com/carbon-design-system/ibm-products/issues/6395)) ([42a1362](https://github.com/carbon-design-system/ibm-products/commit/42a1362ad2ff4998f8286ed0698a82c4ec8ea552)) + + + + + # [2.55.0](https://github.com/carbon-design-system/ibm-products/compare/@carbon/ibm-products@2.55.0-rc.0...@carbon/ibm-products@2.55.0) (2024-12-04) **Note:** Version bump only for package @carbon/ibm-products diff --git a/packages/ibm-products/package.json b/packages/ibm-products/package.json index 0ae4709375..32a41bf4f7 100644 --- a/packages/ibm-products/package.json +++ b/packages/ibm-products/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/ibm-products", "description": "Carbon for IBM Products", - "version": "2.55.0", + "version": "2.56.0-rc.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -81,7 +81,7 @@ "fs-extra": "^11.2.0", "glob": "^10.3.10", "jest": "^29.7.0", - "jest-config-ibm-cloud-cognitive": "^1.14.0", + "jest-config-ibm-cloud-cognitive": "^1.15.0-rc.0", "jest-environment-jsdom": "^29.7.0", "namor": "^1.1.2", "npm-check-updates": "^16.14.12", @@ -96,7 +96,7 @@ "dependencies": { "@babel/runtime": "^7.23.9", "@carbon/feature-flags": "^0.24.0", - "@carbon/ibm-products-styles": "^2.51.0", + "@carbon/ibm-products-styles": "^2.52.0-rc.0", "@carbon/telemetry": "^0.1.0", "@dnd-kit/core": "^6.0.8", "@dnd-kit/modifiers": "^7.0.0", diff --git a/packages/ibm-products/src/components/APIKeyModal/APIKeyDownloader.js b/packages/ibm-products/src/components/APIKeyModal/APIKeyDownloader.js index b947a58e10..2299d6b863 100644 --- a/packages/ibm-products/src/components/APIKeyModal/APIKeyDownloader.js +++ b/packages/ibm-products/src/components/APIKeyModal/APIKeyDownloader.js @@ -64,7 +64,7 @@ APIKeyDownloader.propTypes = { /** * body content for the downloader */ - body: PropTypes.string.isRequired, + body: PropTypes.string, /** * aria-label for the download link */ diff --git a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.stories.jsx b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.stories.jsx index f65a7487da..a47e798cc2 100644 --- a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.stories.jsx +++ b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.stories.jsx @@ -65,7 +65,7 @@ const defaultProps = { copyButtonText: 'Copy', copyIconDescription: 'Copy', hasAPIKeyVisibilityToggle: true, - downloadBodyText: + helperText: 'This is your unique API key and is non-recoverable. If you lose this API key, you will have to reset it.', downloadLinkText: 'Download as JSON', downloadLinkLabel: 'Download API Key in Java Script File format', @@ -74,8 +74,8 @@ const defaultProps = { downloadFileType: 'json', open: true, closeButtonText: 'Close', - generateSuccessTitle: 'API key successfully created', - editSuccessTitle: 'API key successfully saved', + generateSuccessMessage: 'API key successfully created', + editSuccessMessage: 'API key successfully saved', loadingText: 'Generating...', modalLabel: 'An example of Generate API key', }; @@ -301,7 +301,7 @@ const MultiStepTemplate = (args) => { )} {editSuccess && (
- Edited successfully + Edited successfully, API key successfully saved.
)} @@ -415,6 +415,7 @@ export const InstantGenerate = InstantTemplate.bind({}); InstantGenerate.args = { ...defaultProps, apiKeyLabel: 'Unique API Key', + generateTitle: 'Generate an API key', }; export const CustomGenerate = MultiStepTemplate.bind({}); @@ -428,6 +429,7 @@ CustomGenerate.args = { savedAllResources: false, savedResource: '', savedPermissions: '', + generateTitle: 'Generate an API key', }; CustomGenerate.parameters = { docs: { @@ -489,6 +491,7 @@ CustomEdit.args = { savedPermissions: 'Read only', editing: true, editButtonText: 'Save API key', + generateTitle: 'Save an API key', }; CustomEdit.parameters = { docs: { diff --git a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.test.js b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.test.js index 0e69271bec..f0833c35ce 100644 --- a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.test.js +++ b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.test.js @@ -36,20 +36,20 @@ const defaultProps = { copyButtonText: 'copy', copyIconDescription: 'copy icon description', customSteps: [], - downloadBodyText: 'download body', + helperText: 'download body', downloadFileName: 'filename', downloadFileType: 'json', downloadLinkText: 'download', downloadLinkLabel: 'Download API Key in Java Script File format', editButtonText: 'edit button', editSuccess: false, - editSuccessTitle: 'edited successfully', + editSuccessMessage: 'edited successfully', editing: false, error: false, errorText: 'an error occurred', generateButtonText: 'create button', generateSuccessBody: 'created successfully body', - generateSuccessTitle: 'created successfully title', + generateSuccessMessage: 'created successfully title', generateTitle: 'create title', hasAPIKeyVisibilityToggle: true, hasDownloadLink: true, @@ -135,7 +135,7 @@ describe(componentName, () => { getByText(props.loadingText, { selector: 'div' }); rerender(); await waitFor(() => getByText(props.downloadLinkLabel)); - getByText(props.downloadBodyText); + getByText(props.helperText); const modal = getByRole('presentation'); expect(modal.querySelector(`.${carbon.prefix}--text-input`).value).toBe( '444-444-444-444' @@ -201,7 +201,7 @@ describe(componentName, () => { customSteps, hasDownloadLink: false, }; - const { rerender, getByPlaceholderText, getByText } = render( + const { rerender, getByPlaceholderText, getByText, getAllByText } = render( ); @@ -252,7 +252,7 @@ describe(componentName, () => { rerender(); expect(screen.getByLabelText(props.apiKeyLabel).value).toBe('abc-123'); getByText(props.generateSuccessBody); - getByText(props.generateSuccessTitle); + getAllByText(props.generateSuccessMessage); await act(() => click(getByText(props.closeButtonText))); expect(onClose).toHaveBeenCalled(); }); @@ -361,7 +361,7 @@ describe(componentName, () => { onRequestEdit, }; - const { getByText, getByRole, rerender } = render( + const { getByText, getAllByText, getByRole, rerender } = render( ); @@ -373,7 +373,7 @@ describe(componentName, () => { await act(() => click(editButton)); expect(onRequestEdit).toHaveBeenCalledWith(nameInput.value); rerender(); - getByText(props.editSuccessTitle); + getAllByText(props.editSuccessMessage); }); it('toggles key visibility', async () => { diff --git a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.tsx b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.tsx index c7e0809c97..9b1548c4e7 100644 --- a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.tsx +++ b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.tsx @@ -26,7 +26,12 @@ import { Button, unstable_FeatureFlags as FeatureFlags, } from '@carbon/react'; -import { InformationFilled, Copy, ErrorFilled } from '@carbon/react/icons'; +import { + InformationFilled, + Copy, + ErrorFilled, + CheckmarkFilled, +} from '@carbon/react/icons'; import { APIKeyDownloader } from './APIKeyDownloader'; import { pkg } from '../../settings'; import { usePortalTarget } from '../../global/js/hooks/usePortalTarget'; @@ -68,12 +73,14 @@ export let APIKeyModal: React.FC = forwardRef( editButtonText, editSuccess, editSuccessTitle, + editSuccessMessage, editing, error, errorText, generateButtonText, generateSuccessBody, generateSuccessTitle, + generateSuccessMessage, generateTitle, hasAPIKeyVisibilityToggle, hasDownloadLink, @@ -96,6 +103,7 @@ export let APIKeyModal: React.FC = forwardRef( previousStepButtonText, selectorPrimaryFocus, showAPIKeyLabel, + helperText, // Collect any other property values passed in. ...rest @@ -103,6 +111,9 @@ export let APIKeyModal: React.FC = forwardRef( ref: React.Ref ) => { const [title, setTitle] = useState(null); + const [successMessage, setSuccessMessage] = useState< + string | null | undefined + >(null); const [copyError, setCopyError] = useState(false); const [name, setName] = useState(apiKeyName); const [currentStep, setCurrentStep] = useState(0); @@ -121,6 +132,7 @@ export let APIKeyModal: React.FC = forwardRef( }; const blockClass = `${pkg.prefix}--apikey-modal`; const localRef = useRef(undefined); + const PasswordInputRef = useRef(null); const modalRef = (ref || localRef) as MutableRefObject; const { firstElement, keyDownListener } = useFocus( modalRef, @@ -132,6 +144,9 @@ export let APIKeyModal: React.FC = forwardRef( if (copyRef.current && open && apiKeyLoaded) { copyRef.current.focus(); } + if (PasswordInputRef?.current) { + PasswordInputRef?.current.setAttribute('readOnly', 'true'); + } }, [open, apiKeyLoaded]); useEffect(() => { @@ -184,9 +199,11 @@ export let APIKeyModal: React.FC = forwardRef( useEffect(() => { if (editing && editSuccess) { - setTitle(editSuccessTitle); + setTitle(generateTitle); + setSuccessMessage(editSuccessMessage ?? editSuccessTitle); } else if (apiKeyLoaded) { - setTitle(generateSuccessTitle); + setTitle(generateTitle); + setSuccessMessage(generateSuccessMessage ?? generateSuccessTitle); } else if (hasSteps) { setTitle(customSteps[currentStep]?.title); } else { @@ -194,11 +211,14 @@ export let APIKeyModal: React.FC = forwardRef( } }, [ apiKeyLoaded, + loading, editing, editSuccess, editSuccessTitle, + editSuccessMessage, hasSteps, generateSuccessTitle, + generateSuccessMessage, generateTitle, currentStep, customSteps, @@ -276,6 +296,8 @@ export let APIKeyModal: React.FC = forwardRef( showPasswordLabel={showAPIKeyLabel} hidePasswordLabel={hideAPIKeyLabel} tooltipPosition="left" + helperText={helperText} + ref={PasswordInputRef} /> )} {!editing && apiKey && !hasAPIKeyVisibilityToggle && ( @@ -314,7 +336,11 @@ export let APIKeyModal: React.FC = forwardRef(
-

+

{copyError ? copyErrorText : errorText}

@@ -332,12 +358,32 @@ export let APIKeyModal: React.FC = forwardRef( downloadLinkLabel={downloadLinkLabel} /> ) : ( -
+
{generateSuccessBody}
)}
)} + + {(editSuccess || (apiKeyLoaded && successMessage)) && ( +
+ +

+ {successMessage} +

+
+ )} )} @@ -376,6 +422,20 @@ const downloadRequiredProps = (type) => // Return a placeholder if not released and not enabled by feature flag APIKeyModal = pkg.checkComponentEnabled(APIKeyModal, componentName); +export const deprecatedProps = { + /** + * deprecated + * title for a successful edit + */ + editSuccessTitle: PropTypes.string, + + /** + * deprecated + * title for a successful key generation + */ + generateSuccessTitle: PropTypes.string, +}; + APIKeyModal.propTypes = { /** * the api key that's displayed to the user when a request to create is fulfilled. @@ -436,7 +496,7 @@ APIKeyModal.propTypes = { /** * the content that appears that indicates the key is downloadable */ - downloadBodyText: downloadRequiredProps(PropTypes.string), + downloadBodyText: PropTypes.string, /** * designates the name of downloadable json file with the key. if not specified will default to 'apikey' */ @@ -464,7 +524,7 @@ APIKeyModal.propTypes = { /** * title for a successful edit */ - editSuccessTitle: editRequiredProps(PropTypes.string), + editSuccessMessage: editRequiredProps(PropTypes.string), /** * designates if the modal is in the edit mode */ @@ -490,7 +550,7 @@ APIKeyModal.propTypes = { /** * title for a successful key generation */ - generateSuccessTitle: PropTypes.string, + generateSuccessMessage: PropTypes.string, /** * default title for the modal in generate key mode */ @@ -503,6 +563,10 @@ APIKeyModal.propTypes = { * designates if user is able to download the api key */ hasDownloadLink: PropTypes.bool, + /** + * helper text for password input + */ + helperText: PropTypes.string, /** * label text that's displayed when hovering over visibility toggler to hide key */ @@ -582,6 +646,8 @@ APIKeyModal.propTypes = { * label text that's displayed when hovering over visibility toggler to show key */ showAPIKeyLabel: PropTypes.string, + + ...deprecatedProps, }; APIKeyModal.displayName = componentName; diff --git a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.types.ts b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.types.ts index 200528760a..16233bbd7e 100644 --- a/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.types.ts +++ b/packages/ibm-products/src/components/APIKeyModal/APIKeyModal.types.ts @@ -75,9 +75,14 @@ interface APIKeyModalCommonProps { */ generateSuccessBody?: ReactNode; /** + * * @deprecated use `generateSuccessMessage` instead * title for a successful key generation */ generateSuccessTitle?: string; + /** + * success message for a successful key generation + */ + generateSuccessMessage?: string; /** * default title for the modal in generate key mode */ @@ -160,6 +165,10 @@ interface APIKeyModalCommonProps { * label text that's displayed when hovering over visibility toggler to show key */ showAPIKeyLabel?: string; + /** + * helper text for password input + */ + helperText?: string; } type CustomStepConditionalProps = { @@ -195,9 +204,14 @@ type EditingConditionalProps = { */ editSuccess: boolean; /** + * * @deprecated use `editSuccessMessage` instead * title for a successful edit */ - editSuccessTitle: string; + editSuccessTitle?: string; + /** + * success message for edit + */ + editSuccessMessage: string; }; type HasDownloadLinkProps = { @@ -208,7 +222,7 @@ type HasDownloadLinkProps = { /** * the content that appears that indicates the key is downloadable */ - downloadBodyText: string; + downloadBodyText?: string; /** * designates the name of downloadable json file with the key. if not specified will default to 'apikey' */ diff --git a/packages/ibm-products/src/components/Card/Card.tsx b/packages/ibm-products/src/components/Card/Card.tsx index f26e51bba2..ad2d9504c1 100644 --- a/packages/ibm-products/src/components/Card/Card.tsx +++ b/packages/ibm-products/src/components/Card/Card.tsx @@ -118,9 +118,9 @@ export const Card = forwardRef( onClick, onKeyDown, onPrimaryButtonClick, + onSecondaryButtonClick, overflowActions = Object.freeze([]), overflowAriaLabel, - onSecondaryButtonClick, pictogram: Pictogram, primaryButtonDisabled, primaryButtonHref, @@ -179,8 +179,7 @@ export const Card = forwardRef( autoAlign menuAlignment={pos} size={size} - aria-label={overflowAriaLabel} - label={iconDescription} + label={overflowAriaLabel || iconDescription} > {overflowActions.map(({ id, itemText, ...rest }) => ( diff --git a/packages/ibm-products/src/components/Coachmark/Coachmark.test.js b/packages/ibm-products/src/components/Coachmark/Coachmark.test.js index 5ec8084cb6..57fe87bd23 100644 --- a/packages/ibm-products/src/components/Coachmark/Coachmark.test.js +++ b/packages/ibm-products/src/components/Coachmark/Coachmark.test.js @@ -5,14 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; -import { - render, - screen, - act, - waitFor, - fireEvent, -} from '@testing-library/react'; // https://testing-library.com/docs/react-testing-library/intro +import React, { act } from 'react'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; // https://testing-library.com/docs/react-testing-library/intro + import userEvent from '@testing-library/user-event'; import { pkg } from '../../settings'; import uuidv4 from '../../global/js/utils/uuidv4'; @@ -23,8 +18,14 @@ import { CoachmarkOverlayElement, CoachmarkOverlayElements, } from '..'; -import { BEACON_KIND } from './utils/enums'; +import { + BEACON_KIND, + COACHMARK_ALIGNMENT, + COACHMARK_OVERLAY_KIND, +} from './utils/enums'; import { CoachmarkDragbar } from './CoachmarkDragbar'; +import { getOffsetTune } from './utils/constants'; +import { clamp } from './utils/helpers'; const blockClass = `${pkg.prefix}--coachmark`; const componentName = Coachmark.displayName; @@ -74,6 +75,14 @@ describe(componentName, () => { expect(screen.getByTestId(dataTestId)).toHaveClass(blockClass); }); + it('Check coachmark can be open by default', () => { + renderCoachmark({ + 'data-testid': dataTestId, + isOpenByDefault: true, + }); + expect(isCoachmarkVisible()).toBeTruthy(); + }); + it('has no accessibility violations', async () => { const { container } = renderCoachmark(); await expect(container).toBeAccessible(componentName); @@ -211,11 +220,108 @@ describe(componentName, () => { ); }); - it('Check coachmark can be open by default', () => { + it('renders the theme prop', async () => { renderCoachmark({ 'data-testid': dataTestId, - isOpenByDefault: true, + theme: 'dark', }); - expect(isCoachmarkVisible()).toBeTruthy(); + + await expect(screen.getByTestId(dataTestId)).toHaveClass( + `${pkg.prefix}--coachmark__dark` + ); + }); + + it('tests getOffsetTune util', async () => { + let result; + const distanceOffset = 24; + const coachmarkTarget = { + targetRect: { + width: 200, + height: 200, + }, + align: COACHMARK_ALIGNMENT.TOP, + }; + + // Test case when it is a tooltip + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.TOOLTIP); + expect(result.left).toBe(0); + expect(result.top).toBe(0); + + // Test top alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.TOP; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(100); + expect(result.top).toBe(0); + + // Test top left alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.TOP_LEFT; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(distanceOffset); + expect(result.top).toBe(0); + + // Test top right alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.TOP_RIGHT; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(200 - distanceOffset); + expect(result.top).toBe(0); + + // Test bottom alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.BOTTOM; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(100); + expect(result.top).toBe(200); + + // Test bottom left alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.BOTTOM_LEFT; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(distanceOffset); + expect(result.top).toBe(200); + + // Test bottom right alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.BOTTOM_RIGHT; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(200 - distanceOffset); + expect(result.top).toBe(200); + + // Test left alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.LEFT; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(0); + expect(result.top).toBe(100); + + // Test left top alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.LEFT_TOP; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(0); + expect(result.top).toBe(distanceOffset); + + // Test left bottom alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.LEFT_BOTTOM; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(0); + expect(result.top).toBe(200 - distanceOffset); + + // Test right alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.RIGHT; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(200); + expect(result.top).toBe(100); + + // Test right top alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.RIGHT_TOP; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(200); + expect(result.top).toBe(distanceOffset); + + // Test right bottom alignment + coachmarkTarget.align = COACHMARK_ALIGNMENT.RIGHT_BOTTOM; + result = getOffsetTune(coachmarkTarget, COACHMARK_OVERLAY_KIND.FLOATING); + expect(result.left).toBe(200); + expect(result.top).toBe(200 - distanceOffset); + }); + + it('tests clamp helper function', () => { + expect(clamp(100, 50, 20)).toBe(50); + expect(clamp(40, 10, 50)).toBe(40); }); }); diff --git a/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx b/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx index 0e0f29d3aa..4b20c4b738 100644 --- a/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx +++ b/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx @@ -105,6 +105,7 @@ export let CoachmarkOverlay = forwardRef( const handleKeyPress = (event) => { const { shiftKey, key } = event; + /* istanbul ignore next */ if (key === 'Enter' || key === ' ') { setA11yDragMode((prevVal) => !prevVal); } else if (a11yDragMode) { @@ -151,6 +152,7 @@ export let CoachmarkOverlay = forwardRef( return style; }, [isBeacon, isDraggable, coachmark, kind]); + /* istanbul ignore next */ function handleDragBounds(x, y) { let xRes = x; let yRes = y; @@ -254,6 +256,7 @@ const useWindowDimensions = () => { ); useEffect(() => { + /* istanbul ignore next */ function handleResize() { setWindowDimensions(getWindowDimensions()); } diff --git a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js index d9cf59f68f..485911ba7a 100644 --- a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js +++ b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js @@ -22,12 +22,18 @@ const children = `hello, world (${uuidv4()})`; const dataTestId = uuidv4(); const className = `class-${uuidv4()}`; -const childrenContent = ( +const childrenContent = [ -); + />, + , +]; const renderCoachmarkWithOverlayElements = ( { ...rest } = {}, @@ -165,4 +171,22 @@ describe(componentName, () => { expect(screen.getByRole('img')).toBeInTheDocument(); }); + + it('calls onNext', async () => { + const user = userEvent.setup(); + const onNext = jest.fn(); + renderCoachmarkWithOverlayElements({ + 'data-testid': dataTestId, + onNext, + }); + const beaconOrButton = screen.getByRole('button', { + name: 'Show information', + }); + await act(() => user.click(beaconOrButton)); + const nextButton = screen.getByRole('button', { + name: 'Next', + }); + await act(() => user.click(nextButton)); + await expect(onNext).toHaveBeenCalled(); + }); }); diff --git a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx index 2db4b39812..710db555aa 100644 --- a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx +++ b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx @@ -77,6 +77,18 @@ export interface CoachmarkOverlayElementsProps { * The label for the Close button. */ closeButtonLabel?: string; + /** + * Callback called when clicking on the Next button. + */ + onNext?: () => void; + /** + * Callback called when clicking on the Previous button. + */ + onBack?: () => void; + /** + * Current step of the coachmarks. + */ + currentStep?: number; } // NOTE: the component SCSS is not imported here: it is rolled up separately. @@ -96,6 +108,9 @@ const defaults = { nextButtonText: 'Next', previousButtonLabel: 'Back', closeButtonLabel: 'Got it', + onNext: undefined, + onBack: undefined, + currentStep: 0, }; /** * Composable container to allow for the displaying of CoachmarkOverlayElement @@ -112,9 +127,12 @@ export let CoachmarkOverlayElements = React.forwardRef< isVisible = defaults.isVisible, media, renderMedia, + currentStep = defaults.currentStep, nextButtonText = defaults.nextButtonText, previousButtonLabel = defaults.previousButtonLabel, closeButtonLabel = defaults.closeButtonLabel, + onNext = defaults.onNext, + onBack = defaults.onBack, // Collect any other property values passed in. ...rest }, @@ -123,7 +141,7 @@ export let CoachmarkOverlayElements = React.forwardRef< const buttonFocusRef = useRef | undefined>(undefined); const scrollRef = useRef(undefined); const [scrollPosition, setScrollPosition] = useState(0); - const [currentProgStep, _setCurrentProgStep] = useState(0); + const [currentProgStep, _setCurrentProgStep] = useState(currentStep); const coachmark = useCoachmark(); const hasMedia = media || renderMedia; @@ -145,6 +163,16 @@ export let CoachmarkOverlayElements = React.forwardRef< [currentProgStep, renderMedia] ); + useEffect(() => { + // When current step is set by props + // scroll to the appropriate view on the carrousel + const targetStep = clamp(currentStep, progStepFloor, progStepCeil); + + scrollRef?.current?.scrollToView?.(targetStep); + // Avoid circular call to this hook + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentStep]); + useEffect(() => { // On mount, one of the two primary buttons ("next" or "close") // will be rendered and must have focus. (a11y) @@ -222,7 +250,6 @@ export let CoachmarkOverlayElements = React.forwardRef< ) : ( <> } onScroll={(scrollPercent) => { setScrollPosition(scrollPercent); @@ -248,6 +275,7 @@ export let CoachmarkOverlayElements = React.forwardRef< ); scrollRef?.current?.scrollToView?.(targetStep); setCurrentProgStep(targetStep); + onBack?.(); }} > {previousButtonLabel} @@ -268,6 +296,7 @@ export let CoachmarkOverlayElements = React.forwardRef< ); scrollRef?.current?.scrollToView?.(targetStep); setCurrentProgStep(targetStep); + onNext?.(); }} > {nextButtonText} @@ -320,6 +349,10 @@ CoachmarkOverlayElements.propTypes = { * The label for the Close button. */ closeButtonLabel: PropTypes.string, + /** + * Current step of the coachmarks + */ + currentStep: PropTypes.number, /** * The visibility of CoachmarkOverlayElements is * managed in the parent component. @@ -344,6 +377,14 @@ CoachmarkOverlayElements.propTypes = { * The label for the Next button. */ nextButtonText: PropTypes.string, + /** + * Optional callback called when clicking on the Previous button. + */ + onBack: PropTypes.func, + /** + * Optional callback called when clicking on the Next button. + */ + onNext: PropTypes.func, /** * The label for the Previous button. */ diff --git a/packages/ibm-products/src/components/CoachmarkStack/CoachmarkStack.test.js b/packages/ibm-products/src/components/CoachmarkStack/CoachmarkStack.test.js index dd67b208f2..f46f0cdc3e 100644 --- a/packages/ibm-products/src/components/CoachmarkStack/CoachmarkStack.test.js +++ b/packages/ibm-products/src/components/CoachmarkStack/CoachmarkStack.test.js @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; -import { render, screen, act } from '@testing-library/react'; // https://testing-library.com/docs/react-testing-library/intro +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; // https://testing-library.com/docs/react-testing-library/intro import { pkg } from '../../settings'; import uuidv4 from '../../global/js/utils/uuidv4'; @@ -131,4 +131,74 @@ describe(componentName, () => { componentName ); }); + + it('calls the onClose prop', async () => { + const onClose = jest.fn(); + renderCoachmarkStack({ + title: 'Coachmark Stack', + description: 'Coachmark Stack Description', + navLinkLabels: ['Label 1', 'Label 2', 'Label 3'], + tagline: 'Test Tagline', + 'data-testid': dataTestId, + onClose, + }); + expect(onClose).not.toHaveBeenCalled(); + + const coachmarkStackButton = screen.getByRole('button', { + name: /Test Tagline/, + }); + + await act(() => userEvent.click(coachmarkStackButton)); + + const closeButton = screen.getAllByRole('button', { + name: /Close/, + })[0]; + + await act(() => userEvent.click(closeButton)); + + expect(onClose).toHaveBeenCalled(); + }); + + it('opens a stacked coachmark', async () => { + const onClose = jest.fn(); + renderCoachmarkStack({ + title: 'Coachmark Stack', + description: 'Coachmark Stack Description', + navLinkLabels: ['Label 1', 'Label 2', 'Label 3'], + tagline: 'Test Tagline', + 'data-testid': dataTestId, + onClose, + }); + + // gets the trigger to open the overlay + const coachmarkStackButton = screen.getByRole('button', { + name: /Test Tagline/, + }); + await act(() => userEvent.click(coachmarkStackButton)); + + // Gets the label button to open a stacked item + const labelButton = screen.getByRole('button', { + name: /Label 1/, + }); + await act(() => userEvent.click(labelButton)); + + // Gets the overlay element + const coachmarkOverlay = document.querySelector( + `.${pkg.prefix}--coachmark-overlay` + ); + + // tests to see if the element has the is-stacked class + expect(coachmarkOverlay).toHaveClass( + `${pkg.prefix}--coachmark-stack-element--is-stacked` + ); + + // pressing escape should close the stacked item + await act(() => userEvent.keyboard('{Escape}')); + + expect(coachmarkOverlay).not.toHaveClass( + `${pkg.prefix}--coachmark-stack-element--is-stacked` + ); + + await act(() => userEvent.keyboard('{Escape}')); + }); }); diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx index fd86105ec9..460c6235e9 100644 --- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx +++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.tsx @@ -99,8 +99,7 @@ const ConditionBuilderContent = ({ }, [actionState]); useEffect(() => { if (initialState?.enabledDefault) { - setRootState?.(initialConditionState.current as ConditionBuilderState); - initialConditionState.current = null; + setRootState?.(initialState.state); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialState]); @@ -120,12 +119,17 @@ const ConditionBuilderContent = ({ const onRemove = useCallback( (groupId) => { + const groups = rootState?.groups?.filter( + (group) => groupId !== group?.id + ); setRootState?.({ ...rootState, - groups: rootState - ? rootState?.groups?.filter((group) => groupId !== group?.id) - : [], + groups: rootState ? groups : [], }); + //set the initial state to empty. + if (groups?.length === 0) { + initialConditionState.current = null; + } }, [setRootState, rootState] ); diff --git a/packages/ibm-products/src/components/CreateTearsheet/CreateTearsheet.tsx b/packages/ibm-products/src/components/CreateTearsheet/CreateTearsheet.tsx index 97011dbb26..78cf0d3a3c 100644 --- a/packages/ibm-products/src/components/CreateTearsheet/CreateTearsheet.tsx +++ b/packages/ibm-products/src/components/CreateTearsheet/CreateTearsheet.tsx @@ -310,6 +310,7 @@ export let CreateTearsheet = forwardRef( verticalPosition, closeIconDescription: '', }} + currentStep={currentStep} >
diff --git a/packages/ibm-products/src/components/Datagrid/Datagrid/DatagridSelectAll.tsx b/packages/ibm-products/src/components/Datagrid/Datagrid/DatagridSelectAll.tsx index 068c90d101..d2a0597270 100644 --- a/packages/ibm-products/src/components/Datagrid/Datagrid/DatagridSelectAll.tsx +++ b/packages/ibm-products/src/components/Datagrid/Datagrid/DatagridSelectAll.tsx @@ -9,7 +9,10 @@ import React, { useLayoutEffect, useState } from 'react'; import { TableSelectAll } from '@carbon/react'; import cx from 'classnames'; import { pkg } from '../../../settings'; -import { handleOnPageSelectAllRowData } from './addons/stateReducer'; +import { + handleOnPageSelectAllRowData, + handleSelectAllRowData, +} from './addons/stateReducer'; import { DataGridState, DataGridToggleAllRowsProps } from '../types'; const blockClass = `${pkg.prefix}--datagrid`; @@ -41,6 +44,8 @@ const SelectAll = (datagridState: DataGridState) => { dispatch, rows, getRowId, + toggleAllRowsSelected, + withVirtualScroll, onAllRowSelect, } = datagridState; const isFirstColumnStickyLeft = @@ -75,6 +80,33 @@ const SelectAll = (datagridState: DataGridState) => { return onChange?.(event); }; + const handleSelectAllChange = (event) => { + if (indeterminate) { + handleSelectAllRowData({ + dispatch, + rows, + getRowId, + indeterminate: true, + isChecked: undefined, + }); + toggleAllRowsSelected(false); + onAllRowSelect?.(rows, event); + + return onChange?.({ + target: { checked: false }, + } as any); + } + handleSelectAllRowData({ + dispatch, + rows, + getRowId, + isChecked: event.target.checked, + indeterminate, + }); + onAllRowSelect?.(rows, event); + return onChange?.(event); + }; + return ( { } )} name={`${tableId}-select-all-checkbox-name`} - onSelect={handleOnPageSelectAllChange} + onSelect={ + withVirtualScroll ? handleSelectAllChange : handleOnPageSelectAllChange + } disabled={isFetching || selectProps?.disabled} id={`${tableId}-select-all-checkbox-id`} /> diff --git a/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.stories.jsx b/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.stories.jsx index 570d12f0b1..0e546fc78f 100644 --- a/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.stories.jsx +++ b/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.stories.jsx @@ -38,8 +38,11 @@ const Template = (args) => { version. Please migrate to{' '} FullPageError - - . + {' '} + by running{' '} + + npx @carbon/upgrade migrate ibm-products-update-http-errors --write +
} > diff --git a/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.tsx b/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.tsx index 79b62be23f..86eff4c912 100644 --- a/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.tsx +++ b/packages/ibm-products/src/components/HTTPErrors/HTTPError403/HTTPError403.tsx @@ -86,7 +86,7 @@ export let HTTPError403 = React.forwardRef( /**@ts-ignore*/ HTTPError403.deprecated = { level: 'warn', - details: `Please replace ${componentName} with FullPageError`, + details: `${componentName} is deprecated. Please migrate to FullPageError by running npx @carbon/upgrade migrate ibm-products-update-http-errors --write`, }; // Return a placeholder if not released and not enabled by feature flag diff --git a/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.stories.jsx b/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.stories.jsx index 88af50aaae..af62418445 100644 --- a/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.stories.jsx +++ b/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.stories.jsx @@ -45,8 +45,11 @@ const Template = (args) => { version. Please migrate to{' '} FullPageError - - . + {' '} + by running{' '} + + npx @carbon/upgrade migrate ibm-products-update-http-errors --write + } > diff --git a/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.tsx b/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.tsx index 9a3d980936..c44b083c9e 100644 --- a/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.tsx +++ b/packages/ibm-products/src/components/HTTPErrors/HTTPError404/HTTPError404.tsx @@ -76,7 +76,7 @@ export let HTTPError404 = React.forwardRef( /**@ts-ignore*/ HTTPError404.deprecated = { level: 'warn', - details: `Please replace ${componentName} with FullPageError`, + details: `${componentName} is deprecated. Please migrate to FullPageError by running npx @carbon/upgrade migrate ibm-products-update-http-errors --write`, }; // Return a placeholder if not released and not enabled by feature flag diff --git a/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.stories.jsx b/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.stories.jsx index 2c6160e919..f47e27fc7f 100644 --- a/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.stories.jsx +++ b/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.stories.jsx @@ -45,8 +45,11 @@ const Template = (args) => { version. Please migrate to{' '} FullPageError - - . + {' '} + by running{' '} + + npx @carbon/upgrade migrate ibm-products-update-http-errors --write + } > diff --git a/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.tsx b/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.tsx index c8d8fa3c6f..533b9687a6 100644 --- a/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.tsx +++ b/packages/ibm-products/src/components/HTTPErrors/HTTPErrorOther/HTTPErrorOther.tsx @@ -86,7 +86,7 @@ export let HTTPErrorOther = React.forwardRef( /**@ts-ignore*/ HTTPErrorOther.deprecated = { level: 'warn', - details: `Please replace ${componentName} with FullPageError`, + details: `${componentName} is deprecated. Please migrate to FullPageError by running npx @carbon/upgrade migrate ibm-products-update-http-errors --write`, }; // Return a placeholder if not released and not enabled by feature flag diff --git a/packages/ibm-products/src/components/PageHeader/PageHeader.stories.jsx b/packages/ibm-products/src/components/PageHeader/PageHeader.stories.jsx index 22e6b8fd6b..74ac52968c 100644 --- a/packages/ibm-products/src/components/PageHeader/PageHeader.stories.jsx +++ b/packages/ibm-products/src/components/PageHeader/PageHeader.stories.jsx @@ -486,7 +486,7 @@ const pageActionsOverflowLabel = 'Page actions...'; const subtitle = 'Optional subtitle if necessary'; const longSubtitle = - 'Optional subtitle if necessary, which is very long in this case, but will need to be handled somehow. It just keeps going on and on and on and on and on.'; + 'Optional subtitle if necessary, which is very long in this case, but will need to be handled somehow. It just keeps going on and on and on and on and on and on and on and on and on and on and on.'; const demoSubtitle = 'This report details the monthly authentication failures'; const dummyPageContent = ( diff --git a/packages/ibm-products/src/components/PageHeader/PageHeader.tsx b/packages/ibm-products/src/components/PageHeader/PageHeader.tsx index aa2c4a6cc1..8d339ec5e2 100644 --- a/packages/ibm-products/src/components/PageHeader/PageHeader.tsx +++ b/packages/ibm-products/src/components/PageHeader/PageHeader.tsx @@ -15,6 +15,7 @@ import { usePrefix, ButtonProps, PopoverAlignment, + DefinitionTooltip, } from '@carbon/react'; import { TagProps } from '@carbon/react/lib/components/Tag/Tag'; import React, { @@ -51,6 +52,7 @@ import cx from 'classnames'; import { getDevtoolsProps } from '../../global/js/utils/devtools'; import { pkg } from '../../settings'; import { useResizeObserver } from '../../global/js/hooks/useResizeObserver'; +import { checkHeightOverflow } from '../../global/js/utils/checkForOverflow'; const componentName = 'PageHeader'; @@ -901,12 +903,20 @@ export let PageHeader = React.forwardRef( const displayedBreadcrumbs = getBreadcrumbs(); + const subtitleRef = useRef(null); + const isOverflowing = checkHeightOverflow(subtitleRef.current); + const subtitleContent = ( + + {subtitle} + + ); + return ( <>
+ />
) : null} - {subtitle ? ( + {subtitle && ( - {subtitle} + {isOverflowing ? ( + + {subtitleContent} + + ) : ( + subtitleContent + )} - ) : null} + )} {children ? ( diff --git a/packages/ibm-products/src/components/PageHeader/PageHeaderTitle.js b/packages/ibm-products/src/components/PageHeader/PageHeaderTitle.js index 188776d5f8..c0d8371e86 100644 --- a/packages/ibm-products/src/components/PageHeader/PageHeaderTitle.js +++ b/packages/ibm-products/src/components/PageHeader/PageHeaderTitle.js @@ -5,11 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useLayoutEffect, useRef, useState } from 'react'; +import React, { useRef } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { DefinitionTooltip, SkeletonText } from '@carbon/react'; import { EditInPlace } from '../EditInPlace'; +import { checkWidthOverflow } from '../../global/js/utils/checkForOverflow'; /** * @@ -39,25 +40,8 @@ export const PageHeaderTitle = ({ blockClass, hasBreadcrumbRow, title }) => { let titleText; let isEditable = !!onSave; - const [isEllipsisApplied, setIsEllipsisApplied] = useState(); - const longTitleRef = useRef(undefined); - const titleRef = useRef(undefined); - - useLayoutEffect(() => { - setIsEllipsisApplied(isEllipsisActive()); - }, [longTitleRef, titleRef, title]); - - const isEllipsisActive = () => { - if (longTitleRef.current) { - return ( - longTitleRef.current?.offsetWidth < longTitleRef.current?.scrollWidth - ); - } else if (titleRef.current) { - return titleRef.current?.offsetWidth < titleRef.current?.scrollWidth; - } - - return false; - }; + const titleRef = useRef(); + const isEllipsisApplied = checkWidthOverflow(titleRef.current); if (text || !content) { if (text === undefined && typeof title === 'string') { @@ -66,6 +50,12 @@ export const PageHeaderTitle = ({ blockClass, hasBreadcrumbRow, title }) => { } const TitleIcon = icon; + const titleContent = ( + + {text} + + ); + titleInnards = ( <> {icon && !loading ? ( @@ -97,18 +87,10 @@ export const PageHeaderTitle = ({ blockClass, hasBreadcrumbRow, title }) => { definition={text} className={`${blockClass}__tooltip`} > - - {text} - + {titleContent} ) : ( - - {text} - + titleContent )} ); diff --git a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx index c1bd9dccaf..57edc3de02 100644 --- a/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx +++ b/packages/ibm-products/src/components/ProductiveCard/ProductiveCard.tsx @@ -92,7 +92,8 @@ export interface ProductiveCardProps extends PropsWithChildren { */ overflowActions?: overflowAction[]; /** - * Aria label prop required for OverflowMenu + * Sets the text for the OverflowMenu aria label and the OverflowMenu trigger button tooltip. + * Overrides `iconDescription` prop. */ overflowAriaLabel?: string; /** @@ -149,14 +150,17 @@ export interface ProductiveCardProps extends PropsWithChildren { titleSize?: 'default' | 'large'; /** - * Tooltip icon description + * Sets the text for the OverflowMenu trigger button tooltip and OverflowMenu aria label, + * gets overridden by the `overflowAriaLabel` prop. + * + * @deprecated Please use the `overflowAriaLabel` prop instead. */ iconDescription?: string; } export let ProductiveCard = forwardRef( ( - { actionsPlacement = 'top', iconDescription, ...rest }: ProductiveCardProps, + { actionsPlacement = 'top', ...rest }: ProductiveCardProps, ref: ForwardedRef ) => { const validProps = prepareProps(rest, [ @@ -171,7 +175,6 @@ export let ProductiveCard = forwardRef( { const [open, setOpen] = useState(false); @@ -479,6 +493,7 @@ const SlideOverTemplate = ({ ref={testRef} aiLabel={aiLabel && sampleAILabel} slug={slug && sampleAILabel} + decorator={decorator && sampleAILabel} launcherButtonRef={buttonRef} > {!minimalContent && } @@ -492,6 +507,7 @@ const FirstElementDisabledTemplate = ({ actions, aiLabel, slug, + decorator, ...args }) => { const [open, setOpen] = useState(false); @@ -515,6 +531,7 @@ const FirstElementDisabledTemplate = ({ ref={testRef} aiLabel={aiLabel && sampleAILabel} slug={slug && sampleAILabel} + decorator={decorator && sampleAILabel} launcherButtonRef={buttonRef} > {!minimalContent && ( @@ -553,7 +570,7 @@ const FirstElementDisabledTemplate = ({ }; // eslint-disable-next-line react/prop-types -const StepTemplate = ({ actions, aiLabel, slug, ...args }) => { +const StepTemplate = ({ actions, aiLabel, slug, decorator, ...args }) => { const [open, setOpen] = useState(false); const [currentStep, setCurrentStep] = useState(0); const buttonRef = useRef(undefined); @@ -576,6 +593,7 @@ const StepTemplate = ({ actions, aiLabel, slug, ...args }) => { actions={actionSets[actions]} aiLabel={aiLabel && sampleAILabel} slug={slug && sampleAILabel} + decorator={decorator && sampleAILabel} launcherButtonRef={buttonRef} > { }; // eslint-disable-next-line react/prop-types -const SlideInTemplate = ({ actions, aiLabel, slug, ...args }) => { +const SlideInTemplate = ({ actions, aiLabel, slug, decorator, ...args }) => { const [open, setOpen] = useState(false); const buttonRef = useRef(undefined); @@ -612,6 +630,7 @@ const SlideInTemplate = ({ actions, aiLabel, slug, ...args }) => { actions={actionSets[actions]} aiLabel={aiLabel && sampleAILabel} slug={slug && sampleAILabel} + decorator={decorator && sampleAILabel} launcherButtonRef={buttonRef} > @@ -675,7 +694,7 @@ export const WithAILabel = SlideOverTemplate.bind({}); WithAILabel.args = { includeOverlay: true, actions: 0, - aiLabel: 1, + decorator: 1, ...defaultStoryProps, }; diff --git a/packages/ibm-products/src/components/SidePanel/SidePanel.test.js b/packages/ibm-products/src/components/SidePanel/SidePanel.test.js index afcd189839..12cf981362 100644 --- a/packages/ibm-products/src/components/SidePanel/SidePanel.test.js +++ b/packages/ibm-products/src/components/SidePanel/SidePanel.test.js @@ -32,6 +32,26 @@ const selectorPageContentValue = '#side-panel-test-page-content'; const onRequestCloseFn = jest.fn(); const onUnmountFn = jest.fn(); +const sampleAILabel = ( + + +
+

AI Explained

+

84%

+

Confidence score

+

+ This is not really Lorem Ipsum but the spell checker did not like the + previous text with it's non-words which is why this unwieldy + sentence, should one choose to call it that, here. +

+
+

Model type

+

Foundation model

+
+
+
+); + const renderSidePanel = ({ ...rest } = {}, children =

test

) => render( { ); expect(navigationAction).toBeTruthy(); }); - it('should not have AI Label when it is not passed', () => { + + it('should have AI Label when it is passed through slug', () => { + const { container } = renderSidePanel({ + slug: sampleAILabel, + }); + expect(container.querySelector('.aiLabel-container')).toBeTruthy(); + }); + + it('should not have a ai label container when a it is not passed', () => { const { container } = renderSidePanel(); expect(container.querySelector('.aiLabel-container')).toBe(null); }); + it('should have AI Label when it is passed', () => { - const sampleAILabel = ( - - -
-

AI Explained

-

84%

-

Confidence score

-

- This is not really Lorem Ipsum but the spell checker did not like - the previous text with it's non-words which is why this - unwieldy sentence, should one choose to call it that, here. -

-
-

Model type

-

Foundation model

-
-
-
- ); const { container } = renderSidePanel({ aiLabel: sampleAILabel, }); expect(container.querySelector('.aiLabel-container')).toBeTruthy(); }); + + it('should have AI Label when it is passed to decorator', () => { + const { container } = renderSidePanel({ + decorator: sampleAILabel, + }); + expect(container.querySelector('.aiLabel-container')).toBeTruthy(); + }); + it('should throw console warning if labelText passed without Title', () => { const consoleWarnSpy = jest .spyOn(console, 'warn') diff --git a/packages/ibm-products/src/components/SidePanel/SidePanel.tsx b/packages/ibm-products/src/components/SidePanel/SidePanel.tsx index 76534937c3..94c020c21c 100644 --- a/packages/ibm-products/src/components/SidePanel/SidePanel.tsx +++ b/packages/ibm-products/src/components/SidePanel/SidePanel.tsx @@ -170,16 +170,22 @@ type SidePanelBaseProps = { slideIn?: boolean; /** - * @deprecated please use the `aiLabel` prop + * @deprecated please use the `decorator` instead * **Experimental:** Provide a `Slug` component to be rendered inside the `SidePanel` component */ slug?: ReactNode; /** + * @deprecated please use the `decorator` instead * Optional prop that is intended for any scenario where something is being generated by AI to reinforce AI transparency, accountability, and explainability at the UI level. */ aiLabel?: ReactNode; + /** + * Provide a `decorator` component to be rendered inside the `SidePanel` component + */ + decorator?: ReactNode; + /** * Sets the subtitle text */ @@ -247,6 +253,7 @@ export let SidePanel = React.forwardRef( closeIconDescription = defaults.closeIconDescription, condensedActions, currentStep = defaults.currentStep, + decorator, id = blockClass, includeOverlay, labelText, @@ -670,7 +677,9 @@ export let SidePanel = React.forwardRef( [`${blockClass}--right-placement`]: placement === 'right', [`${blockClass}--left-placement`]: placement === 'left', [`${blockClass}--slide-in`]: slideIn, - [`${blockClass}--has-ai-label`]: !!aiLabel || !!slug, + [`${blockClass}--has-decorator`]: decorator, + [`${blockClass}--has-slug`]: slug, + [`${blockClass}--has-ai-label`]: aiLabel, [`${blockClass}--condensed-actions`]: condensedActions, [`${blockClass}--has-overlay`]: includeOverlay, }, @@ -704,29 +713,39 @@ export let SidePanel = React.forwardRef( ); const renderHeader = () => { - const aiLabelCloseSize = + const closeSize = actions && actions.length && /l/.test(size) ? 'md' : 'sm'; - let normalizedAILabel; + let normalizedDecorator; /** * slug is deprecated * can remove this condition in future release */ - if (slug && slug['type']?.displayName === 'Slug') { - normalizedAILabel = React.cloneElement( + if (slug && slug['type']?.displayName === 'AILabel') { + normalizedDecorator = React.cloneElement( slug as React.ReactElement, { // slug size is sm unless actions and size > md - size: aiLabelCloseSize, + size: closeSize, } ); } if (aiLabel && aiLabel['type']?.displayName === 'AILabel') { - normalizedAILabel = React.cloneElement( + normalizedDecorator = React.cloneElement( aiLabel as React.ReactElement, { // aiLabel size is sm unless actions and size > md - size: aiLabelCloseSize, + size: closeSize, + } + ); + } + + if (decorator?.['type']?.displayName === 'AILabel') { + normalizedDecorator = React.cloneElement( + decorator as React.ReactElement, + { + // decorator size is sm unless actions and size > md + size: closeSize, } ); } @@ -745,7 +764,7 @@ export let SidePanel = React.forwardRef( {currentStep > 0 && ( + +
+ {mainText} + +
+
+ + ); +}; + // These are tests than apply to both Tearsheet and TearsheetNarrow // and also (with extra props and omitting button tests) to CreateTearsheetNarrow let tooManyButtonsTestedAlready = false; @@ -262,40 +298,6 @@ const commonTests = (Ts, name, props, testActions) => { }); it('should return focus to the launcher button', async () => { - const mainText = 'Main content 1'; - const inputId = 'stacked-input-1'; - - // eslint-disable-next-line react/prop-types - const DummyComponent = ({ open }) => { - const buttonRef = React.useRef(undefined); - - return ( - <> - - -
- {mainText} - -
-
- - ); - }; - const { rerender, getByText, getByTestId } = render( ); @@ -320,6 +322,19 @@ const commonTests = (Ts, name, props, testActions) => { await act(() => new Promise((resolve) => setTimeout(resolve, 0))); expect(launchButtonEl).toHaveFocus(); }); + + it('should call onBlur only once', async () => { + const { getByTestId } = render(); + + const inputEl = getByTestId(inputId); + const closeButton = screen.getByRole('button', { + name: closeIconDescription, + }); + + expect(inputEl).toHaveFocus(); + await act(() => userEvent.click(closeButton)); + expect(onBlur).toHaveBeenCalledTimes(1); + }); } it('is visible when open is true', async () => { diff --git a/packages/ibm-products/src/components/Tearsheet/TearsheetShell.tsx b/packages/ibm-products/src/components/Tearsheet/TearsheetShell.tsx index 74dd3e7376..707d62a460 100644 --- a/packages/ibm-products/src/components/Tearsheet/TearsheetShell.tsx +++ b/packages/ibm-products/src/components/Tearsheet/TearsheetShell.tsx @@ -65,6 +65,11 @@ interface TearsheetShellProps extends PropsWithChildren { */ className?: string; + /** + * Used to track the current step on components which use `StepsContext` and `TearsheetShell` + */ + currentStep?: number; + /** * A description of the flow, displayed in the header area of the tearsheet. */ @@ -199,13 +204,9 @@ export type CloseIconDescriptionTypes = type stackTypes = { open: Array<{ (a: number, b: number): void; - checkFocus?: () => void; - claimFocus?: () => void; }>; all: Array<{ (a: number, b: number): void; - checkFocus?: () => void; - claimFocus?: () => void; }>; sizes: Array; }; @@ -242,6 +243,7 @@ export const TearsheetShell = React.forwardRef( children, className, closeIconDescription, + currentStep, description, hasCloseIcon, headerActions, @@ -274,7 +276,7 @@ export const TearsheetShell = React.forwardRef( const modalRef = (ref || localRef) as MutableRefObject; const { width } = useResizeObserver(resizer); const prevOpen = usePreviousValue(open); - const { firstElement, keyDownListener, specifiedElement } = useFocus( + const { firstElement, keyDownListener } = useFocus( modalRef, selectorPrimaryFocus ); @@ -309,29 +311,22 @@ export const TearsheetShell = React.forwardRef( setDepth(newDepth); setPosition(newPosition); } - handleStackChange.checkFocus = function () { - // if we are now the topmost tearsheet, ensure we have focus - if ( - open && - position === depth && - modalRefValue && - !modalRefValue.contains(document.activeElement) - ) { - handleStackChange.claimFocus(); - } - }; - - // Callback to give the tearsheet the opportunity to claim focus - handleStackChange.claimFocus = function () { - claimFocus(firstElement, modalRef, selectorPrimaryFocus); - }; useEffect(() => { - if (open) { + if (open && position === depth) { // Focusing the first element or selectorPrimaryFocus element claimFocus(firstElement, modalRef, selectorPrimaryFocus); } - }, [firstElement, modalRef, open, selectorPrimaryFocus]); + }, [ + currentStep, + depth, + firstElement, + modalRef, + modalRefValue, + open, + position, + selectorPrimaryFocus, + ]); useEffect(() => { if (prevOpen && !open && launcherButtonRef) { @@ -341,24 +336,6 @@ export const TearsheetShell = React.forwardRef( } }, [launcherButtonRef, open, prevOpen]); - useEffect(() => { - if (open && position !== depth) { - setTimeout(() => { - if (selectorPrimaryFocus) { - return specifiedElement?.focus(); - } - firstElement?.focus(); - }, 0); - } - }, [ - position, - depth, - firstElement, - open, - specifiedElement, - selectorPrimaryFocus, - ]); - useEffect(() => { const notify = () => stack.all.forEach((handler) => { @@ -366,7 +343,6 @@ export const TearsheetShell = React.forwardRef( Math.min(stack.open.length, maxDepth), stack.open.indexOf(handler) + 1 ); - handler.checkFocus?.(); }); // Register this tearsheet's stack change callback/listener. @@ -401,14 +377,6 @@ export const TearsheetShell = React.forwardRef( }; }, [open, size]); - function handleFocus() { - // If something within us is receiving focus but we are not the topmost - // stacked tearsheet, transfer focus to the topmost tearsheet instead - if (position < depth) { - stack.open[stack.open.length - 1].claimFocus?.(); - } - } - if (position <= depth) { // Include a modal header if and only if one or more of these is given. // We can't use a Wrap for the ModalHeader because ComposedModal requires @@ -464,7 +432,6 @@ export const TearsheetShell = React.forwardRef( !areAllSameSizeVariant(), })} {...{ onClose, open, selectorPrimaryFocus }} - onFocus={handleFocus} onKeyDown={keyDownListener} preventCloseOnClickOutside={!isPassive} ref={modalRef} diff --git a/packages/ibm-products/src/global/js/hooks/useFocus.js b/packages/ibm-products/src/global/js/hooks/useFocus.js index e6a7dd052a..b4cc99a601 100644 --- a/packages/ibm-products/src/global/js/hooks/useFocus.js +++ b/packages/ibm-products/src/global/js/hooks/useFocus.js @@ -122,9 +122,9 @@ export const claimFocus = ( specifiedEl && window?.getComputedStyle(specifiedEl)?.display !== 'none' ) { - return specifiedEl.focus(); + setTimeout(() => specifiedEl.focus(), 0); } + } else { + setTimeout(() => firstElement?.focus(), 0); } - - setTimeout(() => firstElement?.focus(), 0); }; diff --git a/packages/ibm-products/src/global/js/utils/__tests__/checkForOverflow.test.js b/packages/ibm-products/src/global/js/utils/__tests__/checkForOverflow.test.js new file mode 100644 index 0000000000..2144e23e88 --- /dev/null +++ b/packages/ibm-products/src/global/js/utils/__tests__/checkForOverflow.test.js @@ -0,0 +1,40 @@ +/** + * Copyright IBM Corp. 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { checkWidthOverflow, checkHeightOverflow } from '../checkForOverflow'; + +const normalElm = { + offsetWidth: 200, + scrollWidth: 100, + offsetHeight: 200, + scrollHeight: 100, +}; + +const overflowElm = { + offsetWidth: 100, + scrollWidth: 200, + offsetHeight: 100, + scrollHeight: 200, +}; + +describe('checkForOverflow', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('detects width overflow', () => { + expect(checkWidthOverflow(normalElm)).toBe(false); + expect(checkWidthOverflow(overflowElm)).toBe(true); + expect(checkWidthOverflow()).toBe(false); + }); + + it('detects height overflow', () => { + expect(checkHeightOverflow(normalElm)).toBe(false); + expect(checkHeightOverflow(overflowElm)).toBe(true); + expect(checkHeightOverflow()).toBe(false); + }); +}); diff --git a/packages/ibm-products/src/global/js/utils/checkForOverflow.ts b/packages/ibm-products/src/global/js/utils/checkForOverflow.ts new file mode 100644 index 0000000000..386389d4b2 --- /dev/null +++ b/packages/ibm-products/src/global/js/utils/checkForOverflow.ts @@ -0,0 +1,24 @@ +// +// Copyright IBM Corp. 2024, 2024 +// +// This source code is licensed under the Apache-2.0 license found in the +// LICENSE file in the root directory of this source tree. +// + +/** + * used to calculate if a element is overflowing the width or height of an area + */ + +export const checkWidthOverflow = (el: HTMLElement | null): boolean => { + if (el) { + return el?.offsetWidth < el?.scrollWidth; + } + return false; +}; + +export const checkHeightOverflow = (el: HTMLElement | null): boolean => { + if (el) { + return el?.offsetHeight < el?.scrollHeight; + } + return false; +}; diff --git a/packages/ibm-products/telemetry.yml b/packages/ibm-products/telemetry.yml index e9786587f8..248c74a6ca 100644 --- a/packages/ibm-products/telemetry.yml +++ b/packages/ibm-products/telemetry.yml @@ -72,6 +72,7 @@ collect: - DatagridBatchActions - DatagridPagination - datagridState + - decorator - defaultColumn - defaultEmptyRowCount - description @@ -284,6 +285,7 @@ collect: - reactTableFiltersState - readOnlyTable - renderIcon + - renderMedia - renderRowHeader - renderRowHeaderDirection - resetToDefaultLabel @@ -517,6 +519,7 @@ collect: - toggleLabel - toggleLabelAlign # Coachmark + - isOpenByDefault - overlayClassName - overlayKind - overlayRef diff --git a/yarn.lock b/yarn.lock index 6b69ddda23..1c7b90e35f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1690,7 +1690,7 @@ __metadata: resolution: "@carbon/ibm-cloud-cognitive-core@workspace:packages/core" dependencies: "@carbon/grid": "npm:^11.29.0" - "@carbon/ibm-products-styles": "npm:^2.51.0" + "@carbon/ibm-products-styles": "npm:^2.52.0-rc.0" "@carbon/layout": "npm:^11.28.0" "@carbon/motion": "npm:^11.24.0" "@carbon/react": "npm:^1.71.1" @@ -1731,7 +1731,7 @@ __metadata: languageName: unknown linkType: soft -"@carbon/ibm-products-styles@npm:^2.51.0, @carbon/ibm-products-styles@workspace:packages/ibm-products-styles": +"@carbon/ibm-products-styles@npm:^2.52.0-rc.0, @carbon/ibm-products-styles@workspace:packages/ibm-products-styles": version: 0.0.0-use.local resolution: "@carbon/ibm-products-styles@workspace:packages/ibm-products-styles" dependencies: @@ -1741,7 +1741,7 @@ __metadata: cross-env: "npm:^7.0.3" glob: "npm:^10.3.10" jest: "npm:^29.7.0" - jest-config-ibm-cloud-cognitive: "npm:^1.14.0" + jest-config-ibm-cloud-cognitive: "npm:^1.15.0-rc.0" jest-environment-jsdom: "npm:^29.7.0" npm-check-updates: "npm:^16.14.12" npm-run-all: "npm:^4.1.5" @@ -1761,7 +1761,7 @@ __metadata: version: 0.0.0-use.local resolution: "@carbon/ibm-products-web-components@workspace:packages/ibm-products-web-components" dependencies: - "@carbon/ibm-products-styles": "npm:^2.51.0" + "@carbon/ibm-products-styles": "npm:^2.52.0-rc.0" "@carbon/icons": "npm:^11.53.0" "@carbon/motion": "npm:^11.24.0" "@carbon/styles": "npm:1.70.0" @@ -1820,7 +1820,7 @@ __metadata: "@babel/preset-typescript": "npm:^7.21.5" "@babel/runtime": "npm:^7.23.9" "@carbon/feature-flags": "npm:^0.24.0" - "@carbon/ibm-products-styles": "npm:^2.51.0" + "@carbon/ibm-products-styles": "npm:^2.52.0-rc.0" "@carbon/telemetry": "npm:^0.1.0" "@dnd-kit/core": "npm:^6.0.8" "@dnd-kit/modifiers": "npm:^7.0.0" @@ -1845,7 +1845,7 @@ __metadata: glob: "npm:^10.3.10" immutability-helper: "npm:^3.1.1" jest: "npm:^29.7.0" - jest-config-ibm-cloud-cognitive: "npm:^1.14.0" + jest-config-ibm-cloud-cognitive: "npm:^1.15.0-rc.0" jest-environment-jsdom: "npm:^29.7.0" lodash: "npm:^4.17.21" lottie-web: "npm:^5.12.2" @@ -15858,7 +15858,7 @@ __metadata: languageName: node linkType: hard -"jest-config-ibm-cloud-cognitive@npm:^1.14.0, jest-config-ibm-cloud-cognitive@workspace:config/jest-config-ibm-cloud-cognitive": +"jest-config-ibm-cloud-cognitive@npm:^1.15.0-rc.0, jest-config-ibm-cloud-cognitive@workspace:config/jest-config-ibm-cloud-cognitive": version: 0.0.0-use.local resolution: "jest-config-ibm-cloud-cognitive@workspace:config/jest-config-ibm-cloud-cognitive" dependencies: @@ -18610,11 +18610,11 @@ __metadata: linkType: hard "nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" bin: nanoid: bin/nanoid.cjs - checksum: ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679 + checksum: 2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40 languageName: node linkType: hard