Skip to content

Commit

Permalink
Add ResponsiveStack. (#102)
Browse files Browse the repository at this point in the history
This view is a hybrid of HStack and VStack that swaps its axis based on
a given condition.
  • Loading branch information
jverkoey authored Aug 6, 2024
1 parent c717297 commit 20e644e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Slipstream implementations of Tailwind CSS's utility classes.
- ``Container``
- ``HStack``
- ``VStack``
- ``ResponsiveStack``
- <doc:Layout-Display>
- <doc:Layout-Float>

Expand Down
71 changes: 71 additions & 0 deletions Sources/Slipstream/TailwindCSS/Layout/ResponsiveStack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/// A flex view that swaps its layout direction based on a condition.
///
/// ```swift
/// struct MySiteContent: View {
/// var body: some View {
/// Body {
/// ResponsiveStack {
/// Div {
/// Text("Hello,")
/// }
/// Div {
/// Text("world!")
/// }
/// }
/// }
/// }
/// }
/// ```
@available(iOS 17.0, macOS 14.0, *)
public struct ResponsiveStack<Content: View>: View {
/// Creates a responsive stack view.
///
/// - Parameters:
/// - defaultAxis: The default layout axis for this stack.
/// - condition: The condition for when to swap the stack's layout.
/// - spacing: If provided, the amount of spacing to add between child views. The value is
/// expressed in points, and mapped to the closest Tailwind CSS spacing class.
/// - reversed: If true, the contents will be arranged in the reverse direction of text.
/// - content: The content to display with this view.
public init(
_ defaultAxis: Axis = .y,
condition: Condition = .init(startingAt: .medium),
spacing: Double? = nil,
reversed: Bool = false,
@ViewBuilder content: @escaping () -> Content
) {
self.defaultAxis = defaultAxis
self.condition = condition
self.spacing = spacing
self.reversed = reversed
self.content = content
}

@_documentation(visibility: private)
public var body: some View {
let container = Div { content() }
.display(.flex)
.flexDirection(defaultAxis, reversed: reversed)
.flexDirection(oppositeAxis, reversed: reversed, condition: condition)
if let spacing {
container
.flexGap(defaultAxis, width: spacing)
.flexGap(oppositeAxis, width: spacing, condition: condition)
} else {
container
}
}

private var oppositeAxis: Axis {
switch defaultAxis {
case .x: return .y
case .y: return .x
}
}

private let defaultAxis: Axis
private let condition: Condition
private let spacing: Double?
private let reversed: Bool
private let content: () -> Content
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Testing

import Slipstream

struct ResponsiveStackTests {
@Test func emptyBlock() throws {
try #expect(renderHTML(ResponsiveStack {}) == #"<div class="flex flex-col md:flex-row"></div>"#)
try #expect(renderHTML(ResponsiveStack(reversed: true) {}) == #"<div class="flex flex-col-reverse md:flex-row-reverse"></div>"#)
}

@Test func withText() throws {
try #expect(renderHTML(ResponsiveStack {
Text("Hello, world!")
}) == """
<div class="flex flex-col md:flex-row">
Hello, world!
</div>
""")
}
}

0 comments on commit 20e644e

Please sign in to comment.