krz/org-swift

A dependency-free Swift library that renders org-mode to sanitized HTML. html library org-mode swift

Sources/OrgSwiftUI/OrgBadgeRowView.swift

v0.7.0
org-swift/Sources/OrgSwiftUI/OrgBadgeRowView.swift history · blame · raw

49 lines · 1976 bytes

 1import SwiftUI
 2
 3/// The row of status badges a README opens with.
 4///
 5/// Badges keep their own size — a badge is authored at the pixel size it is meant to be
 6/// seen at, so `resizable()` would only blur it. They wrap to the next line when the row is
 7/// wider than the column, which is what a long badge row does on the web.
 8struct OrgBadgeRowView: View {
 9    let badges: [OrgBadge]
10
11    /// The scale badge artwork is served at. Retina artwork is the only way a small raster
12    /// badge stays sharp, and a badge has no other channel to declare its scale.
13    static let scale: CGFloat = 2
14
15    var body: some View {
16        // `ViewThatFits` is not a wrap; `Layout` is more than a badge row needs. A flexible
17        // `HStack` inside a horizontal `ScrollView` keeps a long row reachable without
18        // guessing at line breaks.
19        ScrollView(.horizontal, showsIndicators: false) {
20            HStack(spacing: 6) {
21                ForEach(badges) { badge in
22                    link(badge) {
23                        AsyncImage(url: badge.source, scale: Self.scale) { phase in
24                            switch phase {
25                            case .success(let image):
26                                image
27                            case .failure:
28                                Text(badge.alt).font(.footnote).foregroundStyle(.secondary)
29                            default:
30                                ProgressView().controlSize(.mini)
31                            }
32                        }
33                        .accessibilityLabel(badge.alt)
34                    }
35                }
36            }
37        }
38    }
39
40    /// A badge with no resolvable link is still shown, just not tappable.
41    @ViewBuilder
42    private func link(_ badge: OrgBadge, @ViewBuilder content: () -> some View) -> some View {
43        if let url = badge.link {
44            Link(destination: url, label: content)
45        } else {
46            content()
47        }
48    }
49}