Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[PBIOS-448] Multiple Users Small Bubble Variant #420

Merged
merged 15 commits into from
Aug 6, 2024
Merged
8 changes: 6 additions & 2 deletions Sources/Playbook/Components/Avatar/PBAvatar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ public extension PBAvatar {
return nil
}

enum Size: CaseIterable {
enum Size: CaseIterable, Hashable {
public static var allCases: [PBAvatar.Size] = []


case xxSmall
case xSmall
case small
Expand All @@ -95,7 +98,7 @@ public extension PBAvatar {
case defaultStackedIndicator
case smallStacked
case smallStackedIndicator

case custom(_ size: CGFloat)
var diameter: CGFloat {
switch self {
case .xxSmall: return 20
Expand All @@ -108,6 +111,7 @@ public extension PBAvatar {
case .defaultStackedIndicator: return 18
case .smallStacked: return 22
case .smallStackedIndicator: return 16
case .custom(let size): return size
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@ public struct MultipleUsersCatalog: View {
PBDoc(title: "xSmall") {
xsmallView
}

PBDoc(title: "Small") {
smallView
}

PBDoc(title: "Small Reverse") {
smallReverseView
}

PBDoc(title: "Small Bubble") {
smallUserBubbleView
}
}
}
}
Expand All @@ -39,4 +43,31 @@ extension MultipleUsersCatalog {
PBMultipleUsers(users: Mocks.twoUsers, size: .small, reversed: true)
}
}
var smallUserBubbleView: some View {
HStack(spacing: Spacing.small) {
PBMultipleUsers(
users: Mocks.twoUsers,
variant: .bubble,
bubbleSize: .small,
bubbleCount: .two
)
PBMultipleUsers(
users: Mocks.threeUsers,
variant: .bubble,
bubbleSize: .small,
bubbleCount: .three
)
PBMultipleUsers(
users: Mocks.multipleUsers,
variant: .bubble,
bubbleSize: .small,
bubbleCount: .four
)
}
}
}

#Preview {
registerFonts()
return MultipleUsersCatalog()
}
185 changes: 151 additions & 34 deletions Sources/Playbook/Components/User/Multiple Users/PBMultipleUsers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,73 +11,92 @@ import SwiftUI

public struct PBMultipleUsers: View {
var users: [PBUser]
var size: AvatarSize
var size: PBAvatar.Size
var variant: Variant
var reversed: Bool
var bubbleSize: BubbleSize
var bubbleCount: BubbleCount
var maxDisplayedUsers: Int
@State private var avSize: CGFloat = 20

public init(
users: [PBUser] = [],
size: AvatarSize = .small,
size: PBAvatar.Size = .small,
variant: Variant = .linear,
reversed: Bool = false,
bubbleSize: BubbleSize = .small,
bubbleCount: BubbleCount = .two,
maxDisplayedUsers: Int = 4
) {
self.users = users
self.size = size
self.variant = variant
self.reversed = reversed
self.bubbleSize = bubbleSize
self.bubbleCount = bubbleCount
self.maxDisplayedUsers = maxDisplayedUsers
}

public var body: some View {
variantView
}
}

public extension PBMultipleUsers {
enum Variant {
case linear, bubble
}
@ViewBuilder
var variantView: some View {
switch variant {
case .linear: multipleUsersView
case .bubble: multipleUsersBubbleView
}
}
var filteredUsers: ([PBUser], Int?) {
var displayedUsers = users
var additionalUsers = 0

if users.count > maxDisplayedUsers {
displayedUsers = Array(users.prefix(maxDisplayedUsers - 1))
additionalUsers = users.count - maxDisplayedUsers + 1
}

return (displayedUsers, additionalUsers)
}

func xOffset(index: Int) -> CGFloat {
let offset = size.avatarSize.diameter / 1.5 * CGFloat(index)

let offset = size.diameter / 1.5 * CGFloat(index)
return reversed ? -offset : offset
}

var leadingPadding: CGFloat {
let padding = size.avatarSize.diameter / 1.5 * CGFloat(filteredUsers.0.count - (filteredUsers.1 == 0 ? 1 : 0))

let padding = size.diameter / 1.5 * CGFloat(filteredUsers.0.count - (filteredUsers.1 == 0 ? 1 : 0))
return reversed ? padding : 0
}

var totalWidth: CGFloat {
var width = size.avatarSize.diameter

var width = size.diameter
if filteredUsers.0.count > 1 {
width = size.avatarSize.diameter / 1.5 * CGFloat(filteredUsers.0.count - 1) + size.avatarSize.diameter
width = size.diameter / 1.5 * CGFloat(filteredUsers.0.count - 1) + size.diameter
}

if users.count > maxDisplayedUsers {
width = size.avatarSize.diameter / 1.5 * CGFloat(maxDisplayedUsers - 1) + size.avatarSize.diameter
width = size.diameter / 1.5 * CGFloat(maxDisplayedUsers - 1) + size.diameter
}

return width
}

public var body: some View {
var multipleUsersView: some View {
ZStack {
ForEach(filteredUsers.0.indices, id: \.self) { index in
PBAvatar(
image: filteredUsers.0[index].image,
name: filteredUsers.0[index].name,
size: size.avatarSize,
size: size,
wrapped: true
)
.offset(x: xOffset(index: index), y: 0)
}

PBMultipleUsersIndicator(usersCount: filteredUsers.1, size: size.avatarSize)
PBMultipleUsersIndicator(usersCount: filteredUsers.1, size: size)
.offset(x: xOffset(index: filteredUsers.0.endIndex), y: 0)
}
.padding(.leading, leadingPadding)
Expand All @@ -86,22 +105,120 @@ public struct PBMultipleUsers: View {
}

public extension PBMultipleUsers {
enum AvatarSize {
case xSmall
case small

var avatarSize: PBAvatar.Size {
switch self {
case .xSmall: return .xSmall
case .small: return .small
}
enum BubbleSize {
case small, medium, large, xLarge
}
enum BubbleCount {
case two, three, four
}

var multipleUsersBubbleView: some View {
CircularLayout {
userBubbleView
}
.padding(5)
.background(Color.background(.light))
.clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
}
var userBubbleView: some View {
ForEach(filteredUsers.0.indices, id: \.self) { index in
let avatarSize = avatarSize(for: index, total: filteredUsers.0.count)
PBAvatar(
image: filteredUsers.0[index].image,
name: filteredUsers.0[index].name,
size: .custom(avatarSize),
wrapped: true
)
.padding(index <= 1 ? -1 : -4)
.offset(x: avatarXPosition(for: index, total: filteredUsers.0.count), y: avatarYPosition(for: index, total: filteredUsers.0.count))
}
}

func avatarSize(for index: Int, total: Int) -> CGFloat {
let sizes: [BubbleSize: [Int: [CGFloat]]] = [
.small: [
1: [20],
2: [20, 12],
3: [16, 12, 10],
4: [16, 12, 10, 8]
],
.medium: [
1: [32],
2: [32, 16],
3: [24, 20, 16],
4: [28, 20, 16, 12]
],
.large: [
1: [44],
2: [44, 20],
3: [32, 24, 20],
4: [36, 28, 24, 16]
],
.xLarge: [
1: [56],
2: [56, 24],
3: [44, 32, 24],
4: [44, 32, 24, 16]
]
]
return sizes[bubbleSize]?[total]?[index] ?? 0
}

func avatarXPosition(for index: Int, total: Int) -> CGFloat {
switch (total, index) {
case (2, 0):
return -6
case (2, 1):
return 8
case (3, 0):
return -6
case (3, 1):
return 3
case (3, 2):
return 8
case (4, 0):
return -6
case (4, 1):
return 1
case (4, 2):
return -4
case (4, 3):
return 24
default:
return 0
}
}
func avatarYPosition(for index: Int, total: Int) -> CGFloat {
switch (total, index) {
case (2, 0):
return 0
case (2, 1):
return -2
case (3, 0):
return 0
case (3, 1):
return -4
case (3, 2):
return 4
case (4, 0):
return 0
case (4, 1):
return 4
case (4, 2):
return -4
case (4, 3):
return -10
default:
return 0
}
}
}

public struct PBMultipleUsers_Previews: PreviewProvider {
public static var previews: some View {
#Preview {
registerFonts()
return MultipleUsersCatalog()
}
return VStack {
PBMultipleUsers(users: Mocks.multipleUsers, variant: .linear)
PBMultipleUsers(users: Mocks.multipleUsers, variant: .bubble)
}
.padding()
}
1 change: 1 addition & 0 deletions Sources/Playbook/Resources/Helper Files/Mocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum Mocks {
static let luccile = PBUser(name: "Luccile Black", image: Image("Lu", bundle: .module), size: .small, title: "Senior User Experience Engineer")
static let oneUser = [andrew]
static let twoUsers = [andrew, ana]
static let threeUsers = [andrew, ana, patric]
static let multipleUsers = [andrew, ana, patric, luccile]
static let multipleUsersDictionary: [(String, PBUser)] = [(andrew.name, andrew), (ana.name, ana), (patric.name, patric), (luccile.name, luccile)]
static let avatarXSmall = PBAvatar(image: Image("andrew", bundle: .module), size: .xSmall)
Expand Down