123 lines
4.0 KiB
Swift
123 lines
4.0 KiB
Swift
import SwiftUI
|
|
|
|
struct ComponentsOnboardingView: View {
|
|
@State private var carouselStep = 0
|
|
let onCreate: () -> Void
|
|
let onBrowse: () -> Void
|
|
|
|
private let imageNames = [
|
|
"coffee-onboarding",
|
|
"router-onboarding",
|
|
"charger-onboarding"
|
|
]
|
|
|
|
private let timer = Timer.publish(every: 8, on: .main, in: .common).autoconnect()
|
|
private let animationDuration = 0.8
|
|
|
|
private var loopingImages: [String] {
|
|
guard let first = imageNames.first else { return [] }
|
|
return imageNames + [first]
|
|
}
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Spacer(minLength: 32)
|
|
|
|
OnboardingCarouselView(images: loopingImages, step: carouselStep)
|
|
.frame(minHeight: 80, maxHeight: 240)
|
|
.padding(.horizontal, 0)
|
|
|
|
VStack(spacing: 12) {
|
|
Text("Add your first component")
|
|
.font(.title2.weight(.semibold))
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text("Bring your system to life with components and let **Cable by VoltPlan** handle cable and fuse recommendations.")
|
|
.font(.body)
|
|
.foregroundStyle(Color.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.frame(minHeight: 72)
|
|
.padding(.horizontal, 12)
|
|
}
|
|
.padding(.horizontal, 24)
|
|
|
|
Spacer()
|
|
|
|
VStack(spacing: 12) {
|
|
Button(action: createComponent) {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "plus.circle.fill")
|
|
.font(.system(size: 16))
|
|
Text("Create Component")
|
|
.font(.headline.weight(.semibold))
|
|
}
|
|
.foregroundColor(.white)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 48)
|
|
.background(Color.blue)
|
|
.cornerRadius(12)
|
|
}
|
|
.accessibilityIdentifier("create-component-button")
|
|
.buttonStyle(.plain)
|
|
|
|
Button(action: onBrowse) {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "books.vertical")
|
|
.font(.system(size: 16))
|
|
Text("Browse Library")
|
|
.font(.headline.weight(.semibold))
|
|
}
|
|
.foregroundColor(.blue)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 48)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
.fill(Color.blue.opacity(0.12))
|
|
)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
.stroke(Color.blue.opacity(0.24), lineWidth: 1)
|
|
)
|
|
}
|
|
.accessibilityIdentifier("select-component-button")
|
|
.buttonStyle(.plain)
|
|
}
|
|
.padding(.horizontal, 24)
|
|
}
|
|
.padding(.bottom, 32)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background(Color(.systemGroupedBackground))
|
|
.onAppear(perform: resetState)
|
|
.onReceive(timer) { _ in advanceCarousel() }
|
|
}
|
|
|
|
private func resetState() {
|
|
carouselStep = 0
|
|
}
|
|
|
|
private func createComponent() {
|
|
onCreate()
|
|
}
|
|
|
|
private func advanceCarousel() {
|
|
guard imageNames.count > 1 else { return }
|
|
let next = carouselStep + 1
|
|
|
|
withAnimation(.easeInOut(duration: animationDuration)) {
|
|
carouselStep = next
|
|
}
|
|
|
|
if next == imageNames.count {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) {
|
|
withAnimation(.none) {
|
|
carouselStep = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ComponentsOnboardingView(onCreate: {}, onBrowse: {})
|
|
}
|