Draggable.

This commit is contained in:
Max Goedjen 2025-08-10 16:28:08 -07:00
parent 53a23b265a
commit 67a506dc66
No known key found for this signature in database
2 changed files with 69 additions and 155 deletions

View File

@ -1103,134 +1103,6 @@
}
}
},
"copyable_click_to_copy_button" : {
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Clica per copiar"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Zum Kopieren Anklicken"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Click to Copy"
}
},
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Klikkaa kopioidaksesi"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cliquer pour copier"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Clicca per copiare"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "クリックしてコピー"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "복사하기"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Clique para Copiar"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "点击拷贝"
}
}
}
},
"copyable_copied" : {
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copiat"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kopiert"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copied"
}
},
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kopioitu"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copié"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copiato"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "コピーしました"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "복사됨"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copiado"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "已拷贝"
}
}
}
},
"create_secret_cancel_button" : {
"localizations" : {
"ca" : {

View File

@ -8,9 +8,9 @@ struct CopyableView: View {
var text: String
@State private var interactionState: InteractionState = .normal
@Environment(\.colorScheme) private var colorScheme
@Namespace var namespace
var body: some View {
var content: some View {
VStack(alignment: .leading) {
HStack {
image
@ -22,7 +22,7 @@ struct CopyableView: View {
.foregroundColor(primaryTextColor)
Spacer()
if interactionState != .normal {
Text(hoverText)
hoverIcon
.bold()
.textCase(.uppercase)
.foregroundColor(secondaryTextColor)
@ -39,17 +39,23 @@ struct CopyableView: View {
.multilineTextAlignment(.leading)
.font(.system(.body, design: .monospaced))
}
.background(backgroundColor)
._background(interactionState: interactionState)
.frame(minWidth: 150, maxWidth: .infinity)
.cornerRadius(10)
}
var body: some View {
content
.onHover { hovering in
withAnimation {
interactionState = hovering ? .hovering : .normal
}
}
.onDrag {
.onDrag({
NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier)
}
}, preview: {
content
._background(interactionState: .dragging)
})
.onTapGesture {
copy()
withAnimation {
@ -66,31 +72,20 @@ struct CopyableView: View {
)
}
var hoverText: LocalizedStringKey {
var hoverIcon: Image {
switch interactionState {
case .hovering:
return "copyable_click_to_copy_button"
case .hovering, .dragging:
return Image(systemName: "document.on.document")
case .clicking:
return "copyable_copied"
return Image(systemName: "checkmark.circle.fill")
case .normal:
fatalError()
}
}
var backgroundColor: Color {
switch interactionState {
case .normal:
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
case .hovering:
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
case .clicking:
return .accentColor
}
}
var primaryTextColor: Color {
switch interactionState {
case .normal, .hovering:
case .normal, .hovering, .dragging:
return Color(.textColor)
case .clicking:
return .white
@ -99,7 +94,7 @@ struct CopyableView: View {
var secondaryTextColor: Color {
switch interactionState {
case .normal, .hovering:
case .normal, .hovering, .dragging:
return Color(.secondaryLabelColor)
case .clicking:
return .white
@ -111,12 +106,59 @@ struct CopyableView: View {
NSPasteboard.general.setString(text, forType: .string)
}
private enum InteractionState {
case normal, hovering, clicking
}
fileprivate enum InteractionState {
case normal, hovering, clicking, dragging
}
extension View {
fileprivate func _background(interactionState: InteractionState) -> some View {
modifier(BackgroundViewModifier(interactionState: interactionState))
}
}
fileprivate struct BackgroundViewModifier: ViewModifier {
@Environment(\.colorScheme) private var colorScheme
let interactionState: InteractionState
func body(content: Content) -> some View {
if interactionState == .dragging {
content
.background(backgroundColor(interactionState: interactionState), in: RoundedRectangle(cornerRadius: 15))
} else {
if #available(macOS 26.0, *) {
content
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
} else {
content
.background(backgroundColor(interactionState: interactionState))
.cornerRadius(10)
}
}
}
func backgroundColor(interactionState: InteractionState) -> Color {
switch interactionState {
case .normal:
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
case .hovering, .dragging:
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
case .clicking:
return .accentColor
}
}
}
#if DEBUG
struct CopyableView_Previews: PreviewProvider {