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

Update inline label design #653

Merged
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
Binary file modified Snapshots/iPad/InputContentTests/testInputContent.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPad/InputContentTests/testInputContent.2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPad/InputFieldTests/testInputFields.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPad/SelectTests/testSelects.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPhone/InputContentTests/testInputContent.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPhone/InputContentTests/testInputContent.2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPhone/InputFieldTests/testInputFields.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Snapshots/iPhone/SelectTests/testSelects.1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 8 additions & 27 deletions Sources/Orbit/Components/InputField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import UIKit
/// - Important: Component expands horizontally unless prevented by `fixedSize` modifier.
public struct InputField<Prefix: View, Suffix: View>: View, TextFieldBuildable {

@Environment(\.sizeCategory) private var sizeCategory
@Environment(\.inputFieldBeginEditingAction) private var inputFieldBeginEditingAction
@Environment(\.inputFieldEndEditingAction) private var inputFieldEndEditingAction
@Environment(\.isEnabled) private var isEnabled
@Environment(\.sizeCategory) private var sizeCategory

@State private var isFocused: Bool = false
@State private var isSecureTextRedacted: Bool = true
Expand Down Expand Up @@ -40,20 +40,18 @@ public struct InputField<Prefix: View, Suffix: View>: View, TextFieldBuildable {

public var body: some View {
FieldWrapper(
fieldLabel,
defaultLabel,
message: message,
messageHeight: $messageHeight
) {
InputContent(
state: state,
label: compactLabel,
message: message,
isFocused: isFocused,
isPlaceholder: value.isEmpty
) {
HStack(alignment: .firstTextBaseline, spacing: .small) {
compactLabel
textField
}
textField
} prefix: {
prefix
.accessibility(.inputFieldPrefix)
Expand All @@ -76,21 +74,14 @@ public struct InputField<Prefix: View, Suffix: View>: View, TextFieldBuildable {
}
}

@ViewBuilder private var compactLabel: some View {
FieldLabel(compactFieldLabel)
.textColor(value.isEmpty ? .inkDark : .inkLight)
.padding(.leading, prefix.isEmpty ? .small : 0)
}

@ViewBuilder private var textField: some View {
TextField(
value: $value,
prompt: prompt,
isSecureTextEntry: isSecure && isSecureTextRedacted,
font: .orbit(size: Text.Size.normal.value * sizeCategory.ratio, weight: .regular),
state: state,
leadingPadding: textFieldLeadingPadding,
trailingPadding: textFieldTrailingPadding
leadingPadding: .small,
trailingPadding: .small
)
.returnKeyType(returnKeyType)
.autocorrectionDisabled(isAutocorrectionDisabled)
Expand All @@ -117,30 +108,20 @@ public struct InputField<Prefix: View, Suffix: View>: View, TextFieldBuildable {
}
}

private var fieldLabel: String {
private var defaultLabel: String {
switch labelStyle {
case .default: return label
case .compact: return ""
}
}

private var compactFieldLabel: String {
private var compactLabel: String {
switch labelStyle {
case .default: return ""
case .compact: return label
}
}

private var textFieldLeadingPadding: CGFloat {
prefix.isEmpty && labelStyle == .default
? .small
: 0
}

private var textFieldTrailingPadding: CGFloat {
suffix.isEmpty ? .small : 0
}

private var showSecureTextRedactedButton: Bool {
isSecure && value.description.isEmpty == false && isEnabled
}
Expand Down
40 changes: 12 additions & 28 deletions Sources/Orbit/Components/Select.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public struct Select<Prefix: View, Suffix: View>: View {

public let verticalTextPadding: CGFloat = .small // = 44 @ normal text size

@Environment(\.isEnabled) private var isEnabled: Bool
@Environment(\.isEnabled) private var isEnabled
@Environment(\.isHapticsEnabled) private var isHapticsEnabled

@Binding private var messageHeight: CGFloat
Expand All @@ -25,7 +25,7 @@ public struct Select<Prefix: View, Suffix: View>: View {

public var body: some View {
FieldWrapper(
fieldLabel,
defaultLabel,
message: message,
messageHeight: $messageHeight
) {
Expand All @@ -38,22 +38,17 @@ public struct Select<Prefix: View, Suffix: View>: View {
action()
},
label: {
HStack(alignment: .firstTextBaseline, spacing: .small) {
FieldLabel(compactFieldLabel)
.textColor(value == nil ? .inkDark : .inkLight)

Text(inputLabel)
.textColor(textColor)
.accessibility(.selectValue)
}
.padding(.vertical, verticalTextPadding)
.padding(.leading, leadingPadding)
.padding(.trailing, trailingPadding)
Text(value ?? prompt)
.textColor(valueColor)
.accessibility(.selectValue)
.padding(.horizontal, .small)
.padding(.vertical, verticalTextPadding)
}
)
.buttonStyle(
InputContentButtonStyle(
state: state,
label: compactLabel,
message: message,
isPlaceholder: value == nil
) {
Expand All @@ -72,25 +67,21 @@ public struct Select<Prefix: View, Suffix: View>: View {
.accessibility(addTraits: .isButton)
}

private var fieldLabel: String {
private var defaultLabel: String {
switch labelStyle {
case .default: return label
case .compact: return ""
}
}

private var compactFieldLabel: String {
private var compactLabel: String {
switch labelStyle {
case .default: return ""
case .compact: return label
}
}

private var inputLabel: String {
value ?? prompt
}

private var textColor: Color {
private var valueColor: Color {
if isEnabled {
return value == nil
? state.placeholderColor
Expand All @@ -103,14 +94,6 @@ public struct Select<Prefix: View, Suffix: View>: View {
private var messageDescription: String {
message?.description ?? ""
}

private var leadingPadding: CGFloat {
prefix.isEmpty ? .small : 0
}

private var trailingPadding: CGFloat {
suffix.isEmpty ? .small : 0
}
}

// MARK: - Inits
Expand Down Expand Up @@ -143,6 +126,7 @@ public extension Select {
Icon(prefix)
} suffix: {
Icon(suffix)
.iconColor(.inkDark)
}
}

Expand Down
69 changes: 65 additions & 4 deletions Sources/Orbit/Support/Forms/InputContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import SwiftUI
/// Content for inputs that share common layout with a prefix and suffix.
public struct InputContent<Content: View, Prefix: View, Suffix: View>: View {

@Environment(\.iconColor) private var iconColor
@Environment(\.idealSize) private var idealSize
@Environment(\.isEnabled) private var isEnabled
@Environment(\.textColor) private var textColor

public let verticalPadding: CGFloat = .small // = 44 @ normal text size

private let state: InputState
private let label: String
private let message: Message?
private let isPressed: Bool
private let isFocused: Bool
Expand All @@ -21,18 +23,27 @@ public struct InputContent<Content: View, Prefix: View, Suffix: View>: View {
public var body: some View {
HStack(spacing: 0) {
prefix
.iconColor(prefixIconColor)
.textColor(labelColor)
.padding(.leading, .small)
.padding(.trailing, .xSmall)
.padding(.trailing, -.xxSmall)
.padding(.vertical, verticalPadding)

content
HStack(alignment: .firstTextBaseline, spacing: 0) {
Text(label)
.padding(.leading, .small)
.padding(.trailing, -.xxSmall)
.textColor(labelColor)

content
}

if idealSize.horizontal != true {
Spacer(minLength: 0)
}

suffix
.padding(.leading, .xSmall)
.padding(.leading, -.xxSmall)
.padding(.trailing, .small)
.padding(.vertical, verticalPadding)

Expand Down Expand Up @@ -64,6 +75,18 @@ public struct InputContent<Content: View, Prefix: View, Suffix: View>: View {
? textColor ?? state.textColor
: .cloudDarkActive
}

private var labelColor: Color {
isEnabled
? (textColor ?? .inkNormal)
: .cloudDarkActive
}

private var prefixIconColor: Color? {
isEnabled
? iconColor ?? textColor ?? (label.isEmpty ? .inkDark : .inkNormal)
: .cloudDarkActive
}

private func backgroundColor(isPressed: Bool) -> Color {
if isEnabled == false {
Expand Down Expand Up @@ -101,6 +124,7 @@ public struct InputContent<Content: View, Prefix: View, Suffix: View>: View {

public init(
state: InputState = .default,
label: String = "",
message: Message? = nil,
isPressed: Bool = false,
isFocused: Bool = false,
Expand All @@ -110,6 +134,7 @@ public struct InputContent<Content: View, Prefix: View, Suffix: View>: View {
@ViewBuilder suffix: () -> Suffix = { EmptyView() }
) {
self.state = state
self.label = label
self.message = message
self.isPressed = isPressed
self.isFocused = isFocused
Expand All @@ -134,6 +159,39 @@ struct InputContentPreviews: PreviewProvider {

static var standalone: some View {
VStack(spacing: .medium) {
InputContent(label: "Label") {
TextField(value: .constant("Value"))
.padding(.horizontal, .small)
} prefix: {
Icon(.visa)
} suffix: {
Icon(.checkCircle)
}

InputContent(isPressed: true) {
EmptyView()
} prefix: {
Icon(.visa)
} suffix: {
Icon(.checkCircle)
}

InputContent(label: "Label") {
Text("Value")
.padding(.horizontal, .small)
} suffix: {
Icon(.checkCircle)
}

InputContent(label: "Label") {
EmptyView()
} prefix: {
Icon(.visa)
} suffix: {
Icon(.checkCircle)
}
.disabled(true)

InputContent(isPressed: true) {
EmptyView()
} prefix: {
Expand Down Expand Up @@ -189,10 +247,11 @@ struct InputContentPreviews: PreviewProvider {
}

static var sizing: some View {
VStack {
VStack(spacing: .medium) {
Group {
InputContent {
Text("InputContent")
.padding(.horizontal, .small)
} prefix: {
Icon(.visa)
} suffix: {
Expand All @@ -201,6 +260,7 @@ struct InputContentPreviews: PreviewProvider {

InputContent {
Text("InputContent")
.padding(.horizontal, .small)
} prefix: {
Icon(.visa)
} suffix: {
Expand Down Expand Up @@ -241,6 +301,7 @@ struct InputContentPreviews: PreviewProvider {
static var custom: some View {
InputContent(message: .error("", icon: .alertCircle)) {
contentPlaceholder
.padding(.horizontal, .small)
} prefix: {
Icon(.visa)
} suffix: {
Expand Down
7 changes: 6 additions & 1 deletion Sources/Orbit/Support/Forms/InputContentButtonStyle.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import SwiftUI

/// Button-like appearance for inputs that share common layout with a prefix and suffix.
/// Orbit Button-like appearance for inputs that share common layout with a prefix and suffix.
/// Solves the touch-down, touch-up animations that would otherwise need gesture avoidance logic.
public struct InputContentButtonStyle<Prefix: View, Suffix: View>: ButtonStyle {

private let state: InputState
private let label: String
private let message: Message?
private let isFocused: Bool
private let isPlaceholder: Bool
Expand All @@ -14,6 +15,7 @@ public struct InputContentButtonStyle<Prefix: View, Suffix: View>: ButtonStyle {
public func makeBody(configuration: Configuration) -> some View {
InputContent(
state: state,
label: label,
message: message,
isPressed: configuration.isPressed,
isFocused: isFocused,
Expand All @@ -27,15 +29,18 @@ public struct InputContentButtonStyle<Prefix: View, Suffix: View>: ButtonStyle {
}
}

/// Creates Orbit Button-like appearance for inputs that share common layout with a prefix and suffix.
public init(
state: InputState = .default,
label: String = "",
message: Message? = nil,
isFocused: Bool = false,
isPlaceholder: Bool = false,
@ViewBuilder prefix: () -> Prefix = { EmptyView() },
@ViewBuilder suffix: () -> Suffix = { EmptyView() }
) {
self.state = state
self.label = label
self.message = message
self.isFocused = isFocused
self.isPlaceholder = isPlaceholder
Expand Down
2 changes: 1 addition & 1 deletion Sources/Orbit/Support/Layout/IsEmpty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ extension View {

var isEmpty: Bool {
switch self {
case let view as PotentiallyEmptyView: return view.isEmpty
case is EmptyView: return true
case let view as PotentiallyEmptyView: return view.isEmpty
default: return false
}
}
Expand Down
Loading
Loading