Closed20
shadcn/uiでAvatarGroupを実装したい
ピン留めされたアイテム
完成形!
import { FC } from "react";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/Avatar";
import { getInitial } from "./lib";
type Props = {
avatarDataList: {
image: string;
name: string;
}[];
max?: number | undefined;
};
export const AvatarGroup: FC<Props> = ({ avatarDataList, max }) => {
const avatarDataListWithinMax =
max !== undefined ? avatarDataList.slice(0, max) : avatarDataList;
const excess = max !== undefined ? avatarDataList.length - max : 0;
// 子要素がrelativeのとき、flex-row-reverseでレイアウトすることで
// z-indexを使わずに「先頭から順に下に重なっていく」を実現できるので
// データ配列そのものも裏返すことで順序を元通りにする。
const reversetDataList = [...avatarDataListWithinMax].reverse();
return (
<div className="flex space-x-reverse -space-x-2 flex-row-reverse justify-end">
{excess > 0 && (
<Avatar>
<AvatarFallback> {`+${excess}`}</AvatarFallback>
</Avatar>
)}
{reversetDataList.map((user, i) => (
<Avatar key={i}>
<AvatarImage src={user.image} alt={user.name} />
<AvatarFallback>{getInitial(user.name)}</AvatarFallback>
</Avatar>
))}
</div>
);
};
こういうやつを
これを使いながら
実装したい
愚直にこれを読むか〜
コメントはいってて読みやすいな
const validChildren = getValidChildren(children)
/**
* get the avatars within the max
*/
const childrenWithinMax =
max != null ? validChildren.slice(0, max) : validChildren
- childrenからAvatarを抽出
- 最大数が設定されていればchildrenをそこまでに限定
超過を+nで表示するために計算
/**
* get the remaining avatar count
*/
const excess = max != null ? validChildren.length - max : 0
テクいな...。基本的にchildrenの後ろのほうが上に積み重なるので、それを裏返す。
/**
* Reversing the children is a great way to avoid using zIndex
* to overlap the avatars
*/
const reversedChildren = childrenWithinMax.reverse()
firstAvatarかどうかでスタイルを分けている
const clones = reversedChildren.map((child, index) => {
const isFirstAvatar = index === 0
const childProps = {
marginEnd: isFirstAvatar ? 0 : spacing,
size: props.size,
borderColor: child.props.borderColor ?? borderColor,
showBorder: true,
}
return cloneElement(child, compact(childProps))
})
親のスタイル。justifyContent: "flex-end"
とflexDirection: "row-reverse"
はなるほど?
const groupStyles: SystemStyleObject = {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
flexDirection: "row-reverse",
...styles.group,
}
超過があれば表示
{excess > 0 && (
<chakra.span className="chakra-avatar__excess" __css={excessStyles}>
{`+${excess}`}
</chakra.span>
)}
getValidChildren
の中身はこれ。Chakra特有ではなく、childrenをarrayとして扱いたいときのutilっぽい。
import { Children, isValidElement } from "react"
/**
* Gets only the valid children of a component,
* and ignores any nullish or falsy child.
*
* @param children the children
*/
export function getValidChildren(children: React.ReactNode) {
return Children.toArray(children).filter((child) =>
isValidElement(child),
) as React.ReactElement[]
}
こうやって使うらしいけど
<AvatarGroup size='md' max={2}>
<Avatar name='Ryan Florence' src='https://bit.ly/ryan-florence' />
<Avatar name='Segun Adebayo' src='https://bit.ly/sage-adebayo' />
<Avatar name='Kent Dodds' src='https://bit.ly/kent-c-dodds' />
<Avatar name='Prosper Otemuyiwa' src='https://bit.ly/prosper-baba' />
<Avatar name='Christian Nwamba' src='https://bit.ly/code-beast' />
</AvatarGroup>
こういうinterfaceじゃだめなんか、という気持ちはちょっとある
<AvatarGroup users={users}>
一旦大枠は完成したと思うんだけれど、ちらほらうまくいってない
import { FC } from "react";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/Avatar";
import { getInitial } from "./lib";
type Props = {
avatarDataList: {
image: string;
name: string;
}[];
max?: number | undefined;
};
export const AvatarGroup: FC<Props> = ({ avatarDataList, max }) => {
const avatarDataListWithinMax =
max !== undefined ? avatarDataList.slice(0, max) : avatarDataList;
const excess = max !== undefined ? avatarDataListWithinMax.length - max : 0;
// flex-row-reverseでレイアウトすることでz-indexを使わずに「先頭から下に重なっていく」を実現できるので
// データ配列そのものも裏返すことで順序を元通りにする。
const reversetDataList = [...avatarDataListWithinMax].reverse();
return (
<div className="flex -space-x-2 flex-row-reverse justify-end">
{reversetDataList.map((user, i) => (
<Avatar key={i}>
<AvatarImage src={user.image} alt={user.name} />
<AvatarFallback>{getInitial(user.name)}</AvatarFallback>
</Avatar>
))}
{excess > 0 && (
<Avatar>
<AvatarImage src="" />
<AvatarFallback> {`+${excess}`}</AvatarFallback>
</Avatar>
)}
</div>
);
};
- maxを超えているときもexcessが0になる
- 最後のitemがなんか重なってない
excessの計算間違っているのは確認した。修正版
const excess = max !== undefined ? avatarDataList.length - max : 0;
んあーそっか、こうなるのか
こうしたい
excess分を一番前に持ってこりゃ良いな、row-reverseのせいで直感的ではないが
<div className="flex -space-x-2 flex-row-reverse justify-end">
{excess > 0 && (
<Avatar>
<AvatarFallback> {`+${excess}`}</AvatarFallback>
</Avatar>
)}
{reversetDataList.map((user, i) => (
<Avatar key={i}>
<AvatarImage src={user.image} alt={user.name} />
<AvatarFallback>{getInitial(user.name)}</AvatarFallback>
</Avatar>
))}
</div>
row-reverseを取り除くと最後のitemが重なってない問題が解決するのでここらへんにヒントがありそう
わかった、これで解決した。
<div className="flex space-x-reverse -space-x-4 flex-row-reverse justify-end">
-space-x-n
が、「最初だけマージンつけない」をやってくれていて、reverseになってるからマージンつけない場所が逆転してたのが原因だった。
.-space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(-1rem * var(--tw-space-x-reverse));
margin-left: calc(-1rem * calc(1 - var(--tw-space-x-reverse)));
}
-space-x-4
だと+3の+が見えなかったので、-space-x-2
にしておいた
このスクラップは2023/05/29にクローズされました