mana-swift-ui/Sources/ManaComponentsUI/ManaScreenHeader.swift
Till JS fd7d37f257 ManaScreenHeader: monochromer Titel-Default + Unterseiten-Adoption
- Default-Titelfarbe primary (Akzent) -> foreground (monochrom). Der
  unterstrichene Marken-Titel wirkt jetzt ruhig/einheitlich auf Haupt-
  und Unterseiten; Akzent bleibt Aktionen/Icons vorbehalten.
- Neu in ManaComponentsUI: ManaScreenHeaderRow (Header als erste,
  randlos mitscrollende List-/Form-Zeile) + View.manaScreenHeaderTitleHidden()
  (versteckt System-navigationTitle, Zurueck-Pfeil bleibt).
- ManaAccountView: optionaler title: -> traegt Konto-Titel ueber den
  globalen Header statt System-Titel (Default nil = altes Verhalten).
- ManaView (Mana-Guthaben): globaler Header statt navigationTitle.
- Package.swift: ManaComponentsUI-Dep fuer ManaAuthUI + ManaCreditsUI.

CHANGELOG bewusst ausgelassen (Parallel-Session-WIP in der Datei).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 16:29:50 +02:00

162 lines
6.1 KiB
Swift

import ManaTokens
import SwiftUI
/// Flotten-einheitlicher Seiten-Header — natives Pendant zur Android-
/// `ManaScreenHeader` (mana-kotlin-ui). **Der Stil ist hier zentral
/// festgelegt**, damit der Titel über alle nativen mana-Apps gleich aussieht:
///
/// - Titel: `.title`, **monochrom** (`foreground`), **unterstrichen** — die
/// Marken-Signatur (Referenz: memoro „Deine Memos", pageta „Meine Artikel").
/// Bewusst nicht die Akzentfarbe: der Titel soll über Haupt- **und**
/// Unterseiten ruhig und einheitlich wirken; der Akzent bleibt den
/// Aktionen/Icons vorbehalten. Apps mit eigenem Header-Hintergrund können
/// über `titleColor` übersteuern (z. B. weiß auf dunkler Fläche).
/// - Untertitel (optional): `.subheadline`, gedämpfte Vordergrundfarbe.
/// - **Links-bündig**; Titel-Spalte links, optionaler `trailing`-Slot rechts
/// oben (typisch der Konto-Avatar) — beide auf gleicher Höhe.
/// - Padding zentral: 20 horizontal, 12 vertikal.
///
/// Vor dieser Komponente malte jede App ihren Titel inline mit eigener Schrift,
/// Ausrichtung und Padding. Apps liefern jetzt nur noch Titel/Untertitel/Avatar.
///
/// Die Farben kommen aus der aktiven Theme-Variant über
/// `@Environment(\.manaTheme)`; die App injiziert sie an der Root via
/// `.manaTheme(_:)`.
///
/// ```swift
/// ManaScreenHeader("Deine Memos", subtitle: "local-first") {
/// ManaAccountAvatarButton(auth: auth) { showAccount = true }
/// }
/// ```
public struct ManaScreenHeader<Trailing: View>: View {
@Environment(\.manaTheme) private var theme
private let title: String
private let subtitle: String?
private let titleColor: Color?
private let horizontalPadding: CGFloat
private let verticalPadding: CGFloat
private let trailing: Trailing
/// - Parameters:
/// - title: Seitentitel (unterstrichen, Akzentfarbe).
/// - subtitle: optionaler erklärender Untertitel.
/// - titleColor: Titelfarbe-Override. `nil` → monochromer Theme-
/// `foreground`. Für Apps mit eigenem Header-Hintergrund (z. B. weißer
/// Titel auf dunkler Fläche).
/// - horizontalPadding: seitliches Padding (Default 20).
/// - verticalPadding: vertikales Padding (Default 12).
/// - trailing: rechts-oben angesetzter Slot, typisch der Konto-Avatar.
public init(
_ title: String,
subtitle: String? = nil,
titleColor: Color? = nil,
horizontalPadding: CGFloat = 20,
verticalPadding: CGFloat = 12,
@ViewBuilder trailing: () -> Trailing
) {
self.title = title
self.subtitle = subtitle
self.titleColor = titleColor
self.horizontalPadding = horizontalPadding
self.verticalPadding = verticalPadding
self.trailing = trailing()
}
public var body: some View {
HStack(alignment: .top, spacing: 12) {
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.title.weight(.bold))
.underline()
.foregroundStyle(titleColor ?? theme.colors.foreground)
if let subtitle {
Text(subtitle)
.font(.subheadline)
.foregroundStyle(theme.colors.mutedForeground)
}
}
Spacer(minLength: 0)
trailing
}
.padding(.horizontal, horizontalPadding)
.padding(.vertical, verticalPadding)
}
}
public extension ManaScreenHeader where Trailing == EmptyView {
/// Header ohne `trailing`-Slot (kein Avatar/Aktion rechts).
init(
_ title: String,
subtitle: String? = nil,
titleColor: Color? = nil,
horizontalPadding: CGFloat = 20,
verticalPadding: CGFloat = 12
) {
self.init(
title,
subtitle: subtitle,
titleColor: titleColor,
horizontalPadding: horizontalPadding,
verticalPadding: verticalPadding
) { EmptyView() }
}
}
/// Der ``ManaScreenHeader`` als **erste, randlos mitscrollende Zeile** einer
/// `List`/`Form` — der einheitliche Weg, eine Unterseite mit dem globalen
/// Header zu betiteln statt mit dem System-`navigationTitle`. Kein Trenner,
/// kein Row-Hintergrund: der Titel scrollt aus dem Bild, dann kleben die
/// Section-Header oben (Muster aus memoro „Deine Memos").
///
/// Zusammen mit ``SwiftUI/View/manaScreenHeaderTitleHidden()`` (versteckt den
/// System-Titel, behält den Zurück-Pfeil) ergibt das die Unterseiten-Variante:
///
/// ```swift
/// List {
/// ManaScreenHeaderRow("Statistik")
/// // … restliche Sektionen …
/// }
/// .manaScreenHeaderTitleHidden()
/// ```
public struct ManaScreenHeaderRow: View {
private let title: String
private let subtitle: String?
private let titleColor: Color?
public init(_ title: String, subtitle: String? = nil, titleColor: Color? = nil) {
self.title = title
self.subtitle = subtitle
self.titleColor = titleColor
}
public var body: some View {
ManaScreenHeader(title, subtitle: subtitle, titleColor: titleColor)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
}
}
public extension View {
/// Versteckt den System-`navigationTitle` (die Unterseite trägt ihren Titel
/// stattdessen über ``ManaScreenHeaderRow`` im Inhalt), behält aber den
/// Zurück-Pfeil. iOS: zusätzlich `.inline`, damit keine große Titelfläche
/// reserviert wird.
///
/// - Parameter hidden: `false` → no-op (lässt den App-eigenen
/// `navigationTitle` unangetastet). Erlaubt opt-in an Komponenten, die
/// den Header nur bedingt selbst setzen (z. B. ``ManaAccountView``).
@ViewBuilder
func manaScreenHeaderTitleHidden(_ hidden: Bool = true) -> some View {
if hidden {
#if os(iOS)
navigationTitle("").navigationBarTitleDisplayMode(.inline)
#else
navigationTitle("")
#endif
} else {
self
}
}
}