433 lines
11 KiB
Swift
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()
|
|
}
|
|
|