Skip to content

Commit

Permalink
Merge pull request #807 from kiwicom/806-expose-bindingsource-as-public
Browse files Browse the repository at this point in the history
Expose `BindingSource`
  • Loading branch information
PavelHolec authored Jun 17, 2024
2 parents 4c27ba6 + 9ade40b commit 0b3cbd9
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 23 deletions.
15 changes: 9 additions & 6 deletions Sources/Orbit/Components/Collapse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public struct Collapse<Header: View, Content: View>: View {

private let headerVerticalPadding: CGFloat
private let showSeparator: Bool
private let isExpanded: Binding<Bool>?
private let isExpanded: OptionalBindingSource<Bool>
@ViewBuilder private let content: Content
@ViewBuilder private let header: Header

public var body: some View {
BindingSource(isExpanded, fallbackInitialValue: false) { $isExpanded in
OptionalBinding(isExpanded) { $isExpanded in
VStack(alignment: .leading, spacing: 0) {
SwiftUI.Button {
withAnimation(.easeInOut(duration: 0.2)) {
Expand Down Expand Up @@ -71,7 +71,7 @@ public extension Collapse {
self.headerVerticalPadding = 0
self.content = content()
self.showSeparator = showSeparator
self.isExpanded = isExpanded
self.isExpanded = .binding(isExpanded)
}

/// Creates Orbit ``Collapse`` component.
Expand All @@ -80,7 +80,7 @@ public extension Collapse {
self.headerVerticalPadding = 0
self.content = content()
self.showSeparator = showSeparator
self.isExpanded = nil
self.isExpanded = .state(false)
}
}

Expand All @@ -92,7 +92,7 @@ public extension Collapse where Header == Text {
self.headerVerticalPadding = .small
self.content = content()
self.showSeparator = showSeparator
self.isExpanded = isExpanded
self.isExpanded = .binding(isExpanded)
}

/// Creates Orbit ``Collapse`` component.
Expand All @@ -101,7 +101,7 @@ public extension Collapse where Header == Text {
self.headerVerticalPadding = .small
self.content = content()
self.showSeparator = showSeparator
self.isExpanded = nil
self.isExpanded = .state(false)
}
}

Expand Down Expand Up @@ -144,6 +144,9 @@ struct CollapsePreviews: PreviewProvider {
headerPlaceholder
}
}
Collapse("Toggle with internal state") {
contentPlaceholder
}
}
.padding(.medium)
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Orbit/Orbit.docc/Uncategorized.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ Supporting types and components under development.
- ``IDPreferenceKey``
- ``IDPreference``
- ``HorizontalScrollViewProxy``
- ``OptionalBinding``
- ``OptionalBindingSource``
47 changes: 30 additions & 17 deletions Sources/Orbit/Support/BindingSource.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
import SwiftUI

/// A view that provides a binding to its content.
/// A binding source for the ``OptionalBinding``.
public enum OptionalBindingSource<Value> {
case binding(Binding<Value>)
case state(Value)
}

/// A view that provides either a binding to its content or an internal state, based on provided ``OptionalBindingSource`` value.
///
/// This binding can either be supplied, in which case it is used directly,
/// or one is derived from internal state (starting with `defaultValue`).
/// The binding can either be supplied, in which case it is used directly,
/// or one is derived from internal state.
///
/// This is is useful for components that can manage their own state,
/// but we also want to make it possible for that state to be driven
/// from the outside if a binding is passed.
struct BindingSource<Value, Content: View>: View {

let outer: Binding<Value>?
@State var inner: Value
/// This is is useful for components that need to manage their own state,
/// but also allow that state to be overridden
/// using the binding provided from the outside.
public struct OptionalBinding<Value, Content: View>: View {
let binding: Binding<Value>?
@State var state: Value
let content: (Binding<Value>) -> Content

var body: some View {
content(outer ?? $inner)
public var body: some View {
content(binding ?? $state)
}

init(
_ binding: Binding<Value>?,
fallbackInitialValue: Value,
/// Create a view with either a binding to its content or an internal state.
public init(
_ source: OptionalBindingSource<Value>,
@ViewBuilder content: @escaping (Binding<Value>) -> Content
) {
self.outer = binding
self._inner = State(wrappedValue: fallbackInitialValue)
switch source {
case .binding(let binding):
self.binding = binding
self._state = .init(wrappedValue: binding.wrappedValue)
case .state(let value):
self.binding = nil
self._state = State(wrappedValue: value)
}

self.content = content
}
}

0 comments on commit 0b3cbd9

Please sign in to comment.