diff --git a/.changeset/lazy-jobs-pump.md b/.changeset/lazy-jobs-pump.md new file mode 100644 index 00000000000..d57adc23a22 --- /dev/null +++ b/.changeset/lazy-jobs-pump.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Add `loading` state to `Button` and `IconButton` diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-colorblind-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-colorblind-linux.png index 879ddfa9027..542f4d12f11 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-colorblind-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-dimmed-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-dimmed-linux.png index bc0be329541..c517d2e358e 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-dimmed-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-high-contrast-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-high-contrast-linux.png index aa26a849c19..6b17d7ba3dc 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-high-contrast-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-linux.png index 879ddfa9027..542f4d12f11 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-tritanopia-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-tritanopia-linux.png index 879ddfa9027..542f4d12f11 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-tritanopia-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-colorblind-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-colorblind-linux.png index 425a179a4db..17dc1630ec9 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-colorblind-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-high-contrast-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-high-contrast-linux.png index 701417e7166..6b6775825dd 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-high-contrast-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-linux.png index 5af933d70ac..17dc1630ec9 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-tritanopia-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-tritanopia-linux.png index 425a179a4db..17dc1630ec9 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-tritanopia-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Groups-And-Descriptions-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Single-Select-light-colorblind-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Single-Select-light-colorblind-linux.png index 6168409daf4..30eabc018ce 100644 Binary files a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Single-Select-light-colorblind-linux.png and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Single-Select-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-colorblind-linux.png new file mode 100644 index 00000000000..2bb594dea00 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-dimmed-linux.png new file mode 100644 index 00000000000..f95602f4191 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-high-contrast-linux.png new file mode 100644 index 00000000000..9599511d3f6 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-linux.png new file mode 100644 index 00000000000..2bb594dea00 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-tritanopia-linux.png new file mode 100644 index 00000000000..2bb594dea00 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-colorblind-linux.png new file mode 100644 index 00000000000..c5971cbb5f1 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-linux.png new file mode 100644 index 00000000000..915404b77a1 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-tritanopia-linux.png new file mode 100644 index 00000000000..c5971cbb5f1 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-Custom-Announcement-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-colorblind-linux.png new file mode 100644 index 00000000000..edf5ad510c9 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-dimmed-linux.png new file mode 100644 index 00000000000..333817f5f9a Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-high-contrast-linux.png new file mode 100644 index 00000000000..ab03362b29e Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-linux.png new file mode 100644 index 00000000000..c100db093f3 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-tritanopia-linux.png new file mode 100644 index 00000000000..edf5ad510c9 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-colorblind-linux.png new file mode 100644 index 00000000000..52d68822575 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-linux.png new file mode 100644 index 00000000000..f72a495ec0e Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-tritanopia-linux.png new file mode 100644 index 00000000000..52d68822575 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Leading-Visual-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-colorblind-linux.png new file mode 100644 index 00000000000..e3a75c8e668 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-dimmed-linux.png new file mode 100644 index 00000000000..126d1152800 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-high-contrast-linux.png new file mode 100644 index 00000000000..3b23d3611cc Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-linux.png new file mode 100644 index 00000000000..e3a75c8e668 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-tritanopia-linux.png new file mode 100644 index 00000000000..e3a75c8e668 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-colorblind-linux.png new file mode 100644 index 00000000000..a0091e1cc9a Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-high-contrast-linux.png new file mode 100644 index 00000000000..8bf033303e5 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-linux.png new file mode 100644 index 00000000000..a0091e1cc9a Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-tritanopia-linux.png new file mode 100644 index 00000000000..a0091e1cc9a Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Action-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-colorblind-linux.png new file mode 100644 index 00000000000..a9d2801000c Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-dimmed-linux.png new file mode 100644 index 00000000000..ba56fd1422f Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-high-contrast-linux.png new file mode 100644 index 00000000000..b7203f08d54 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-linux.png new file mode 100644 index 00000000000..f62eba040a0 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-tritanopia-linux.png new file mode 100644 index 00000000000..a9d2801000c Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-colorblind-linux.png new file mode 100644 index 00000000000..76c390511a2 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-linux.png new file mode 100644 index 00000000000..a0a7ddaa426 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-tritanopia-linux.png new file mode 100644 index 00000000000..76c390511a2 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-With-Trailing-Visual-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-colorblind-linux.png new file mode 100644 index 00000000000..2bb594dea00 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-dimmed-linux.png new file mode 100644 index 00000000000..f95602f4191 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-high-contrast-linux.png new file mode 100644 index 00000000000..9599511d3f6 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-linux.png new file mode 100644 index 00000000000..2bb594dea00 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-tritanopia-linux.png new file mode 100644 index 00000000000..2bb594dea00 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-colorblind-linux.png new file mode 100644 index 00000000000..c5971cbb5f1 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-linux.png new file mode 100644 index 00000000000..915404b77a1 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-tritanopia-linux.png new file mode 100644 index 00000000000..c5971cbb5f1 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Loading-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-colorblind-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-colorblind-linux.png index 71c6f79a646..1285c748d25 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-colorblind-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-dimmed-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-dimmed-linux.png index f8a71e52668..5f865837797 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-dimmed-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-high-contrast-linux.png index 34ace65d6db..3c8138aed23 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-high-contrast-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-linux.png index 3d6431e4eee..1db5e3b3ab3 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-tritanopia-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-tritanopia-linux.png index 9a7ee276952..d3336aa4dee 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-tritanopia-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-colorblind-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-colorblind-linux.png index 6a02bd322e8..5124071d6c8 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-colorblind-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-high-contrast-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-high-contrast-linux.png index 5e2c2ae3695..82df9e9ff44 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-high-contrast-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-linux.png index 4dcce520606..40ca0fc448d 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-tritanopia-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-tritanopia-linux.png index 9529d66fdeb..4636f8f603c 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-tritanopia-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Default-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-colorblind-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-colorblind-linux.png index f79c9c5872a..c4b78b9b2a2 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-colorblind-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-dimmed-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-dimmed-linux.png index 6260eb2d553..48c74ada840 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-dimmed-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-high-contrast-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-high-contrast-linux.png index 87d3cfeff15..6a165b007f1 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-high-contrast-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-linux.png index 077cb9ee5fb..12fa78af014 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-tritanopia-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-tritanopia-linux.png index 4663e46ac7d..4bb1ae1398e 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-tritanopia-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-colorblind-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-colorblind-linux.png index 51a9ee57deb..6480afe8175 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-colorblind-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-high-contrast-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-high-contrast-linux.png index 9537583c804..68f44f82863 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-high-contrast-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-linux.png index 3b8af491224..69cdf049125 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-linux.png differ diff --git a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-tritanopia-linux.png b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-tritanopia-linux.png index 72397be86a9..7896c2580cd 100644 Binary files a/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-tritanopia-linux.png and b/.playwright/snapshots/components/Dialog.test.ts-snapshots/Dialog-Stress-Test-light-tritanopia-linux.png differ diff --git a/e2e/components/Button.test.ts b/e2e/components/Button.test.ts index cc8eee590ef..822a95aa46b 100644 --- a/e2e/components/Button.test.ts +++ b/e2e/components/Button.test.ts @@ -479,6 +479,184 @@ test.describe('Button', () => { } }) + test.describe('Loading', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(`Button.Loading.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) + + test.describe('Loading Custom Announcement', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-custom-announcement', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Button.Loading Custom Announcement.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-custom-announcement', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) + + test.describe('Loading With Leading Visual', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-with-leading-visual', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Button.Loading With Leading Visual.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-with-leading-visual', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) + + test.describe('Loading With Trailing Visual', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-with-trailing-visual', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Button.Loading With Trailing Visual.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-with-trailing-visual', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) + + test.describe('Loading With Trailing Action', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-with-trailing-action', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Button.Loading With Trailing Action.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-button-features--loading-with-trailing-action', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) + test.describe('Dev: Invisible Variants', () => { for (const theme of themes) { test.describe(theme, () => { diff --git a/package-lock.json b/package-lock.json index e942c8990d0..cb1a959ea15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,6 @@ { "name": "primer", + "version": "36.4.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/packages/react/src/Button/Button.docs.json b/packages/react/src/Button/Button.docs.json index 53f346410ec..627f75d6808 100644 --- a/packages/react/src/Button/Button.docs.json +++ b/packages/react/src/Button/Button.docs.json @@ -32,11 +32,6 @@ "type": "number | string", "description": "For counter buttons, the number to display." }, - { - "name": "disabled", - "type": "boolean", - "description": "Items that are disabled can not be clicked, selected, or navigated through." - }, { "name": "inactive", "type": "boolean", @@ -53,6 +48,17 @@ "type": "React.ElementType", "description": "A visual to display before the button text." }, + { + "name": "loading", + "type": "boolean", + "description": "When true, the button is in a loading state." + }, + { + "name": "loadingAnnouncement", + "type": "string", + "description": "The content to announce to screen readers when loading. This requires `loading` prop to be true" + }, + { "name": "ref", "type": "React.RefObject" diff --git a/packages/react/src/Button/Button.examples.stories.tsx b/packages/react/src/Button/Button.examples.stories.tsx new file mode 100644 index 00000000000..ce1ebf82ff2 --- /dev/null +++ b/packages/react/src/Button/Button.examples.stories.tsx @@ -0,0 +1,79 @@ +import React from 'react' +import type {Meta} from '@storybook/react' +import {Button} from '.' +import {DownloadIcon} from '@primer/octicons-react' +import {Banner} from '../drafts' +import {AriaStatus} from '../live-region' + +const meta: Meta = { + title: 'Components/Button/Examples', +} as Meta + +export default meta + +export const LoadingStatusAnnouncementSuccessful = () => { + const [loading, setLoading] = React.useState(false) + const [success, setSuccess] = React.useState(false) + + const resolveAction = async () => { + setLoading(true) + await new Promise(resolve => setTimeout(resolve, 1500)) + setLoading(false) + + return await true + } + + const onClick = (resolveType: 'error' | 'success') => async () => { + const actionResult = await resolveAction() + + if (resolveType === 'error') { + setSuccess(!actionResult) + return + } + + setSuccess(actionResult) + } + + return ( + <> + {!loading && success ? 'Export completed' : null} + + + ) +} + +export const LoadingStatusAnnouncementError = () => { + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState(false) + + const resolveAction = async () => { + setLoading(true) + await new Promise(resolve => setTimeout(resolve, 1500)) + setLoading(false) + + return await true + } + + const onClick = (resolveType: 'error' | 'success') => async () => { + const actionResult = await resolveAction() + + if (resolveType === 'error') { + setError(actionResult) + return + } + + setError(!actionResult) + } + + return ( + <> + {!loading && error ? : null} + + + + ) +} diff --git a/packages/react/src/Button/Button.features.stories.tsx b/packages/react/src/Button/Button.features.stories.tsx index bc9fac28f55..842bb2eed3a 100644 --- a/packages/react/src/Button/Button.features.stories.tsx +++ b/packages/react/src/Button/Button.features.stories.tsx @@ -1,4 +1,4 @@ -import {EyeIcon, TriangleDownIcon, HeartIcon, CommentIcon} from '@primer/octicons-react' +import {EyeIcon, TriangleDownIcon, HeartIcon, DownloadIcon, CommentIcon} from '@primer/octicons-react' import React, {useState} from 'react' import {Button} from '.' import {Stack} from '../Stack/Stack' @@ -141,6 +141,46 @@ export const Medium = () => export const Large = () => +export const Loading = () => + +export const LoadingCustomAnnouncement = () => ( + +) + +export const LoadingWithLeadingVisual = () => ( + +) + +export const LoadingWithTrailingVisual = () => ( + +) + +export const LoadingWithTrailingAction = () => ( + +) + +export const LoadingTrigger = () => { + const [isLoading, setIsLoading] = useState(false) + + const handleClick = () => { + setIsLoading(true) + } + + return ( + + ) +} + export const LabelWrap = () => { return ( diff --git a/packages/react/src/Button/Button.stories.tsx b/packages/react/src/Button/Button.stories.tsx index 7642d726e04..017aa3de456 100644 --- a/packages/react/src/Button/Button.stories.tsx +++ b/packages/react/src/Button/Button.stories.tsx @@ -48,11 +48,21 @@ Playground.argTypes = { type: 'boolean', }, }, + loading: { + control: { + type: 'boolean', + }, + }, labelWrap: { control: { type: 'boolean', }, }, + count: { + control: { + type: 'number', + }, + }, leadingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]), trailingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]), trailingAction: OcticonArgType([TriangleDownIcon]), @@ -64,6 +74,7 @@ Playground.args = { inactive: false, variant: 'default', alignContent: 'center', + loading: false, trailingVisual: null, leadingVisual: null, trailingAction: null, diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx index 5ca0530e967..8b66fb72394 100644 --- a/packages/react/src/Button/ButtonBase.tsx +++ b/packages/react/src/Button/ButtonBase.tsx @@ -10,7 +10,23 @@ import {StyledButton} from './types' import {getVariantStyles, getButtonStyles, getAlignContentSize} from './styles' import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef' import {defaultSxProp} from '../utils/defaultSxProp' +import {VisuallyHidden} from '../internal/components/VisuallyHidden' +import Spinner from '../Spinner' import CounterLabel from '../CounterLabel' +import {useId} from '../hooks' +import {ConditionalWrapper} from '../internal/components/ConditionalWrapper' +import {AriaStatus} from '../live-region' + +const iconWrapStyles = { + display: 'flex', + pointerEvents: 'none', +} + +const renderVisual = (Visual: React.ElementType, loading: boolean, visualName: string) => ( + + {loading ? : } + +) const ButtonBase = forwardRef( ({children, as: Component = 'button', sx: sxProp = defaultSxProp, ...props}, forwardedRef): JSX.Element => { @@ -18,13 +34,19 @@ const ButtonBase = forwardRef( leadingVisual: LeadingVisual, trailingVisual: TrailingVisual, trailingAction: TrailingAction, + ['aria-describedby']: ariaDescribedBy, + ['aria-labelledby']: ariaLabelledBy, count, icon: Icon, + id, variant = 'default', size = 'medium', alignContent = 'center', block = false, + loading, + loadingAnnouncement = 'Loading', inactive, + onClick, labelWrap, ...rest } = props @@ -39,10 +61,8 @@ const ButtonBase = forwardRef( const sxStyles = useMemo(() => { return merge(baseStyles, sxProp) }, [baseStyles, sxProp]) - const iconWrapStyles = { - display: 'flex', - pointerEvents: 'none', - } + const uuid = useId(id) + const loadingAnnouncementID = `${uuid}-loading-announcement` if (__DEV__) { /** @@ -65,46 +85,100 @@ const ButtonBase = forwardRef( } return ( - - {Icon ? ( - - ) : ( - <> - - {LeadingVisual && ( - - - - )} - {children && {children}} - {count !== undefined && !TrailingVisual ? ( - - {count} - - ) : TrailingVisual ? ( - - - - ) : null} - - {TrailingAction && ( - - + Boolean(descriptionID)) + .join(' ')} + // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state. + // We only set it when the button is in a loading state because it will supercede the aria-label when the screen + // reader announces the button name. + aria-labelledby={ + loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy + } + id={id} + onClick={loading ? undefined : onClick} + > + {Icon ? ( + loading ? ( + + ) : ( + + ) + ) : ( + <> + + { + /* If there are no leading/trailing visuals/actions to replace with a loading spinner, + render a loading spiner in place of the button content. */ + loading && + !LeadingVisual && + !TrailingVisual && + !TrailingAction && + renderVisual(Spinner, loading, 'loadingSpinner') + } + { + /* Render a leading visual unless the button is in a loading state. + Then replace the leading visual with a loading spinner. */ + LeadingVisual && renderVisual(LeadingVisual, Boolean(loading), 'leadingVisual') + } + {children && ( + + {children} + + )} + { + /* If there is a count, render a counter label unless there is a trailing visual. + Then render the counter label as a trailing visual. + Replace the counter label or the trailing visual with a loading spinner if: + - the button is in a loading state + - there is no leading visual to replace with a loading spinner + */ + count !== undefined && !TrailingVisual + ? renderVisual( + () => {count}, + Boolean(loading) && !LeadingVisual, + 'trailingVisual', + ) + : TrailingVisual + ? renderVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual') + : null + } - )} - + { + /* If there is a trailing action, render it unless the button is in a loading state + and there is no leading or trailing visual to replace with a loading spinner. */ + TrailingAction && + renderVisual(TrailingAction, Boolean(loading) && !LeadingVisual && !TrailingVisual, 'trailingAction') + } + + )} + + {loading && ( + + {loadingAnnouncement} + )} - + ) }, ) as PolymorphicForwardRefComponent<'button' | 'a', ButtonProps> diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx index 745eb362574..5d00b7678b3 100644 --- a/packages/react/src/Button/IconButton.features.stories.tsx +++ b/packages/react/src/Button/IconButton.features.stories.tsx @@ -1,5 +1,5 @@ -import {HeartIcon, InboxIcon, ChevronDownIcon, BoldIcon} from '@primer/octicons-react' -import React from 'react' +import React, {useState} from 'react' +import {HeartIcon, InboxIcon, ChevronDownIcon, DownloadIcon, BoldIcon} from '@primer/octicons-react' import {IconButton} from '.' import {ActionMenu} from '../ActionMenu' import {ActionList} from '../ActionList' @@ -89,6 +89,20 @@ export const AsAMenuAnchor = () => ( ) +export const Loading = () => + +export const LoadingTrigger = () => { + const [isLoading, setIsLoading] = useState(false) + + const handleClick = () => { + setIsLoading(true) + setTimeout(() => { + setIsLoading(false) + }, 3000) + } + + return +} export const KeyshortcutsOnDescription = () => ( ( Default ) + +export const Loading = () => + +export const LoadingCustomAnnouncement = () => ( + +) + +export const LoadingWithLeadingVisual = () => ( + +) + +export const LoadingWithTrailingVisual = () => ( + +) diff --git a/packages/react/src/Button/LinkButton.stories.tsx b/packages/react/src/Button/LinkButton.stories.tsx index 2ef1d371c5a..0a4c6828a3d 100644 --- a/packages/react/src/Button/LinkButton.stories.tsx +++ b/packages/react/src/Button/LinkButton.stories.tsx @@ -41,6 +41,11 @@ Playground.argTypes = { trailingIcon: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]), trailingAction: OcticonArgType([ChevronRightIcon]), href: {control: 'text'}, + loading: { + control: { + type: 'boolean', + }, + }, } Playground.args = { block: false, @@ -50,6 +55,7 @@ Playground.args = { trailingIcon: null, leadingIcon: null, href: '/', + loading: false, } export const Default = () => ( diff --git a/packages/react/src/Button/__tests__/Button.test.tsx b/packages/react/src/Button/__tests__/Button.test.tsx index 882e5f1cdce..976cb5c8474 100644 --- a/packages/react/src/Button/__tests__/Button.test.tsx +++ b/packages/react/src/Button/__tests__/Button.test.tsx @@ -3,13 +3,35 @@ import {render, screen, fireEvent} from '@testing-library/react' import axe from 'axe-core' import React from 'react' import {IconButton, Button} from '../../Button' +import type {ButtonProps} from '../../Button' import {behavesAsComponent} from '../../utils/testing' +type StatefulLoadingButtonProps = { + children?: React.ReactNode + id?: string + ['aria-describedby']?: string + loadingAnnouncement?: string +} + +const TestButton = (props: ButtonProps) => ) + const container = render() const button = container.getByRole('button') expect(button.textContent).toEqual('Default') }) @@ -29,7 +51,11 @@ describe('Button', () => { }) it('respects block prop', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) @@ -48,31 +74,51 @@ describe('Button', () => { }) it('respects the small size prop', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) it('respects the large size prop', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) it('styles primary button appropriately', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) it('styles invisible button appropriately', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) it('styles danger button appropriately', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) @@ -84,7 +130,11 @@ describe('Button', () => { }) it('respects the alignContent prop', () => { - const container = render() + const container = render( + , + ) const button = container.getByRole('button') expect(button).toMatchSnapshot() }) @@ -114,6 +164,92 @@ describe('Button', () => { expect(position).toBe(Node.DOCUMENT_POSITION_FOLLOWING) }) + it('should describe the button with a default loading announcement, and only when the button is in a loading state', () => { + const buttonId = 'loading-button' + const container = render( + + content + , + ) + const buttonNode = container.getByRole('button') + + expect(buttonNode.getAttribute('aria-describedby')).toBe(`${buttonId}-loading-announcement`) + + expect(buttonNode).not.toHaveAccessibleDescription('Loading') + + fireEvent.click(buttonNode) + + // not sure why, but we need to wait a tick for the the loading state to actually be set + setTimeout(() => { + expect(buttonNode).toHaveAccessibleDescription('Loading') + }, 0) + }) + + it('should render a custom loading announcement, and only when the button is in a loading state', () => { + const buttonId = 'loading-button' + const container = render( + + content + , + ) + const buttonNode = container.getByRole('button') + + expect(buttonNode.getAttribute('aria-describedby')).toBe(`${buttonId}-loading-announcement`) + + expect(buttonNode).not.toHaveAccessibleDescription('Action loading') + + fireEvent.click(buttonNode) + + // not sure why, but we need to wait a tick for the the loading state to actually be set + setTimeout(() => { + expect(buttonNode).toHaveAccessibleDescription('Action loading') + }, 0) + }) + + it('should be described by loading announcement AND whatever is passed to aria-describedby', () => { + const buttonDescriptionId = 'button-description' + const buttonId = 'loading-button' + const container = render( + + content + , + ) + const buttonDescribedBy = container.getByRole('button').getAttribute('aria-describedby') + const loadingAnnouncementId = `${buttonId}-loading-announcement` + + expect(buttonDescribedBy).toContain(loadingAnnouncementId) + + expect(buttonDescribedBy).toContain(buttonDescriptionId) + }) + + it('should only set aria-disabled to "true" when the button is in a loading state', () => { + const container = render( + + content + , + ) + const buttonNode = container.getByRole('button') + + expect(buttonNode.getAttribute('aria-disabled')).not.toBe('true') + + // not sure why, but we need to wait a tick for the the loading state to actually be set + setTimeout(() => { + expect(buttonNode.getAttribute('aria-disabled')).toBe('true') + }, 0) + }) + + it('allows the consumer to override `aria-disabled`', () => { + const container = render() + + expect(container.getByRole('button')).toHaveAttribute('aria-disabled', 'true') + }) + + it('should preserve the accessible button name when the button is in a loading state', () => { + const container = render() + + expect(container.getByRole('button')).toHaveAccessibleName('content') + }) + it('should render tooltip on an icon button when unsafeDisableTooltip prop is passed as false', () => { const {getByRole, getByText} = render( , @@ -129,7 +265,7 @@ describe('Button', () => { const triggerEL = getByRole('button') expect(triggerEL).toHaveAttribute('aria-label', 'Heart') const tooltipEl = getByText('Love is all around') - expect(triggerEL).toHaveAttribute('aria-describedby', tooltipEl.id) + expect(triggerEL.getAttribute('aria-describedby')).toContain(tooltipEl.id) }) it('should render tooltip on an icon button by default', () => { const {getByRole, getByText} = render() @@ -155,7 +291,7 @@ describe('Button', () => { expect(triggerEl).toHaveAttribute('aria-labelledby', tooltipEl.id) }) it('should render aria-keyshorts on an icon button when keyshortcuts prop is passed (Description Type)', () => { - const {getByRole, getByText} = render( + const {getByRole} = render( { />, ) const triggerEl = getByRole('button') - const tooltipEl = getByText('Love is all around, Command+H') - expect(triggerEl).toHaveAttribute('aria-describedby', tooltipEl.id) + expect(triggerEl).toHaveAttribute('aria-keyshortcuts', 'Command+H') }) it('should append the keyshortcuts to the tooltip text that describes the icon button when keyshortcuts prop is passed (Description Type)', () => { const {getByRole, getByText} = render( @@ -181,6 +316,6 @@ describe('Button', () => { const triggerEl = getByRole('button') const tooltipEl = getByText('Love is all around, Command+H') expect(tooltipEl).toBeInTheDocument() - expect(triggerEl).toHaveAttribute('aria-describedby', tooltipEl.id) + expect(triggerEl.getAttribute('aria-describedby')).toEqual(expect.stringContaining(tooltipEl.id)) }) }) diff --git a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap index 03a5c0187ed..87c8f973d08 100644 --- a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap +++ b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap @@ -253,6 +253,16 @@ exports[`Button respects block prop 1`] = ` margin-right: 8px; } +.c0 [data-component="loadingSpinner"] { + grid-area: text; + margin-right: 0px !important; + place-self: center; +} + +.c0 [data-component="loadingSpinner"] + [data-component="text"] { + visibility: hidden; +} + .c0:hover:not([disabled]):not([data-inactive]) { background-color: btn.hoverBg; border-color: var(--button-default-borderColor-hover,undefined); @@ -285,9 +295,12 @@ exports[`Button respects block prop 1`] = ` } + + + + ) +} diff --git a/packages/react/src/ButtonGroup/ButtonGroup.tsx b/packages/react/src/ButtonGroup/ButtonGroup.tsx index 251045b990a..0ba399c9ad3 100644 --- a/packages/react/src/ButtonGroup/ButtonGroup.tsx +++ b/packages/react/src/ButtonGroup/ButtonGroup.tsx @@ -8,7 +8,7 @@ const ButtonGroup = styled.div` vertical-align: middle; isolation: isolate; - && > * { + && > *:not([data-loading-wrapper]) { margin-inline-end: -1px; position: relative; border-radius: 0; @@ -30,6 +30,37 @@ const ButtonGroup = styled.div` } } + // if child is loading button + [data-loading-wrapper] { + :first-child { + button, + a { + border-top-left-radius: ${get('radii.2')}; + border-bottom-left-radius: ${get('radii.2')}; + } + } + + :last-child { + button, + a { + border-top-right-radius: ${get('radii.2')}; + border-bottom-right-radius: ${get('radii.2')}; + } + } + } + + [data-loading-wrapper] > * { + margin-inline-end: -1px; + position: relative; + border-radius: 0; + + :focus, + :active, + :hover { + z-index: 1; + } + } + ${sx}; ` diff --git a/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap b/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap index 9a72798f35b..b52856e870b 100644 --- a/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap +++ b/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap @@ -1274,15 +1274,15 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav class="c0" >