Skip to content
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ let package = Package(
]
),
.target(
name: "SlotsExamples",
name: "SlotExamples",
dependencies: ["Slots"]
),
.testTarget(
Expand Down
18 changes: 18 additions & 0 deletions Sources/SlotExamples/ActionButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Slots
import SwiftUI

@Slots public struct ActionButton<Label: View>: View {
var action: () -> Void
@Slot(.text, .unlabeled) var label: Label

public var body: some View {
Button(action: action) {
label
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(Capsule().fill(.tint))
.foregroundStyle(.white)
}
.buttonStyle(.plain)
}
}
41 changes: 41 additions & 0 deletions Sources/SlotExamples/Banner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Slots
import SwiftUI

public enum BannerStyle {
case info, warning, error
}

@Slots public struct Banner<Message: View>: View {
@Slot(.text) var message: Message
var style: BannerStyle

public var body: some View {
HStack {
image
message
Spacer()
}
.padding()
.background(backgroundColor.opacity(0.15))
.cornerRadius(8)
}

private var image: some View {
let name: String =
switch style {
case .info: "info.circle.fill"
case .warning: "exclamationmark.triangle.fill"
case .error: "xmark.octagon.fill"
}
return Image(systemName: name)
.foregroundStyle(backgroundColor)
}

private var backgroundColor: Color {
switch style {
case .info: .blue
case .warning: .orange
case .error: .red
}
}
}
26 changes: 26 additions & 0 deletions Sources/SlotExamples/Card.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Slots
import SwiftUI

@Slots public struct Card<Header: View, Media: View, Body: View, Footer: View>: View {
@Slot(.text) var header: Header
@Slot(.systemImage) var media: Media?
@Slot(.text) var body_: Body?
var footer: Footer?

public var body: some View {
VStack(alignment: .leading, spacing: 12) {
header
.font(.headline)
if let media { media }
if let body_ {
body_
.font(.body)
.foregroundStyle(.secondary)
}
if let footer { footer }
}
.padding()
.background(RoundedRectangle(cornerRadius: 12).fill(.background))
.shadow(radius: 2)
}
}
19 changes: 19 additions & 0 deletions Sources/SlotExamples/Chip.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Slots
import SwiftUI

@Slots public struct Chip<Icon: View, Label: View, Accessory: View>: View {
@Slot(.systemImage) var icon: Icon?
@Slot(.text) var label: Label
var accessory: Accessory

public var body: some View {
HStack {
if let icon { icon }
label
.font(.caption.weight(.medium))
accessory
}
.padding()
.background(Capsule().fill(.quaternary))
}
}
23 changes: 23 additions & 0 deletions Sources/SlotExamples/EmptyState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Slots
import SwiftUI

@Slots public struct EmptyState<Icon: View, Title: View, Action: View>: View {
@Slot(.systemImage) var icon: Icon?
@Slot(.text) var title: Title
var action: Action?

public var body: some View {
VStack(spacing: 16) {
if let icon {
icon
.font(.largeTitle)
.foregroundStyle(.secondary)
}
title
.font(.title3.weight(.medium))
if let action { action }
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
}
}
21 changes: 21 additions & 0 deletions Sources/SlotExamples/ListRow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Slots
import SwiftUI

@Slots public struct ListRow<Leading: View, Content: View, Trailing: View>: View {
@Slot(.systemImage) var leading: Leading?
@Slot(.text) var content: Content
@Slot(.text) var trailing: Trailing?

public var body: some View {
HStack {
if let leading { leading }
content
Spacer()
if let trailing {
trailing
.foregroundStyle(.secondary)
}
}
.padding(.vertical, 4)
}
}
58 changes: 58 additions & 0 deletions Sources/SlotExamples/Previews.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import SwiftUI

struct Examples_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
VStack(spacing: 24) {
// Chip examples
Chip(label: "Default", accessory: { EmptyView() })
Chip(iconSystemName: "star.fill", label: "Featured", accessory: { EmptyView() })

// Banner examples
Banner(message: "Sync complete.", style: .info)
Banner(message: "Storage nearly full.", style: .warning)
Banner(message: "Upload failed.", style: .error)

// ListRow examples
ListRow(content: "Wi-Fi", trailing: "Connected")
ListRow(leadingSystemName: "wifi", content: "Wi-Fi", trailing: "Connected")

// Card examples
Card(header: "Welcome Back", body_: "Pick up where you left off.")
Card(header: "Photo", mediaSystemName: "photo", body_: "A landscape shot.")

// EmptyState examples
EmptyState(iconSystemName: "tray", title: "No Messages")
EmptyState(
title: "Nothing Here",
action: {
Button("Refresh") {}
})

// TaskRow examples — uses custom resolver slot with .unlabeled
TaskRow(title: "Buy groceries", .high)
TaskRow(title: "Read article", .low)
TaskRow(title: "No priority")
TaskRow(title: "Custom badge") {
Image(systemName: "star.fill").foregroundStyle(.yellow)
}

// ActionButton examples — uses .unlabeled so no label: prefix
ActionButton("Save", action: {})
ActionButton(action: {}) { Text("Custom Label").bold() }

// ToolbarRow examples
ToolbarRow(title: "Inbox")
ToolbarRow(
title: "Details",
leading: {
Button(action: {}) { Image(systemName: "chevron.left") }
},
trailing: {
Button(action: {}) { Image(systemName: "ellipsis") }
})
}
.padding()
}
}
}
56 changes: 56 additions & 0 deletions Sources/SlotExamples/ResolverExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Slots
import SwiftUI

// MARK: - Custom type and view

public enum Priority: String, Sendable {
case low, medium, high
}

public struct PriorityBadge: View {
let priority: Priority

public var body: some View {
Text(priority.rawValue.capitalized)
.font(.caption2.weight(.semibold))
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(color.opacity(0.15))
.foregroundStyle(color)
.clipShape(Capsule())
}

private var color: Color {
switch priority {
case .low: .green
case .medium: .orange
case .high: .red
}
}
}

// MARK: - Resolver: maps Priority → PriorityBadge for use as a slot

public enum PriorityResolver: SlotResolver {
public typealias Input = Priority
public typealias Output = PriorityBadge
public static func resolve(_ input: Priority) -> PriorityBadge {
PriorityBadge(priority: input)
}
}

// MARK: - Component using the resolver

@Slots public struct TaskRow<Title: View, Badge: View>: View {
@Slot(.text) var title: Title
@Slot(PriorityResolver.self, .unlabeled) var badge: Badge?

public var body: some View {
HStack {
title
Spacer()
if let badge { badge }
}
.padding(.vertical, 4)
}
}
20 changes: 20 additions & 0 deletions Sources/SlotExamples/ToolbarRow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Slots
import SwiftUI

@Slots public struct ToolbarRow<Leading: View, Title: View, Trailing: View>: View {
var leading: Leading?
@Slot(.text) var title: Title
var trailing: Trailing?

public var body: some View {
HStack {
if let leading { leading }
Spacer()
title.font(.headline)
Spacer()
if let trailing { trailing }
}
.padding(.horizontal)
.padding(.vertical, 8)
}
}
Loading
Loading