update design for stacked avatars
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
import {useState} from 'react'
|
||||||
|
import {
|
||||||
|
type LayoutChangeEvent,
|
||||||
|
type StyleProp,
|
||||||
|
View,
|
||||||
|
type ViewStyle,
|
||||||
|
} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -11,7 +17,7 @@ import {
|
|||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, useBreakpoints, useLayoutBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useLayoutBreakpoints, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person'
|
import {Person_Filled as PersonIcon} from '#/components/icons/Person'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
@@ -51,7 +57,7 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
|
|||||||
a.flex_col,
|
a.flex_col,
|
||||||
a.gap_md,
|
a.gap_md,
|
||||||
a.rounded_md,
|
a.rounded_md,
|
||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_50,
|
||||||
a.p_lg,
|
a.p_lg,
|
||||||
style,
|
style,
|
||||||
]}>
|
]}>
|
||||||
@@ -112,70 +118,80 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
|
|||||||
|
|
||||||
function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) {
|
function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {centerColumnOffset} = useLayoutBreakpoints()
|
const [containerWidth, setContainerWidth] = useState(0)
|
||||||
|
|
||||||
// Smaller avatars for narrower viewport
|
const onLayout = (e: LayoutChangeEvent) => {
|
||||||
const avatarSize = centerColumnOffset ? 30 : 37
|
setContainerWidth(e.nativeEvent.layout.width)
|
||||||
const overlap = centerColumnOffset ? 9 : 11
|
}
|
||||||
const iconSize = centerColumnOffset ? 14 : 18
|
|
||||||
|
// Overlap ratio (22% of avatar size)
|
||||||
|
const overlapRatio = 0.22
|
||||||
|
|
||||||
|
// Calculate avatar size to fill container width
|
||||||
|
// Formula: containerWidth = avatarSize * count - overlap * (count - 1)
|
||||||
|
// Where overlap = avatarSize * overlapRatio
|
||||||
|
const visiblePortions = TOTAL_AVATARS - overlapRatio * (TOTAL_AVATARS - 1)
|
||||||
|
const avatarSize = containerWidth > 0 ? containerWidth / visiblePortions : 0
|
||||||
|
const overlap = avatarSize * overlapRatio
|
||||||
|
const iconSize = avatarSize * 0.45
|
||||||
|
|
||||||
// Use actual follows count, not the guide's event counter
|
|
||||||
const followedAvatars = follows?.slice(0, TOTAL_AVATARS) ?? []
|
const followedAvatars = follows?.slice(0, TOTAL_AVATARS) ?? []
|
||||||
const remainingSlots = TOTAL_AVATARS - followedAvatars.length
|
const remainingSlots = TOTAL_AVATARS - followedAvatars.length
|
||||||
|
|
||||||
// Total width calculation: first avatar + (remaining * visible portion)
|
|
||||||
const totalWidth = avatarSize + (TOTAL_AVATARS - 1) * (avatarSize - overlap)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.flex_row, a.self_start, {width: totalWidth}]}>
|
<View style={[a.flex_row, a.flex_1]} onLayout={onLayout}>
|
||||||
{/* Show followed user avatars */}
|
{containerWidth > 0 && (
|
||||||
{followedAvatars.map((follow, i) => (
|
<>
|
||||||
<View
|
{/* Show followed user avatars */}
|
||||||
key={follow.did}
|
{followedAvatars.map((follow, i) => (
|
||||||
style={[
|
<View
|
||||||
a.rounded_full,
|
key={follow.did}
|
||||||
{
|
style={[
|
||||||
marginLeft: i === 0 ? 0 : -overlap,
|
a.rounded_full,
|
||||||
zIndex: TOTAL_AVATARS - i,
|
{
|
||||||
borderWidth: 2,
|
marginLeft: i === 0 ? 0 : -overlap,
|
||||||
borderColor: t.atoms.bg_contrast_25.backgroundColor,
|
zIndex: TOTAL_AVATARS - i,
|
||||||
},
|
borderWidth: 1,
|
||||||
]}>
|
borderColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||||
<UserAvatar
|
},
|
||||||
type="user"
|
]}>
|
||||||
size={avatarSize - 4}
|
<UserAvatar
|
||||||
avatar={follow.avatar}
|
type="user"
|
||||||
/>
|
size={avatarSize - 2}
|
||||||
</View>
|
avatar={follow.avatar}
|
||||||
))}
|
/>
|
||||||
{/* Show placeholder avatars for remaining slots */}
|
</View>
|
||||||
{Array(remainingSlots)
|
))}
|
||||||
.fill(0)
|
{/* Show placeholder avatars for remaining slots */}
|
||||||
.map((_, i) => (
|
{Array(remainingSlots)
|
||||||
<View
|
.fill(0)
|
||||||
key={`placeholder-${i}`}
|
.map((_, i) => (
|
||||||
style={[
|
<View
|
||||||
a.align_center,
|
key={`placeholder-${i}`}
|
||||||
a.justify_center,
|
style={[
|
||||||
a.rounded_full,
|
a.align_center,
|
||||||
t.atoms.bg_contrast_100,
|
a.justify_center,
|
||||||
{
|
a.rounded_full,
|
||||||
width: avatarSize,
|
t.atoms.bg_contrast_300,
|
||||||
height: avatarSize,
|
{
|
||||||
marginLeft:
|
width: avatarSize,
|
||||||
followedAvatars.length === 0 && i === 0 ? 0 : -overlap,
|
height: avatarSize,
|
||||||
zIndex: TOTAL_AVATARS - followedAvatars.length - i,
|
marginLeft:
|
||||||
borderWidth: 2,
|
followedAvatars.length === 0 && i === 0 ? 0 : -overlap,
|
||||||
borderColor: t.atoms.bg_contrast_25.backgroundColor,
|
zIndex: TOTAL_AVATARS - followedAvatars.length - i,
|
||||||
},
|
borderWidth: 1,
|
||||||
]}>
|
borderColor: t.atoms.border_contrast_low.borderColor,
|
||||||
<PersonIcon
|
},
|
||||||
width={iconSize}
|
]}>
|
||||||
height={iconSize}
|
<PersonIcon
|
||||||
fill={t.atoms.text_contrast_low.color}
|
width={iconSize}
|
||||||
/>
|
height={iconSize}
|
||||||
</View>
|
fill={t.atoms.bg_contrast_50.backgroundColor}
|
||||||
))}
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,3 +36,8 @@ export const PersonPlus_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
|||||||
export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||||
path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z',
|
path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const Person_Filled = createSinglePathSVG({
|
||||||
|
viewBox: '0 0 14 16',
|
||||||
|
path: 'M6.5 0C4.567 0 3 1.567 3 3.5C3 5.433 4.567 7 6.5 7C8.43301 7 10 5.433 10 3.5C10 1.567 8.43301 0 6.5 0Z M6.50002 8C3.43062 8 1.08148 9.78593 0.127626 12.2902C-0.145062 13.0062 0.0416292 13.7118 0.475104 14.2134C0.897533 14.7023 1.54964 15 2.25212 15H10.7479C11.4503 15 12.1024 14.7023 12.5249 14.2134C12.9584 13.7118 13.145 13.0062 12.8724 12.2902C11.9185 9.78593 9.56941 8 6.50002 8Z',
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user