aboutsummaryrefslogtreecommitdiff
path: root/Sources/NorgUI/Theme
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-06-15 14:35:36 +0200
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-06-16 14:47:30 +0200
commit657aa83a5ca24dc35572c040df64a0abb85e8612 (patch)
treefe0b6fd425d8cad9dc6a28ada4ad8600e42b367e /Sources/NorgUI/Theme
Initial extraction from Norganize
Diffstat (limited to 'Sources/NorgUI/Theme')
-rw-r--r--Sources/NorgUI/Theme/EnvironmentValues+NorgTheme.swift19
-rw-r--r--Sources/NorgUI/Theme/NorgTheme.swift128
2 files changed, 147 insertions, 0 deletions
diff --git a/Sources/NorgUI/Theme/EnvironmentValues+NorgTheme.swift b/Sources/NorgUI/Theme/EnvironmentValues+NorgTheme.swift
new file mode 100644
index 0000000..58c2c00
--- /dev/null
+++ b/Sources/NorgUI/Theme/EnvironmentValues+NorgTheme.swift
@@ -0,0 +1,19 @@
+import SwiftUI
+
+private struct NorgThemeKey: EnvironmentKey {
+ static let defaultValue = NorgTheme.default
+}
+
+/// Makes NorgTheme values accessible via the environment.
+extension EnvironmentValues {
+ public var norgTheme: NorgTheme {
+ get { self[NorgThemeKey.self] }
+ set { self[NorgThemeKey.self] = newValue }
+ }
+}
+
+extension View {
+ public func norgTheme(_ theme: NorgTheme) -> some View {
+ environment(\.norgTheme, theme)
+ }
+}
diff --git a/Sources/NorgUI/Theme/NorgTheme.swift b/Sources/NorgUI/Theme/NorgTheme.swift
new file mode 100644
index 0000000..47dd161
--- /dev/null
+++ b/Sources/NorgUI/Theme/NorgTheme.swift
@@ -0,0 +1,128 @@
+import NorgKit
+import SwiftUI
+
+/// Typography and colours used when rendering a Norg document.
+public struct NorgTheme: Sendable {
+
+ /// Font for a heading at the given (1-based) level.
+ public var heading: @Sendable (Int) -> Font
+ /// Base font for body text, list items and paragraphs.
+ public var body: Font
+ /// Font for the title of a definition, footnote or table cell.
+ public var termFont: Font
+ /// Foreground colour for `verbatim` inline spans.
+ public var verbatimColor: Color
+ /// Foreground colour for `spoiler` inline spans.
+ public var spoilerForeground: Color
+ /// Background colour for `spoiler` inline spans.
+ public var spoilerBackground: Color
+ /// Foreground colour applied to link spans.
+ public var linkColor: Color
+ /// Baseline offset applied to `superscript` spans.
+ public var superscriptOffset: CGFloat
+ /// Baseline offset applied to `subscript` spans.
+ public var subscriptOffset: CGFloat
+ /// Foreground colour for block quotes.
+ public var quoteColor: Color
+ /// Fill colour for the vertical bar drawn alongside a quote.
+ public var quoteBarColor: Color
+ /// Background fill for fenced code blocks and ranged tags.
+ public var codeBackground: AnyShapeStyle
+ /// Font for code block and ranged-tag contents.
+ public var codeFont: Font
+ /// Horizontal indentation applied per nesting level.
+ public var indentWidth: CGFloat
+ /// Tint for a task's status symbol.
+ public var statusTint: @Sendable (TaskStatus) -> Color
+ /// SF Symbol name for a task's status symbol.
+ public var statusSymbol: @Sendable (TaskStatus) -> String
+ /// Localized label for a task status, shown in the status menu and used
+ /// for accessibility. Defaults to ``TaskStatus/displayName``; override to
+ /// supply wording from your own string catalog.
+ public var statusLabel: @Sendable (TaskStatus) -> LocalizedStringResource
+
+ public init(
+ heading: @escaping @Sendable (Int) -> Font,
+ body: Font,
+ termFont: Font,
+ verbatimColor: Color,
+ spoilerForeground: Color,
+ spoilerBackground: Color,
+ linkColor: Color,
+ superscriptOffset: CGFloat,
+ subscriptOffset: CGFloat,
+ quoteColor: Color,
+ quoteBarColor: Color,
+ codeBackground: AnyShapeStyle,
+ codeFont: Font,
+ indentWidth: CGFloat,
+ statusTint: @escaping @Sendable (TaskStatus) -> Color,
+ statusSymbol: @escaping @Sendable (TaskStatus) -> String,
+ statusLabel: @escaping @Sendable (TaskStatus) -> LocalizedStringResource = { $0.displayName }
+ ) {
+ self.heading = heading
+ self.body = body
+ self.termFont = termFont
+ self.verbatimColor = verbatimColor
+ self.spoilerForeground = spoilerForeground
+ self.spoilerBackground = spoilerBackground
+ self.linkColor = linkColor
+ self.superscriptOffset = superscriptOffset
+ self.subscriptOffset = subscriptOffset
+ self.quoteColor = quoteColor
+ self.quoteBarColor = quoteBarColor
+ self.codeBackground = codeBackground
+ self.codeFont = codeFont
+ self.indentWidth = indentWidth
+ self.statusTint = statusTint
+ self.statusSymbol = statusSymbol
+ self.statusLabel = statusLabel
+ }
+
+ /// The default theme.
+ public static let `default` = NorgTheme(
+ heading: { level in
+ switch level {
+ case 1: return .title.bold()
+ case 2: return .title2.bold()
+ case 3: return .title3.bold()
+ case 4: return .headline
+ case 5: return .subheadline.bold()
+ default: return .subheadline
+ }
+ },
+ body: .body,
+ termFont: .body.bold(),
+ verbatimColor: .pink,
+ spoilerForeground: .secondary,
+ spoilerBackground: .secondary.opacity(0.25),
+ linkColor: .accentColor,
+ superscriptOffset: 5,
+ subscriptOffset: -3,
+ quoteColor: .secondary,
+ quoteBarColor: .secondary,
+ codeBackground: AnyShapeStyle(.quaternary),
+ codeFont: .system(.callout, design: .monospaced),
+ indentWidth: 18,
+ statusTint: { status in
+ switch status {
+ case .done: return .green
+ case .urgent: return .red
+ case .pending: return .blue
+ default: return .secondary
+ }
+ },
+ statusSymbol: { status in
+ switch status {
+ case .undone: return "circle"
+ case .done: return "checkmark.circle.fill"
+ case .needsInput: return "questionmark.circle"
+ case .urgent: return "exclamationmark.circle"
+ case .recurring: return "repeat.circle"
+ case .pending: return "ellipsis.circle"
+ case .onHold: return "pause.circle"
+ case .cancelled: return "xmark.circle"
+ }
+ }
+ )
+}