import SwiftUI /// The row of status badges a README opens with. /// /// Badges keep their own size — a badge is authored at the pixel size it is meant to be /// seen at, so `resizable()` would only blur it. They wrap to the next line when the row is /// wider than the column, which is what a long badge row does on the web. struct OrgBadgeRowView: View { let badges: [OrgBadge] /// The scale badge artwork is served at. Retina artwork is the only way a small raster /// badge stays sharp, and a badge has no other channel to declare its scale. static let scale: CGFloat = 2 var body: some View { // `ViewThatFits` is not a wrap; `Layout` is more than a badge row needs. A flexible // `HStack` inside a horizontal `ScrollView` keeps a long row reachable without // guessing at line breaks. ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 6) { ForEach(badges) { badge in link(badge) { AsyncImage(url: badge.source, scale: Self.scale) { phase in switch phase { case .success(let image): image case .failure: Text(badge.alt).font(.footnote).foregroundStyle(.secondary) default: ProgressView().controlSize(.mini) } } .accessibilityLabel(badge.alt) } } } } } /// A badge with no resolvable link is still shown, just not tappable. @ViewBuilder private func link(_ badge: OrgBadge, @ViewBuilder content: () -> some View) -> some View { if let url = badge.link { Link(destination: url, label: content) } else { content() } } }