@arbor-education/design-system.components 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/avatarGroup/AvatarGroup.d.ts +32 -0
  3. package/dist/components/avatarGroup/AvatarGroup.d.ts.map +1 -0
  4. package/dist/components/avatarGroup/AvatarGroup.js +37 -0
  5. package/dist/components/avatarGroup/AvatarGroup.js.map +1 -0
  6. package/dist/components/avatarGroup/AvatarGroup.stories.d.ts +14 -0
  7. package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -0
  8. package/dist/components/avatarGroup/AvatarGroup.stories.js +91 -0
  9. package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -0
  10. package/dist/components/avatarGroup/AvatarGroup.test.d.ts +2 -0
  11. package/dist/components/avatarGroup/AvatarGroup.test.d.ts.map +1 -0
  12. package/dist/components/avatarGroup/AvatarGroup.test.js +155 -0
  13. package/dist/components/avatarGroup/AvatarGroup.test.js.map +1 -0
  14. package/dist/components/avatarGroup/avatarGroupOverflow.d.ts +8 -0
  15. package/dist/components/avatarGroup/avatarGroupOverflow.d.ts.map +1 -0
  16. package/dist/components/avatarGroup/avatarGroupOverflow.js +14 -0
  17. package/dist/components/avatarGroup/avatarGroupOverflow.js.map +1 -0
  18. package/dist/components/avatarGroup/collectAvatarGroupChildren.d.ts +4 -0
  19. package/dist/components/avatarGroup/collectAvatarGroupChildren.d.ts.map +1 -0
  20. package/dist/components/avatarGroup/collectAvatarGroupChildren.js +26 -0
  21. package/dist/components/avatarGroup/collectAvatarGroupChildren.js.map +1 -0
  22. package/dist/components/avatarGroup/useAvatarGroupItems.d.ts +9 -0
  23. package/dist/components/avatarGroup/useAvatarGroupItems.d.ts.map +1 -0
  24. package/dist/components/avatarGroup/useAvatarGroupItems.js +24 -0
  25. package/dist/components/avatarGroup/useAvatarGroupItems.js.map +1 -0
  26. package/dist/components/table/Table.d.ts +1 -0
  27. package/dist/components/table/Table.d.ts.map +1 -1
  28. package/dist/components/table/Table.js +2 -0
  29. package/dist/components/table/Table.js.map +1 -1
  30. package/dist/components/table/Table.stories.d.ts.map +1 -1
  31. package/dist/components/table/Table.stories.js +447 -77
  32. package/dist/components/table/Table.stories.js.map +1 -1
  33. package/dist/components/table/Table.test.js +1 -1
  34. package/dist/components/table/Table.test.js.map +1 -1
  35. package/dist/components/table/TableSettingsDropdown.d.ts.map +1 -0
  36. package/dist/components/table/{pagination/TableSettingsDropdown.js → TableSettingsDropdown.js} +6 -6
  37. package/dist/components/table/TableSettingsDropdown.js.map +1 -0
  38. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts +2 -5
  39. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts.map +1 -1
  40. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js +7 -5
  41. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js.map +1 -1
  42. package/dist/index.css +33 -0
  43. package/dist/index.css.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +1 -0
  47. package/dist/index.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/components/avatarGroup/AvatarGroup.stories.tsx +218 -0
  50. package/src/components/avatarGroup/AvatarGroup.test.tsx +298 -0
  51. package/src/components/avatarGroup/AvatarGroup.tsx +127 -0
  52. package/src/components/avatarGroup/avatarGroup.scss +31 -0
  53. package/src/components/avatarGroup/avatarGroupOverflow.ts +29 -0
  54. package/src/components/avatarGroup/collectAvatarGroupChildren.ts +30 -0
  55. package/src/components/avatarGroup/useAvatarGroupItems.ts +39 -0
  56. package/src/components/table/Table.stories.tsx +678 -265
  57. package/src/components/table/Table.test.tsx +1 -1
  58. package/src/components/table/Table.tsx +2 -0
  59. package/src/components/table/{pagination/TableSettingsDropdown.tsx → TableSettingsDropdown.tsx} +1 -1
  60. package/src/components/table/cellRenderers/SelectDropdownCellRenderer.tsx +42 -12
  61. package/src/global.scss +1 -1
  62. package/src/index.scss +1 -0
  63. package/src/index.ts +7 -0
  64. package/src/tokens.scss +2 -0
  65. package/tokens/json/Arbor.json +13 -1
  66. package/dist/components/table/pagination/TableSettingsDropdown.d.ts.map +0 -1
  67. package/dist/components/table/pagination/TableSettingsDropdown.js.map +0 -1
  68. /package/dist/components/table/{pagination/TableSettingsDropdown.d.ts → TableSettingsDropdown.d.ts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,+CAA+C,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,gDAAgD,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uEAAuE,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,2DAA2D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oDAAoD,CAAC;AACtF,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,+CAA+C,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,gDAAgD,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uEAAuE,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,2DAA2D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oDAAoD,CAAC;AACtF,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,6BAA6B,EAClC,KAAK,gBAAgB,GACtB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC"}
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ export { Progress } from './components/progress/Progress';
32
32
  export { Toast } from './components/toast/Toast';
33
33
  export { DatePicker } from './components/datePicker/DatePicker';
34
34
  export { Avatar } from './components/avatar/Avatar';
35
+ export { AvatarGroup, } from './components/avatarGroup/AvatarGroup';
35
36
  export { UserDropdown } from './components/userDropdown/UserDropdown';
36
37
  export { SearchBar } from './components/searchBar/SearchBar';
37
38
  export { ArborLogo, GovhubLogo, KeyLogo, SampeopleLogo, RobinLogo, TimetablerLogo } from './components/userDropdown/assets/logos';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,+CAA+C,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,gDAAgD,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uEAAuE,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,2DAA2D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,SAAS,EAAuB,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oDAAoD,CAAC;AACtF,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,MAAM,EAAoB,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,+CAA+C,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,gDAAgD,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uEAAuE,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,2DAA2D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,SAAS,EAAuB,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oDAAoD,CAAC;AACtF,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,MAAM,EAAoB,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EACL,WAAW,GAKZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbor-education/design-system.components",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "The component library for the design system (the baby)",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -0,0 +1,218 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Avatar } from 'Components/avatar/Avatar';
3
+ import { AvatarGroup } from './AvatarGroup';
4
+
5
+ const meta: Meta<typeof AvatarGroup> = {
6
+ title: 'Components/AvatarGroup',
7
+ component: AvatarGroup,
8
+ tags: ['autodocs'],
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof AvatarGroup>;
14
+
15
+ const sampleSrc = (id: number) => `https://i.pravatar.cc/150?img=${id}`;
16
+
17
+ export const FromProps: Story = {
18
+ render: () => (
19
+ <AvatarGroup
20
+ label="Team from props"
21
+ items={[
22
+ { src: sampleSrc(1), alt: 'Member 1' },
23
+ { src: sampleSrc(2), alt: 'Member 2' },
24
+ { initials: 'CM', alt: 'Chris Montgomery' },
25
+ ]}
26
+ />
27
+ ),
28
+ };
29
+
30
+ export const SizeOverridesPerAvatar: Story = {
31
+ name: 'Size Override',
32
+ render: () => (
33
+ <AvatarGroup
34
+ label="All medium even though children are set as small"
35
+ size="medium"
36
+ showMaxItems={4}
37
+ items={[
38
+ <Avatar
39
+ key="1"
40
+ size="small"
41
+ initials="S1"
42
+ alt="Small 1"
43
+ />,
44
+ <Avatar
45
+ key="2"
46
+ size="small"
47
+ initials="S2"
48
+ alt="Small 2"
49
+ />,
50
+ { size: 'small', initials: 'S3', alt: 'Small 3' },
51
+ { size: 'small', initials: 'S4', alt: 'Small 4' },
52
+ { size: 'small', initials: 'S5', alt: 'Small 5' },
53
+ ]}
54
+ />
55
+ ),
56
+ };
57
+
58
+ export const ComposableChildren: Story = {
59
+ name: 'Composable Children',
60
+ parameters: {
61
+ docs: {
62
+ description: {
63
+ story:
64
+ 'Pass `<Avatar />` nodes as `children`.',
65
+ },
66
+ },
67
+ },
68
+ render: () => (
69
+ <AvatarGroup
70
+ label="Team (children API)"
71
+ showMaxItems={3}
72
+ listOrder="ascending"
73
+ >
74
+ <>
75
+ <Avatar
76
+ size="small"
77
+ initials="A"
78
+ alt="User A"
79
+ />
80
+ <Avatar
81
+ size="small"
82
+ initials="B"
83
+ alt="User B"
84
+ />
85
+ </>
86
+ <Avatar
87
+ size="small"
88
+ initials="C"
89
+ alt="User C"
90
+ />
91
+ <Avatar
92
+ size="small"
93
+ initials="D"
94
+ alt="User D"
95
+ />
96
+ <Avatar
97
+ size="small"
98
+ initials="E"
99
+ alt="User E"
100
+ />
101
+ </AvatarGroup>
102
+ ),
103
+ };
104
+
105
+ export const ComposableChildrenDescending: Story = {
106
+ name: 'Composed + Descending Order',
107
+ render: () => (
108
+ <AvatarGroup
109
+ label="Last three visible"
110
+ size="medium"
111
+ showMaxItems={3}
112
+ listOrder="descending"
113
+ >
114
+ <Avatar
115
+ initials="A"
116
+ alt="User A"
117
+ />
118
+ <Avatar
119
+ initials="B"
120
+ alt="User B"
121
+ />
122
+ <Avatar
123
+ initials="C"
124
+ alt="User C"
125
+ />
126
+ <Avatar
127
+ initials="D"
128
+ alt="User D"
129
+ />
130
+ <Avatar
131
+ initials="E"
132
+ alt="User E"
133
+ />
134
+ </AvatarGroup>
135
+ ),
136
+ };
137
+
138
+ export const AscendingWithOverflow: Story = {
139
+ render: () => (
140
+ <AvatarGroup
141
+ label="Ascending overflow"
142
+ listOrder="ascending"
143
+ showMaxItems={3}
144
+ items={[
145
+ { initials: 'A', alt: 'User A' },
146
+ { initials: 'B', alt: 'User B' },
147
+ { initials: 'C', alt: 'User C' },
148
+ { initials: 'D', alt: 'User D' },
149
+ { initials: 'E', alt: 'User E' },
150
+ ]}
151
+ />
152
+ ),
153
+ };
154
+
155
+ export const DescendingWithOverflow: Story = {
156
+ render: () => (
157
+ <AvatarGroup
158
+ label="Descending overflow"
159
+ listOrder="descending"
160
+ showMaxItems={3}
161
+ items={[
162
+ { initials: 'A', alt: 'User A' },
163
+ { initials: 'B', alt: 'User B' },
164
+ { initials: 'C', alt: 'User C' },
165
+ { initials: 'D', alt: 'User D' },
166
+ { initials: 'E', alt: 'User E' },
167
+ ]}
168
+ />
169
+ ),
170
+ };
171
+
172
+ export const CustomOverflowLabel: Story = {
173
+ parameters: {
174
+ docs: {
175
+ description: {
176
+ story:
177
+ 'Custom `overflowCountLabel` values replace the full screen-reader label for the overflow badge instead of appending to the visible `+N` text.',
178
+ },
179
+ },
180
+ },
181
+ render: () => (
182
+ <AvatarGroup
183
+ label="Custom overflow copy"
184
+ showMaxItems={2}
185
+ overflowCountLabel={n => `${n} more students`}
186
+ items={[
187
+ { initials: 'J', alt: 'Jacob' },
188
+ { initials: 'E', alt: 'Edward' },
189
+ { initials: 'B', alt: 'Bella' },
190
+ ]}
191
+ />
192
+ ),
193
+ };
194
+
195
+ export const AnnouncedOverflowUpdates: Story = {
196
+ name: 'Announced Overflow Updates',
197
+ parameters: {
198
+ docs: {
199
+ description: {
200
+ story:
201
+ 'Enables polite live announcements for the built-in overflow badge. Overflow members are not rendered; only the count is shown.',
202
+ },
203
+ },
204
+ },
205
+ render: () => (
206
+ <AvatarGroup
207
+ label="Live updates team"
208
+ showMaxItems={2}
209
+ presentAllUpdatesToScreenReader
210
+ items={[
211
+ { initials: 'A', alt: 'User A' },
212
+ { initials: 'B', alt: 'User B' },
213
+ { initials: 'C', alt: 'User C' },
214
+ { initials: 'D', alt: 'User D' },
215
+ ]}
216
+ />
217
+ ),
218
+ };
@@ -0,0 +1,298 @@
1
+ import React from 'react';
2
+ import { describe, expect, test, vi } from 'vitest';
3
+ import { render, screen, within } from '@testing-library/react';
4
+ import { Avatar } from 'Components/avatar/Avatar';
5
+ import { AvatarGroup } from './AvatarGroup';
6
+ import { partitionAvatarGroupOverflow } from './avatarGroupOverflow';
7
+ import { collectAvatarsFromChildren } from './collectAvatarGroupChildren';
8
+ import type { AvatarGroupNormalizedEntry } from './useAvatarGroupItems';
9
+ import '@testing-library/jest-dom/vitest';
10
+
11
+ const fourPeople: { initials: string; alt: string }[] = [
12
+ { initials: 'A', alt: 'User A' },
13
+ { initials: 'B', alt: 'User B' },
14
+ { initials: 'C', alt: 'User C' },
15
+ { initials: 'D', alt: 'User D' },
16
+ ];
17
+
18
+ describe('partitionAvatarGroupOverflow', () => {
19
+ const mockItems = (n: number): AvatarGroupNormalizedEntry[] =>
20
+ Array.from({ length: n }, (_, i) => ({
21
+ key: String(i),
22
+ avatarProps: { alt: `User ${i}` },
23
+ }));
24
+
25
+ test('ascending keeps head visible and counts overflow', () => {
26
+ const items = mockItems(4);
27
+ const { visibleItems, overflowCount } = partitionAvatarGroupOverflow(items, 2, 'ascending');
28
+ expect(visibleItems.map(x => x.key)).toEqual(['0', '1']);
29
+ expect(overflowCount).toBe(2);
30
+ });
31
+
32
+ test('descending keeps tail visible and counts overflow', () => {
33
+ const items = mockItems(4);
34
+ const { visibleItems, overflowCount } = partitionAvatarGroupOverflow(items, 2, 'descending');
35
+ expect(visibleItems.map(x => x.key)).toEqual(['2', '3']);
36
+ expect(overflowCount).toBe(2);
37
+ });
38
+
39
+ test('no overflow when showMaxItems omits or covers all', () => {
40
+ const items = mockItems(3);
41
+ expect(partitionAvatarGroupOverflow(items, undefined, 'ascending').overflowCount).toBe(0);
42
+ expect(partitionAvatarGroupOverflow(items, 10, 'ascending').overflowCount).toBe(0);
43
+ });
44
+
45
+ test('floors decimal values and treats negative values as show all', () => {
46
+ const items = mockItems(4);
47
+ const decimalResult = partitionAvatarGroupOverflow(items, 2.9, 'ascending');
48
+ const negativeResult = partitionAvatarGroupOverflow(items, -1, 'descending');
49
+
50
+ expect(decimalResult.visibleItems.map(x => x.key)).toEqual(['0', '1']);
51
+ expect(decimalResult.overflowCount).toBe(2);
52
+ expect(negativeResult.visibleItems.map(x => x.key)).toEqual(['0', '1', '2', '3']);
53
+ expect(negativeResult.overflowCount).toBe(0);
54
+ });
55
+ });
56
+
57
+ describe('AvatarGroup', () => {
58
+ test('renders avatar props objects', () => {
59
+ render(
60
+ <AvatarGroup
61
+ items={[
62
+ { initials: 'CM', alt: 'Chris M' },
63
+ { initials: 'JD', alt: 'Jay D' },
64
+ ]}
65
+ />,
66
+ );
67
+ expect(screen.getByLabelText('Chris M')).toBeInTheDocument();
68
+ expect(screen.getByLabelText('Jay D')).toBeInTheDocument();
69
+ });
70
+
71
+ test('renders pre-built Avatar elements', () => {
72
+ render(
73
+ <AvatarGroup
74
+ items={[
75
+ <Avatar
76
+ key="one"
77
+ initials="X"
78
+ alt="Xavier"
79
+ />,
80
+ <Avatar
81
+ key="two"
82
+ initials="Y"
83
+ alt="Yolanda"
84
+ />,
85
+ ]}
86
+ />,
87
+ );
88
+ expect(screen.getByLabelText('Xavier')).toBeInTheDocument();
89
+ expect(screen.getByLabelText('Yolanda')).toBeInTheDocument();
90
+ });
91
+
92
+ test('mixes Avatar elements and props objects', () => {
93
+ render(
94
+ <AvatarGroup
95
+ items={[
96
+ <Avatar
97
+ key="e"
98
+ initials="E"
99
+ alt="Element user"
100
+ />,
101
+ { initials: 'P', alt: 'Props user' },
102
+ ]}
103
+ />,
104
+ );
105
+ expect(screen.getByLabelText('Element user')).toBeInTheDocument();
106
+ expect(screen.getByLabelText('Props user')).toBeInTheDocument();
107
+ });
108
+
109
+ test('applies showMaxItems and overflow +N with default aria-label', () => {
110
+ render(
111
+ <AvatarGroup
112
+ items={fourPeople}
113
+ showMaxItems={2}
114
+ listOrder="ascending"
115
+ />,
116
+ );
117
+ expect(screen.getByText('+2')).toBeInTheDocument();
118
+ const overflow = screen.getByRole('status', { name: 'plus 2 more' });
119
+ expect(overflow).toBeInTheDocument();
120
+ expect(screen.getByLabelText('User A')).toBeInTheDocument();
121
+ expect(screen.getByLabelText('User B')).toBeInTheDocument();
122
+ expect(screen.queryByLabelText('User C')).not.toBeInTheDocument();
123
+ expect(screen.queryByLabelText('User D')).not.toBeInTheDocument();
124
+ });
125
+
126
+ test('descending shows last N avatars in the main list', () => {
127
+ const { container } = render(
128
+ <AvatarGroup
129
+ items={fourPeople}
130
+ showMaxItems={2}
131
+ listOrder="descending"
132
+ />,
133
+ );
134
+ const visibleLis = container.querySelectorAll('.ds-avatar-group > .ds-avatar-group__item');
135
+ expect(visibleLis).toHaveLength(2);
136
+ expect(within(visibleLis[0] as HTMLElement).getByText('C')).toBeInTheDocument();
137
+ expect(within(visibleLis[1] as HTMLElement).getByText('D')).toBeInTheDocument();
138
+
139
+ expect(screen.queryByLabelText('User A')).not.toBeInTheDocument();
140
+ expect(screen.queryByLabelText('User B')).not.toBeInTheDocument();
141
+ });
142
+
143
+ test('overflowCountLabel string override', () => {
144
+ render(
145
+ <AvatarGroup
146
+ items={fourPeople}
147
+ showMaxItems={2}
148
+ overflowCountLabel="two teammates not shown"
149
+ />,
150
+ );
151
+ expect(screen.getByRole('status', { name: 'two teammates not shown' })).toBeInTheDocument();
152
+ });
153
+
154
+ test('overflowCountLabel function override', () => {
155
+ render(
156
+ <AvatarGroup
157
+ items={fourPeople}
158
+ showMaxItems={2}
159
+ overflowCountLabel={n => `${n} extra`}
160
+ />,
161
+ );
162
+ expect(screen.getByRole('status', { name: '2 extra' })).toBeInTheDocument();
163
+ });
164
+
165
+ test('outer list uses aria-label from label prop', () => {
166
+ render(
167
+ <AvatarGroup
168
+ label="Project contributors"
169
+ items={[{ initials: 'Z', alt: 'Zed' }]}
170
+ />,
171
+ );
172
+ const list = screen.getByRole('list', { name: 'Project contributors' });
173
+ expect(list).toHaveAttribute('role', 'list');
174
+ });
175
+
176
+ test('forwards refs to the root list element', () => {
177
+ const ref = React.createRef<HTMLUListElement>();
178
+ render(
179
+ <AvatarGroup
180
+ ref={ref}
181
+ label="Ref team"
182
+ items={[{ initials: 'Z', alt: 'Zed' }]}
183
+ />,
184
+ );
185
+ expect(ref.current).toHaveAttribute('role', 'list');
186
+ });
187
+
188
+ test('AvatarGroup.OverflowCount standalone uses status semantics', () => {
189
+ render(
190
+ <AvatarGroup.OverflowCount
191
+ count={5}
192
+ overflowCountLabel="five more wolves"
193
+ />,
194
+ );
195
+ const overflow = screen.getByRole('status', { name: 'five more wolves' });
196
+ expect(overflow).toHaveTextContent('+5');
197
+ expect(overflow).not.toHaveAttribute('aria-live');
198
+ expect(overflow).not.toHaveAttribute('aria-atomic');
199
+ });
200
+
201
+ test('AvatarGroup.OverflowCount can announce all updates to screen readers', () => {
202
+ render(
203
+ <AvatarGroup.OverflowCount
204
+ count={5}
205
+ overflowCountLabel="five more wolves"
206
+ presentAllUpdatesToScreenReader
207
+ />,
208
+ );
209
+ const overflow = screen.getByRole('status', { name: 'five more wolves' });
210
+ expect(overflow).toHaveAttribute('aria-live', 'polite');
211
+ expect(overflow).toHaveAttribute('aria-atomic', 'true');
212
+ });
213
+
214
+ test('group prop plumbs live-region behaviour to the built-in overflow badge', () => {
215
+ render(
216
+ <AvatarGroup
217
+ items={fourPeople}
218
+ showMaxItems={2}
219
+ presentAllUpdatesToScreenReader
220
+ />,
221
+ );
222
+ const overflow = screen.getByRole('status', { name: 'plus 2 more' });
223
+ expect(overflow).toHaveAttribute('aria-live', 'polite');
224
+ expect(overflow).toHaveAttribute('aria-atomic', 'true');
225
+ });
226
+
227
+ test('size on group overrides avatar size', () => {
228
+ const { container } = render(
229
+ <AvatarGroup
230
+ size="large"
231
+ items={[
232
+ { initials: 'S', alt: 'Small marked', size: 'small' },
233
+ <Avatar
234
+ key="m"
235
+ initials="M"
236
+ alt="Medium marked"
237
+ size="medium"
238
+ />,
239
+ ]}
240
+ />,
241
+ );
242
+ const avatars = container.querySelectorAll('.ds-avatar--large');
243
+ expect(avatars).toHaveLength(2);
244
+ });
245
+
246
+ test('renders avatars from children with fragments and overflow', () => {
247
+ render(
248
+ <AvatarGroup
249
+ label="Composable team"
250
+ showMaxItems={2}
251
+ size="medium"
252
+ >
253
+ <>
254
+ <Avatar
255
+ initials="A"
256
+ alt="User A"
257
+ />
258
+ <Avatar
259
+ initials="B"
260
+ alt="User B"
261
+ />
262
+ </>
263
+ <Avatar
264
+ initials="C"
265
+ alt="User C"
266
+ />
267
+ </AvatarGroup>,
268
+ );
269
+ expect(screen.getByRole('list', { name: 'Composable team' })).toBeInTheDocument();
270
+ expect(screen.getByLabelText('User A')).toBeInTheDocument();
271
+ expect(screen.getByLabelText('User B')).toBeInTheDocument();
272
+ expect(screen.getByText('+1')).toBeInTheDocument();
273
+ expect(screen.queryByLabelText('User C')).not.toBeInTheDocument();
274
+ });
275
+ });
276
+
277
+ describe('collectAvatarsFromChildren', () => {
278
+ test('warns in development when non-Avatar React elements are passed', () => {
279
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
280
+
281
+ const result = collectAvatarsFromChildren(
282
+ <>
283
+ <Avatar
284
+ initials="A"
285
+ alt="User A"
286
+ />
287
+ <span>Ignored child</span>
288
+ </>,
289
+ );
290
+
291
+ expect(result).toHaveLength(1);
292
+ expect(warnSpy).toHaveBeenCalledWith(
293
+ 'AvatarGroup only collects `<Avatar />` children when using the children API.',
294
+ );
295
+
296
+ warnSpy.mockRestore();
297
+ });
298
+ });
@@ -0,0 +1,127 @@
1
+ import classNames from 'classnames';
2
+ import { Avatar, type AvatarSize } from 'Components/avatar/Avatar';
3
+ import React from 'react';
4
+ import { partitionAvatarGroupOverflow, type AvatarGroupListOrder } from './avatarGroupOverflow';
5
+ import { collectAvatarsFromChildren } from './collectAvatarGroupChildren';
6
+ import { useAvatarGroupItems, type AvatarGroupItem } from './useAvatarGroupItems';
7
+
8
+ export type { AvatarGroupItem, AvatarGroupListOrder };
9
+
10
+ export type AvatarGroupOverflowCountProps = {
11
+ count: number;
12
+ overflowCountLabel?: string | ((count: number) => string);
13
+ className?: string;
14
+ presentAllUpdatesToScreenReader?: boolean;
15
+ };
16
+
17
+ function resolveOverflowAriaLabel(
18
+ count: number,
19
+ overflowCountLabel?: string | ((c: number) => string),
20
+ ): string {
21
+ if (typeof overflowCountLabel === 'function') {
22
+ return overflowCountLabel(count);
23
+ }
24
+ if (typeof overflowCountLabel === 'string') {
25
+ return overflowCountLabel;
26
+ }
27
+ return `plus ${count} more`;
28
+ }
29
+
30
+ function AvatarGroupOverflowCount({
31
+ count,
32
+ overflowCountLabel,
33
+ className,
34
+ presentAllUpdatesToScreenReader = false,
35
+ }: AvatarGroupOverflowCountProps) {
36
+ return (
37
+ <span
38
+ role="status"
39
+ className={classNames('ds-avatar-group__overflow-count', className)}
40
+ aria-label={resolveOverflowAriaLabel(count, overflowCountLabel)}
41
+ aria-live={presentAllUpdatesToScreenReader ? 'polite' : undefined}
42
+ aria-atomic={presentAllUpdatesToScreenReader ? 'true' : undefined}
43
+ >
44
+ +
45
+ {count}
46
+ </span>
47
+ );
48
+ }
49
+
50
+ type AvatarGroupSharedProps = {
51
+ showMaxItems?: number;
52
+ listOrder?: AvatarGroupListOrder;
53
+ label?: string;
54
+ overflowCountLabel?: string | ((count: number) => string);
55
+ presentAllUpdatesToScreenReader?: boolean;
56
+ size?: AvatarSize;
57
+ className?: string;
58
+ };
59
+
60
+ export type AvatarGroupProps = AvatarGroupSharedProps
61
+ & (
62
+ | { items: readonly AvatarGroupItem[]; children?: undefined }
63
+ | { children: React.ReactNode; items?: undefined }
64
+ )
65
+ & Omit<React.HTMLAttributes<HTMLUListElement>, 'children'>;
66
+
67
+ const AvatarGroupRoot = React.forwardRef<HTMLUListElement, AvatarGroupProps>(({
68
+ items,
69
+ children,
70
+ size: groupSize,
71
+ showMaxItems,
72
+ listOrder = 'ascending',
73
+ label,
74
+ overflowCountLabel,
75
+ presentAllUpdatesToScreenReader = false,
76
+ className,
77
+ ...rest
78
+ }: AvatarGroupProps, ref) => {
79
+ const sourceItems = React.useMemo(() => {
80
+ if (children != null) {
81
+ return collectAvatarsFromChildren(children);
82
+ }
83
+ return items ?? [];
84
+ }, [children, items]);
85
+
86
+ const normalized = useAvatarGroupItems(sourceItems, groupSize);
87
+ const { visibleItems, overflowCount } = React.useMemo(
88
+ () => partitionAvatarGroupOverflow(normalized, showMaxItems, listOrder),
89
+ [normalized, showMaxItems, listOrder],
90
+ );
91
+
92
+ return (
93
+ <ul
94
+ ref={ref}
95
+ role="list"
96
+ className={classNames('ds-avatar-group', className)}
97
+ aria-label={label}
98
+ {...rest}
99
+ >
100
+ {visibleItems.map(entry => (
101
+ <li
102
+ key={entry.key}
103
+ className="ds-avatar-group__item"
104
+ >
105
+ <Avatar {...entry.avatarProps} />
106
+ </li>
107
+ ))}
108
+ {overflowCount > 0
109
+ ? (
110
+ <li className="ds-avatar-group__overflow">
111
+ <AvatarGroupOverflowCount
112
+ count={overflowCount}
113
+ overflowCountLabel={overflowCountLabel}
114
+ presentAllUpdatesToScreenReader={presentAllUpdatesToScreenReader}
115
+ />
116
+ </li>
117
+ )
118
+ : null}
119
+ </ul>
120
+ );
121
+ });
122
+
123
+ AvatarGroupRoot.displayName = 'AvatarGroup';
124
+
125
+ export const AvatarGroup = Object.assign(AvatarGroupRoot, {
126
+ OverflowCount: AvatarGroupOverflowCount,
127
+ });