Skip to content

Commit

Permalink
Add padding modifier.
Browse files Browse the repository at this point in the history
Part of #51.
  • Loading branch information
jverkoey committed Aug 3, 2024
1 parent 19785d2 commit 76a833c
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 16 deletions.
11 changes: 11 additions & 0 deletions Sources/Slipstream/Documentation.docc/Views/TailwindCSS/Padding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Padding

## Topics

### Modifiers

- ``View/padding(_:_:)``

### Enumerations

- ``Edge``
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Slipstream implementations of Tailwind CSS's classes.

- ``Container``

### Spacing

- <doc:Padding>

### Typography

- <doc:FontSize>
Expand Down
70 changes: 70 additions & 0 deletions Sources/Slipstream/TailwindCSS/Edge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/// A structure that represents the edges of a rectangle, used for specifying
/// which sides to apply modifications to, such as padding or margin.
///
/// This struct conforms to the `OptionSet` protocol, allowing for the combination
/// of multiple edges using set operations.
///
/// Usage:
///
/// ```swift
/// let edges: Edge = [.top, .left]
/// ```
public enum Edge: Int8, CaseIterable {
/// The top edge of a rectangle.
case top = 0b0000_0001

/// The left edge of a rectangle.
case left = 0b0000_0010

/// The bottom edge of a rectangle.
case bottom = 0b0000_0100

/// The right edge of a rectangle.
case right = 0b0000_1000

/// A set of edges.
public struct Set: OptionSet {
/// The element type of the option set.
///
/// To inherit all the default implementations from the `OptionSet` protocol,
/// the `Element` type must be `Self`, the default.
public typealias Element = Edge.Set

/// The raw integer value representing the edge. Used internally to distinguish
/// different edges and combinations of edges.
public let rawValue: Int8

/// Creates a new `Edge` instance from the given raw value.
///
/// - Parameter rawValue: The raw integer value representing the edge.
public init(rawValue: Int8) {
self.rawValue = rawValue
}

/// Creates set of edges containing only the specified edge.
public init(_ e: Edge) {
self.rawValue = e.rawValue
}

/// The top edge of a rectangle.
public static let top = Edge.Set(.top)

/// The left edge of a rectangle.
public static let left = Edge.Set(.left)

/// The bottom edge of a rectangle.
public static let bottom = Edge.Set(.bottom)

/// The right edge of a rectangle.
public static let right = Edge.Set(.right)

/// A convenience option representing both the left and right edges.
public static let horizontal: Edge.Set = [.left, .right]

/// A convenience option representing both the top and bottom edges.
public static let vertical: Edge.Set = [.top, .bottom]

/// A convenience option representing all edges (top, left, bottom, right).
public static let all: Edge.Set = [.top, .left, .bottom, .right]
}
}
90 changes: 90 additions & 0 deletions Sources/Slipstream/TailwindCSS/Spacing/View+padding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
extension View {
/// Set the padding for specific edges.
///
/// - Parameters:
/// - edges: The edges to which padding should be applied.
/// - length: The size of the padding to apply, in points. If the padding is exactly between
/// two padding classes, then the smaller padding class will be used.
///
/// - SeeAlso: Tailwind CSS' [`padding`](https://tailwindcss.com/docs/padding) documentation.
@available(iOS 17.0, macOS 14.0, *)
public func padding(_ edges: Edge.Set, _ length: Double) -> some View {
let paddingClass = closestTailwindPadding(ptLength: length)
var classes = [String]()

if edges == .all {
classes = ["p-" + paddingClass]
} else {
if Edge.Set.horizontal.isSubset(of: edges) {
classes.append("px-" + paddingClass)
} else {
if edges.contains(.left) {
classes.append("pl-" + paddingClass)
}
if edges.contains(.right) {
classes.append("pr-" + paddingClass)
}
}
if Edge.Set.vertical.isSubset(of: edges) {
classes.append("py-" + paddingClass)
} else {
if edges.contains(.top) {
classes.append("pt-" + paddingClass)
}
if edges.contains(.bottom) {
classes.append("pb-" + paddingClass)
}
}
}
return self.modifier(ClassModifier(add: classes.joined(separator: " ")))
}

/// Map a point size to the closest Tailwind CSS padding class.
///
/// - Parameter size: The size in points to be mapped.
/// - Returns: The Tailwind CSS padding class string.
@available(iOS 17.0, macOS 14.0, *)
private func closestTailwindPadding(ptLength: Double) -> String {
// Tailwind padding classes and their corresponding sizes in points
let paddingMapping: [(paddingClass: String, ptLength: Double)] = [
("0", 0), // 0pt
("0.5", 2), // 0.125rem
("1", 4), // 0.25rem
("1.5", 6), // 0.375rem
("2", 8), // 0.5rem
("2.5", 10), // 0.625rem
("3", 12), // 0.75rem
("3.5", 14), // 0.875rem
("4", 16), // 1rem
("5", 20), // 1.25rem
("6", 24), // 1.5rem
("7", 28), // 1.75rem
("8", 32), // 2rem
("9", 36), // 2.25rem
("10", 40), // 2.5rem
("11", 44), // 2.75rem
("12", 48), // 3rem
("14", 56), // 3.5rem
("16", 64), // 4rem
("20", 80), // 5rem
("24", 96), // 6rem
("28", 112), // 7rem
("32", 128), // 8rem
("36", 144), // 9rem
("40", 160), // 10rem
("44", 176), // 11rem
("48", 192), // 12rem
("52", 208), // 13rem
("56", 224), // 14rem
("60", 240), // 15rem
("64", 256), // 16rem
("72", 288), // 18rem
("80", 320), // 20rem
("96", 384) // 24rem
]

// Find the closest matching padding size class
let closestPadding = paddingMapping.min { abs($0.ptLength - ptLength) < abs($1.ptLength - ptLength) }
return closestPadding?.paddingClass ?? "0"
}
}
29 changes: 15 additions & 14 deletions Sources/Slipstream/TailwindCSS/Typography/View+fontSize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ extension View {

/// Set the font size to the closest equivalent Tailwind CSS font size.
///
/// - Parameter fontSize: A font size in `pt` units. if the size is exactly between
/// - Parameter fontSize: A font size in `pt` units. The closest Tailwind font
/// size class that matches this point size will be used. If the size is exactly between
/// two font classes, then the smaler font will be used.
///
/// - SeeAlso: Tailwind CSS' [`font-size`](https://tailwindcss.com/docs/font-size) documentation.
Expand All @@ -44,19 +45,19 @@ extension View {
private func closestTailwindFontSize(ptSize: Int) -> FontSize {
// Mapping of Tailwind CSS font size classes to their point sizes.
let fontSizeMapping: [(fontSize: FontSize, ptSize: Int)] = [
(.extraSmall, 12), // 0.75rem (12pt)
(.small, 14), // 0.875rem (14pt)
(.base, 16), // 1rem (16pt)
(.large, 18), // 1.125rem (18pt)
(.extraLarge, 20), // 1.25rem (20pt)
(.extraExtraLarge, 24), // 1.5rem (24pt)
(.extraExtraExtraLarge, 30), // 1.875rem (30pt)
(.fourXLarge, 36), // 2.25rem (36pt)
(.fiveXLarge, 48), // 3rem (48pt)
(.sixXLarge, 60), // 3.75rem (60pt)
(.sevenXLarge, 72), // 4.5rem (72pt)
(.eightXLarge, 96), // 6rem (96pt)
(.nineXLarge, 128) // 8rem (128pt)
(.extraSmall, 12), // 0.75rem
(.small, 14), // 0.875rem
(.base, 16), // 1rem
(.large, 18), // 1.125rem
(.extraLarge, 20), // 1.25rem
(.extraExtraLarge, 24), // 1.5rem
(.extraExtraExtraLarge, 30), // 1.875rem
(.fourXLarge, 36), // 2.25rem
(.fiveXLarge, 48), // 3rem
(.sixXLarge, 60), // 3.75rem
(.sevenXLarge, 72), // 4.5rem
(.eightXLarge, 96), // 6rem
(.nineXLarge, 128) // 8rem
]
// Find the closest matching font size
let closestFontSize = fontSizeMapping.min { abs($0.ptSize - ptSize) < abs($1.ptSize - ptSize) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ extension View {

/// Set the font weight to the closest equivalent Tailwind CSS font weight.
///
/// - Parameter weight: A font weight value. If the weight is exactly between
/// - Parameter weight: A font weight value. The closest Tailwind font
/// weight class that matches this weight will be used. If the weight is exactly between
/// two weight classes, then the lighter weight will be used.
///
/// - SeeAlso: Tailwind CSS' [`font-weight`](https://tailwindcss.com/docs/font-weight) documentation.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/// Constants that specify the visual alignment of the text.
@available(iOS 17.0, macOS 14.0, *)
public enum TextAlignment: String {
/// Aligns text to the left edge of the text container.
case left = "left"

/// Aligns text to the left edge of the text container in
/// left-to-right (LTR) languages, and to the right edge
/// in right-to-left (RTL) languages..
Expand All @@ -9,6 +12,9 @@ public enum TextAlignment: String {
/// Aligns text to the center of the text container.
case center

/// Aligns text to the right edge of the text container.
case right = "right"

/// Aligns text to the right edge of the text container in
/// left-to-right (LTR) languages, and to the left edge
/// in right-to-left (RTL) languages..
Expand Down
3 changes: 2 additions & 1 deletion Tests/SlipstreamTests/Sites/CatalogSiteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ private struct CatalogSite: View {
H5("Heading 5")
H6("Heading 6")
}
.padding(.horizontal, 48)
}
.id("root")
}
Expand All @@ -53,7 +54,7 @@ struct CatalogSiteTests {
<link rel="stylesheet" href="/css/bootstrap.css" />
</head>
<body id="root">
<div class="container">
<div class="container px-12">
Hello
<br />world!
<h1 class="text-xl font-bold text-start">Heading 1</h1>
Expand Down
32 changes: 32 additions & 0 deletions Tests/SlipstreamTests/TailwindCSS/PaddingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Testing
import Slipstream

struct PaddingTests {
@Test func paddingEdges() throws {
try #expect(renderHTML(Div {}.padding(.all, 16)) == #"<div class="p-4"></div>"#)
try #expect(renderHTML(Div {}.padding(.horizontal, 8)) == #"<div class="px-2"></div>"#)
try #expect(renderHTML(Div {}.padding(.vertical, 12)) == #"<div class="py-3"></div>"#)
try #expect(renderHTML(Div {}.padding([.top, .left], 24)) == #"<div class="pl-6 pt-6"></div>"#)

try #expect(renderHTML(Div {}.padding(.top, 0)) == #"<div class="pt-0"></div>"#)
try #expect(renderHTML(Div {}.padding(.left, 4)) == #"<div class="pl-1"></div>"#)
try #expect(renderHTML(Div {}.padding(.bottom, 32)) == #"<div class="pb-8"></div>"#)
try #expect(renderHTML(Div {}.padding(.right, 64)) == #"<div class="pr-16"></div>"#)
}

@Test func specificPaddingSizes() throws {
try #expect(renderHTML(Div {}.padding(.top, 0)) == #"<div class="pt-0"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 0.5)) == #"<div class="pt-0"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 1)) == #"<div class="pt-0"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 2)) == #"<div class="pt-0.5"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 3)) == #"<div class="pt-0.5"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 4)) == #"<div class="pt-1"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 32)) == #"<div class="pt-8"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 64)) == #"<div class="pt-16"></div>"#)
}

@Test func repeatedPaddingModifications() throws {
try #expect(renderHTML(Div {}.padding(.top, 0).padding(.right, 4)) == #"<div class="pt-0 pr-1"></div>"#)
try #expect(renderHTML(Div {}.padding(.top, 0).padding(.top, 4)) == #"<div class="pt-0 pt-1"></div>"#)
}
}
2 changes: 2 additions & 0 deletions Tests/SlipstreamTests/TailwindCSS/TextAlignmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import Slipstream

struct TextAlignmentTests {
@Test func alignments() throws {
try #expect(renderHTML(Div {}.textAlignment(.left)) == #"<div class="text-left"></div>"#)
try #expect(renderHTML(Div {}.textAlignment(.leading)) == #"<div class="text-start"></div>"#)
try #expect(renderHTML(Div {}.textAlignment(.center)) == #"<div class="text-center"></div>"#)
try #expect(renderHTML(Div {}.textAlignment(.right)) == #"<div class="text-right"></div>"#)
try #expect(renderHTML(Div {}.textAlignment(.trailing)) == #"<div class="text-end"></div>"#)
try #expect(renderHTML(Div {}.textAlignment(.justify)) == #"<div class="text-justify"></div>"#)
}
Expand Down

0 comments on commit 76a833c

Please sign in to comment.