From 5aa68afb6ef6a014216a70fb8fdc78134b504e46 Mon Sep 17 00:00:00 2001 From: Daniel Saidi Date: Thu, 15 Feb 2024 15:04:53 +0100 Subject: [PATCH] Make sheet presentation opt-in for format toolbar --- RELEASE_NOTES.md | 7 +- .../Format/RichTextFormatSheet.swift | 189 ++++++++++++------ 2 files changed, 139 insertions(+), 57 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4c5225db0..a2f3336e9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -23,13 +23,18 @@ By deprecating these functions, we can simplify the library in 1.0, and focus mo * `RichTextCommand.ActionButtonGroup` has new inits. * `RichTextCommand.FormatMenu` is a lot more configurable. * `RichTextFormatToolbar` is now available on all platforms. -* `RichTextFormatToolbar` now has a custom configuration type. +* `RichTextFormatToolbar` has new configuration and style type. * `RichTextKeyboardToolbar` has a new config to always be shown. * `RichTextView` has a new theme that lets you define its style. * `RichTextViewComponent` has a new `hasRichTextStyle` function. * `RichTextViewComponent` has a new `toggleRichTextStyle` function. * `RichTextViewComponent` now handles superscript changes properly. +### 🚨 Important + +* `RichTextFormatToolbar` is no longer navigation wrapped by default. +* `RichTextFormatToolbar` has a new `asSheet()` function that does this. + ### 💡 Adjustments * `RichTextColor` `icon` is no longer optional. diff --git a/Sources/RichTextKit/Format/RichTextFormatSheet.swift b/Sources/RichTextKit/Format/RichTextFormatSheet.swift index 6a277e9ce..bd0a08e17 100644 --- a/Sources/RichTextKit/Format/RichTextFormatSheet.swift +++ b/Sources/RichTextKit/Format/RichTextFormatSheet.swift @@ -18,6 +18,9 @@ import SwiftUI You can provide a custom configuration to adjust the format options that are presented. When presented, the font picker will take up the available vertical height. + + You can style this view by applying a style anywhere in the + view hierarchy, using `.richTextFormatToolbarStyle`. */ public struct RichTextFormatToolbar: View { @@ -38,56 +41,38 @@ public struct RichTextFormatToolbar: View { @ObservedObject private var context: RichTextContext - - @Environment(\.presentationMode) - private var presentationMode - - /// The sheet padding. - public var padding = 10.0 - - /// The sheet top offset. - public var topOffset = -35.0 + + @Environment(\.richTextFormatToolbarStyle) + private var style /// The configuration to use. private let config: Configuration public var body: some View { + VStack(spacing: 0) { + fontPicker + toolbar + } + } +} + +public extension RichTextFormatToolbar { + + /// Convert the toolbar to a sheet, with a close button. + func asSheet( + dismiss: @escaping () -> Void + ) -> some View { NavigationView { - VStack(spacing: 0) { - fontPicker - VStack(spacing: padding) { - VStack { - fontRow - paragraphRow - Divider() - }.padding(.horizontal, padding) - VStack(spacing: padding) { - ForEach(config.colorPickers) { - RichTextColor.Picker( - type: $0, - value: context.binding(for: $0), - quickColors: .quickPickerColors - ) - } - }.padding(.leading, padding) - } - .padding(.vertical, padding) - .environment(\.sizeCategory, .medium) - .background(background) - } - .withAutomaticToolbarRole() - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - EmptyView() - } - ToolbarItem(placement: .navigationBarTrailing) { - Button(RTKL10n.done.text) { - presentationMode.wrappedValue.dismiss() + self + .padding(.top, -35) + .withAutomaticToolbarRole() + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button(RTKL10n.done.text, action: dismiss) } } - } - .navigationTitle("") - .navigationBarTitleDisplayMode(.inline) + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) } .navigationViewStyle(.stack) } @@ -117,6 +102,70 @@ public extension RichTextFormatToolbar.Configuration { static var standard = Self.init() } +public extension RichTextFormatToolbar { + + /// This struct can be used to style a format sheet. + struct Style { + + public init( + padding: Double = 10, + spacing: Double = 10 + ) { + self.padding = padding + self.spacing = spacing + } + + public var padding: Double + public var spacing: Double + } + + /// This environment key defines a format toolbar style. + struct StyleKey: EnvironmentKey { + + public static let defaultValue = RichTextFormatToolbar.Style() + } +} + +public extension View { + + /// Apply a rich text format toolbar style. + func richTextFormatToolbarStyle( + _ style: RichTextFormatToolbar.Style + ) -> some View { + self.environment(\.richTextFormatToolbarStyle, style) + } +} + +public extension EnvironmentValues { + + /// This environment value defines format toolbar styles. + var richTextFormatToolbarStyle: RichTextFormatToolbar.Style { + get { self [RichTextFormatToolbar.StyleKey.self] } + set { self [RichTextFormatToolbar.StyleKey.self] = newValue } + } +} + +private extension RichTextFormatToolbar { + + @ViewBuilder + var fontPicker: some View { + if config.fontPicker { + RichTextFont.ListPicker(selection: $context.fontName) + Divider() + } + } + + var toolbar: some View { + VStack(spacing: style.spacing) { + controls + colorPickers + } + .padding(.vertical, style.padding) + .environment(\.sizeCategory, .medium) + .background(background) + } +} + private extension RichTextFormatToolbar { var background: some View { @@ -126,16 +175,31 @@ private extension RichTextFormatToolbar { .edgesIgnoringSafeArea(.all) } + var controls: some View { + VStack(spacing: style.spacing) { + fontRow + paragraphRow + } + .padding(.horizontal, style.padding) + } + @ViewBuilder - var fontPicker: some View { - if config.fontPicker { - RichTextFont.ListPicker(selection: $context.fontName) - .offset(y: topOffset) - .padding(.bottom, topOffset) - Divider() + var colorPickers: some View { + if !config.colorPickers.isEmpty { + VStack(spacing: style.spacing) { + Divider() + ForEach(config.colorPickers) { + RichTextColor.Picker( + type: $0, + value: context.binding(for: $0), + quickColors: .quickPickerColors + ) + } + } + .padding(.leading, style.padding) } } - + var fontRow: some View { HStack { styleButtons @@ -186,18 +250,31 @@ private extension View { struct RichTextFormatToolbar_Previews: PreviewProvider { struct Preview: View { - + @StateObject private var context = RichTextContext() + @State + private var isSheetPresented = false + var body: some View { - RichTextFormatToolbar( - context: context, - config: .init( - colorPickers: [.foreground], - fontPicker: false + Button("Toggle sheet") { + isSheetPresented.toggle() + } + .sheet(isPresented: $isSheetPresented) { + RichTextFormatToolbar( + context: context, + config: .init( + colorPickers: [.foreground], + fontPicker: true + ) ) - ) + .asSheet { isSheetPresented = false } + .richTextFormatToolbarStyle(.init( + padding: 10, + spacing: 10 + )) + } } }