Files
AlloVoisinsSwiftUI/AlloVoisinsSwiftUI/Core/Views/Sequoia/Components/SQRadio.swift
Victor Bodinaud 08666a6818 Add Views
2025-06-30 11:25:36 +02:00

190 lines
6.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// SQRadio.swift
// Sequoia
//
// Created by Victor on 10/10/2024.
//
import SwiftUI
enum SQRadioOrientation {
case horizontal
case vertical
}
struct SQRadioOption: Identifiable {
let id: Int = UUID().hashValue
let title: String
var desc: String? = nil
init(title: String, desc: String? = nil) {
self.title = title
self.desc = desc
}
}
struct SQRadio: View {
let title: String?
let titleSize: CGFloat
var radioTextSize: CGFloat = 16
var orientation: SQRadioOrientation = .vertical
let options: [SQRadioOption]
var error: Binding<SQFormFieldError>
@Binding var selectedIndex: Int?
init(title: String? = nil, titleSize: CGFloat = 24, radioTextSize: CGFloat = 16, orientation: SQRadioOrientation = .vertical, options: [SQRadioOption], selectedIndex: Binding<Int?>, error: Binding<SQFormFieldError> = .constant(.none)) {
self.title = title
self.titleSize = titleSize
self.radioTextSize = radioTextSize
self.orientation = orientation
self.options = options
self.error = error
self._selectedIndex = selectedIndex
}
var body: some View {
VStack(alignment: orientation == .vertical ? .leading : .center, spacing: 16) {
if let title = title {
SQText(title, size: titleSize, font: .bold)
.multilineTextAlignment(.center)
}
Group {
if orientation == .horizontal {
HorizontalRadioButtons(options: options, titleSize: radioTextSize, selectedIndex: $selectedIndex, isInError: error.wrappedValue.isInError)
.frame(maxWidth: .infinity)
} else {
VStack(alignment: .leading, spacing: 16) {
radioButtons
}
}
}
if error.wrappedValue != .none {
SQText(error.wrappedValue.message, size: 12, font: .demiBold, textColor: Color.sqSemanticRed)
}
}
}
private var radioButtons: some View {
ForEach(Array(options.enumerated()), id: \.offset) { index, option in
RadioButton(
title: option.title,
desc: option.desc,
titleSize: radioTextSize,
isSelected: Binding(
get: { selectedIndex == index },
set: { _ in selectedIndex = index }
),
isInError: error.wrappedValue.isInError,
orientation: orientation
)
}
}
private struct HorizontalRadioButtons: View {
let options: [SQRadioOption]
let titleSize: CGFloat
@Binding var selectedIndex: Int?
var isInError: Bool
var body: some View {
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0) {
Spacer()
ForEach(Array(options.enumerated()), id: \.offset) { index, option in
RadioButton(
title: option.title,
desc: option.desc,
titleSize: titleSize,
isSelected: Binding(
get: { selectedIndex == index },
set: { _ in selectedIndex = index }
),
isInError: isInError,
orientation: .horizontal
)
.frame(width: geometry.size.width / CGFloat(options.count))
}
Spacer()
}
}
.frame(height: 80)
}
}
private struct RadioButton: View {
let title: String
let desc: String?
let titleSize: CGFloat
@Binding var isSelected: Bool
var isInError: Bool
let orientation: SQRadioOrientation
var body: some View {
Group {
if orientation == .horizontal {
VStack(spacing: 8) {
radioCircle
SQText(title)
.multilineTextAlignment(.center)
}
} else {
VStack(alignment: .leading) {
HStack(spacing: 8) {
radioCircle
SQText(title)
}
if let desc = desc {
SQText(desc, size: 12)
}
}
}
}
.contentShape(Rectangle()) // Make the entire area tappable
.onTapGesture {
isSelected.toggle()
}
}
private var radioCircle: some View {
ZStack {
Circle()
.stroke(isInError ? Color.sqSemanticRed :
(isSelected ?
Color.sqNeutral(100) :
Color.sqNeutral(30)), lineWidth: 1)
.frame(width: 20, height: 20)
if isSelected {
Circle()
.inset(by: 3)
.stroke(isInError ? Color.sqSemanticRed : Color.sqNeutral(100), lineWidth: 6)
.frame(width: 20, height: 20)
}
}
}
}
}
#Preview {
VStack(alignment: .leading, spacing: 32) {
SQRadio(title: "Vertical", options: [
SQRadioOption(title: "Option 1", desc: "Lutilisateur semble proposer un service relevant dune activité réglementée sans disposer des autorisations requises."),
SQRadioOption(title: "Option 2", desc: "Lutilisateur semble évoquer ou proposer des activités interdites par la loi (trafic danimaux, substances illicites, armes, etc.)."),
SQRadioOption(title: "Option 3", desc: "Lutilisateur semble fournir de fausses informations dans son profil."),
SQRadioOption(title: "Option 4", desc: "Lutilisateur semble avoir usurpé mon identité ou lidentité dun autre utilisateur (SIRET, numéro de téléphone...).")
], selectedIndex: .constant(0), error: .constant(.custom("Merci de détailler votre signalement")))
SQRadio(title: "Horizontal", orientation: .horizontal, options: [
SQRadioOption(title: "1\n Non Jamais"),
SQRadioOption(title: "2"),
SQRadioOption(title: "3"),
SQRadioOption(title: "4"),
SQRadioOption(title: "5\nOui")
], selectedIndex: .constant(2))
}
.padding()
}