Skip to content

Commit

Permalink
Add button types and localize components
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsaidi committed Apr 30, 2024
1 parent 377da61 commit 88c910c
Show file tree
Hide file tree
Showing 27 changed files with 733 additions and 114 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PackageDescription

let package = Package(
name: "SwiftUIKit",
defaultLocalization: "en",
platforms: [
.iOS(.v15),
.tvOS(.v15),
Expand All @@ -21,7 +22,8 @@ let package = Package(
targets: [
.target(
name: "SwiftUIKit",
dependencies: []
dependencies: [],
resources: [.process("Resources")]
),
.testTarget(
name: "SwiftUIKitTests",
Expand Down
13 changes: 13 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ SwiftUIKit makes its best effort to honor semver, but breaking changes can occur



## 4.3

This version adds localization support, and makes more types use `LocalizedStringKey` instead of `String`.

This version also adds standard button types, which makes it easier to create standard button types.

### ✨ New features

* `Button+Init` adds a new button initializer.
* `Button+Standard` adds new standard button types.



## 4.2.3

### 💡 Behavior changes
Expand Down
34 changes: 34 additions & 0 deletions Sources/SwiftUIKit/Buttons/Button+Init.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Button+Init.swift
// SwiftUIKit
//
// Created by Daniel Saidi on 2024-04-30.
// Copyright © 2024 Daniel Saidi. All rights reserved.
//

import SwiftUI

public extension Button where Label == SwiftUI.Label<Text, Image> {

/// This initializer lets you use buttons with less code.
init(
_ text: LocalizedStringKey,
_ icon: Image,
_ bundle: Bundle = .main,
action: @escaping () -> Void
) {
self.init(action: action) {
Label(
title: { Text(text, bundle: bundle) },
icon: { icon }
)
}
}
}

#Preview {

Button("Preview.Button", .symbol("checkmark"), .module) {
print("Tapped!")
}
}
126 changes: 126 additions & 0 deletions Sources/SwiftUIKit/Buttons/Button+Standard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// Button+Standard.swift
// SwiftUIKit
//
// Created by Daniel Saidi on 2024-04-30.
// Copyright © 2024 Daniel Saidi. All rights reserved.
//

import SwiftUI

public extension Button {

/// This is a standard add button.
init(
_ type: StandardType,
_ title: LocalizedStringKey? = nil,
_ icon: Image? = nil,
bundle: Bundle? = nil,
action: @escaping () -> Void
) where Label == SwiftUI.Label<Text, Image?> {
self.init(role: type.role, action: action) {
Label(
title: { Text(title ?? type.title, bundle: title == nil ? .module : bundle) },
icon: { icon ?? type.image }
)
}
}

/// This enum defines standard button types and provides
/// standard localized texts and icons.
enum StandardType: String, CaseIterable, Identifiable {
case add, addFavorite, addToFavorites,
cancel, call, copy,
delete, deselect, done,
edit, email,
ok,
paste,
removeFavorite, removeFromFavorites,
select, share
}
}

public extension Button.StandardType {

var id: String{ rawValue }

var image: Image? {
guard let imageName else { return nil }
return .symbol(imageName)
}

var imageName: String? {
switch self {
case .add: "plus"
case .addFavorite: "star.circle"
case .addToFavorites: "star.circle"
case .cancel: "xmark"
case .call: "phone"
case .copy: "doc.on.doc"
case .delete: "trash"
case .deselect: "checkmark.circle.fill"
case .done: "checkmark"
case .edit: "pencil"
case .email: "envelope"
case .ok: "checkmark"
case .paste: "clipboard"
case .removeFavorite: "star.circle.fill"
case .removeFromFavorites: "star.circle.fill"
case .select: "checkmark.circle"
case .share: "square.and.arrow.up"
}
}

var role: ButtonRole? {
switch self {
case .cancel: .cancel
case .delete: .destructive
default: nil
}
}

var title: LocalizedStringKey {
switch self {
case .add: "Button.Add"
case .addFavorite: "Button.AddFavorite"
case .addToFavorites: "Button.AddToFavorites"
case .call: "Button.Call"
case .cancel: "Button.Cancel"
case .copy: "Button.Copy"
case .deselect: "Button.Deselect"
case .edit: "Button.Edit"
case .email: "Button.Email"
case .delete: "Button.Delete"
case .done: "Button.Done"
case .ok: "Button.OK"
case .paste: "Button.Paste"
case .removeFavorite: "Button.RemoveFavorite"
case .removeFromFavorites: "Button.RemoveFromFavorites"
case .select: "Button.Select"
case .share: "Button.Share"
}
}
}

#Preview {

@ViewBuilder
func buttons() -> some View {
Section {
ForEach(Button.StandardType.allCases) {
Button($0) {}
}
}
}

return List {
buttons()
buttons().labelStyle(.titleOnly)
buttons().labelStyle(.iconOnly)
}
.toolbar {
ToolbarItemGroup {
buttons()
}
}
}
13 changes: 11 additions & 2 deletions Sources/SwiftUIKit/Extensions/Label+Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ public extension Label where Icon == Image, Title == Text {
}

/// Create a label with a string and a plain image icon.
init(_ text: LocalizedStringKey, _ image: Image) {
init(
_ text: LocalizedStringKey,
_ image: Image,
_ bundle: Bundle? = nil
) {
self.init {
Text(text)
Text(text, bundle: bundle)
} icon: {
image
}
}
}

#Preview {

Label("Preview.Label", .symbol("checkmark"), .module)
}
4 changes: 2 additions & 2 deletions Sources/SwiftUIKit/Extensions/View+Label.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ public extension View {

VStack {
Color.red
.label("Red")
.label("Preview.Label", bundle: .module)
Image.symbol("checkmark")
.label("Test")
.label("Preview.Label", bundle: .module)
}
.frame(width: 200, height: 100)
}
6 changes: 3 additions & 3 deletions Sources/SwiftUIKit/Extensions/View+Prefers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public extension View {

VStack {
#if os(iOS) || os(macOS)
Menu("Test") {
Button("1") {}
Button("2") {}
Menu("Preview.Menu") {
Button("Preview.Button.\(1)") {}
Button("Preview.Button.\(2)") {}
}
.prefersMenuOrderFixed()
#endif
Expand Down
68 changes: 23 additions & 45 deletions Sources/SwiftUIKit/Lists/ListActionRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,94 +23,72 @@ public struct ListActionRow: View {
/// - title: The row title.
/// - text: The row text.
/// - action: The ``ListAction`` to use.
/// - hideIfEmpty: Whether or not to hide the view if the text is empty, by default `false`.
/// - trailingView: An optional trailing view to apply to the view.
public init(
title: String,
text: String,
action: ListAction?,
hideIfEmpty: Bool = false
title: LocalizedStringKey,
text: LocalizedStringKey,
bundle: Bundle? = nil,
action: ListAction?
) {
self.title = title
self.text = text
self.bundle = bundle
self.action = action
self.hideIfEmpty = hideIfEmpty
}

private let text: String
private let title: String
private let hideIfEmpty: Bool
private let title: LocalizedStringKey
private let text: LocalizedStringKey
private let bundle: Bundle?
private let action: ListAction?

public var body: some View {
if hasEmptyText && hideIfEmpty {
EmptyView()
} else {
stack
}
}
}

private extension ListActionRow {

var hasEmptyText: Bool {
text.trimmingCharacters(in: .whitespaces)
.isEmpty
}

var stack: some View {
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 5) {
Text(title)
Text(title, bundle: bundle)
.lineLimit(1)
.font(.footnote)
.foregroundColor(.secondary)
Text(text)
Text(text, bundle: bundle)
}

if let action {
Spacer()
action.button
}
}
.padding(.vertical, 3)
}
}

#Preview {

List {
ListActionRow(
title: "Title 1",
text: "Text 1",
title: "Preview.Title.\(1)",
text: "Preview.Text.\(1)",
bundle: .module,
action: .call(phoneNumber: "1234")
)

ListActionRow(
title: "Title 2",
text: "Text 2",
title: "Preview.Title.\(2)",
text: "Preview.Text.\(2)",
bundle: .module,
action: .copy("")
)
.buttonStyle(.borderedProminent)

ListActionRow(
title: "Title 3",
text: "Long\nmultuline\ntext that could have been entered in a text editor.",
title: "Preview.Title.\(3)",
text: "Preview.Text.Long",
bundle: .module,
action: .email(address: "")
)

ListActionRow(
title: "Title 4",
text: "",
action: nil,
hideIfEmpty: true
)

ListActionRow(
title: "Title 5",
text: "",
action: nil,
hideIfEmpty: false
title: "Preview.Title.\(4)",
text: "Preview.Text.\(4)",
bundle: .module,
action: nil
)
}
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/SwiftUIKit/Lists/ListButtonGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,18 @@ public struct ListButtonGroup<Content: View>: View {

ListButtonGroup {
HStack {
"Report Bug".previewButton(.bug)
"Bug".previewButton(.bug)
"Camera".previewButton(.camera).disabled(true)
"Photos".previewButton(.camera).opacity(0.5)
"Feedback".previewButton(.feedback)
}
}

Section {
Text("Row")
Text("Row")
Text("Row")
Text("Row")
Text("Preview.Row")
Text("Preview.Row")
Text("Preview.Row")
Text("Preview.Row")
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftUIKit/Lists/ListButtonStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public extension ButtonStyle where Self == ListButtonStyle {
var body: some View {
List {
ForEach(0...100, id: \.self) { index in
Button("Button \(index)") {
Button("Preview.Button.\(index)") {
overlayText = "\(index) tapped!"
}
.buttonStyle(index == 0 ? .list : .list(pressedOpacity: 0.1))
Expand Down
Loading

0 comments on commit 88c910c

Please sign in to comment.