116 lines
4.3 KiB
Swift
116 lines
4.3 KiB
Swift
import SwiftUI
|
|
|
|
extension ProviderStatusIndicator {
|
|
/// Traffic-light color used for the per-component dot in the status submenu.
|
|
fileprivate var dotColor: Color {
|
|
switch self {
|
|
case .none: Color(red: 0.20, green: 0.78, blue: 0.35)
|
|
case .minor, .maintenance: Color(red: 0.96, green: 0.77, blue: 0.13)
|
|
case .major, .critical: Color(red: 0.91, green: 0.30, blue: 0.24)
|
|
case .unknown: Color.secondary
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Renders the list of statuspage.io component rows inside the provider's status submenu.
|
|
/// Each leaf row is: colored dot (far left) · service name · right-aligned status text.
|
|
/// A component group renders as an expandable dropdown: the parent shows the group's own
|
|
/// status, and a chevron reveals the individual child statuses indented beneath it
|
|
/// (modeled on the "Other" disclosure in StorageBreakdownMenuView).
|
|
struct StatusComponentsMenuView: View {
|
|
let components: [ProviderStatusComponent]
|
|
let width: CGFloat
|
|
/// Invoked after a group expands or collapses so the host can re-measure the row height.
|
|
let onToggle: (() -> Void)?
|
|
|
|
@State private var expandedGroupIDs: Set<String> = []
|
|
|
|
init(
|
|
components: [ProviderStatusComponent],
|
|
width: CGFloat,
|
|
onToggle: (() -> Void)? = nil)
|
|
{
|
|
self.components = components
|
|
self.width = width
|
|
self.onToggle = onToggle
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
ForEach(self.components) { component in
|
|
if component.isGroup {
|
|
self.groupRow(component)
|
|
} else {
|
|
self.statusRow(component)
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 14)
|
|
.padding(.vertical, 8)
|
|
.frame(width: self.width, alignment: .leading)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
/// A single leaf row: dot · name · right-aligned status.
|
|
private func statusRow(_ component: ProviderStatusComponent, indented: Bool = false) -> some View {
|
|
HStack(spacing: 8) {
|
|
Circle()
|
|
.fill(component.indicator.dotColor)
|
|
.frame(width: 8, height: 8)
|
|
Text(component.name)
|
|
.font(.system(size: 13))
|
|
.foregroundStyle(.primary)
|
|
.lineLimit(1)
|
|
Spacer(minLength: 16)
|
|
Text(component.statusLabel)
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
.padding(.leading, indented ? 17 : 0)
|
|
}
|
|
|
|
/// An expandable group: parent status row with a chevron, revealing children when expanded.
|
|
private func groupRow(_ group: ProviderStatusComponent) -> some View {
|
|
let isExpanded = self.expandedGroupIDs.contains(group.id)
|
|
return VStack(alignment: .leading, spacing: 6) {
|
|
Button {
|
|
if isExpanded {
|
|
self.expandedGroupIDs.remove(group.id)
|
|
} else {
|
|
self.expandedGroupIDs.insert(group.id)
|
|
}
|
|
self.onToggle?()
|
|
} label: {
|
|
HStack(spacing: 8) {
|
|
Circle()
|
|
.fill(group.indicator.dotColor)
|
|
.frame(width: 8, height: 8)
|
|
Text(group.name)
|
|
.font(.system(size: 13))
|
|
.foregroundStyle(.primary)
|
|
.lineLimit(1)
|
|
Image(systemName: isExpanded ? "chevron.down" : "chevron.right")
|
|
.font(.system(size: 9, weight: .semibold))
|
|
.foregroundStyle(.secondary)
|
|
Spacer(minLength: 16)
|
|
Text(group.statusLabel)
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
|
|
if isExpanded {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
ForEach(group.children) { child in
|
|
self.statusRow(child, indented: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|