實作時感覺最魔術的地方是 flex-direction: row-reverse; 搭配 justify-content: flex-end; 來做出堆疊效果。其他基本上都是小事。

首先參考一下 MUI Avatar 的使用方式,可以透過 props.src 來指定圖片,或是也可將內容當做 children 包進元件。
為了實現以上需求,採用以下的條件來決定最後 Avatar 元件究竟要渲染什麼內容:
const finalRender = useMemo(() => {
if (onError) {
return (
<div
className={cn(imgStyle, withBorder && figureStyle, withChildrenStyle)}
>
<PersonIcon fill="#fff" />
</div>
);
}
if (children) {
return (
<div
className={cn(
imgStyle,
withBorder && figureStyle,
withChildrenStyle,
withBorder && withChildrenBorderStyle
)}
>
{children}
</div>
);
}
if (src) {
return (
<ImageBase
src={src}
classes={{
figure: cn(withBorder && figureStyle, imgStyle),
img: imgStyle,
onError: onErrorStyle,
}}
onError={fallBackToText}
/>
);
}
return (
<div className={cn(imgStyle, withBorder && figureStyle, withChildrenStyle)}>
<PersonIcon fill="#fff" />
</div>
);
}, [children, src, onError, withBorder, fallBackToText]);
回傳內容的優先度由高到低排序如下:
<PersonIcon fill="#fff" /> 來作為圖片死去的替代顯示。Avatar 傳入 props.children 時,顯示該 children 內容。props.src 時,透過 ImageBase 元件來處理圖片內容。props.children 也沒有提供 props.src 的情況下,回傳(與圖片出錯時一致的)預設外觀。概念:將 props.children 傳遞進來的內容複製一份到 result 陣列裡,如果使用者有設定 props.max 的話,則將超過 max 數值的 Avatar 元件捨去,並透過 result.push(<Avatar withBorder>+{childrenLength - (max - 1)}</Avatar>); 顯示「扣掉 props.max 後,還有多少個 Avatar 元件」此數值。
最後透過 reverse() 反轉 result 陣列,再搭配 flex-direction: row-reverse; 與 justify-content: flex-end; 讓 Avatar 元件們可以正確排序,最後使用 baseStyle 中的 & > div: { marginLeft: '-12px' } 做出元件堆疊的效果。
useEffect(() => {
let result: JSX.Element[] = [];
React.Children.forEach(children, (child) => {
const c = child as JSX.Element;
result.push(React.cloneElement(c, { withBorder: true }));
});
const childrenLength = React.Children.count(children);
if (typeof max === 'number' && max > 1 && max < childrenLength) {
result = result.slice(0, max - 1);
result.push(<Avatar withBorder>+{childrenLength - (max - 1)}</Avatar>);
}
setChildrenArr(result.reverse());
}, [children, max]);
PersonIcon 改成其他圖片或是文字來替代。Avatar 的間距:這部分是透過 withChildrenBorderStyle 將 borderColor 設定的與背景顏色一致來做出距離效果。如果你複製貼上今天的 code 且專案的背景顏色不是 #f9f4ef 的話,記得將 withChildrenBorderStyle 中的 #f9f4ef 改成正確的背景色。一直以來都以為 row-reverse 頂多拿來對應一些奇妙的排版邊際案例,沒想到還可以這樣用...?