From 7db7578a74845e703b8e526ea09003467c92f978 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 4 Mar 2026 12:38:28 +0100 Subject: [PATCH 1/3] ci: run monosize compare only if target branch is master (#35831) --- .github/workflows/bundle-size.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index 46db7d5155d3c7..9c9afb4229a9d8 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -44,13 +44,22 @@ jobs: - name: Build packages & create reports run: yarn nx affected -t bundle-size --nxBail + # NOTE: monosize-storage-azure only stores base reports for the default branch (master), + # so compare-reports will fail for PRs targeting other branches. + - name: Skip bundle size comparison notice + if: ${{ github.event.pull_request.base.ref != 'master' }} + run: echo "::warning::Bundle size comparison skipped — monosize-storage-azure only stores base reports for the default branch (master). PR targets '${{ github.event.pull_request.base.ref }}'." + - name: Compare bundle size with base + if: ${{ github.event.pull_request.base.ref == 'master' }} run: npx monosize compare-reports --branch=${{ github.event.pull_request.base.ref }} --output=markdown --quiet > ./monosize-report.md - name: Save PR number + if: ${{ github.event.pull_request.base.ref == 'master' }} run: echo ${{ github.event.number }} > pr.txt - uses: actions/upload-artifact@v6 + if: ${{ github.event.pull_request.base.ref == 'master' }} with: name: monosize-report retention-days: 1 From fb4409e56d2fce78dc6d35c5db55e69278927098 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 4 Mar 2026 14:34:17 +0100 Subject: [PATCH 2/3] chore: replace commit hooks infra with husky + nano-staged (#35832) --- .githooks/pre-commit | 16 --- .githooks/pre-push | 1 - {.githooks => .husky}/post-checkout | 2 - {.githooks => .husky}/post-merge | 2 - .husky/pre-commit | 1 + lint-staged.config.js => nano-staged.js | 4 +- package.json | 5 +- scripts/package-manager/src/postinstall.js | 9 -- yarn.lock | 124 +++++---------------- 9 files changed, 33 insertions(+), 131 deletions(-) delete mode 100755 .githooks/pre-commit delete mode 100755 .githooks/pre-push rename {.githooks => .husky}/post-checkout (92%) mode change 100755 => 100644 rename {.githooks => .husky}/post-merge (93%) mode change 100755 => 100644 create mode 100644 .husky/pre-commit rename lint-staged.config.js => nano-staged.js (85%) diff --git a/.githooks/pre-commit b/.githooks/pre-commit deleted file mode 100755 index c9548d87b901c1..00000000000000 --- a/.githooks/pre-commit +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -if (!fs.existsSync(path.resolve(__dirname, '../node_modules/lint-staged'))) { - console.error('lint-staged is not installed. Please run `yarn` then try again.'); - process.exit(1); -} else if (!fs.existsSync(path.resolve(__dirname, '../node_modules/lint-staged/bin/lint-staged.js'))) { - // If someone updates from master but doesn't run yarn, by default they'd be unable to commit - // with some cryptic error. Explicitly detect that condition and show a helpful error. - console.error('lint-staged version is out of date! Please run `yarn` to update dependencies.'); - process.exit(1); -} - -require('lint-staged/bin/lint-staged.js'); diff --git a/.githooks/pre-push b/.githooks/pre-push deleted file mode 100755 index 0617957607436e..00000000000000 --- a/.githooks/pre-push +++ /dev/null @@ -1 +0,0 @@ -#!/usr/bin/env sh diff --git a/.githooks/post-checkout b/.husky/post-checkout old mode 100755 new mode 100644 similarity index 92% rename from .githooks/post-checkout rename to .husky/post-checkout index 6b9eeb6ed80652..ee5e406ad1d20e --- a/.githooks/post-checkout +++ b/.husky/post-checkout @@ -1,4 +1,2 @@ -#!/bin/sh - changedFiles="$(git diff-tree -r --name-only --no-commit-id $1 $2)" node ./scripts/package-manager/src/notify-on-file-changes.js $changedFiles diff --git a/.githooks/post-merge b/.husky/post-merge old mode 100755 new mode 100644 similarity index 93% rename from .githooks/post-merge rename to .husky/post-merge index fa1901f2602d06..1030afadb4fed5 --- a/.githooks/post-merge +++ b/.husky/post-merge @@ -1,4 +1,2 @@ -#!/bin/sh - changedFiles="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" node ./scripts/package-manager/src/notify-on-file-changes.js $changedFiles diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000000000..ac3d43c289566e --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +nano-staged diff --git a/lint-staged.config.js b/nano-staged.js similarity index 85% rename from lint-staged.config.js rename to nano-staged.js index 0a64c208cbe57e..fd662096ebbd39 100644 --- a/lint-staged.config.js +++ b/nano-staged.js @@ -19,8 +19,8 @@ const nonJsExtensions = [ prettierSupportedFileExtensionsByContext.others, ].flat(); -// https://www.npmjs.com/package/lint-staged +// https://github.com/usmanyunusov/nano-staged module.exports = { [`**/*.{${nonJsExtensions}}`]: [commands.format], - [`**/*.{${prettierSupportedFileExtensionsByContext.js}}`]: [/* commands.format, */ commands.lint], + [`**/*.{${prettierSupportedFileExtensionsByContext.js}}`]: [commands.format, commands.lint], }; diff --git a/package.json b/package.json index c34041a9bee17e..66909233e2a5ff 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "create-package": "yarn nx g @fluentui/workspace-plugin:react-library", "format": "node scripts/executors/src/format.js", "generate-version-files": "node -r ./scripts/ts-node/src/register ./scripts/generators/src/generate-version-files", - "postinstall": "yarn patch-package && node ./scripts/package-manager/src/postinstall.js", + "postinstall": "yarn patch-package && husky && node ./scripts/package-manager/src/postinstall.js", "preinstall": "node ./scripts/package-manager/src/preinstall.js", "publish:beachball": "beachball publish -b origin/master --access public -y", "start": "node -r ./scripts/ts-node/src/register ./scripts/executors/src/start", @@ -225,6 +225,7 @@ "glob": "7.2.0", "globals": "13.24.0", "html-webpack-plugin": "5.6.0", + "husky": "9.1.7", "ignore-not-found-export-webpack-plugin": "1.0.2", "imports-loader": "1.2.0", "jest": "30.2.0", @@ -239,7 +240,6 @@ "jsonc-eslint-parser": "2.4.1", "just-scripts": "1.8.2", "license-webpack-plugin": "4.0.2", - "lint-staged": "10.2.10", "loader-utils": "2.0.4", "lodash": "4.17.21", "markdown-table": "2.0.0", @@ -248,6 +248,7 @@ "monosize": "0.6.3", "monosize-bundler-webpack": "0.1.6", "monosize-storage-azure": "0.0.16", + "nano-staged": "0.9.0", "node-plop": "0.25.0", "nx": "21.6.10", "p-queue": "6.6.2", diff --git a/scripts/package-manager/src/postinstall.js b/scripts/package-manager/src/postinstall.js index 317b572c6fd451..7bb13bd8f8b47e 100644 --- a/scripts/package-manager/src/postinstall.js +++ b/scripts/package-manager/src/postinstall.js @@ -1,5 +1,3 @@ -const { spawnSync } = require('node:child_process'); - const chalk = require('chalk'); const { isCI } = require('./is-ci'); @@ -7,8 +5,6 @@ const { isCI } = require('./is-ci'); main(); function main() { - registerCustomGitHooksDirectory(); - if (isCI()) { process.exit(0); } @@ -16,11 +12,6 @@ function main() { gettingStarted(); } -function registerCustomGitHooksDirectory() { - // git v2.9.0 supports a custom hooks directory. This means we just need to check in the hooks scripts. - spawnSync('git', ['config', 'core.hooksPath', '.githooks']); -} - function gettingStarted() { const COMMAND_PREFIX = `${chalk.cyan('>')} ${chalk.inverse(chalk.bold(chalk.cyan(' GETTING STARTED ')))}`; diff --git a/yarn.lock b/yarn.lock index e15f5e169d7c08..2a0964696b4191 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7379,7 +7379,7 @@ cli-table@0.3.11, cli-table@^0.3.1: dependencies: colors "1.0.3" -cli-truncate@2.1.0, cli-truncate@^2.1.0: +cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== @@ -7641,11 +7641,6 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - commander@^6.0.0, commander@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" @@ -7894,17 +7889,6 @@ cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" @@ -9051,7 +9035,7 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.17.0, enh graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@2.3.6, enquirer@^2.3.5, enquirer@^2.3.6, enquirer@~2.3.6: +enquirer@2.3.6, enquirer@^2.3.6, enquirer@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -9840,7 +9824,7 @@ events@^3.0.0, events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@4.1.0, execa@^4.0.1: +execa@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -10829,11 +10813,6 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@ hasown "^2.0.2" math-intrinsics "^1.1.0" -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203" - integrity sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg== - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -11662,6 +11641,11 @@ humps@^2.0.1: resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" integrity sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao= +husky@9.1.7: + version "9.1.7" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" + integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== + hyphenate-style-name@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" @@ -11736,7 +11720,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -12270,7 +12254,7 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0, is-obj@^1.0.1: +is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= @@ -12372,11 +12356,6 @@ is-regex@^1.2.1: has-tostringtag "^1.0.2" hasown "^2.0.2" -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= - is-relative@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -13706,46 +13685,11 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@10.2.10: - version "10.2.10" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.10.tgz#f0f78bf8786bbe90e1775a0dc540f7f12b6a79b2" - integrity sha512-dgelFaNH6puUGAcU+OVMgbfpKSerNYsPSn6+nlbRDjovL0KigpsVpCu0PFZG6BJxX8gnHJqaZlR9krZamQsb0w== - dependencies: - chalk "^4.0.0" - cli-truncate "2.1.0" - commander "^5.1.0" - cosmiconfig "^6.0.0" - debug "^4.1.1" - dedent "^0.7.0" - enquirer "^2.3.5" - execa "^4.0.1" - listr2 "^2.1.0" - log-symbols "^4.0.0" - micromatch "^4.0.2" - normalize-path "^3.0.0" - please-upgrade-node "^3.2.0" - string-argv "0.3.1" - stringify-object "^3.3.0" - listenercount@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== -listr2@^2.1.0: - version "2.1.7" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.1.7.tgz#8107c12c699bac778f1567739298052d8ebb9c27" - integrity sha512-XCC1sWLkBFFIMIRwG/LedgHUzN2XLEo02ZqXn6fwuP0GlXGE5BCuL6EAbQFb4vZB+++YEonzEXDPWQe+jCoF6Q== - dependencies: - chalk "^4.0.0" - cli-truncate "^2.1.0" - figures "^3.2.0" - indent-string "^4.0.0" - log-update "^4.0.0" - p-map "^4.0.0" - rxjs "^6.5.5" - through "^2.3.8" - listr2@^3.8.3: version "3.14.0" resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" @@ -15319,6 +15263,13 @@ n-readlines@^1.0.0: resolved "https://registry.yarnpkg.com/n-readlines/-/n-readlines-1.0.0.tgz#c353797f216c253fdfef7e91da4e8b17c29a91a6" integrity sha512-ISDqGcspVu6U3VKqtJZG1uR55SmNNF9uK0EMq1IvNVVZOui6MW6VR0+pIZhqz85ORAGp+4zW+5fJ/SE7bwEibA== +nano-staged@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/nano-staged/-/nano-staged-0.9.0.tgz#4d957f3a40470d82dd26ecd739da9d8838a2b20b" + integrity sha512-0JfyX4i0Vp5HhC9RDtJ1kp7psz8CFuS3Gya3Z6WZv//QCwA9dPzi1S803VdR0c0P6R7sSvweZ5mSJmYQ/N+loQ== + dependencies: + picocolors "^1.0.0" + nanoid@^3.3.6, nanoid@^3.3.8: version "3.3.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" @@ -16499,13 +16450,6 @@ playwright@1.55.1, playwright@^1.34.3, playwright@^1.55.1: optionalDependencies: fsevents "2.3.2" -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - plop@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/plop/-/plop-2.6.0.tgz#b0c3a9879c35008a22bb681bfba975ed2bc3c38c" @@ -17770,20 +17714,20 @@ rw@1: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== -rxjs@>=6.4.0, rxjs@^6.5.5, rxjs@^6.6.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -rxjs@^7.5.1: +rxjs@>=6.4.0, rxjs@^7.5.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: tslib "^2.1.0" +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + sade@^1.7.3: version "1.8.1" resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" @@ -17944,11 +17888,6 @@ selfsigned@^2.1.1: "@types/node-forge" "^1.3.0" node-forge "^1" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -18598,7 +18537,7 @@ streamx@^2.15.0: optionalDependencies: bare-events "^2.2.0" -string-argv@0.3.1, string-argv@~0.3.1: +string-argv@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== @@ -18741,15 +18680,6 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -stringify-object@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== - dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" - strip-ansi@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -20918,7 +20848,7 @@ yallist@^5.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== -yaml@^1.10.0, yaml@^1.7.2: +yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From ace36179073ac4600a3635304a0941bd7dcda21d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:06:43 +0100 Subject: [PATCH 3/3] feat(react-skeleton): Add `size` and `shape` props to `Skeleton` for context-based propagation to children (#35787) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ValentinaKozlova <11574680+ValentinaKozlova@users.noreply.github.com> Co-authored-by: Valentyna --- ...-35c22083-9e08-4a77-9057-e7ee7a429c76.json | 7 +++ .../library/etc/react-skeleton.api.md | 12 +++-- .../react-skeleton/library/src/Skeleton.ts | 8 +++- .../library/src/SkeletonItem.ts | 7 +-- .../src/components/Skeleton/Skeleton.test.tsx | 31 +++++++++++++ .../src/components/Skeleton/Skeleton.types.ts | 22 ++++++++- .../library/src/components/Skeleton/index.ts | 8 +++- .../src/components/Skeleton/useSkeleton.ts | 4 +- .../Skeleton/useSkeletonContextValues.ts | 6 ++- .../SkeletonItem/SkeletonItem.types.ts | 45 ++++++------------- .../src/components/SkeletonItem/index.ts | 2 +- .../SkeletonItem/useSkeletonItem.tsx | 11 +++-- .../library/src/contexts/SkeletonContext.ts | 3 ++ .../src/Skeleton/SkeletonRow.stories.tsx | 20 ++++----- 14 files changed, 124 insertions(+), 62 deletions(-) create mode 100644 change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json diff --git a/change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json b/change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json new file mode 100644 index 00000000000000..ecde1adb16cf55 --- /dev/null +++ b/change/@fluentui-react-skeleton-35c22083-9e08-4a77-9057-e7ee7a429c76.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat(react-skeleton): Add size and shape props to Skeleton component", + "packageName": "@fluentui/react-skeleton", + "email": "v.kozlova13@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md b/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md index fe78c6e0445799..c8a7cd1d9e5789 100644 --- a/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md +++ b/packages/react-components/react-skeleton/library/etc/react-skeleton.api.md @@ -33,6 +33,10 @@ export interface SkeletonContextValue { animation?: 'wave' | 'pulse'; // (undocumented) appearance?: 'opaque' | 'translucent'; + // (undocumented) + shape?: 'circle' | 'square' | 'rectangle'; + // (undocumented) + size?: SkeletonItemSize; } // @public (undocumented) @@ -42,11 +46,9 @@ export const SkeletonItem: ForwardRefComponent; export const skeletonItemClassNames: SlotClassNames; // @public -export type SkeletonItemProps = ComponentProps & { +export type SkeletonItemProps = ComponentProps & Pick & { animation?: 'wave' | 'pulse'; appearance?: 'opaque' | 'translucent'; - size?: SkeletonItemSize; - shape?: 'circle' | 'square' | 'rectangle'; }; // @public (undocumented) @@ -62,6 +64,8 @@ export type SkeletonProps = Omit>, 'width' animation?: 'wave' | 'pulse'; appearance?: 'opaque' | 'translucent'; width?: number | string; + size?: SkeletonItemSize; + shape?: 'circle' | 'square' | 'rectangle'; }; // @public (undocumented) @@ -70,7 +74,7 @@ export type SkeletonSlots = { }; // @public -export type SkeletonState = ComponentState & Required>; +export type SkeletonState = ComponentState & Required> & Pick; // @public export const useSkeleton_unstable: (props: SkeletonProps, ref: React_2.Ref) => SkeletonState; diff --git a/packages/react-components/react-skeleton/library/src/Skeleton.ts b/packages/react-components/react-skeleton/library/src/Skeleton.ts index fe0a7eaa49f374..66ae99e820b53e 100644 --- a/packages/react-components/react-skeleton/library/src/Skeleton.ts +++ b/packages/react-components/react-skeleton/library/src/Skeleton.ts @@ -1,4 +1,10 @@ -export type { SkeletonContextValues, SkeletonProps, SkeletonSlots, SkeletonState } from './components/Skeleton/index'; +export type { + SkeletonContextValues, + SkeletonItemSize, + SkeletonProps, + SkeletonSlots, + SkeletonState, +} from './components/Skeleton/index'; export { Skeleton, renderSkeleton_unstable, diff --git a/packages/react-components/react-skeleton/library/src/SkeletonItem.ts b/packages/react-components/react-skeleton/library/src/SkeletonItem.ts index dfc0f84ceec0a4..ee90e2f8960d36 100644 --- a/packages/react-components/react-skeleton/library/src/SkeletonItem.ts +++ b/packages/react-components/react-skeleton/library/src/SkeletonItem.ts @@ -1,9 +1,4 @@ -export type { - SkeletonItemProps, - SkeletonItemSize, - SkeletonItemSlots, - SkeletonItemState, -} from './components/SkeletonItem/index'; +export type { SkeletonItemProps, SkeletonItemSlots, SkeletonItemState } from './components/SkeletonItem/index'; export { SkeletonItem, renderSkeletonItem_unstable, diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx index f9fe36b1c36e9e..209e33e6bf08df 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.test.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import { Skeleton } from './Skeleton'; +import { useSkeletonItem_unstable } from '../SkeletonItem/useSkeletonItem'; +import { SkeletonContextProvider } from '../../contexts/SkeletonContext'; import { isConformant } from '../../testing/isConformant'; describe('Skeleton', () => { @@ -23,4 +26,32 @@ describe('Skeleton', () => { const result = render(); expect(result.getByRole('progressbar').getAttribute('aria-busy')).toBeDefined(); }); + it('passes size prop to SkeletonItem via context', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({}, React.createRef()), { wrapper }); + expect(result.current.size).toBe(24); + }); + it('passes shape prop to SkeletonItem via context', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({}, React.createRef()), { wrapper }); + expect(result.current.shape).toBe('circle'); + }); + it('allows SkeletonItem to override Skeleton size prop', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({ size: 48 }, React.createRef()), { wrapper }); + expect(result.current.size).toBe(48); + }); + it('allows SkeletonItem to override Skeleton shape prop', () => { + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useSkeletonItem_unstable({ shape: 'square' }, React.createRef()), { wrapper }); + expect(result.current.shape).toBe('square'); + }); }); diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts index 8a3f571c25f8c9..069e990666d89f 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/Skeleton.types.ts @@ -9,6 +9,11 @@ export type SkeletonSlots = { root: NonNullable>; }; +/** + * Sizes for the SkeletonItem + */ +export type SkeletonItemSize = 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128; + /** * Skeleton Props */ @@ -31,6 +36,19 @@ export type SkeletonProps = Omit>, 'width' * @deprecated Use `className` prop to set width */ width?: number | string; + + /** + * Sets the size of the SkeletonItems inside the Skeleton in pixels. + * Size is restricted to a limited set of values recommended for most uses (see SkeletonItemSize). + * This value can be overridden by the individual SkeletonItem's `size` prop. + */ + size?: SkeletonItemSize; + + /** + * Sets the shape of the SkeletonItems inside the Skeleton. + * This value can be overridden by the individual SkeletonItem's `shape` prop. + */ + shape?: 'circle' | 'square' | 'rectangle'; }; export type SkeletonContextValues = { @@ -40,4 +58,6 @@ export type SkeletonContextValues = { /** * State used in rendering Skeleton */ -export type SkeletonState = ComponentState & Required>; +export type SkeletonState = ComponentState & + Required> & + Pick; diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts b/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts index 14f197b578a05b..d2623ecf2ff769 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/index.ts @@ -1,5 +1,11 @@ export { Skeleton } from './Skeleton'; -export type { SkeletonContextValues, SkeletonProps, SkeletonSlots, SkeletonState } from './Skeleton.types'; +export type { + SkeletonContextValues, + SkeletonItemSize, + SkeletonProps, + SkeletonSlots, + SkeletonState, +} from './Skeleton.types'; export { renderSkeleton_unstable } from './renderSkeleton'; export { useSkeleton_unstable } from './useSkeleton'; export { useSkeletonContextValues } from './useSkeletonContextValues'; diff --git a/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts b/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts index a6bed8c0d4f159..3481ac79a8fd2b 100644 --- a/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts +++ b/packages/react-components/react-skeleton/library/src/components/Skeleton/useSkeleton.ts @@ -16,7 +16,7 @@ import { useSkeletonContext } from '../../contexts/SkeletonContext'; */ export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref): SkeletonState => { const { animation: contextAnimation, appearance: contextAppearance } = useSkeletonContext(); - const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque' } = props; + const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque', size, shape } = props; const root = slot.always( getIntrinsicElementProps('div', { @@ -30,5 +30,5 @@ export const useSkeleton_unstable = (props: SkeletonProps, ref: React.Ref { - const { animation, appearance } = state; + const { animation, appearance, size, shape } = state; const skeletonGroup = React.useMemo( () => ({ animation, appearance, + size, + shape, }), - [animation, appearance], + [animation, appearance, size, shape], ); return { skeletonGroup }; diff --git a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts index 59241135dfc7b0..90c96541835b7e 100644 --- a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts +++ b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/SkeletonItem.types.ts @@ -1,44 +1,27 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { SkeletonProps } from '../Skeleton/Skeleton.types'; export type SkeletonItemSlots = { root: Slot<'div', 'span'>; }; -/** - * Sizes for the SkeletonItem - */ -export type SkeletonItemSize = 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 48 | 56 | 64 | 72 | 96 | 120 | 128; - /** * SkeletonItem Props */ -export type SkeletonItemProps = ComponentProps & { - /** - * Sets the animation of the SkeletonItem - * @default wave - */ - animation?: 'wave' | 'pulse'; +export type SkeletonItemProps = ComponentProps & + Pick & { + /** + * Sets the animation of the SkeletonItem + * @default wave + */ + animation?: 'wave' | 'pulse'; - /** - * Sets the appearance of the SkeletonItem - * @default opaque - */ - appearance?: 'opaque' | 'translucent'; - - /** - * Sets the size of the SkeletonItem in pixels. - * Size is restricted to a limited set of values recommended for most uses(see SkeletonItemSize). - * To set a non-supported size, set `width` and `height` to override the rendered size. - * @default 16 - */ - size?: SkeletonItemSize; - - /** - * Sets the shape of the SkeletonItem. - * @default rectangle - */ - shape?: 'circle' | 'square' | 'rectangle'; -}; + /** + * Sets the appearance of the SkeletonItem + * @default opaque + */ + appearance?: 'opaque' | 'translucent'; + }; /** * State used in rendering SkeletonItem diff --git a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts index 3d22f9b76ec57b..9f136a182013eb 100644 --- a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts +++ b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/index.ts @@ -1,5 +1,5 @@ export { SkeletonItem } from './SkeletonItem'; -export type { SkeletonItemProps, SkeletonItemSize, SkeletonItemSlots, SkeletonItemState } from './SkeletonItem.types'; +export type { SkeletonItemProps, SkeletonItemSlots, SkeletonItemState } from './SkeletonItem.types'; export { renderSkeletonItem_unstable } from './renderSkeletonItem'; export { useSkeletonItem_unstable } from './useSkeletonItem'; export { skeletonItemClassNames, useSkeletonItemStyles_unstable } from './useSkeletonItemStyles.styles'; diff --git a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx index c38635d67ca873..78da929c233e2a 100644 --- a/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx +++ b/packages/react-components/react-skeleton/library/src/components/SkeletonItem/useSkeletonItem.tsx @@ -15,12 +15,17 @@ import type { SkeletonItemProps, SkeletonItemState } from './SkeletonItem.types' * @param ref - reference to root HTMLElement of SkeletonItem */ export const useSkeletonItem_unstable = (props: SkeletonItemProps, ref: React.Ref): SkeletonItemState => { - const { animation: contextAnimation, appearance: contextAppearance } = useSkeletonContext(); + const { + animation: contextAnimation, + appearance: contextAppearance, + size: contextSize, + shape: contextShape, + } = useSkeletonContext(); const { animation = contextAnimation ?? 'wave', appearance = contextAppearance ?? 'opaque', - size = 16, - shape = 'rectangle', + size = contextSize ?? 16, + shape = contextShape ?? 'rectangle', } = props; const root = slot.always( diff --git a/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts b/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts index 79157c2f107f5c..3872834ffb3610 100644 --- a/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts +++ b/packages/react-components/react-skeleton/library/src/contexts/SkeletonContext.ts @@ -1,12 +1,15 @@ 'use client'; import * as React from 'react'; +import type { SkeletonItemSize } from '../components/Skeleton/Skeleton.types'; const SkeletonContext = React.createContext(undefined); export interface SkeletonContextValue { animation?: 'wave' | 'pulse'; appearance?: 'opaque' | 'translucent'; + size?: SkeletonItemSize; + shape?: 'circle' | 'square' | 'rectangle'; } const skeletonContextDefaultValue: SkeletonContextValue = {}; diff --git a/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx b/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx index db57b17533bc39..cc7098ece62c73 100644 --- a/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx +++ b/packages/react-components/react-skeleton/stories/src/Skeleton/SkeletonRow.stories.tsx @@ -29,24 +29,24 @@ export const Row = (props: Partial): JSXElement => { const styles = useStyles(); return (
- +
- +
- - - - + + + +
- - - - + + + +