| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import SwiftUI
- struct GradientButton: View {
- let title: String
- let action: () -> Void
- @State private var isHovered = false
- var body: some View {
- Button(action: action) {
- HStack(spacing: 10) {
- Image(systemName: "sparkles")
- .font(.system(size: 15, weight: .semibold))
- Text(title)
- .font(.system(size: 16, weight: .semibold))
- .textSelection(.disabled)
- Image(systemName: "arrow.right")
- .font(.system(size: 14, weight: .semibold))
- }
- .foregroundStyle(.white)
- .padding(.horizontal, 28)
- .padding(.vertical, 15)
- .frame(maxWidth: .infinity)
- .background(isHovered ? AppTheme.primaryGradientHover : AppTheme.primaryGradient)
- .clipShape(RoundedRectangle(cornerRadius: AppTheme.buttonCornerRadius))
- .shadow(
- color: isHovered ? AppTheme.primaryShadowHover : AppTheme.primaryShadow,
- radius: isHovered ? 14 : 10,
- y: isHovered ? 6 : 4
- )
- .scaleEffect(isHovered ? 1.02 : 1.0)
- .animation(.easeOut(duration: 0.15), value: isHovered)
- }
- .buttonStyle(.plain)
- .onHover { isHovered = $0 }
- }
- }
- struct ToolbarButton: View {
- let title: String
- let iconName: String
- let action: () -> Void
- @State private var isHovered = false
- var body: some View {
- Button(action: action) {
- HStack(spacing: 6) {
- Image(systemName: iconName)
- .font(.system(size: 12))
- Text(title)
- .font(.system(size: 12, weight: .medium))
- }
- .foregroundStyle(isHovered ? AppTheme.toolbarForegroundHover : AppTheme.textSecondary)
- .padding(.horizontal, 24)
- .padding(.vertical, 7)
- .background(isHovered ? AppTheme.toolbarBackgroundHover : Color.white)
- .clipShape(RoundedRectangle(cornerRadius: 8))
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .stroke(isHovered ? AppTheme.toolbarBorderHover : AppTheme.border, lineWidth: 1)
- )
- .shadow(
- color: isHovered ? AppTheme.toolbarShadowHover : AppTheme.toolbarShadow,
- radius: isHovered ? 4 : 2,
- y: isHovered ? 2 : 1
- )
- .scaleEffect(isHovered ? 1.02 : 1.0)
- .animation(.easeOut(duration: 0.15), value: isHovered)
- }
- .buttonStyle(.plain)
- .onHover { isHovered = $0 }
- }
- }
- struct ToolbarMenuButton<MenuContent: View>: View {
- let title: String
- let iconName: String
- @ViewBuilder let menuContent: () -> MenuContent
- @State private var isHovered = false
- var body: some View {
- Menu {
- menuContent()
- } label: {
- HStack(spacing: 6) {
- Image(systemName: iconName)
- .font(.system(size: 12))
- Text(title)
- .font(.system(size: 12, weight: .medium))
- Image(systemName: "chevron.down")
- .font(.system(size: 10, weight: .semibold))
- }
- .foregroundStyle(isHovered ? AppTheme.toolbarForegroundHover : AppTheme.textSecondary)
- .padding(.horizontal, 24)
- .padding(.vertical, 7)
- .background(
- RoundedRectangle(cornerRadius: 8, style: .continuous)
- .fill(isHovered ? AppTheme.toolbarBackgroundHover : Color.white)
- )
- .overlay(
- RoundedRectangle(cornerRadius: 8, style: .continuous)
- .stroke(isHovered ? AppTheme.toolbarBorderHover : AppTheme.border, lineWidth: 1)
- )
- .shadow(
- color: isHovered ? AppTheme.toolbarShadowHover : AppTheme.toolbarShadow,
- radius: isHovered ? 4 : 2,
- y: isHovered ? 2 : 1
- )
- .scaleEffect(isHovered ? 1.02 : 1.0)
- .animation(.easeOut(duration: 0.15), value: isHovered)
- .compositingGroup()
- }
- .fixedSize(horizontal: true, vertical: false)
- .menuStyle(.borderlessButton)
- .buttonStyle(.plain)
- .onHover { isHovered = $0 }
- }
- }
- struct IconButton: View {
- let action: () -> Void
- private let iconName: String?
- private let customIcon: (() -> AnyView)?
- @State private var isHovered = false
- init(iconName: String, action: @escaping () -> Void) {
- self.iconName = iconName
- self.customIcon = nil
- self.action = action
- }
- init<Content: View>(@ViewBuilder icon: @escaping () -> Content, action: @escaping () -> Void) {
- self.iconName = nil
- self.customIcon = { AnyView(icon()) }
- self.action = action
- }
- @ViewBuilder
- private var iconContent: some View {
- if let iconName {
- Image(systemName: iconName)
- .font(.system(size: 14))
- } else if let customIcon {
- customIcon()
- }
- }
- var body: some View {
- Button(action: action) {
- iconContent
- .foregroundStyle(isHovered ? AppTheme.toolbarForegroundHover : AppTheme.textSecondary)
- .padding(4)
- .contentShape(Rectangle())
- .scaleEffect(isHovered ? 1.05 : 1.0)
- .animation(.easeOut(duration: 0.15), value: isHovered)
- }
- .buttonStyle(.plain)
- .onHover { isHovered = $0 }
- }
- }
- struct ClearButton: View {
- let action: () -> Void
- @State private var isHovered = false
- var body: some View {
- Button(action: action) {
- HStack(spacing: 6) {
- Image(systemName: "eraser.fill")
- .font(.system(size: 12, weight: .semibold))
- .symbolRenderingMode(.hierarchical)
- Text("Clear")
- .font(.system(size: 12, weight: .semibold))
- }
- .foregroundStyle(isHovered ? AppTheme.clearForegroundHover : AppTheme.clearForeground)
- .padding(.horizontal, 12)
- .padding(.vertical, 7)
- .background(isHovered ? AppTheme.clearBackgroundHover : AppTheme.clearBackground)
- .clipShape(RoundedRectangle(cornerRadius: 8))
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .stroke(isHovered ? AppTheme.clearForeground.opacity(0.35) : AppTheme.clearBorder, lineWidth: 1)
- )
- .shadow(
- color: isHovered ? AppTheme.clearShadow : AppTheme.clearShadow.opacity(0.5),
- radius: isHovered ? 6 : 3,
- y: isHovered ? 3 : 1
- )
- .scaleEffect(isHovered ? 1.02 : 1.0)
- .animation(.easeOut(duration: 0.15), value: isHovered)
- }
- .buttonStyle(.plain)
- .onHover { isHovered = $0 }
- .accessibilityLabel("Clear text")
- }
- }
|