- 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>
162 lines
6.1 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|