Skip to content

Commit

Permalink
[feature] Add types for buttons and text fields. (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
jverkoey authored Aug 13, 2024
1 parent c603eff commit f9cd18e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``Button``

## Topics

### Supporting types

- ``ButtonType``
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``TextField``

## Topics

### Supporting types

- ``TextFieldInputType``
25 changes: 21 additions & 4 deletions Sources/Slipstream/W3C/Elements/Forms/Button.swift
Original file line number Diff line number Diff line change
@@ -1,49 +1,66 @@
import SwiftSoup

/// Constants that control the behavior of a ``Button`` when it is activated.
///
/// - SeeAlso: W3C [button type](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type) specification.
@available(iOS 17.0, macOS 14.0, *)
public enum ButtonType: String {
case submit
case reset
}

/// A control that initiates an action.
///
/// - SeeAlso: W3C [button](https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element) specification.
@available(iOS 17.0, macOS 14.0, *)
public struct Button<Label>: View where Label: View {
/// Creates a button that displays a custom label and executes a custom action
/// when clicked.
public init(action: String, @ViewBuilder label: @escaping () -> Label) {
public init(action: String, type: ButtonType? = nil, @ViewBuilder label: @escaping () -> Label) {
self.label = label
self.action = action
self.type = type
}

/// Creates a button that displays a custom label.
public init(@ViewBuilder label: @escaping () -> Label) {
public init(type: ButtonType? = nil, @ViewBuilder label: @escaping () -> Label) {
self.label = label
self.action = nil
self.type = type
}

/// Creates a button that generates its label from a string.
public init(_ text: String) where Label == DOMString {
public init(_ text: String, type: ButtonType? = nil) where Label == DOMString {
self.label = {
DOMString(text)
}
self.action = nil
self.type = type
}

/// Creates a button that generates its label from a string and executes a custom
/// action when clicked.
public init(_ text: String, action: String) where Label == DOMString {
public init(_ text: String, action: String, type: ButtonType? = nil) where Label == DOMString {
self.label = {
DOMString(text)
}
self.action = action
self.type = type
}

@ViewBuilder private let label: () -> Label
private let action: String?
private let type: ButtonType?

@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {
let element = try container.appendElement("button")
if let action {
try element.attr("onclick", action)
}
if let type {
try element.attr("type", type.rawValue)
}
try self.label().render(element, environment: environment)
}
}
91 changes: 89 additions & 2 deletions Sources/Slipstream/W3C/Elements/Forms/TextField.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
import SwiftSoup

/// Constants that control the content type of a ``TextField``.
///
/// - SeeAlso: W3C [input type](https://html.spec.whatwg.org/multipage/input.html#attr-input-type) specification.
@available(iOS 17.0, macOS 14.0, *)
public enum TextFieldInputType: String {
/// Represents a one line plain text edit control.
///
/// This is the default input type if no type is provided.
///
/// - SeeAlso: W3C [input type="text"](https://html.spec.whatwg.org/multipage/input.html#text-(type=text)-state-and-search-state-(type=search)) specification.
case text

/// Similar to ``text``
///
/// Note from the W3C spec: The difference between the ``text`` state and the
/// ``search`` state is primarily stylistic: on platforms where search controls
/// are distinguished from regular text controls, the ``search`` state might
/// result in an appearance consistent with the platform's search controls
/// rather than appearing like a regular text control.
///
/// - SeeAlso: W3C [input type="search"](https://html.spec.whatwg.org/multipage/input.html#text-(type=text)-state-and-search-state-(type=search)) specification.
case search

/// Represents a control for editing a telephone number.
///
/// - SeeAlso: W3C [input type="tel"](https://html.spec.whatwg.org/multipage/input.html#telephone-state-(type=tel)) specification.
case telephone = "tel"

/// Represents a control for editing a single absolute URL.
///
/// - SeeAlso: W3C [input type="url"](https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url)) specification.
case url

/// Represents a control for editing an email.
case email
///
/// - SeeAlso: W3C [input type="email"](https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)) specification.

/// Represents a one line plain text edit control.
///
/// The browser will typically obscure the value so that people other than
/// the user cannot see it.
///
/// - SeeAlso: W3C [input type="password"](https://html.spec.whatwg.org/multipage/input.html#password-state-(type=password)) specification.
case password

/// Represents a control for setting the view''s value to a string
/// representing a specific date.
///
/// - SeeAlso: W3C [input type="date"](https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date)) specification.
case date

/// Represents a control for setting the view''s value to a string
/// representing a specific month.
///
/// - SeeAlso: W3C [input type="month"](https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month)) specification.
case month

/// Represents a control for setting the view''s value to a string
/// representing a specific week.
///
/// - SeeAlso: W3C [input type="week"](https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week)) specification.
case week

/// Represents a control for setting the view''s value to a string
/// representing a specific time.
///
/// - SeeAlso: W3C [input type="time"](https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time)) specification.
case time

/// Represents a control for setting the view''s value to a string
/// representing a local date and time, with no time-zone offset
/// information.
///
/// - SeeAlso: W3C [input type="datetime-local"](https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local)) specification.
case localDateAndTime = "datetime-local"

/// Represents a control for setting the view''s value to a string
/// representing a number.
///
/// - SeeAlso: W3C [input type="number"](https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number)) specification.
case number
}

/// A control that displays an editable text interface.
///
/// - SeeAlso: W3C [input](https://html.spec.whatwg.org/multipage/input.html#the-input-element) specification.
Expand All @@ -9,18 +93,20 @@ public struct TextField: View {
///
/// - Parameters:
/// - text: The placeholder text to display in the text field when it is empty.
/// - type: The text field's content type.
/// - autoFocus: If true, indicates that the view should be focused as soon as
/// the page is loaded, allowing the user to start typing without having to
/// manually focus the main view.
public init(_ text: String, autoFocus: Bool = false) {
public init(_ text: String, type: TextFieldInputType = .text, autoFocus: Bool = false) {
self.text = text
self.type = type
self.autoFocus = autoFocus
}

@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {
let element = try container.appendElement("input")
try element.attr("type", "text")
try element.attr("type", type.rawValue)
try element.attr("placeholder", text)
if autoFocus {
try element.attr("autofocus", "")
Expand All @@ -29,4 +115,5 @@ public struct TextField: View {

private let text: String
private let autoFocus: Bool
private let type: TextFieldInputType
}
5 changes: 5 additions & 0 deletions Tests/SlipstreamTests/W3C/Forms/ButtonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ struct ButtonTests {
try #expect(renderHTML(Button("Tap me")) == #"<button>Tap me</button>"#)
}

@Test func withType() throws {
try #expect(renderHTML(Button("Tap me", type: .submit)) == #"<button type="submit">Tap me</button>"#)
try #expect(renderHTML(Button("Tap me", type: .reset)) == #"<button type="reset">Tap me</button>"#)
}

@Test func withAction() throws {
try #expect(renderHTML(Button(action: "alert('hello world')") {
DOMString("Tap me")
Expand Down
15 changes: 15 additions & 0 deletions Tests/SlipstreamTests/W3C/Forms/TextFieldTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,19 @@ struct TextFieldTests {
@Test func autoFocus() throws {
try #expect(renderHTML(TextField("Placeholder", autoFocus: true)) == #"<input type="text" placeholder="Placeholder" autofocus />"#)
}

@Test func withType() throws {
try #expect(renderHTML(TextField("Placeholder", type: .text)) == #"<input type="text" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .search)) == #"<input type="search" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .telephone)) == #"<input type="tel" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .url)) == #"<input type="url" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .email)) == #"<input type="email" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .password)) == #"<input type="password" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .date)) == #"<input type="date" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .month)) == #"<input type="month" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .week)) == #"<input type="week" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .time)) == #"<input type="time" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .localDateAndTime)) == #"<input type="datetime-local" placeholder="Placeholder" />"#)
try #expect(renderHTML(TextField("Placeholder", type: .number)) == #"<input type="number" placeholder="Placeholder" />"#)
}
}

0 comments on commit f9cd18e

Please sign in to comment.