Skip to content
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

Expose BindingSource #807

Merged
merged 1 commit into from
Jun 17, 2024
Merged
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
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
}
}
Loading