Skip to content

Commit cc60c6a

Browse files
authored
Merge pull request #25 from PureSwift/feature/catalog
Component catalog demo app
2 parents ce028e5 + 2abf648 commit cc60c6a

21 files changed

Lines changed: 620 additions & 79 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#if canImport(AndroidSwiftUI)
2+
import AndroidSwiftUI
3+
#else
4+
import SwiftUI
5+
#endif
6+
7+
/// A component catalog: one playground per supported SwiftUI feature, navigated
8+
/// with a `NavigationStack` and a lazy `List`.
9+
struct ContentView: View {
10+
11+
var body: some View {
12+
NavigationStack {
13+
List(CatalogEntry.all) { entry in
14+
NavigationLink(entry.title, destination: entry.screen)
15+
}
16+
.navigationTitle("Catalog")
17+
}
18+
}
19+
}
20+
21+
struct CatalogEntry: Identifiable {
22+
let id: String
23+
let title: String
24+
let screen: AnyCatalogScreen
25+
26+
static let all: [CatalogEntry] = [
27+
CatalogEntry(id: "text", title: "Text", screen: AnyCatalogScreen(TextPlayground())),
28+
CatalogEntry(id: "button", title: "Button", screen: AnyCatalogScreen(ButtonPlayground())),
29+
CatalogEntry(id: "toggle", title: "Toggle", screen: AnyCatalogScreen(TogglePlayground())),
30+
CatalogEntry(id: "slider", title: "Slider", screen: AnyCatalogScreen(SliderPlayground())),
31+
CatalogEntry(id: "textfield", title: "TextField", screen: AnyCatalogScreen(TextFieldPlayground())),
32+
CatalogEntry(id: "picker", title: "Picker", screen: AnyCatalogScreen(PickerPlayground())),
33+
CatalogEntry(id: "progress", title: "ProgressView", screen: AnyCatalogScreen(ProgressViewPlayground())),
34+
CatalogEntry(id: "stack", title: "Stacks", screen: AnyCatalogScreen(StackPlayground())),
35+
CatalogEntry(id: "spacer", title: "Spacer & Divider", screen: AnyCatalogScreen(SpacerDividerPlayground())),
36+
CatalogEntry(id: "color", title: "Color", screen: AnyCatalogScreen(ColorPlayground())),
37+
CatalogEntry(id: "scroll", title: "ScrollView", screen: AnyCatalogScreen(ScrollViewPlayground())),
38+
CatalogEntry(id: "list", title: "List", screen: AnyCatalogScreen(ListPlayground())),
39+
CatalogEntry(id: "grid", title: "Grid", screen: AnyCatalogScreen(GridPlayground())),
40+
CatalogEntry(id: "modifier", title: "Modifiers", screen: AnyCatalogScreen(ModifierPlayground())),
41+
CatalogEntry(id: "navigation", title: "Navigation", screen: AnyCatalogScreen(NavigationPlayground())),
42+
CatalogEntry(id: "tab", title: "TabView", screen: AnyCatalogScreen(TabViewPlayground())),
43+
CatalogEntry(id: "sheet", title: "Sheet", screen: AnyCatalogScreen(SheetPlayground())),
44+
CatalogEntry(id: "alert", title: "Alert", screen: AnyCatalogScreen(AlertPlayground())),
45+
CatalogEntry(id: "state", title: "State", screen: AnyCatalogScreen(StatePlayground())),
46+
CatalogEntry(id: "environment", title: "Environment", screen: AnyCatalogScreen(EnvironmentPlayground())),
47+
CatalogEntry(id: "observable", title: "Observable", screen: AnyCatalogScreen(ObservablePlayground())),
48+
]
49+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#if canImport(AndroidSwiftUI)
2+
import AndroidSwiftUI
3+
#else
4+
import SwiftUI
5+
#endif
6+
7+
/// Type-erased catalog screen (a small AnyView shim usable as a navigation
8+
/// destination while keeping the entry list homogeneous).
9+
struct AnyCatalogScreen: View {
10+
private let content: AnyView
11+
init<V: View>(_ view: V) { self.content = AnyView(view) }
12+
var body: some View { content }
13+
}
14+
15+
/// A titled example row shared by the playgrounds.
16+
struct Example<Content: View>: View {
17+
let title: String
18+
let content: Content
19+
init(_ title: String, @ViewBuilder content: () -> Content) {
20+
self.title = title
21+
self.content = content()
22+
}
23+
var body: some View {
24+
VStack(alignment: .leading, spacing: 8) {
25+
Text(title)
26+
content
27+
Divider()
28+
}
29+
.padding()
30+
}
31+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#if canImport(AndroidSwiftUI)
2+
import AndroidSwiftUI
3+
#else
4+
import SwiftUI
5+
#endif
6+
7+
struct ListPlayground: View {
8+
@State private var rows = (1...25).map(CatalogRow.init)
9+
var body: some View {
10+
List(rows) { row in
11+
Text(row.title)
12+
}
13+
.refreshable {
14+
try? await Task.sleep(for: .seconds(1))
15+
rows.insert(CatalogRow(id: rows.count + 1), at: 0)
16+
}
17+
}
18+
}
19+
20+
struct CatalogRow: Identifiable {
21+
let id: Int
22+
var title: String { "Row \(id)" }
23+
}
24+
25+
struct GridPlayground: View {
26+
var body: some View {
27+
ScrollView {
28+
VStack(alignment: .leading, spacing: 0) {
29+
Example("Three fixed columns") {
30+
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]) {
31+
ForEach(1...9, id: \.self) { n in
32+
Text("\(n)").padding().background(Color.blue).cornerRadius(6)
33+
}
34+
}
35+
}
36+
Example("Adaptive columns") {
37+
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
38+
ForEach(1...8, id: \.self) { n in
39+
Text("Item \(n)").padding().background(Color.orange).cornerRadius(6)
40+
}
41+
}
42+
}
43+
Example("Two rows, horizontal") {
44+
LazyHGrid(rows: [GridItem(.flexible()), GridItem(.flexible())]) {
45+
ForEach(1...8, id: \.self) { n in
46+
Text("Cell \(n)").padding().background(Color.green).cornerRadius(6)
47+
}
48+
}
49+
.frame(height: 160)
50+
}
51+
}
52+
}
53+
}
54+
}
55+
56+
struct ModifierPlayground: View {
57+
var body: some View {
58+
ScrollView {
59+
VStack(alignment: .leading, spacing: 0) {
60+
Example("Padding") { Text("Padded").padding().background(Color.blue) }
61+
Example("Frame 200x60") { Text("Fixed").frame(width: 200, height: 60).background(Color.green) }
62+
Example("Background") { Text("Colored").padding().background(Color.orange) }
63+
Example("Corner radius") { Text("Rounded").padding().background(Color.purple).cornerRadius(16) }
64+
Example("Offset") { Text("Shifted").offset(x: 30, y: 0).background(Color.red) }
65+
Example("Rotation") { Text("Rotated").padding().background(Color.yellow).rotationEffect(.degrees(15)) }
66+
Example("Scale") { Text("Scaled").padding().background(Color.pink).scaleEffect(1.4) }
67+
Example("Opacity") { Text("Faded").padding().background(Color.blue).opacity(0.4) }
68+
}
69+
}
70+
}
71+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#if canImport(AndroidSwiftUI)
2+
import AndroidSwiftUI
3+
#else
4+
import SwiftUI
5+
#endif
6+
7+
struct TextPlayground: View {
8+
@State private var counter = 0
9+
var body: some View {
10+
ScrollView {
11+
VStack(alignment: .leading, spacing: 0) {
12+
Example("Plain") { Text("Hello, world") }
13+
Example("Verbatim") { Text(verbatim: "Raw string, no interpolation") }
14+
Example("Interpolated") { Text("Counter is \(counter)") }
15+
Example("Bump the counter") { Button("Increment") { counter += 1 } }
16+
Example("Multiline") {
17+
Text("A longer passage of text that wraps onto multiple lines when it no longer fits within the width of the screen.")
18+
}
19+
Example("Styled") {
20+
Text("Blue on a rounded chip")
21+
.padding()
22+
.background(Color.blue)
23+
.cornerRadius(12)
24+
}
25+
}
26+
}
27+
}
28+
}
29+
30+
struct ButtonPlayground: View {
31+
@State private var taps = 0
32+
var body: some View {
33+
ScrollView {
34+
VStack(alignment: .leading, spacing: 0) {
35+
Example("Taps: \(taps)") { Text("Tap any button below") }
36+
Example("Title initializer") { Button("Tap me") { taps += 1 } }
37+
Example("Label closure") {
38+
Button(action: { taps += 1 }) { Text("Custom label") }
39+
}
40+
Example("Styled label") {
41+
Button(action: { taps += 1 }) {
42+
Text("Padded label")
43+
.padding()
44+
.background(Color.green)
45+
.cornerRadius(8)
46+
}
47+
}
48+
Example("Reset") { Button("Reset to zero") { taps = 0 } }
49+
}
50+
}
51+
}
52+
}
53+
54+
struct TogglePlayground: View {
55+
@State private var a = false
56+
@State private var b = true
57+
var body: some View {
58+
ScrollView {
59+
VStack(alignment: .leading, spacing: 0) {
60+
Example("Off by default") {
61+
VStack(alignment: .leading, spacing: 8) {
62+
Toggle("Enable feature", isOn: $a)
63+
Text(a ? "Feature is on" : "Feature is off")
64+
}
65+
}
66+
Example("On by default") {
67+
VStack(alignment: .leading, spacing: 8) {
68+
Toggle("Notifications", isOn: $b)
69+
Text(b ? "Notifications on" : "Notifications off")
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
struct SliderPlayground: View {
78+
@State private var unit = 0.5
79+
@State private var wide = 25.0
80+
var body: some View {
81+
ScrollView {
82+
VStack(alignment: .leading, spacing: 0) {
83+
Example("0...1") {
84+
VStack(alignment: .leading, spacing: 8) {
85+
Slider(value: $unit, in: 0...1)
86+
Text("Value: \(Int(unit * 100))%")
87+
}
88+
}
89+
Example("0...100") {
90+
VStack(alignment: .leading, spacing: 8) {
91+
Slider(value: $wide, in: 0...100)
92+
Text("Value: \(Int(wide))")
93+
}
94+
}
95+
}
96+
}
97+
}
98+
}
99+
100+
struct TextFieldPlayground: View {
101+
@State private var name = ""
102+
@State private var city = ""
103+
var body: some View {
104+
ScrollView {
105+
VStack(alignment: .leading, spacing: 0) {
106+
Example("Name") {
107+
VStack(alignment: .leading, spacing: 8) {
108+
TextField("Your name", text: $name)
109+
Text(name.isEmpty ? "Nothing typed yet" : "Hello, \(name)")
110+
}
111+
}
112+
Example("City") {
113+
VStack(alignment: .leading, spacing: 8) {
114+
TextField("Your city", text: $city)
115+
Text(city.isEmpty ? "" : "You are in \(city)")
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
struct PickerPlayground: View {
124+
@State private var fruit = "Apple"
125+
@State private var size = 1
126+
var body: some View {
127+
ScrollView {
128+
VStack(alignment: .leading, spacing: 0) {
129+
Example("String selection") {
130+
VStack(alignment: .leading, spacing: 8) {
131+
Picker("Fruit", selection: $fruit) {
132+
Text("Apple").tag("Apple")
133+
Text("Banana").tag("Banana")
134+
Text("Cherry").tag("Cherry")
135+
}
136+
Text("Selected: \(fruit)")
137+
}
138+
}
139+
Example("Int selection") {
140+
VStack(alignment: .leading, spacing: 8) {
141+
Picker("Size", selection: $size) {
142+
Text("Small").tag(0)
143+
Text("Medium").tag(1)
144+
Text("Large").tag(2)
145+
}
146+
Text("Size index: \(size)")
147+
}
148+
}
149+
}
150+
}
151+
}
152+
}
153+
154+
struct ProgressViewPlayground: View {
155+
@State private var progress = 0.25
156+
var body: some View {
157+
ScrollView {
158+
VStack(alignment: .leading, spacing: 0) {
159+
Example("Indeterminate") { ProgressView() }
160+
Example("Determinate") {
161+
VStack(alignment: .leading, spacing: 8) {
162+
ProgressView(value: progress)
163+
Text("\(Int(progress * 100))%")
164+
Button("Advance") { progress = progress >= 1 ? 0 : progress + 0.25 }
165+
}
166+
}
167+
}
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)