|
|
@@ -14,16 +14,16 @@ struct EmailWriterView: View {
|
|
|
.padding(.bottom, 16)
|
|
|
|
|
|
contentCard
|
|
|
- .frame(maxWidth: 1200, maxHeight: .infinity, alignment: .topLeading)
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
+ .frame(maxHeight: .infinity, alignment: .topLeading)
|
|
|
+ .padding(.bottom, 24)
|
|
|
|
|
|
- GradientButton(title: "Generate Email", size: .large) {
|
|
|
+ GradientButton(title: "Generate Email") {
|
|
|
viewModel.generateEmail()
|
|
|
}
|
|
|
.disabled(!viewModel.canGenerate)
|
|
|
.opacity(viewModel.canGenerate ? 1 : 0.55)
|
|
|
- .padding(.top, 16)
|
|
|
}
|
|
|
+ .frame(maxWidth: 1200, maxHeight: .infinity, alignment: .topLeading)
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
.padding(.horizontal, dynamicHorizontalPadding)
|
|
|
.padding(.top, dynamicTopPadding)
|
|
|
@@ -252,24 +252,67 @@ private struct EmailOptionMenu<Option: Identifiable & Equatable>: View {
|
|
|
let options: [Option]
|
|
|
let label: KeyPath<Option, String>
|
|
|
|
|
|
+ @State private var isPresented = false
|
|
|
+ @State private var isHovered = false
|
|
|
+
|
|
|
+ private var iconName: String {
|
|
|
+ title == "Type" ? "envelope" : "textformat"
|
|
|
+ }
|
|
|
+
|
|
|
var body: some View {
|
|
|
- ToolbarMenuButton(
|
|
|
- title: "\(title): \(selection[keyPath: label])",
|
|
|
- iconName: title == "Type" ? "envelope" : "textformat"
|
|
|
- ) {
|
|
|
- ForEach(options) { option in
|
|
|
- Button {
|
|
|
- selection = option
|
|
|
- } label: {
|
|
|
- HStack {
|
|
|
- Text(option[keyPath: label])
|
|
|
- if selection == option {
|
|
|
- Spacer()
|
|
|
- Image(systemName: "checkmark")
|
|
|
+ Button {
|
|
|
+ isPresented.toggle()
|
|
|
+ } label: {
|
|
|
+ HStack(spacing: 6) {
|
|
|
+ Image(systemName: iconName)
|
|
|
+ .font(.system(size: 12))
|
|
|
+
|
|
|
+ Text("\(title): \(selection[keyPath: label])")
|
|
|
+ .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(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 }
|
|
|
+ .popover(isPresented: $isPresented, arrowEdge: .bottom) {
|
|
|
+ VStack(alignment: .leading, spacing: 4) {
|
|
|
+ ForEach(options) { option in
|
|
|
+ Button {
|
|
|
+ selection = option
|
|
|
+ isPresented = false
|
|
|
+ } label: {
|
|
|
+ HStack {
|
|
|
+ Text(option[keyPath: label])
|
|
|
+ if selection == option {
|
|
|
+ Spacer()
|
|
|
+ Image(systemName: "checkmark")
|
|
|
+ }
|
|
|
}
|
|
|
+ .frame(minWidth: 180, alignment: .leading)
|
|
|
+ .contentShape(Rectangle())
|
|
|
}
|
|
|
+ .buttonStyle(.plain)
|
|
|
}
|
|
|
}
|
|
|
+ .padding(8)
|
|
|
}
|
|
|
.accessibilityLabel("\(title): \(selection[keyPath: label])")
|
|
|
}
|