diff --git a/.changeset/chatty-paws-attend.md b/.changeset/chatty-paws-attend.md deleted file mode 100644 index 2deee72b523..00000000000 --- a/.changeset/chatty-paws-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/react': patch ---- - -Add keyboard-accessible tooltip for truncated ActionList.Description diff --git a/.changeset/gentle-carpets-bake.md b/.changeset/gentle-carpets-bake.md deleted file mode 100644 index bb055b64e48..00000000000 --- a/.changeset/gentle-carpets-bake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/mcp': patch ---- - -Add motion and animation token support to MCP design token search, including `animation` and `duration` semantic prefixes, transition usage hints, and updated optimization recipes diff --git a/.changeset/gold-rooms-bake.md b/.changeset/gold-rooms-bake.md new file mode 100644 index 00000000000..1bc2ffe73d6 --- /dev/null +++ b/.changeset/gold-rooms-bake.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Banner: stack inline actions vertically on narrow viewports. diff --git a/.changeset/mcp-lineheight-tokens.md b/.changeset/mcp-lineheight-tokens.md deleted file mode 100644 index 206fa178de0..00000000000 --- a/.changeset/mcp-lineheight-tokens.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/mcp': patch ---- - -Expose lineHeight tokens in design token search results, added color name to token conversion and `lint_css` tool for self-check loop diff --git a/.changeset/modern-buckets-chew.md b/.changeset/modern-buckets-chew.md deleted file mode 100644 index d549ae837cb..00000000000 --- a/.changeset/modern-buckets-chew.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@primer/react": patch ---- - -- Fixes a bug where `ActionBar` menu items would be out of order if new items were mounted after the initial render -- Improves initial render performance for `ActionBar` diff --git a/.changeset/nine-buttons-lose.md b/.changeset/nine-buttons-lose.md new file mode 100644 index 00000000000..69945bd669e --- /dev/null +++ b/.changeset/nine-buttons-lose.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +AnchoredOverlay: Add CSS Anchor Positioning to `AnchoredOverlay` (under a feature flag) diff --git a/.changeset/odd-houses-pull.md b/.changeset/odd-houses-pull.md deleted file mode 100644 index a0fa93119ce..00000000000 --- a/.changeset/odd-houses-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": patch ---- - -Fix FormControl + SelectPanel accessible name to address SR issues diff --git a/.changeset/rotten-kids-refuse.md b/.changeset/rotten-kids-refuse.md deleted file mode 100644 index eafc4827c9e..00000000000 --- a/.changeset/rotten-kids-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": patch ---- - -Push margin-top of TimelineBody +1px diff --git a/.changeset/smart-queens-allow.md b/.changeset/smart-queens-allow.md deleted file mode 100644 index 5c2cf2d5442..00000000000 --- a/.changeset/smart-queens-allow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/react': patch ---- - -Fix(useRefObjectAsForwardedRef): fix ref failing to update when target changes diff --git a/.changeset/spinner-css-animation-sync.md b/.changeset/spinner-css-animation-sync.md deleted file mode 100644 index 1a47ed4d810..00000000000 --- a/.changeset/spinner-css-animation-sync.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/react': patch ---- - -perf(Spinner): replace Web Animations API with CSS animation-delay sync diff --git a/.changeset/stupid-bees-marry.md b/.changeset/stupid-bees-marry.md deleted file mode 100644 index ab7c9a9fe65..00000000000 --- a/.changeset/stupid-bees-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": minor ---- - -chore: always render ActionMenu in viewport when inside Dialog under feature flag diff --git a/.changeset/three-suns-move.md b/.changeset/three-suns-move.md deleted file mode 100644 index a122ea655c7..00000000000 --- a/.changeset/three-suns-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/react': patch ---- - -TextInputWithTokens: announce selected token values for screen readers. diff --git a/.changeset/toggle-switch-knob-spacing.md b/.changeset/toggle-switch-knob-spacing.md deleted file mode 100644 index 9dc19356d63..00000000000 --- a/.changeset/toggle-switch-knob-spacing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@primer/react': patch ---- - -ToggleSwitch: Updated with a 1px space around the knob to work better with updated primitives. diff --git a/.changeset/wise-pumas-grab.md b/.changeset/wise-pumas-grab.md deleted file mode 100644 index c70d4c6034c..00000000000 --- a/.changeset/wise-pumas-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": minor ---- - -Add `align` and `style` props to Dialog component diff --git a/.github/workflows/aat-reports.yml b/.github/workflows/aat-reports.yml index f85137e928b..1559c13b68d 100644 --- a/.github/workflows/aat-reports.yml +++ b/.github/workflows/aat-reports.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -65,7 +65,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/assign_release_conductor.yml b/.github/workflows/assign_release_conductor.yml index 1896175f61d..f258961d3ab 100644 --- a/.github/workflows/assign_release_conductor.yml +++ b/.github/workflows/assign_release_conductor.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' - run: npm ci diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ca7200b57d..da3bce3b4c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -36,7 +36,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -63,7 +63,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -96,7 +96,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -128,7 +128,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -153,7 +153,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -184,7 +184,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 93dcc36aa97..411e5f2f524 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 3c9b00503d9..ea0b2236310 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -10,7 +10,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/deploy_preview.yml b/.github/workflows/deploy_preview.yml index 41999d2eaa8..002207925d8 100644 --- a/.github/workflows/deploy_preview.yml +++ b/.github/workflows/deploy_preview.yml @@ -24,7 +24,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/deploy_preview_forks.yml b/.github/workflows/deploy_preview_forks.yml index 57f8a81fa95..d95ee8a7942 100644 --- a/.github/workflows/deploy_preview_forks.yml +++ b/.github/workflows/deploy_preview_forks.yml @@ -24,7 +24,7 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/figma_connect_publish.yml b/.github/workflows/figma_connect_publish.yml index e08c5bc9d66..4af700814f5 100644 --- a/.github/workflows/figma_connect_publish.yml +++ b/.github/workflows/figma_connect_publish.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' diff --git a/.github/workflows/lint-autofix.yml b/.github/workflows/lint-autofix.yml index dc6cc1a9be8..a3d17a814ce 100644 --- a/.github/workflows/lint-autofix.yml +++ b/.github/workflows/lint-autofix.yml @@ -92,7 +92,7 @@ jobs: - name: Set up Node.js if: steps.check_jobs.outputs.should_fix == 'true' && steps.pr.outputs.head_ref != '' - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/migration-status.yml b/.github/workflows/migration-status.yml index 38af60c38bb..ec46577c598 100644 --- a/.github/workflows/migration-status.yml +++ b/.github/workflows/migration-status.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index acde6f57679..d12add587db 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' diff --git a/.github/workflows/recommend-integration-tests.yml b/.github/workflows/recommend-integration-tests.yml index 031f23825ff..55bedc46d14 100644 --- a/.github/workflows/recommend-integration-tests.yml +++ b/.github/workflows/recommend-integration-tests.yml @@ -12,7 +12,7 @@ jobs: fetch-depth: 0 - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' @@ -58,7 +58,7 @@ jobs: await github.rest.issues.addLabels({...issue, labels: [INTEGRATION_LABEL_NAMES.recommended]}) await github.rest.issues.createComment({ ...issue, - body: '\n\n # ⚠️ Action required \n\n :wave: Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the [integration workflow](https://gh.io/testing_primer_at_dotcom). Or, apply the `integration-tests: skipped manually` label to skip these checks.' + body: '\n\n # ⚠️ Action required \n\n :wave: Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the [integration workflow](https://github.com/github/github-ui/actions/workflows/primer-react-pr-test.yml). Check the [integration testing docs](https://gh.io/testing_primer_at_dotcom) for step-by-step instructions. Or, apply the `integration-tests: skipped manually` label to skip these checks.' }) } else if (hasPassingLabel) { // recommend running integration tests again as there are new commits that might change the status @@ -66,6 +66,6 @@ jobs: await github.rest.issues.addLabels({...issue, labels: [INTEGRATION_LABEL_NAMES.recommended]}) await github.rest.issues.createComment({ ...issue, - body: '\n\n # ⚠️ Action required \n\n :wave: Hi, there are new commits since the last successful integration test. If you are GitHub staff, test these changes with github/github-ui using the [integration workflow](https://gh.io/testing_primer_at_dotcom). Or, apply the `integration-tests: skipped manually` label to skip these checks.' + body: '\n\n # ⚠️ Action required \n\n :wave: Hi, there are new commits since the last successful integration test. If you are GitHub staff, test these changes with github/github-ui using the [integration workflow](https://github.com/github/github-ui/actions/workflows/primer-react-pr-test.yml). Check the [integration testing docs](https://gh.io/testing_primer_at_dotcom) for step-by-step instructions. Or, apply the `integration-tests: skipped manually` label to skip these checks.' }) } diff --git a/.github/workflows/release-schedule.yml b/.github/workflows/release-schedule.yml index 78ecce0cafe..62fa54c7a12 100644 --- a/.github/workflows/release-schedule.yml +++ b/.github/workflows/release-schedule.yml @@ -28,7 +28,7 @@ jobs: schedule-id: 'P3IIVC4' token: ${{ secrets.PAGERDUTY_API_KEY_SID }} - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' - name: Install packages for github-script diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4edfc3ca13..3ca0dba1fb4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: persist-credentials: false - name: Set up Node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -75,7 +75,7 @@ jobs: fetch-depth: 0 - name: Set up Node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -119,7 +119,7 @@ jobs: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - name: Set up Node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/statuses.yml b/.github/workflows/statuses.yml index b24d68d68a7..69af7c39a84 100644 --- a/.github/workflows/statuses.yml +++ b/.github/workflows/statuses.yml @@ -20,7 +20,7 @@ jobs: with: ruby-version: 2.7 - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' - name: Install node deps diff --git a/.github/workflows/storybook-tests.yml b/.github/workflows/storybook-tests.yml index 1192d793d61..4e8eb149590 100644 --- a/.github/workflows/storybook-tests.yml +++ b/.github/workflows/storybook-tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/stress-tests.yml b/.github/workflows/stress-tests.yml index 8fb226a67e9..ae1e53f1c19 100644 --- a/.github/workflows/stress-tests.yml +++ b/.github/workflows/stress-tests.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/vrt-reports.yml b/.github/workflows/vrt-reports.yml index d199519406b..b5b2a97f88f 100644 --- a/.github/workflows/vrt-reports.yml +++ b/.github/workflows/vrt-reports.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' @@ -76,7 +76,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/vrt.yml b/.github/workflows/vrt.yml index 43023119d12..acac441b4f5 100644 --- a/.github/workflows/vrt.yml +++ b/.github/workflows/vrt.yml @@ -35,7 +35,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Alignment-light-css-anchor-positioning-linux.png b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Alignment-light-css-anchor-positioning-linux.png index a1d1eb327c3..4d42f44e486 100644 Binary files a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Alignment-light-css-anchor-positioning-linux.png and b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Alignment-light-css-anchor-positioning-linux.png differ diff --git a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Position-Grid-light-css-anchor-positioning-linux.png b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Position-Grid-light-css-anchor-positioning-linux.png index 7ca823934ce..cfc272e058d 100644 Binary files a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Position-Grid-light-css-anchor-positioning-linux.png and b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Anchor-Position-Grid-light-css-anchor-positioning-linux.png differ diff --git a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Fullscreen-Variant-light-css-anchor-positioning-linux.png b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Fullscreen-Variant-light-css-anchor-positioning-linux.png index d8905c4e284..edb90f94d08 100644 Binary files a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Fullscreen-Variant-light-css-anchor-positioning-linux.png and b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Fullscreen-Variant-light-css-anchor-positioning-linux.png differ diff --git a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Alignment-From-Anchor-light-css-anchor-positioning-linux.png b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Alignment-From-Anchor-light-css-anchor-positioning-linux.png index 9fd40da8ea8..0dfe120c911 100644 Binary files a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Alignment-From-Anchor-light-css-anchor-positioning-linux.png and b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Alignment-From-Anchor-light-css-anchor-positioning-linux.png differ diff --git a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Position-From-Anchor-light-css-anchor-positioning-linux.png b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Position-From-Anchor-light-css-anchor-positioning-linux.png index cd1446b4fb2..b3853ddc166 100644 Binary files a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Position-From-Anchor-light-css-anchor-positioning-linux.png and b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Offset-Position-From-Anchor-light-css-anchor-positioning-linux.png differ diff --git a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Scroll-With-Anchor-light-css-anchor-positioning-linux.png b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Scroll-With-Anchor-light-css-anchor-positioning-linux.png index 6b80e1f6afe..940be94e376 100644 Binary files a/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Scroll-With-Anchor-light-css-anchor-positioning-linux.png and b/.playwright/snapshots/components/AnchoredOverlay.test.ts-snapshots/AnchoredOverlay-Scroll-With-Anchor-light-css-anchor-positioning-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-primer-breakpoint-xs-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-primer-breakpoint-xs-linux.png new file mode 100644 index 00000000000..4bcd5ea5876 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-primer-breakpoint-xs-linux.png differ diff --git a/e2e/components/Banner.test.ts b/e2e/components/Banner.test.ts index 1ce4a1676d0..e5bfa8d35f8 100644 --- a/e2e/components/Banner.test.ts +++ b/e2e/components/Banner.test.ts @@ -73,6 +73,7 @@ const stories: Array<{title: string; id: string; viewports?: Array=18.0.0" } }, + "node_modules/@oddbird/css-anchor-positioning": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@oddbird/css-anchor-positioning/-/css-anchor-positioning-0.9.0.tgz", + "integrity": "sha512-G5nfb4sU0auxJH7VHafPwVJjr1GhH5uPSkmytGqhNftCpT3QEh8pFtMd4YHt1dRwb4o9qVZxlGSKUIc4TIrysQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@floating-ui/dom": "^1.7.5", + "@types/css-tree": "^2.3.11", + "css-tree": "^3.1.0", + "nanoid": "^5.1.6" + } + }, + "node_modules/@oddbird/css-anchor-positioning/node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@oddbird/css-anchor-positioning/node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/@oddbird/css-anchor-positioning/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -9037,6 +9083,12 @@ "@types/node": "*" } }, + "node_modules/@types/css-tree": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.11.tgz", + "integrity": "sha512-aEokibJOI77uIlqoBOkVbaQGC9zII0A+JH1kcTNKW2CwyYWD8KM6qdo+4c77wD3wZOQfJuNWAr9M4hdk+YhDIg==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "dev": true, @@ -27743,6 +27795,7 @@ "@github/relative-time-element": "^4.5.0", "@github/tab-container-element": "^4.8.2", "@lit-labs/react": "1.2.1", + "@oddbird/css-anchor-positioning": "^0.9.0", "@oddbird/popover-polyfill": "^0.5.2", "@primer/behaviors": "^1.10.2", "@primer/live-region-element": "^0.7.1", diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index fb74b257ec5..76514fa3178 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @primer/mcp +## 0.3.1 + +### Patch Changes + +- [#7632](https://github.com/primer/react/pull/7632) [`49d32f2`](https://github.com/primer/react/commit/49d32f26aa42d788d94a4962b0695c2b611037e1) Thanks [@lukasoppermann](https://github.com/lukasoppermann)! - Add motion and animation token support to MCP design token search, including `animation` and `duration` semantic prefixes, transition usage hints, and updated optimization recipes + +- [#7601](https://github.com/primer/react/pull/7601) [`e103951`](https://github.com/primer/react/commit/e10395113bae756edae8143fc51d39b7b519aa7b) Thanks [@lukasoppermann](https://github.com/lukasoppermann)! - Expose lineHeight tokens in design token search results, added color name to token conversion and `lint_css` tool for self-check loop + +- Updated dependencies [[`7e108fe`](https://github.com/primer/react/commit/7e108fea1a9f92ce22f46ff1d55bfe4753d89ad8), [`3dd2c78`](https://github.com/primer/react/commit/3dd2c78f768ad560ee0f37947af2c17ca8d7938c), [`f6d4311`](https://github.com/primer/react/commit/f6d431194d217fbb7d456e58bcbbcbb434896fe1), [`79c855a`](https://github.com/primer/react/commit/79c855abeb6f46d3fefafb236f00ea65dfcd1ed4), [`74762e2`](https://github.com/primer/react/commit/74762e265a44b4fa46fcb4db8fd5194cb81b14c4), [`9585669`](https://github.com/primer/react/commit/958566907a580b54b484ff2339b32315b8a3b4b7), [`f7bdd1c`](https://github.com/primer/react/commit/f7bdd1c04f8cbb17b6a913ba55f7d0855c8eebf1), [`17a103c`](https://github.com/primer/react/commit/17a103c0726ff2903e008a69dfd141461f99591f), [`e649da3`](https://github.com/primer/react/commit/e649da3c89b38f477fb574acf5bb06a49b41ee9d), [`1e54bdf`](https://github.com/primer/react/commit/1e54bdf72c9466f23c567cfdc73b7b5c243782a4)]: + - @primer/react@38.15.0 + ## 0.3.0 ### Minor Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index d9cc8cef99e..b520bbdbe2a 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,7 +1,7 @@ { "name": "@primer/mcp", "description": "An MCP server that connects AI tools to the Primer Design System", - "version": "0.3.0", + "version": "0.3.1", "type": "module", "bin": { "mcp": "./bin/mcp.js" @@ -37,7 +37,7 @@ "@modelcontextprotocol/sdk": "^1.24.0", "@primer/octicons": "^19.15.5", "@primer/primitives": "10.x || 11.x", - "@primer/react": "^38.12.0", + "@primer/react": "^38.15.0", "cheerio": "^1.0.0", "turndown": "^7.2.0", "zod": "^4.3.5" diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 1eb64517f28..e0c69077985 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,33 @@ # @primer/react +## 38.15.0 + +### Minor Changes + +- [#7524](https://github.com/primer/react/pull/7524) [`f7bdd1c`](https://github.com/primer/react/commit/f7bdd1c04f8cbb17b6a913ba55f7d0855c8eebf1) Thanks [@francinelucca](https://github.com/francinelucca)! - chore: always render ActionMenu in viewport when inside Dialog under feature flag + +- [#7594](https://github.com/primer/react/pull/7594) [`1e54bdf`](https://github.com/primer/react/commit/1e54bdf72c9466f23c567cfdc73b7b5c243782a4) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Add `align` and `style` props to Dialog component + +### Patch Changes + +- [#7529](https://github.com/primer/react/pull/7529) [`7e108fe`](https://github.com/primer/react/commit/7e108fea1a9f92ce22f46ff1d55bfe4753d89ad8) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - Add keyboard-accessible tooltip for truncated ActionList.Description + +- [#7585](https://github.com/primer/react/pull/7585) [`3dd2c78`](https://github.com/primer/react/commit/3dd2c78f768ad560ee0f37947af2c17ca8d7938c) Thanks [@iansan5653](https://github.com/iansan5653)! - - Fixes a bug where `ActionBar` menu items would be out of order if new items were mounted after the initial render + + - Improves initial render performance for `ActionBar` + +- [#7624](https://github.com/primer/react/pull/7624) [`f6d4311`](https://github.com/primer/react/commit/f6d431194d217fbb7d456e58bcbbcbb434896fe1) Thanks [@llastflowers](https://github.com/llastflowers)! - Fix FormControl + SelectPanel accessible name to address SR issues + +- [#7617](https://github.com/primer/react/pull/7617) [`79c855a`](https://github.com/primer/react/commit/79c855abeb6f46d3fefafb236f00ea65dfcd1ed4) Thanks [@llastflowers](https://github.com/llastflowers)! - Push margin-top of TimelineBody +1px + +- [#7635](https://github.com/primer/react/pull/7635) [`74762e2`](https://github.com/primer/react/commit/74762e265a44b4fa46fcb4db8fd5194cb81b14c4) Thanks [@iansan5653](https://github.com/iansan5653)! - Fix(useRefObjectAsForwardedRef): fix ref failing to update when target changes + +- [#7550](https://github.com/primer/react/pull/7550) [`9585669`](https://github.com/primer/react/commit/958566907a580b54b484ff2339b32315b8a3b4b7) Thanks [@hectahertz](https://github.com/hectahertz)! - perf(Spinner): replace Web Animations API with CSS animation-delay sync + +- [#7618](https://github.com/primer/react/pull/7618) [`17a103c`](https://github.com/primer/react/commit/17a103c0726ff2903e008a69dfd141461f99591f) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - TextInputWithTokens: announce selected token values for screen readers. + +- [#7588](https://github.com/primer/react/pull/7588) [`e649da3`](https://github.com/primer/react/commit/e649da3c89b38f477fb574acf5bb06a49b41ee9d) Thanks [@lukasoppermann](https://github.com/lukasoppermann)! - ToggleSwitch: Updated with a 1px space around the knob to work better with updated primitives. + ## 38.14.0 ### Minor Changes diff --git a/packages/react/package.json b/packages/react/package.json index dc741d23341..ce4b65c4361 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "@primer/react", "type": "module", - "version": "38.14.0", + "version": "38.15.0", "description": "An implementation of GitHub's Primer Design System using React", "main": "./dist/index.js", "module": "./dist/index.js", @@ -78,6 +78,7 @@ "@github/relative-time-element": "^4.5.0", "@github/tab-container-element": "^4.8.2", "@lit-labs/react": "1.2.1", + "@oddbird/css-anchor-positioning": "^0.9.0", "@oddbird/popover-polyfill": "^0.5.2", "@primer/behaviors": "^1.10.2", "@primer/live-region-element": "^0.7.1", diff --git a/packages/react/src/ActionMenu/ActionMenu.examples.stories.tsx b/packages/react/src/ActionMenu/ActionMenu.examples.stories.tsx index 1f2b3bf420e..d55efb4b151 100644 --- a/packages/react/src/ActionMenu/ActionMenu.examples.stories.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.examples.stories.tsx @@ -759,3 +759,95 @@ export const InsideDialog = () => { ) } + +export const CenteredOnPage = () => { + const [open, setOpen] = React.useState(false) + + return ( +
+ + Open menu + + + alert('Copy link clicked')}> + Copy link + ⌘C + + alert('Quote reply clicked')}> + Quote reply + ⌘Q + + alert('Edit comment clicked')}> + Edit comment + ⌘E + + + alert('Delete file clicked')}> + Delete file + ⌘D + + + + +
+ ) +} + +export const TwoActionMenus = () => { + return ( +
+ + First menu + + + alert('Copy clicked')}> + + + + Copy + ⌘C + + alert('Archive clicked')}> + + + + Archive + + + alert('Delete clicked')}> + Delete + ⌘D + + + + + + + Second menu + + + alert('Settings clicked')}> + + + + Settings + + alert('Workflows clicked')}> + + + + Workflows + + + + + + + Documentation + + + + +
+ ) +} diff --git a/packages/react/src/ActionMenu/ActionMenu.test.tsx b/packages/react/src/ActionMenu/ActionMenu.test.tsx index f7e74c3db77..8d00bb8ea28 100644 --- a/packages/react/src/ActionMenu/ActionMenu.test.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.test.tsx @@ -9,6 +9,7 @@ import {Tooltip as TooltipV2} from '../TooltipV2/Tooltip' import {SingleSelect} from '../ActionMenu/ActionMenu.features.stories' import {MixedSelection} from '../ActionMenu/ActionMenu.examples.stories' import {SearchIcon, KebabHorizontalIcon} from '@primer/octicons-react' +import anchoredOverlayClasses from '../AnchoredOverlay/AnchoredOverlay.module.css' import {getAnchoredPosition} from '@primer/behaviors' import type {AnchorPosition} from '@primer/behaviors' @@ -622,58 +623,124 @@ describe('ActionMenu', () => { expect(baseAnchor).not.toHaveAttribute('aria-expanded', 'true') }) + it('supports className prop on ActionMenu.Anchor with css anchor positioning flag', async () => { + const component = HTMLRender( + + + + + + + + + New file + + Copy link + Edit file + event.preventDefault()}> + Delete file + + + Github + + + + + + , + ) + const anchor = component.getByRole('button', {name: 'Toggle Menu'}) + expect(anchor).toHaveClass('test-class') + expect(anchor).toHaveClass(anchoredOverlayClasses.Anchor) + }) + + it('supports className prop on ActionMenu.Button with css anchor positioning flag', async () => { + const component = HTMLRender( + + + + Toggle Menu + + + New file + + Copy link + Edit file + event.preventDefault()}> + Delete file + + + Github + + + + + + , + ) + const button = component.getByRole('button', {name: 'Toggle Menu'}) + expect(button).toHaveClass('test-class') + expect(button).toHaveClass(anchoredOverlayClasses.Anchor) + }) + it('supports className prop on ActionMenu.Anchor', async () => { const component = HTMLRender( - - - - - - - - New file - - Copy link - Edit file - event.preventDefault()}> - Delete file - - - Github - - - - - , + + + + + + + + + New file + + Copy link + Edit file + event.preventDefault()}> + Delete file + + + Github + + + + + + , ) const anchor = component.getByRole('button', {name: 'Toggle Menu'}) expect(anchor).toHaveClass('test-class') + expect(anchor).not.toHaveClass(anchoredOverlayClasses.Anchor) }) it('supports className prop on ActionMenu.Button', async () => { const component = HTMLRender( - - - Toggle Menu - - - New file - - Copy link - Edit file - event.preventDefault()}> - Delete file - - - Github - - - - - , + + + + Toggle Menu + + + New file + + Copy link + Edit file + event.preventDefault()}> + Delete file + + + Github + + + + + + , ) const button = component.getByRole('button', {name: 'Toggle Menu'}) expect(button).toHaveClass('test-class') + expect(button).not.toHaveClass(anchoredOverlayClasses.Anchor) }) }) diff --git a/packages/react/src/ActionMenu/ActionMenu.tsx b/packages/react/src/ActionMenu/ActionMenu.tsx index e501b117377..d36ae802ca7 100644 --- a/packages/react/src/ActionMenu/ActionMenu.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.tsx @@ -1,4 +1,5 @@ import React, {useCallback, useContext, useMemo, useEffect, useState} from 'react' +import {clsx} from 'clsx' import {TriangleDownIcon, ChevronRightIcon} from '@primer/octicons-react' import type {AnchoredOverlayProps} from '../AnchoredOverlay' import {AnchoredOverlay} from '../AnchoredOverlay' @@ -73,6 +74,10 @@ const mergeAnchorHandlers = (anchorProps: React.HTMLAttributes, but mergedAnchorProps.onKeyDown = mergedOnAnchorKeyDown } + if (buttonProps.className) { + mergedAnchorProps.className = clsx(anchorProps.className, buttonProps.className) + } + return mergedAnchorProps } @@ -153,7 +158,11 @@ const Menu: FCWithSlotMarker> = ({ } } } else { - renderAnchor = anchorProps => React.cloneElement(child, anchorProps) + renderAnchor = anchorProps => + React.cloneElement(child, { + ...anchorProps, + className: clsx(anchorProps.className, child.props.className), + }) } return null } else if (child.type === MenuButton || isSlot(child, MenuButton)) { @@ -234,6 +243,7 @@ const Anchor: WithSlotMarker< {React.cloneElement(child, { ...anchorProps, ref: anchorRef, + className: clsx(anchorProps.className, child.props.className), onClick: onButtonClick, onKeyDown: onButtonKeyDown, })} diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css index 512a890b5ff..72485a8d665 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.module.css @@ -12,3 +12,48 @@ display: inline-grid; } } + +.Wrapper { + anchor-scope: --anchored-overlay-anchor; +} + +.Anchor { + /* Anchor name, this is currently tied to `renderAnchor` */ + anchor-name: --anchored-overlay-anchor; +} + +.AnchoredOverlay { + /* Anchor position, this is currently tied to `` */ + position-anchor: --anchored-overlay-anchor; + position-try-fallbacks: + flip-block, + flip-inline, + flip-block flip-inline; + position-visibility: anchors-visible; + z-index: 100; + position: fixed; + + &[data-side='outside-bottom'] { + /* stylelint-disable primer/spacing */ + top: calc(anchor(bottom) + var(--base-size-4)); + left: anchor(left); + } + + &[data-side='outside-top'] { + margin-bottom: var(--base-size-4); + bottom: anchor(top); + left: anchor(left); + } + + &[data-side='outside-left'] { + right: anchor(left); + top: anchor(top); + margin-right: var(--base-size-4); + } + + &[data-side='outside-right'] { + left: anchor(right); + top: anchor(top); + margin-left: var(--base-size-4); + } +} diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx index 279bcc112d1..8bcb8d46284 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx @@ -7,8 +7,10 @@ import {Button} from '../Button' import BaseStyles from '../BaseStyles' import type {AnchorPosition} from '@primer/behaviors' import {implementsClassName} from '../utils/testing' +import {FeatureFlags} from '../FeatureFlags' import overlayClasses from '../Overlay/Overlay.module.css' +import anchoredOverlayClasses from './AnchoredOverlay.module.css' type TestComponentSettings = { initiallyOpen?: boolean @@ -16,6 +18,7 @@ type TestComponentSettings = { onCloseCallback?: (gesture: string) => void onPositionChange?: ({position}: {position: AnchorPosition}) => void className?: string + withCSSAnchorPositioningFeatureFlag?: boolean } const AnchoredOverlayTestComponent = ({ @@ -24,6 +27,7 @@ const AnchoredOverlayTestComponent = ({ onCloseCallback, onPositionChange, className, + withCSSAnchorPositioningFeatureFlag, }: TestComponentSettings = {}) => { const [open, setOpen] = useState(initiallyOpen) const onOpen = useCallback( @@ -40,7 +44,8 @@ const AnchoredOverlayTestComponent = ({ }, [onCloseCallback], ) - return ( + + const content = ( ) + + if (withCSSAnchorPositioningFeatureFlag !== undefined) { + return ( + + {content} + + ) + } + + return content } -describe('AnchoredOverlay', () => { - implementsClassName(props => , overlayClasses.Overlay) - it('should call onOpen when the anchor is clicked', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - const anchoredOverlay = render( - , +describe.each([true, false])( + 'AnchoredOverlay (primer_react_css_anchor_positioning=%s)', + (withCSSAnchorPositioningFeatureFlag: boolean) => { + implementsClassName( + props => ( + + ), + overlayClasses.Overlay, ) - const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! - await act(async () => { - await userEvent.click(anchor) + + it('should call onOpen when the anchor is clicked', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() + const anchoredOverlay = render( + , + ) + const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! + await act(async () => { + await userEvent.click(anchor) + }) + + expect(mockOpenCallback).toHaveBeenCalledTimes(1) + expect(mockOpenCallback).toHaveBeenCalledWith('anchor-click') + expect(mockCloseCallback).toHaveBeenCalledTimes(0) }) - expect(mockOpenCallback).toHaveBeenCalledTimes(1) - expect(mockOpenCallback).toHaveBeenCalledWith('anchor-click') - expect(mockCloseCallback).toHaveBeenCalledTimes(0) - }) + it('should call onOpen when the anchor activated by a key press', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() + const anchoredOverlay = render( + , + ) + const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! + await act(async () => { + await userEvent.type(anchor, '{Space}') + }) - it('should call onOpen when the anchor activated by a key press', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - const anchoredOverlay = render( - , - ) - const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! - await act(async () => { - await userEvent.type(anchor, '{Space}') + expect(mockOpenCallback).toHaveBeenCalledTimes(1) + expect(mockOpenCallback).toHaveBeenCalledWith('anchor-key-press') + expect(mockCloseCallback).toHaveBeenCalledTimes(0) }) - expect(mockOpenCallback).toHaveBeenCalledTimes(1) - expect(mockOpenCallback).toHaveBeenCalledWith('anchor-key-press') - expect(mockCloseCallback).toHaveBeenCalledTimes(0) - }) + it('should call onClose when the user clicks off of the overlay', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() + const anchoredOverlay = render( + , + ) + await act(async () => { + await userEvent.click(anchoredOverlay.baseElement) + }) - it('should call onClose when the user clicks off of the overlay', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - const anchoredOverlay = render( - , - ) - await act(async () => { - await userEvent.click(anchoredOverlay.baseElement) + expect(mockOpenCallback).toHaveBeenCalledTimes(0) + expect(mockCloseCallback).toHaveBeenCalledTimes(1) + expect(mockCloseCallback).toHaveBeenCalledWith('click-outside') }) - expect(mockOpenCallback).toHaveBeenCalledTimes(0) - expect(mockCloseCallback).toHaveBeenCalledTimes(1) - expect(mockCloseCallback).toHaveBeenCalledWith('click-outside') - }) + it('should call onClose when the escape key is pressed', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() - it('should call onClose when the escape key is pressed', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() + render( + , + ) - render( - , - ) + await act(async () => { + await userEvent.keyboard('{Escape}') + }) - await act(async () => { - await userEvent.keyboard('{Escape}') + expect(mockOpenCallback).toHaveBeenCalledTimes(0) + expect(mockCloseCallback).toHaveBeenCalledTimes(1) + expect(mockCloseCallback).toHaveBeenCalledWith('escape') }) - expect(mockOpenCallback).toHaveBeenCalledTimes(0) - expect(mockCloseCallback).toHaveBeenCalledTimes(1) - expect(mockCloseCallback).toHaveBeenCalledWith('escape') - }) + it('should call onPositionChange when provided', async () => { + const mockPositionChangeCallback = vi.fn(({position}: {position: AnchorPosition}) => position) + render( + , + ) + + await act(async () => { + await userEvent.keyboard('{Escape}') + }) + + expect(mockPositionChangeCallback).toHaveBeenCalled() + expect(mockPositionChangeCallback).toHaveBeenCalledWith({ + position: { + anchorAlign: 'start', + anchorSide: 'outside-bottom', + left: 0, + top: 36, + }, + }) + }) + + it('should support a `ref` through `overlayProps` on the overlay element', () => { + const ref = createRef() + + function Test() { + const anchorRef = useRef(null) + return ( + + { + return ( + + ) + }} + > +
content
+
+
+ ) + } + + render() + + expect(document.getElementById('overlay')).toBe(ref.current) + }) + }, +) + +describe('AnchoredOverlay feature flag specific behavior', () => { + describe('with primer_react_css_anchor_positioning feature flag enabled', () => { + it('should render wrapper div when flag is enabled', () => { + const {container} = render( + + + , + ) + + const wrapper = container.querySelector(`.${anchoredOverlayClasses.Wrapper}`) + expect(wrapper).toBeInTheDocument() + }) - it('should call onPositionChange when provided', async () => { - const mockPositionChangeCallback = vi.fn(({position}: {position: AnchorPosition}) => position) - render() + it('should apply Anchor class to anchor element when flag is enabled', () => { + const {container} = render( + + + , + ) - await act(async () => { - await userEvent.keyboard('{Escape}') + const anchor = container.querySelector('[aria-haspopup="true"]') + expect(anchor).toHaveClass(anchoredOverlayClasses.Anchor) }) - expect(mockPositionChangeCallback).toHaveBeenCalled() - expect(mockPositionChangeCallback).toHaveBeenCalledWith({ - position: { - anchorAlign: 'start', - anchorSide: 'outside-bottom', - left: 0, - top: 36, - }, + it('should render overlay as visible immediately when flag is enabled', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).toHaveAttribute('data-visibility-visible', '') + }) + + it('should not use portal when flag is enabled', () => { + const {baseElement, container} = render( + + + , + ) + + // The overlay should be inside the component tree, not in the portal root + const portalRoot = baseElement.querySelector('#__primerPortalRoot__') + const overlayInPortal = portalRoot?.querySelector('[data-component="AnchoredOverlay"]') + expect(overlayInPortal).toBeNull() + + // The overlay should be inside the wrapper + const wrapper = container.querySelector(`.${anchoredOverlayClasses.Wrapper}`) + const overlayInWrapper = wrapper?.querySelector('[data-component="AnchoredOverlay"]') + expect(overlayInWrapper).toBeInTheDocument() + }) + + it('should apply AnchoredOverlay class to overlay when flag is enabled', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).toHaveClass(anchoredOverlayClasses.AnchoredOverlay) + }) + + it('should set data-anchor-position attribute when flag is enabled', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).toHaveAttribute('data-anchor-position', 'true') }) }) - it('should support a `ref` through `overlayProps` on the overlay element', () => { - const ref = createRef() - - function Test() { - const anchorRef = useRef(null) - return ( - { - return ( - - ) - }} - > -
content
-
+ describe('with primer_react_css_anchor_positioning feature flag disabled', () => { + it('should not render wrapper div when flag is disabled', () => { + const {container} = render( + + + , + ) + + const wrapper = container.querySelector(`.${anchoredOverlayClasses.Wrapper}`) + expect(wrapper).not.toBeInTheDocument() + }) + + it('should not apply Anchor class to anchor element when flag is disabled', () => { + const {container} = render( + + + , + ) + + const anchor = container.querySelector('[aria-haspopup="true"]') + expect(anchor).not.toHaveClass(anchoredOverlayClasses.Anchor) + }) + + it('should use portal when flag is disabled', () => { + const {baseElement} = render( + + + , ) - } - render() + // The overlay should be inside the portal root + const portalRoot = baseElement.querySelector('#__primerPortalRoot__') + const overlayInPortal = portalRoot?.querySelector('[data-component="AnchoredOverlay"]') + expect(overlayInPortal).toBeInTheDocument() + }) - expect(document.getElementById('overlay')).toBe(ref.current) + it('should set data-anchor-position to false when flag is disabled', () => { + const {baseElement} = render( + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') + expect(overlay).toHaveAttribute('data-anchor-position', 'false') + }) }) }) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index f60070447b3..3f9513f94f9 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -14,6 +14,7 @@ import {IconButton, type IconButtonProps} from '../Button' import {XIcon} from '@primer/octicons-react' import classes from './AnchoredOverlay.module.css' import {clsx} from 'clsx' +import {useFeatureFlag} from '../FeatureFlags' interface AnchoredOverlayPropsWithAnchor { /** @@ -123,6 +124,13 @@ export type AnchoredOverlayProps = AnchoredOverlayBaseProps & (AnchoredOverlayPropsWithAnchor | AnchoredOverlayPropsWithoutAnchor) & Partial> +const applyAnchorPositioningPolyfill = async () => { + if (typeof window !== 'undefined' && !('anchorName' in document.documentElement.style)) { + const {default: polyfill} = await import('@oddbird/css-anchor-positioning/fn') + polyfill() + } +} + const defaultVariant = { regular: 'anchored', narrow: 'anchored', @@ -160,6 +168,7 @@ export const AnchoredOverlay: React.FC { + const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning') const anchorRef = useProvidedRefOrCreate(externalAnchorRef) const [overlayRef, updateOverlayRef] = useRenderForcingRef() const anchorId = useId(externalAnchorId) @@ -218,7 +227,11 @@ export const AnchoredOverlay: React.FC {renderAnchor && renderAnchor({ @@ -242,6 +257,7 @@ export const AnchoredOverlay: React.FC { if (overlayProps?.ref) { assignRef(overlayProps.ref, node) } updateOverlayRef(node) }} + data-anchor-position={cssAnchorPositioning} + data-side={cssAnchorPositioning ? side : position?.anchorSide} > {showXIcon ? (
@@ -291,6 +309,12 @@ export const AnchoredOverlay: React.FC ) + + if (cssAnchorPositioning) { + return
{innerContent}
+ } + + return innerContent } function assignRef( diff --git a/packages/react/src/Banner/Banner.module.css b/packages/react/src/Banner/Banner.module.css index 07b04346567..06f944beaef 100644 --- a/packages/react/src/Banner/Banner.module.css +++ b/packages/react/src/Banner/Banner.module.css @@ -45,6 +45,20 @@ & .BannerActions :where([data-primary-action='leading']) { display: none; } + + @media screen and (--viewportRange-narrow) { + & .BannerContainer { + flex-direction: column; + } + + & .BannerActions :where([data-primary-action='trailing']) { + display: none; + } + + & .BannerActions :where([data-primary-action='leading']) { + display: flex; + } + } } &[data-layout='compact'] { diff --git a/packages/react/src/Button/Button.features.stories.tsx b/packages/react/src/Button/Button.features.stories.tsx index 27091cdd25b..0b7d3da149b 100644 --- a/packages/react/src/Button/Button.features.stories.tsx +++ b/packages/react/src/Button/Button.features.stories.tsx @@ -6,6 +6,7 @@ import {announce} from '@primer/live-region-element' import {Tooltip} from '../TooltipV2/Tooltip' import {KeybindingHint} from '../KeybindingHint' import VisuallyHidden from '../_VisuallyHidden' + export default { title: 'Components/Button/Features', } @@ -129,7 +130,7 @@ export const Disabled = () => ( ) export const Inactive = () => ( -
+
Inactive