diff --git a/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/Padding.md b/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/Padding.md new file mode 100644 index 0000000..6757a58 --- /dev/null +++ b/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/Padding.md @@ -0,0 +1,11 @@ +# Padding + +## Topics + +### Modifiers + +- ``View/padding(_:_:)`` + +### Enumerations + +- ``Edge`` diff --git a/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/TailwindCSSClasses.md b/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/TailwindCSSClasses.md index 3975fa7..1e72245 100644 --- a/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/TailwindCSSClasses.md +++ b/Sources/Slipstream/Documentation.docc/Views/TailwindCSS/TailwindCSSClasses.md @@ -8,6 +8,10 @@ Slipstream implementations of Tailwind CSS's classes. - ``Container`` +### Spacing + +- + ### Typography - diff --git a/Sources/Slipstream/TailwindCSS/Edge.swift b/Sources/Slipstream/TailwindCSS/Edge.swift new file mode 100644 index 0000000..b2b6cb5 --- /dev/null +++ b/Sources/Slipstream/TailwindCSS/Edge.swift @@ -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] + } +} diff --git a/Sources/Slipstream/TailwindCSS/Spacing/View+padding.swift b/Sources/Slipstream/TailwindCSS/Spacing/View+padding.swift new file mode 100644 index 0000000..dce9562 --- /dev/null +++ b/Sources/Slipstream/TailwindCSS/Spacing/View+padding.swift @@ -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" + } +} diff --git a/Sources/Slipstream/TailwindCSS/Typography/View+fontSize.swift b/Sources/Slipstream/TailwindCSS/Typography/View+fontSize.swift index 57ee721..356025a 100644 --- a/Sources/Slipstream/TailwindCSS/Typography/View+fontSize.swift +++ b/Sources/Slipstream/TailwindCSS/Typography/View+fontSize.swift @@ -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. @@ -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) } diff --git a/Sources/Slipstream/TailwindCSS/Typography/View+fontWeight.swift b/Sources/Slipstream/TailwindCSS/Typography/View+fontWeight.swift index 7e97235..d93459a 100644 --- a/Sources/Slipstream/TailwindCSS/Typography/View+fontWeight.swift +++ b/Sources/Slipstream/TailwindCSS/Typography/View+fontWeight.swift @@ -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. diff --git a/Sources/Slipstream/TailwindCSS/Typography/View+textAlignment.swift b/Sources/Slipstream/TailwindCSS/Typography/View+textAlignment.swift index 43f6e36..37fd437 100644 --- a/Sources/Slipstream/TailwindCSS/Typography/View+textAlignment.swift +++ b/Sources/Slipstream/TailwindCSS/Typography/View+textAlignment.swift @@ -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.. @@ -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.. diff --git a/Tests/SlipstreamTests/Sites/CatalogSiteTests.swift b/Tests/SlipstreamTests/Sites/CatalogSiteTests.swift index 6725831..24f9e01 100644 --- a/Tests/SlipstreamTests/Sites/CatalogSiteTests.swift +++ b/Tests/SlipstreamTests/Sites/CatalogSiteTests.swift @@ -35,6 +35,7 @@ private struct CatalogSite: View { H5("Heading 5") H6("Heading 6") } + .padding(.horizontal, 48) } .id("root") } @@ -53,7 +54,7 @@ struct CatalogSiteTests { -
+
Hello
world!

Heading 1

diff --git a/Tests/SlipstreamTests/TailwindCSS/PaddingTests.swift b/Tests/SlipstreamTests/TailwindCSS/PaddingTests.swift new file mode 100644 index 0000000..056a717 --- /dev/null +++ b/Tests/SlipstreamTests/TailwindCSS/PaddingTests.swift @@ -0,0 +1,32 @@ +import Testing +import Slipstream + +struct PaddingTests { + @Test func paddingEdges() throws { + try #expect(renderHTML(Div {}.padding(.all, 16)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.horizontal, 8)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.vertical, 12)) == #"
"#) + try #expect(renderHTML(Div {}.padding([.top, .left], 24)) == #"
"#) + + try #expect(renderHTML(Div {}.padding(.top, 0)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.left, 4)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.bottom, 32)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.right, 64)) == #"
"#) + } + + @Test func specificPaddingSizes() throws { + try #expect(renderHTML(Div {}.padding(.top, 0)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 0.5)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 1)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 2)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 3)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 4)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 32)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 64)) == #"
"#) + } + + @Test func repeatedPaddingModifications() throws { + try #expect(renderHTML(Div {}.padding(.top, 0).padding(.right, 4)) == #"
"#) + try #expect(renderHTML(Div {}.padding(.top, 0).padding(.top, 4)) == #"
"#) + } +} diff --git a/Tests/SlipstreamTests/TailwindCSS/TextAlignmentTests.swift b/Tests/SlipstreamTests/TailwindCSS/TextAlignmentTests.swift index 821a844..ea512ec 100644 --- a/Tests/SlipstreamTests/TailwindCSS/TextAlignmentTests.swift +++ b/Tests/SlipstreamTests/TailwindCSS/TextAlignmentTests.swift @@ -4,8 +4,10 @@ import Slipstream struct TextAlignmentTests { @Test func alignments() throws { + try #expect(renderHTML(Div {}.textAlignment(.left)) == #"
"#) try #expect(renderHTML(Div {}.textAlignment(.leading)) == #"
"#) try #expect(renderHTML(Div {}.textAlignment(.center)) == #"
"#) + try #expect(renderHTML(Div {}.textAlignment(.right)) == #"
"#) try #expect(renderHTML(Div {}.textAlignment(.trailing)) == #"
"#) try #expect(renderHTML(Div {}.textAlignment(.justify)) == #"
"#) }