Skip to content

Add inline style customization #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,31 @@ Markdown {
![](Sources/MarkdownUI/Documentation.docc/Resources/[email protected]#gh-light-mode-only)
![](Sources/MarkdownUI/Documentation.docc/Resources/[email protected]#gh-dark-mode-only)

You can also replace the default rendering of inline elements using the
`markdownInlineStyle(_:body:)` modifier.

```swift
Markdown {
"Visit "
InlineLink("Swift", destination: URL(string: "https://swift.org")!)
" and run "
InlineCode("swift build")
"."
}
.markdownInlineStyle(\.inlineCode) { configuration in
configuration.label
.padding(4)
.background(Color.yellow.opacity(0.2))
.clipShape(RoundedRectangle(cornerRadius: 4))
}
.markdownInlineStyle(\.inlineLink) { configuration in
HStack(spacing: 2) {
configuration.label
Image(systemName: "link")
}
}
```

Another way to customize the appearance of Markdown content is to create your own theme. To create
a theme, start by instantiating an empty `Theme` and chain together the different text and block
styles in a single expression.
Expand Down
22 changes: 22 additions & 0 deletions Sources/MarkdownUI/Theme/InlineStyle/CodeInlineConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import SwiftUI

/// The properties of an inline code element.
///
/// The ``Theme/inlineCode`` inline style receives a `CodeInlineConfiguration`
/// value in its body closure.
public struct CodeInlineConfiguration {
/// A type-erased view of an inline code element.
public struct Label: View {
init<L: View>(_ label: L) {
self.body = AnyView(label)
}

public let body: AnyView
}

/// The code string displayed by the inline code element.
public let text: String

/// The default inline code view.
public let label: Label
}
30 changes: 30 additions & 0 deletions Sources/MarkdownUI/Theme/InlineStyle/InlineStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import SwiftUI

/// A type that applies a custom appearance to specific inline views in a Markdown view.
///
/// Inline styles are used by ``Theme`` to customize the appearance of inline code or link
/// elements. You typically don't create inline styles directly. Instead, you use the
/// ``View/markdownInlineStyle(_:body:)`` modifier to override a particular inline style of the
/// current theme.
public struct InlineStyle<Configuration> {
private let body: (Configuration) -> AnyView

/// Creates an inline style that customizes an inline element by applying the given body.
/// - Parameter body: A view builder that receives the inline configuration and returns the
/// customized view.
public init<Body: View>(@ViewBuilder body: @escaping (_ configuration: Configuration) -> Body) {
self.body = { AnyView(body($0)) }
}

func makeBody(configuration: Configuration) -> AnyView {
self.body(configuration)
}
}

extension InlineStyle where Configuration == Void {
/// Creates an inline style for an inline element with no configuration.
/// - Parameter body: A view builder that returns the customized inline element.
public init<Body: View>(@ViewBuilder body: @escaping () -> Body) {
self.init { _ in body() }
}
}
25 changes: 25 additions & 0 deletions Sources/MarkdownUI/Theme/InlineStyle/LinkInlineConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import SwiftUI

/// The properties of a Markdown link inline element.
///
/// The ``Theme/inlineLink`` inline style receives a `LinkInlineConfiguration`
/// value in its body closure.
public struct LinkInlineConfiguration {
/// A type-erased view of a link label.
public struct Label: View {
init<L: View>(_ label: L) {
self.body = AnyView(label)
}

public let body: AnyView
}

/// The link destination URL string.
public let destination: String

/// The plain text contents of the link.
public let text: String

/// The default link view.
public let label: Label
}
26 changes: 26 additions & 0 deletions Sources/MarkdownUI/Theme/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ public struct Theme: Sendable {
/// The link style.
public var link: TextStyle = EmptyTextStyle()

/// The inline code style.
public var inlineCode = InlineStyle<CodeInlineConfiguration> { $0.label }

/// The inline link style.
public var inlineLink = InlineStyle<LinkInlineConfiguration> { $0.label }

var headings = Array(
repeating: BlockStyle<BlockConfiguration> { $0.label },
count: 6
Expand Down Expand Up @@ -216,6 +222,26 @@ extension Theme {
return theme
}

/// Adds an inline code style to the theme.
/// - Parameter body: A view builder that returns the customized inline code view.
public func inlineCode<Body: View>(
@ViewBuilder body: @escaping (_ configuration: CodeInlineConfiguration) -> Body
) -> Theme {
var theme = self
theme.inlineCode = .init(body: body)
return theme
}

/// Adds an inline link style to the theme.
/// - Parameter body: A view builder that returns the customized inline link view.
public func inlineLink<Body: View>(
@ViewBuilder body: @escaping (_ configuration: LinkInlineConfiguration) -> Body
) -> Theme {
var theme = self
theme.inlineLink = .init(body: body)
return theme
}

/// Adds an emphasis style to the theme.
/// - Parameter emphasis: A text style builder that returns the emphasis style.
public func emphasis<S: TextStyle>(@TextStyleBuilder emphasis: () -> S) -> Theme {
Expand Down
24 changes: 24 additions & 0 deletions Sources/MarkdownUI/Views/Environment/Environment+Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@ extension View {
self.environment((\EnvironmentValues.theme).appending(path: keyPath), .init(body: body))
}

/// Replaces a specific inline style on the current ``Theme`` with an inline style
/// initialized with the given body closure.
/// - Parameters:
/// - keyPath: The ``Theme`` key path to the inline style to replace.
/// - body: A view builder that receives the inline configuration and returns the customized inline view.
public func markdownInlineStyle<Configuration, Body: View>(
_ keyPath: WritableKeyPath<Theme, InlineStyle<Configuration>>,
@ViewBuilder body: @escaping (_ configuration: Configuration) -> Body
) -> some View {
self.environment((\EnvironmentValues.theme).appending(path: keyPath), .init(body: body))
}

/// Replaces a specific inline style on the current ``Theme`` with an inline style
/// initialized with the given body closure.
/// - Parameters:
/// - keyPath: The ``Theme`` key path to the inline style to replace.
/// - body: A view builder that returns the customized inline view.
public func markdownInlineStyle<Body: View>(
_ keyPath: WritableKeyPath<Theme, InlineStyle<Void>>,
@ViewBuilder body: @escaping () -> Body
) -> some View {
self.environment((\EnvironmentValues.theme).appending(path: keyPath), .init(body: body))
}

/// Replaces the current ``Theme`` task list marker with the given list marker.
public func markdownTaskListMarker(
_ value: BlockStyle<TaskListMarkerConfiguration>
Expand Down
Loading