Compare commits

..

1 Commits

Author SHA1 Message Date
Max Goedjen
52a351d75e Fix toolbar appearance 2025-08-10 14:58:44 -07:00
4 changed files with 196 additions and 85 deletions

View File

@@ -1103,6 +1103,134 @@
} }
} }
}, },
"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" : { "create_secret_cancel_button" : {
"localizations" : { "localizations" : {
"ca" : { "ca" : {

View File

@@ -44,9 +44,14 @@ struct ContentView: View {
extension ContentView { extension ContentView {
@ToolbarContentBuilder
func toolbarItem(_ view: some View, id: String) -> ToolbarItem<String, some View> { func toolbarItem(_ view: some View, id: String) -> some ToolbarContent {
ToolbarItem(id: id) { view } if #available(macOS 26.0, *) {
ToolbarItem(id: id) { view }
.sharedBackgroundVisibility(.hidden)
} else {
ToolbarItem(id: id) { view }
}
} }
var needsSetup: Bool { var needsSetup: Bool {

View File

@@ -8,9 +8,9 @@ struct CopyableView: View {
var text: String var text: String
@State private var interactionState: InteractionState = .normal @State private var interactionState: InteractionState = .normal
@Namespace var namespace @Environment(\.colorScheme) private var colorScheme
var content: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
image image
@@ -22,7 +22,7 @@ struct CopyableView: View {
.foregroundColor(primaryTextColor) .foregroundColor(primaryTextColor)
Spacer() Spacer()
if interactionState != .normal { if interactionState != .normal {
hoverIcon Text(hoverText)
.bold() .bold()
.textCase(.uppercase) .textCase(.uppercase)
.foregroundColor(secondaryTextColor) .foregroundColor(secondaryTextColor)
@@ -39,23 +39,17 @@ struct CopyableView: View {
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.font(.system(.body, design: .monospaced)) .font(.system(.body, design: .monospaced))
} }
._background(interactionState: interactionState) .background(backgroundColor)
.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 {
@@ -72,20 +66,31 @@ struct CopyableView: View {
) )
} }
var hoverIcon: Image { var hoverText: LocalizedStringKey {
switch interactionState { switch interactionState {
case .hovering, .dragging: case .hovering:
return Image(systemName: "document.on.document") return "copyable_click_to_copy_button"
case .clicking: case .clicking:
return Image(systemName: "checkmark.circle.fill") return "copyable_copied"
case .normal: case .normal:
fatalError() 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 { var primaryTextColor: Color {
switch interactionState { switch interactionState {
case .normal, .hovering, .dragging: case .normal, .hovering:
return Color(.textColor) return Color(.textColor)
case .clicking: case .clicking:
return .white return .white
@@ -94,7 +99,7 @@ struct CopyableView: View {
var secondaryTextColor: Color { var secondaryTextColor: Color {
switch interactionState { switch interactionState {
case .normal, .hovering, .dragging: case .normal, .hovering:
return Color(.secondaryLabelColor) return Color(.secondaryLabelColor)
case .clicking: case .clicking:
return .white return .white
@@ -106,59 +111,12 @@ 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
struct CopyableView_Previews: PreviewProvider { struct CopyableView_Previews: PreviewProvider {

View File

@@ -17,21 +17,41 @@ struct ToolbarButtonStyle: ButtonStyle {
self.darkColor = darkColor self.darkColor = darkColor
} }
@available(macOS 26.0, *)
private var glassTint: Color {
if !hovering {
colorScheme == .light ? lightColor : darkColor
} else {
colorScheme == .light ? lightColor.exposureAdjust(1) : darkColor.exposureAdjust(1)
}
}
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
configuration.label if #available(macOS 26.0, *) {
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8)) configuration
.background(colorScheme == .light ? lightColor : darkColor) .label
.foregroundColor(.white) .foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 5)) .padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
.overlay( .glassEffect(.regular.tint(glassTint), in: .capsule)
RoundedRectangle(cornerRadius: 5) .onHover { hovering in
.stroke(colorScheme == .light ? .black.opacity(0.15) : .white.opacity(0.15), lineWidth: 1)
.background(hovering ? (colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.05)) : Color.clear)
)
.onHover { hovering in
withAnimation {
self.hovering = hovering self.hovering = hovering
} }
} } else {
configuration
.label
.background(colorScheme == .light ? lightColor : darkColor)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 5))
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(colorScheme == .light ? .black.opacity(0.15) : .white.opacity(0.15), lineWidth: 1)
.background(hovering ? (colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.05)) : Color.clear)
)
.onHover { hovering in
withAnimation {
self.hovering = hovering
}
}
}
} }
} }