smlflg's picture
Initial public upload from Projekte/PromtGarageMac
4977db2 verified
Raw History Blame Contribute Delete
5.55 kB
import SwiftUI
enum AppUI {
static let accent = Color(red: 0.91, green: 0.48, blue: 0.12)
static let cardCornerRadius: CGFloat = 14
static let compactCornerRadius: CGFloat = 8
}
struct LocalStatusLabel: View {
let databaseName: String?
var body: some View {
HStack(spacing: 7) {
Circle()
.fill(.green)
.frame(width: 7, height: 7)
Text("Lokal · nicht synchronisiert")
.font(.caption)
.foregroundStyle(.secondary)
if let databaseName {
Text("·")
.foregroundStyle(.tertiary)
Text(databaseName)
.font(.caption.monospaced())
.foregroundStyle(.tertiary)
.lineLimit(1)
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Lokale Datenbank, keine Synchronisierung")
}
}
struct StatusChip: View {
let title: String
var systemImage: String? = nil
var tint: Color = .secondary
var body: some View {
HStack(spacing: 5) {
if let systemImage {
Image(systemName: systemImage)
}
Text(title)
.lineLimit(1)
}
.font(.caption.weight(.medium))
.foregroundStyle(tint)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(tint.opacity(0.11), in: Capsule())
}
}
struct ErrorBanner: View {
let error: AppErrorPresentation
let dismiss: () -> Void
let copyDiagnostic: () -> Void
var body: some View {
HStack(alignment: .top, spacing: 12) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
.padding(.top, 2)
VStack(alignment: .leading, spacing: 3) {
Text(error.title)
.font(.subheadline.weight(.semibold))
Text(error.message)
.font(.caption)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
Spacer(minLength: 16)
Button("Diagnose kopieren", action: copyDiagnostic)
.buttonStyle(.borderless)
.font(.caption)
Button(action: dismiss) {
Image(systemName: "xmark")
}
.buttonStyle(.borderless)
.accessibilityLabel("Fehler ausblenden")
}
.padding(12)
.background(.red.opacity(0.08))
.overlay(alignment: .bottom) {
Divider().overlay(.red.opacity(0.2))
}
}
}
struct SuccessToast: View {
let message: String
var body: some View {
Label(message, systemImage: "checkmark.circle.fill")
.font(.callout.weight(.medium))
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(.regularMaterial, in: Capsule())
.shadow(color: .black.opacity(0.13), radius: 16, y: 7)
.accessibilityAddTraits(.isStaticText)
}
}
struct MetricCard: View {
let title: String
let value: Int
let systemImage: String
var tint: Color = AppUI.accent
var body: some View {
HStack(spacing: 13) {
Image(systemName: systemImage)
.font(.title2)
.foregroundStyle(tint)
.frame(width: 36, height: 36)
.background(tint.opacity(0.12), in: RoundedRectangle(cornerRadius: 9))
VStack(alignment: .leading, spacing: 2) {
Text(value.formatted())
.font(.title2.weight(.semibold).monospacedDigit())
Text(title)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 0)
}
.padding(14)
.background(
Color(nsColor: .controlBackgroundColor),
in: RoundedRectangle(cornerRadius: AppUI.cardCornerRadius)
)
.overlay {
RoundedRectangle(cornerRadius: AppUI.cardCornerRadius)
.stroke(Color(nsColor: .separatorColor).opacity(0.55), lineWidth: 1)
}
}
}
struct RatingControl: View {
let rating: Int?
var size: Font = .body
var onChange: ((Int?) -> Void)?
var body: some View {
HStack(spacing: 2) {
ForEach(1...5, id: \.self) { value in
Button {
onChange?(rating == value ? nil : value)
} label: {
Image(systemName: value <= (rating ?? 0) ? "star.fill" : "star")
.font(size)
.foregroundStyle(value <= (rating ?? 0) ? AppUI.accent : .secondary)
}
.buttonStyle(.plain)
.disabled(onChange == nil)
.accessibilityLabel("\(value) von 5 Sternen")
.accessibilityAddTraits(rating == value ? .isSelected : [])
}
}
}
}
struct LoadingOverlay: View {
let label: String
var body: some View {
VStack(spacing: 12) {
ProgressView()
.controlSize(.large)
Text(label)
.font(.callout)
.foregroundStyle(.secondary)
}
.padding(24)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: AppUI.cardCornerRadius))
.shadow(color: .black.opacity(0.12), radius: 20, y: 8)
}
}