Files
wehub-resource-sync e9a2f726c9
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:29:51 +08:00

199 lines
5.6 KiB
Swift

// PR 3 — labeled row, the workhorse layout inside `ListGroup`. Mirrors JSX `Row`.
// Two variants:
// • `Row(label:sublabel:isLast:trailing:)` — most uses
// • `Row(isLast:content:)` — free-form (chip strips, custom layouts)
import SwiftUI
struct Row<Trailing: View>: View {
let label: String?
let sublabel: String?
let isLast: Bool
let trailing: Trailing
@Environment(\.omlxTheme) private var theme
init(
label: String? = nil,
sublabel: String? = nil,
isLast: Bool = false,
@ViewBuilder trailing: () -> Trailing
) {
self.label = label
self.sublabel = sublabel
self.isLast = isLast
self.trailing = trailing()
}
var body: some View {
HStack(alignment: .center, spacing: 12) {
if let label {
VStack(alignment: .leading, spacing: 2) {
Text(label)
.font(.omlxText(13, weight: .medium))
.foregroundStyle(theme.text)
if let sublabel, !sublabel.isEmpty {
Text(sublabel)
.font(.omlxText(11.5))
.foregroundStyle(theme.textSecondary)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
}
}
Spacer(minLength: 12)
}
trailing
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
.frame(minHeight: 36)
.frame(maxWidth: .infinity, alignment: label == nil ? .leading : .center)
.overlay(alignment: .bottom) {
if !isLast {
Rectangle()
.fill(theme.rowSep)
.frame(height: 0.5)
.padding(.horizontal, 14)
}
}
}
}
extension Row where Trailing == EmptyView {
/// Label-only row (no trailing slot).
init(label: String, sublabel: String? = nil, isLast: Bool = false) {
self.label = label
self.sublabel = sublabel
self.isLast = isLast
self.trailing = EmptyView()
}
}
/// Free-form variant — caller provides the entire row body. Used for chip
/// strips, multi-line custom rows, etc.
struct FreeRow<Content: View>: View {
let isLast: Bool
let content: () -> Content
@Environment(\.omlxTheme) private var theme
init(isLast: Bool = false, @ViewBuilder content: @escaping () -> Content) {
self.isLast = isLast
self.content = content
}
var body: some View {
content()
.padding(.horizontal, 14)
.padding(.vertical, 10)
.frame(maxWidth: .infinity, alignment: .leading)
.overlay(alignment: .bottom) {
if !isLast {
Rectangle()
.fill(theme.rowSep)
.frame(height: 0.5)
.padding(.horizontal, 14)
}
}
}
}
struct LinkRow<Icon: View>: View {
let label: String
let sublabel: String
let url: URL
var isLast: Bool = false
private let iconView: Icon
@Environment(\.omlxTheme) private var theme
init(
label: String,
sublabel: String,
url: URL,
isLast: Bool = false,
@ViewBuilder iconView: () -> Icon
) {
self.label = label
self.sublabel = sublabel
self.url = url
self.isLast = isLast
self.iconView = iconView()
}
init(
label: String,
sublabel: String,
icon: String,
url: URL,
isLast: Bool = false
) where Icon == AnyView {
self.label = label
self.sublabel = sublabel
self.url = url
self.isLast = isLast
self.iconView = AnyView(
Image(systemName: icon)
.font(.system(size: 14))
.frame(width: 18, alignment: .center)
)
}
var body: some View {
FreeRow(isLast: isLast) {
HStack(spacing: 10) {
iconView
.foregroundStyle(theme.textSecondary)
VStack(alignment: .leading, spacing: 2) {
Text(label)
.font(.omlxText(13, weight: .medium))
.foregroundStyle(theme.text)
Text(sublabel)
.font(.omlxText(11))
.foregroundStyle(theme.textSecondary)
.lineLimit(2)
}
Spacer(minLength: 8)
Button {
NSWorkspace.shared.open(url)
} label: {
Label("common.open", systemImage: "arrow.up.right.square")
.labelStyle(.iconOnly)
}
.buttonStyle(.omlx(.plain, size: .small))
}
}
}
}
#Preview("Row in ListGroup") {
@Previewable @State var autoStart = true
@Previewable @State var requireKey = false
return ListGroup {
Row(
label: "Listen Address",
sublabel: "Default 8000. Restart server to apply."
) {
CodeChip(value: "127.0.0.1:8000")
}
Row(label: "Auto-start on launch") {
Toggle("", isOn: $autoStart).labelsHidden().toggleStyle(.switch)
}
Row(
label: "Require API Key",
sublabel: "Reject unauthenticated /v1 requests",
isLast: true
) {
Toggle("", isOn: $requireKey).labelsHidden().toggleStyle(.switch)
}
}
.padding(.vertical, 14)
.frame(width: 560)
.omlxThemed()
}