Add glass appearance on macOS 26 (#609)

* Accessibility.

* Cleanup
This commit is contained in:
Max Goedjen 2025-08-17 15:45:07 -05:00 committed by GitHub
parent 197f63d1eb
commit 609adf04bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -8,9 +8,8 @@ struct CopyableView: View {
var text: String var text: String
@State private var interactionState: InteractionState = .normal @State private var interactionState: InteractionState = .normal
@Environment(\.colorScheme) private var colorScheme
var body: some View { var content: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
image image
@ -22,7 +21,7 @@ struct CopyableView: View {
.foregroundColor(primaryTextColor) .foregroundColor(primaryTextColor)
Spacer() Spacer()
if interactionState != .normal { if interactionState != .normal {
Text(hoverText) hoverIcon
.bold() .bold()
.textCase(.uppercase) .textCase(.uppercase)
.foregroundColor(secondaryTextColor) .foregroundColor(secondaryTextColor)
@ -39,17 +38,23 @@ struct CopyableView: View {
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.font(.system(.body, design: .monospaced)) .font(.system(.body, design: .monospaced))
} }
.background(backgroundColor) ._background(interactionState: interactionState)
.frame(minWidth: 150, maxWidth: .infinity) .frame(minWidth: 150, maxWidth: .infinity)
.cornerRadius(10) }
var body: some View {
content
.onHover { hovering in .onHover { hovering in
withAnimation { withAnimation {
interactionState = hovering ? .hovering : .normal interactionState = hovering ? .hovering : .normal
} }
} }
.onDrag { .onDrag({
NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier) NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier)
} }, preview: {
content
._background(interactionState: .dragging)
})
.onTapGesture { .onTapGesture {
copy() copy()
withAnimation { withAnimation {
@ -66,31 +71,23 @@ struct CopyableView: View {
) )
} }
var hoverText: LocalizedStringKey { @ViewBuilder
var hoverIcon: some View {
switch interactionState { switch interactionState {
case .hovering: case .hovering:
return "copyable_click_to_copy_button" Image(systemName: "document.on.document")
.accessibilityLabel(String(localized: "copyable_click_to_copy_button"))
case .clicking: case .clicking:
return "copyable_copied" Image(systemName: "checkmark.circle.fill")
case .normal: .accessibilityLabel(String(localized: "copyable_copied"))
fatalError() case .normal, .dragging:
} EmptyView()
}
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 { var primaryTextColor: Color {
switch interactionState { switch interactionState {
case .normal, .hovering: case .normal, .hovering, .dragging:
return Color(.textColor) return Color(.textColor)
case .clicking: case .clicking:
return .white return .white
@ -99,7 +96,7 @@ struct CopyableView: View {
var secondaryTextColor: Color { var secondaryTextColor: Color {
switch interactionState { switch interactionState {
case .normal, .hovering: case .normal, .hovering, .dragging:
return Color(.secondaryLabelColor) return Color(.secondaryLabelColor)
case .clicking: case .clicking:
return .white return .white
@ -111,10 +108,57 @@ struct CopyableView: View {
NSPasteboard.general.setString(text, forType: .string) 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 #if DEBUG