diff --git a/.changeset/four-fans-pump.md b/.changeset/four-fans-pump.md new file mode 100644 index 00000000000..5aa62707c55 --- /dev/null +++ b/.changeset/four-fans-pump.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Adds character counts to TextInput and TextArea components diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-colorblind-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-colorblind-linux.png new file mode 100644 index 00000000000..7848a14449e Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-dimmed-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-dimmed-linux.png new file mode 100644 index 00000000000..94e13d7404d Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-high-contrast-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-high-contrast-linux.png new file mode 100644 index 00000000000..12c44d4ad9f Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-linux.png new file mode 100644 index 00000000000..19ea68447e1 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-tritanopia-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-tritanopia-linux.png new file mode 100644 index 00000000000..19ea68447e1 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-colorblind-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-colorblind-linux.png new file mode 100644 index 00000000000..09c527f70bf Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-high-contrast-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-high-contrast-linux.png new file mode 100644 index 00000000000..c058e943598 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-linux.png new file mode 100644 index 00000000000..7e4baa42c99 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-tritanopia-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-tritanopia-linux.png new file mode 100644 index 00000000000..7e4baa42c99 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-Exceeded-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-colorblind-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-colorblind-linux.png new file mode 100644 index 00000000000..0fe557946a9 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-dimmed-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-dimmed-linux.png new file mode 100644 index 00000000000..75ed62db7ad Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-high-contrast-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-high-contrast-linux.png new file mode 100644 index 00000000000..37b3327b680 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-linux.png new file mode 100644 index 00000000000..0fe557946a9 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-tritanopia-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-tritanopia-linux.png new file mode 100644 index 00000000000..0fe557946a9 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-colorblind-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-colorblind-linux.png new file mode 100644 index 00000000000..9e16b19115d Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-high-contrast-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-high-contrast-linux.png new file mode 100644 index 00000000000..05e9304d789 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-linux.png new file mode 100644 index 00000000000..9e16b19115d Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-tritanopia-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-tritanopia-linux.png new file mode 100644 index 00000000000..9e16b19115d Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-and-Caption-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-colorblind-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-colorblind-linux.png new file mode 100644 index 00000000000..4be77a4a4a6 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-dimmed-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-dimmed-linux.png new file mode 100644 index 00000000000..8d1ab59bf5c Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-high-contrast-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-high-contrast-linux.png new file mode 100644 index 00000000000..81232723e8f Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-linux.png new file mode 100644 index 00000000000..4be77a4a4a6 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-tritanopia-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-tritanopia-linux.png new file mode 100644 index 00000000000..4be77a4a4a6 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-colorblind-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-colorblind-linux.png new file mode 100644 index 00000000000..24336fb51c7 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-high-contrast-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-high-contrast-linux.png new file mode 100644 index 00000000000..8a9099de3d2 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-linux.png new file mode 100644 index 00000000000..24336fb51c7 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-linux.png differ diff --git a/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-tritanopia-linux.png b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-tritanopia-linux.png new file mode 100644 index 00000000000..24336fb51c7 Binary files /dev/null and b/.playwright/snapshots/components/TextInput.test.ts-snapshots/TextInput-With-Character-Limit-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-colorblind-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-colorblind-linux.png new file mode 100644 index 00000000000..94caf2a60c7 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-dimmed-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-dimmed-linux.png new file mode 100644 index 00000000000..6634eaa7900 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-high-contrast-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-high-contrast-linux.png new file mode 100644 index 00000000000..7942c3dda03 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-linux.png new file mode 100644 index 00000000000..c789e187374 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-tritanopia-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-tritanopia-linux.png new file mode 100644 index 00000000000..c789e187374 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-colorblind-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-colorblind-linux.png new file mode 100644 index 00000000000..673628626b6 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-high-contrast-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-high-contrast-linux.png new file mode 100644 index 00000000000..67e40b6c01d Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-linux.png new file mode 100644 index 00000000000..7a5d6c8f2da Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-tritanopia-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-tritanopia-linux.png new file mode 100644 index 00000000000..7a5d6c8f2da Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-Exceeded-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-colorblind-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-colorblind-linux.png new file mode 100644 index 00000000000..a8433e0bbc8 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-dimmed-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-dimmed-linux.png new file mode 100644 index 00000000000..312d4f9e07f Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-high-contrast-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-high-contrast-linux.png new file mode 100644 index 00000000000..99036a706ee Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-linux.png new file mode 100644 index 00000000000..a8433e0bbc8 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-tritanopia-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-tritanopia-linux.png new file mode 100644 index 00000000000..a8433e0bbc8 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-colorblind-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-colorblind-linux.png new file mode 100644 index 00000000000..86f345890f0 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-high-contrast-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-high-contrast-linux.png new file mode 100644 index 00000000000..050d43f7d6b Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-linux.png new file mode 100644 index 00000000000..86f345890f0 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-tritanopia-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-tritanopia-linux.png new file mode 100644 index 00000000000..86f345890f0 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-and-Caption-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-colorblind-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-colorblind-linux.png new file mode 100644 index 00000000000..d48542f2803 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-dimmed-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-dimmed-linux.png new file mode 100644 index 00000000000..3e050e1ec55 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-high-contrast-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-high-contrast-linux.png new file mode 100644 index 00000000000..5bad01f4d7a Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-linux.png new file mode 100644 index 00000000000..d48542f2803 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-tritanopia-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-tritanopia-linux.png new file mode 100644 index 00000000000..d48542f2803 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-colorblind-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-colorblind-linux.png new file mode 100644 index 00000000000..55302ab9683 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-high-contrast-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-high-contrast-linux.png new file mode 100644 index 00000000000..bfe54dd8cae Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-linux.png new file mode 100644 index 00000000000..55302ab9683 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-linux.png differ diff --git a/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-tritanopia-linux.png b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-tritanopia-linux.png new file mode 100644 index 00000000000..55302ab9683 Binary files /dev/null and b/.playwright/snapshots/components/Textarea.test.ts-snapshots/Textarea-With-Character-Limit-light-tritanopia-linux.png differ diff --git a/e2e/components/TextInput.test.ts b/e2e/components/TextInput.test.ts index 114854866b2..d640f166fae 100644 --- a/e2e/components/TextInput.test.ts +++ b/e2e/components/TextInput.test.ts @@ -165,6 +165,60 @@ test.describe('TextInput', () => { } }) + test.describe('With Character Limit', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-textinput-features--with-character-limit', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`TextInput.With Character Limit.${theme}.png`) + }) + }) + } + }) + + test.describe('With Character Limit and Caption', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-textinput-features--with-character-limit-and-caption', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`TextInput.With Character Limit and Caption.${theme}.png`) + }) + }) + } + }) + + test.describe('With Character Limit Exceeded', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-textinput-features--with-character-limit-exceeded', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`TextInput.With Character Limit Exceeded.${theme}.png`) + }) + }) + } + }) + test.describe('With Leading Visual', () => { for (const theme of themes) { test.describe(theme, () => { diff --git a/e2e/components/Textarea.test.ts b/e2e/components/Textarea.test.ts index 4b57c97097a..a87a1743782 100644 --- a/e2e/components/Textarea.test.ts +++ b/e2e/components/Textarea.test.ts @@ -43,6 +43,18 @@ const stories = [ id: 'components-textarea-features--custom-width', title: 'Custom Width', }, + { + id: 'components-textarea-features--with-character-limit', + title: 'With Character Limit', + }, + { + id: 'components-textarea-features--with-character-limit-and-caption', + title: 'With Character Limit and Caption', + }, + { + id: 'components-textarea-features--with-character-limit-exceeded', + title: 'With Character Limit Exceeded', + }, { id: 'components-textarea-dev--dev-default', title: 'Dev Default', diff --git a/packages/react/src/TextInput/TextInput.docs.json b/packages/react/src/TextInput/TextInput.docs.json index 7da337b54cb..ff68d969c31 100644 --- a/packages/react/src/TextInput/TextInput.docs.json +++ b/packages/react/src/TextInput/TextInput.docs.json @@ -73,6 +73,11 @@ "defaultValue": "false", "description": "Creates a full-width input element" }, + { + "name": "characterLimit", + "type": "number", + "description": "The maximum number of characters allowed in the input" + }, { "name": "contrast", "type": "boolean", diff --git a/packages/react/src/TextInput/TextInput.features.stories.tsx b/packages/react/src/TextInput/TextInput.features.stories.tsx index 7f68ff98f1c..46009e46a2f 100644 --- a/packages/react/src/TextInput/TextInput.features.stories.tsx +++ b/packages/react/src/TextInput/TextInput.features.stories.tsx @@ -318,3 +318,44 @@ export const WithAutocompleteAttribute = () => ( ) + +export const WithCharacterLimit = () => { + const [value, setValue] = useState('') + + return ( +
+ + Username + setValue(e.target.value)} characterLimit={20} /> + +
+ ) +} + +export const WithCharacterLimitAndCaption = () => { + const [value, setValue] = useState('') + + return ( +
+ + Username + setValue(e.target.value)} characterLimit={20} /> + Choose a unique username + +
+ ) +} + +export const WithCharacterLimitExceeded = () => { + const [value, setValue] = useState('This is a very long text that exceeds the limit') + + return ( +
+ + Bio + setValue(e.target.value)} characterLimit={20} /> + Keep it short + +
+ ) +} diff --git a/packages/react/src/TextInput/TextInput.module.css b/packages/react/src/TextInput/TextInput.module.css new file mode 100644 index 00000000000..c261a151557 --- /dev/null +++ b/packages/react/src/TextInput/TextInput.module.css @@ -0,0 +1,10 @@ +.CharacterCounter { + display: flex; + align-items: center; + gap: var(--control-xsmall-gap); + color: var(--fgColor-muted); +} + +.CharacterCounter--error { + color: var(--fgColor-danger); +} diff --git a/packages/react/src/TextInput/TextInput.test.tsx b/packages/react/src/TextInput/TextInput.test.tsx index a4aa0659153..a8928761d79 100644 --- a/packages/react/src/TextInput/TextInput.test.tsx +++ b/packages/react/src/TextInput/TextInput.test.tsx @@ -31,7 +31,7 @@ describe('TextInput', () => { }) it('renders error', () => { - expect(render().container).toMatchSnapshot() + expect(render().container).toMatchSnapshot() }) it('renders sets aria-invalid="true" on error', () => { @@ -40,15 +40,15 @@ describe('TextInput', () => { }) it('renders contrast', () => { - expect(render().container).toMatchSnapshot() + expect(render().container).toMatchSnapshot() }) it('renders monospace', () => { - expect(render().container).toMatchSnapshot() + expect(render().container).toMatchSnapshot() }) it('renders placeholder', () => { - expect(render().container).toMatchSnapshot() + expect(render().container).toMatchSnapshot() }) it('renders leadingVisual', () => { @@ -194,7 +194,7 @@ describe('TextInput', () => { }) it('should render a password input', () => { - expect(render().container).toMatchSnapshot() + expect(render().container).toMatchSnapshot() }) it('should not override prop aria-invalid', () => { @@ -270,4 +270,99 @@ describe('TextInput', () => { const {getByRole} = render() expect(getByRole('textbox')).not.toHaveAttribute('aria-describedby') }) + + describe('character counter', () => { + it('should render character counter when characterLimit is provided', () => { + const {container} = render() + expect(container.textContent).toContain('20 characters remaining') + }) + + it('should update character count on input', async () => { + const user = userEvent.setup() + const {getByRole, container} = render() + const input = getByRole('textbox') + + await user.type(input, 'Hello') + expect(container.textContent).toContain('15 characters remaining') + }) + + it('should show singular "character" when one character remains', async () => { + const user = userEvent.setup() + const {getByRole, container} = render() + const input = getByRole('textbox') + + await user.type(input, 'Test') + expect(container.textContent).toContain('1 character remaining') + }) + + it('should show error state when character limit is exceeded', async () => { + const user = userEvent.setup() + const {getByRole, container} = render() + const input = getByRole('textbox') + + await user.type(input, 'Hello World') + expect(container.textContent).toContain('6 characters over') + expect(input).toHaveAttribute('aria-invalid', 'true') + }) + + it('should show alert icon when character limit is exceeded', async () => { + const user = userEvent.setup() + const {getByRole, container} = render() + const input = getByRole('textbox') + + await user.type(input, 'Hello World') + const icon = container.querySelector('svg') + expect(icon).toBeInTheDocument() + }) + + it('should clear error state when back under limit', async () => { + const user = userEvent.setup() + const {getByRole, container} = render() + const input = getByRole('textbox') + + expect(container.textContent).toContain('2 characters over') + + await user.clear(input) + await user.type(input, 'Hello') + + expect(container.textContent).toContain('5 characters remaining') + expect(input).not.toHaveAttribute('aria-invalid', 'true') + }) + + it('should have aria-describedby pointing to static message', () => { + const {getByRole, container} = render() + const input = getByRole('textbox') + const describedBy = input.getAttribute('aria-describedby') + expect(describedBy).toBeTruthy() + + const staticMessage = Array.from(container.querySelectorAll('[id]')).find(el => + el.textContent.includes('You can enter up to'), + ) + expect(staticMessage).toBeTruthy() + expect(describedBy).toContain(staticMessage?.id) + }) + + it('should have screen reader announcement element', () => { + const {container} = render() + const srElement = container.querySelector('[aria-live="polite"]') + expect(srElement).toBeInTheDocument() + expect(srElement).toHaveAttribute('role', 'status') + }) + + it('should have static screen reader message', () => { + const {container} = render() + expect(container.textContent).toContain('You can enter up to 20 characters') + }) + + it('should show singular character in static message when limit is 1', () => { + const {container} = render() + expect(container.textContent).toContain('You can enter up to 1 character') + }) + + it('should not announce on initial load', () => { + const {container} = render() + const srElement = container.querySelector('[aria-live="polite"]') + expect(srElement?.textContent).toBe('') + }) + }) }) diff --git a/packages/react/src/TextInput/TextInput.tsx b/packages/react/src/TextInput/TextInput.tsx index dee4f95edef..6f30c31428e 100644 --- a/packages/react/src/TextInput/TextInput.tsx +++ b/packages/react/src/TextInput/TextInput.tsx @@ -1,9 +1,11 @@ import type {MouseEventHandler} from 'react' -import React, {useCallback, useState, useId} from 'react' +import React, {useCallback, useState, useId, useEffect, useRef} from 'react' import {isValidElementType} from 'react-is' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' import {clsx} from 'clsx' +import {AlertFillIcon} from '@primer/octicons-react' +import classes from './TextInput.module.css' import TextInputInnerVisualSlot from '../internal/components/TextInputInnerVisualSlot' import {useProvidedRefOrCreate} from '../hooks' import type {Merge} from '../utils/types' @@ -12,6 +14,8 @@ import TextInputWrapper from '../internal/components/TextInputWrapper' import TextInputAction from '../internal/components/TextInputInnerAction' import UnstyledTextInput from '../internal/components/UnstyledTextInput' import VisuallyHidden from '../_VisuallyHidden' +import {CharacterCounter} from '../utils/character-counter' +import Text from '../Text' export type TextInputNonPassthroughProps = { /** @deprecated Use `leadingVisual` or `trailingVisual` prop instead */ @@ -39,6 +43,11 @@ export type TextInputNonPassthroughProps = { * A visual that renders inside the input after the typing area */ trailingAction?: React.ReactElement> + /** + * Optional character limit for the input. If provided, a character counter will be displayed below the input. + * When the limit is exceeded, validation styling will be applied. + */ + characterLimit?: number } & Partial< Pick< StyledWrapperProps, @@ -85,12 +94,21 @@ const TextInput = React.forwardRef( // end deprecated props type = 'text', required, + characterLimit, + onChange, + value, + defaultValue, ...inputProps }, ref, ) => { const [isInputFocused, setIsInputFocused] = useState(false) const inputRef = useProvidedRefOrCreate(ref as React.RefObject) + const [characterCount, setCharacterCount] = useState('') + const [isOverLimit, setIsOverLimit] = useState(false) + const [screenReaderMessage, setScreenReaderMessage] = useState('') + const characterCounterRef = useRef(null) + // this class is necessary to style FilterSearch, plz no touchy! const wrapperClasses = clsx(className, 'TextInput-wrapper') const showLeadingLoadingIndicator = @@ -134,64 +152,137 @@ const TextInput = React.forwardRef( [onBlur], ) + // Initialize character counter + useEffect(() => { + if (characterLimit) { + characterCounterRef.current = new CharacterCounter({ + onCountUpdate: (count, overLimit, message) => { + setCharacterCount(message) + setIsOverLimit(overLimit) + }, + onScreenReaderAnnounce: message => { + setScreenReaderMessage(message) + }, + }) + + return () => { + characterCounterRef.current?.cleanup() + characterCounterRef.current = null + } + } + }, [characterLimit]) + + // Update character count when value changes or on mount + useEffect(() => { + if (characterLimit && characterCounterRef.current) { + const currentValue = + value !== undefined ? String(value) : defaultValue !== undefined ? String(defaultValue) : '' + characterCounterRef.current.updateCharacterCount(currentValue.length, characterLimit) + } + }, [value, defaultValue, characterLimit]) + + // Handle input change with character counter + const handleInputChange = useCallback( + (e: React.ChangeEvent) => { + if (characterLimit && characterCounterRef.current) { + characterCounterRef.current.updateCharacterCount(e.target.value.length, characterLimit) + } + onChange?.(e) + }, + [onChange, characterLimit], + ) + + const characterCountId = useId() + const characterCountStaticMessageId = useId() + + const isValid = isOverLimit ? 'error' : validationStatus + return ( - - {IconComponent && } - - {typeof LeadingVisual !== 'string' && isValidElementType(LeadingVisual) ? : LeadingVisual} - - + - {loading && {loaderText}} - - {typeof TrailingVisual !== 'string' && isValidElementType(TrailingVisual) ? ( - - ) : ( - TrailingVisual - )} - - {trailingAction} - + {IconComponent && } + + {typeof LeadingVisual !== 'string' && isValidElementType(LeadingVisual) ? : LeadingVisual} + + + {loading && {loaderText}} + + {typeof TrailingVisual !== 'string' && isValidElementType(TrailingVisual) ? ( + + ) : ( + TrailingVisual + )} + + {trailingAction} + + {characterLimit && ( + <> + + {screenReaderMessage} + + + You can enter up to {characterLimit} {characterLimit === 1 ? 'character' : 'characters'} + + + + )} + ) }, ) as PolymorphicForwardRefComponent<'input', TextInputProps> diff --git a/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap b/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap index b378ae7a1ea..b491ab7d0bf 100644 --- a/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap +++ b/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap @@ -12,6 +12,7 @@ exports[`TextInput > renders contrast 1`] = ` data-component="input" name="zipcode" type="text" + value="" /> @@ -30,6 +31,7 @@ exports[`TextInput > renders error 1`] = ` data-component="input" name="zipcode" type="text" + value="" /> @@ -47,6 +49,7 @@ exports[`TextInput > renders monospace 1`] = ` data-component="input" name="zipcode" type="text" + value="" /> @@ -64,6 +67,7 @@ exports[`TextInput > renders placeholder 1`] = ` name="zipcode" placeholder="560076" type="text" + value="" /> @@ -80,6 +84,7 @@ exports[`TextInput > should render a password input 1`] = ` data-component="input" name="password" type="password" + value="" /> diff --git a/packages/react/src/Textarea/TextArea.module.css b/packages/react/src/Textarea/TextArea.module.css index 9c0b16520c9..9397b4f8b3d 100644 --- a/packages/react/src/Textarea/TextArea.module.css +++ b/packages/react/src/Textarea/TextArea.module.css @@ -32,3 +32,14 @@ .TextArea:disabled { resize: none; } + +.CharacterCounter { + display: flex; + align-items: center; + gap: var(--control-xsmall-gap); + color: var(--fgColor-muted); +} + +.CharacterCounter--error { + color: var(--fgColor-danger); +} diff --git a/packages/react/src/Textarea/Textarea.docs.json b/packages/react/src/Textarea/Textarea.docs.json index 55f5f70b5e4..d657b622f17 100644 --- a/packages/react/src/Textarea/Textarea.docs.json +++ b/packages/react/src/Textarea/Textarea.docs.json @@ -67,6 +67,11 @@ "defaultValue": "'both'", "description": "Changes the resize behavior" }, + { + "name": "characterLimit", + "type": "number", + "description": "The maximum number of characters allowed in the textarea" + }, { "name": "contrast", "type": "boolean", diff --git a/packages/react/src/Textarea/Textarea.features.stories.tsx b/packages/react/src/Textarea/Textarea.features.stories.tsx index ea1c33ae7b9..f25f68a8709 100644 --- a/packages/react/src/Textarea/Textarea.features.stories.tsx +++ b/packages/react/src/Textarea/Textarea.features.stories.tsx @@ -1,3 +1,4 @@ +import {useState} from 'react' import {FormControl, Heading, Stack} from '..' import Textarea from '../Textarea' @@ -122,3 +123,46 @@ export const MaximumHeight = () => ( ) + +export const WithCharacterLimit = () => { + const [value, setValue] = useState('') + + return ( +
+ + Bio +