Files
AlloVoisinsSwiftUI/AlloVoisinsSwiftUI/Core/Views/Sequoia/Components/SQButton.swift
Victor Bodinaud e305b1697a New views
2025-12-10 16:31:08 +01:00

433 lines
11 KiB
Swift

//
// SQButton.swift
//
//
// Created by Victor on 12/06/2024.
//
import Lottie
import SwiftUI
struct SQButton: View {
let title: String
let action: () -> Void
@State private var type: SQButtonType = .solid
@State private var colorScheme: SQButtonColorScheme = .neutral
@State private var textSize: CGFloat = 16
@State private var font: SQTextFont = .semiBold
@State private var icon: SQIcon? = nil
@State private var isPressed: Bool = false
@State private var isLarge: Bool = false
@Binding var isLoading: Bool
@Environment(\.isEnabled) private var isEnabled
private var textWidth: CGFloat {
let font = UIFont.systemFont(ofSize: textSize)
let attributes = [NSAttributedString.Key.font: font]
let size = (title as NSString).size(withAttributes: attributes)
return size.width + (icon != nil ? 24 : 0)
}
init(_ title: String,
isLoading: Binding<Bool> = .constant(false),
action: @escaping () -> Void)
{
self.title = title
self._isLoading = isLoading
self.action = action
}
private var buttonStyle: SQButtonStyle {
SQButtonStyle(
type: type,
colorScheme: colorScheme,
isLoading: isLoading,
isDisabled: !isEnabled,
isPressed: isPressed
)
}
var body: some View {
Button(action: {
if !isLoading, !isEnabled {
action()
}
}, label: {
HStack {
if isLoading {
LottieView(animation: .named("av_loader"))
.looping()
.frame(width: 20)
} else {
if icon != nil {
icon
.foregroundColor(buttonStyle.textColor)
}
SQText(title)
.sqSize(textSize)
.sqFont(font)
.sqColor(buttonStyle.textColor)
}
}
.frame(minWidth: textWidth)
.frame(maxWidth: isLarge ? .infinity : nil, alignment: .center)
.padding(.horizontal, 30)
.padding(.vertical, 12)
.frame(height: 40, alignment: .center)
.background(buttonStyle.backgroundColor)
.cornerRadius(100)
.overlay(
RoundedRectangle(cornerRadius: 100)
.inset(by: 0.5)
.stroke(buttonStyle.borderColor, lineWidth: 1)
)
})
.buttonStyle(PressedButtonStyle(isPressed: $isPressed))
}
}
struct PressedButtonStyle: ButtonStyle {
@Binding var isPressed: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.onChange(of: configuration.isPressed) { newValue in
isPressed = newValue
}
}
}
// MARK: - Modifiers
extension SQButton {
func sqButtonType(_ type: SQButtonType) -> SQButton {
var copy = self
copy._type = State(initialValue: type)
return copy
}
func sqColorScheme(_ scheme: SQButtonColorScheme) -> SQButton {
var copy = self
copy._colorScheme = State(initialValue: scheme)
return copy
}
func sqTextSize(_ size: CGFloat) -> SQButton {
var copy = self
copy._textSize = State(initialValue: size)
return copy
}
func sqFont(_ font: SQTextFont) -> SQButton {
var copy = self
copy._font = State(initialValue: font)
return copy
}
func sqIcon(_ icon: SQIcon?) -> SQButton {
var copy = self
copy._icon = State(initialValue: icon)
return copy
}
func sqLarge(_ isLarge: Bool = true) -> SQButton {
var copy = self
copy._isLarge = State(initialValue: isLarge)
return copy
}
}
// MARK: - Properties
enum SQButtonType: String, CaseIterable {
case solid
case line
case light
case glass
}
enum SQButtonColorScheme: String, CaseIterable {
case neutral
case green
case pink
case blue
case yellow
case gold
case orange
case red
case grape
case forest
case royal
case white
var baseColor: Color {
switch self {
case .neutral:
return .sqNeutral(100)
case .green:
return .sqGreen(60)
case .pink:
return .sqPink(60)
case .blue:
return .sqBlue(50)
case .yellow:
return .sqYellow(50)
case .gold:
return .sqGold(50)
case .orange:
return .sqOrange(50)
case .red:
return .sqRed(60)
case .grape:
return .sqGrape(80)
case .forest:
return .sqForest(100)
case .royal:
return .sqRoyal(60)
case .white:
return .white
}
}
var mediumColor: Color {
switch self {
case .neutral:
return .sqNeutral(30)
case .green:
return .sqGreen(30)
case .pink:
return .sqPink(30)
case .blue:
return .sqBlue(30)
case .yellow:
return .sqYellow(30)
case .gold:
return .sqGold(30)
case .orange:
return .sqOrange(30)
case .red:
return .sqRed(10)
case .grape:
return .sqGrape(30)
case .forest:
return .sqForest(50)
case .royal:
return .sqRoyal(30)
case .white:
return .white
}
}
var lightColor: Color {
switch self {
case .neutral:
return .sqNeutral(15)
case .green:
return .sqGreen(10)
case .pink:
return .sqPink(10)
case .blue:
return .sqBlue(10)
case .yellow:
return .sqYellow(10)
case .gold:
return .sqGold(10)
case .orange:
return .sqOrange(10)
case .red:
return .sqRed(10)
case .grape:
return .sqGrape(10)
case .forest:
return .sqForest(10)
case .royal:
return .sqRoyal(10)
case .white:
return .white
}
}
}
struct SQButtonStyle {
let type: SQButtonType
let colorScheme: SQButtonColorScheme
let isLoading: Bool
let isDisabled: Bool
let isPressed: Bool
var backgroundColor: Color {
if isPressed && !isDisabled && !isLoading {
switch type {
case .solid:
return colorScheme.baseColor.opacity(0.8)
case .line:
return .clear
case .light:
return colorScheme.lightColor
case .glass:
return .clear
}
}
if isDisabled {
switch type {
case .solid:
return colorScheme.mediumColor
case .line:
return .clear
case .light:
return colorScheme.lightColor
case .glass:
return .clear
}
}
if isLoading {
switch type {
case .solid:
return colorScheme.mediumColor
case .line:
return .clear
case .light:
return colorScheme.lightColor
case .glass:
return .clear
}
}
switch type {
case .solid:
return colorScheme.baseColor
case .line:
return .clear
case .light:
return colorScheme.lightColor
case .glass:
return .clear
}
}
var borderColor: Color {
switch type {
case .solid:
if isPressed && !isDisabled && !isLoading {
return colorScheme.baseColor.opacity(0.8)
}
if isDisabled {
return colorScheme.lightColor.opacity(0.5)
}
if isLoading {
return colorScheme.lightColor.opacity(0.8)
}
case .line:
if isPressed && !isDisabled && !isLoading {
return colorScheme.baseColor.opacity(0.8)
}
if isDisabled {
return colorScheme.mediumColor
}
if isLoading {
return colorScheme.mediumColor
}
return colorScheme.baseColor
case .light:
if isPressed && !isDisabled && !isLoading {
return colorScheme.lightColor.opacity(0.8)
}
if isDisabled {
return colorScheme.lightColor.opacity(0.5)
}
if isLoading {
return colorScheme.lightColor.opacity(0.8)
}
return colorScheme.lightColor
case .glass:
return .clear
}
return colorScheme.baseColor
}
var textColor: Color {
if isPressed && !isDisabled && !isLoading {
switch type {
case .solid:
return .white
case .line, .light, .glass:
return colorScheme.baseColor.opacity(0.8)
}
}
if isDisabled {
switch type {
case .solid:
return .white
case .line, .light, .glass:
return colorScheme.mediumColor
}
}
switch type {
case .solid:
return .white
case .line, .light, .glass:
return colorScheme.baseColor
}
}
}
#Preview {
VStack(spacing: 32) {
HStack {
VStack(spacing: 16) {
SQButton("C'est parti !") {}
.sqColorScheme(.royal)
SQButton("C'est parti !") {}
.sqColorScheme(.royal)
.sqButtonType(.line)
SQButton("C'est parti !") {}
.sqColorScheme(.royal)
.sqButtonType(.light)
SQButton("C'est parti !") {}
.sqColorScheme(.royal)
.sqButtonType(.glass)
}
VStack(spacing: 16) {
SQButton("C'est parti !", isLoading: .constant(true)) {}
.sqColorScheme(.royal)
SQButton("C'est parti !", isLoading: .constant(true)) {}
.sqColorScheme(.royal)
.sqButtonType(.line)
SQButton("C'est parti !", isLoading: .constant(true)) {}
.sqColorScheme(.royal)
.sqButtonType(.light)
SQButton("C'est parti !", isLoading: .constant(true)) {}
.sqColorScheme(.royal)
.sqButtonType(.glass)
}
}
SQButton("Continuer avec Apple") {}
.sqButtonType(.line)
.sqIcon(SQIcon(.apple_brand))
.sqLarge()
}
.padding()
}