mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-09-15 08:50:57 +00:00
More UI tweaks and fixes (#697)
* Integrations to window * Cleanup of presenting. * Older name for copy * For copyable view too
This commit is contained in:
parent
21fc834fd9
commit
67ec4fee12
@ -73,6 +73,7 @@
|
|||||||
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
||||||
|
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -239,6 +240,7 @@
|
|||||||
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
|
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -293,15 +295,16 @@
|
|||||||
path = Helpers;
|
path = Helpers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
504788ED2E681EB200B4556F /* Styles */ = {
|
504788ED2E681EB200B4556F /* Modifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */,
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
||||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
||||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||||
);
|
);
|
||||||
path = Styles;
|
path = Modifiers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
504788EE2E681EC300B4556F /* Secrets */ = {
|
504788EE2E681EC300B4556F /* Secrets */ = {
|
||||||
@ -432,7 +435,7 @@
|
|||||||
children = (
|
children = (
|
||||||
504788EF2E681ED700B4556F /* Configuration */,
|
504788EF2E681ED700B4556F /* Configuration */,
|
||||||
504788EE2E681EC300B4556F /* Secrets */,
|
504788EE2E681EC300B4556F /* Secrets */,
|
||||||
504788ED2E681EB200B4556F /* Styles */,
|
504788ED2E681EB200B4556F /* Modifiers */,
|
||||||
504788F02E681F0100B4556F /* Views */,
|
504788F02E681F0100B4556F /* Views */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@ -686,6 +689,7 @@
|
|||||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
||||||
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
||||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||||
|
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
||||||
|
@ -4,54 +4,17 @@ import SecureEnclaveSecretKit
|
|||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import Brief
|
import Brief
|
||||||
|
|
||||||
extension EnvironmentValues {
|
|
||||||
|
|
||||||
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
|
||||||
@MainActor fileprivate static let _secretStoreList: SecretStoreList = {
|
|
||||||
let list = SecretStoreList()
|
|
||||||
let cryptoKit = SecureEnclave.Store()
|
|
||||||
let migrator = SecureEnclave.CryptoKitMigrator()
|
|
||||||
try? migrator.migrate(to: cryptoKit)
|
|
||||||
list.add(store: cryptoKit)
|
|
||||||
list.add(store: SmartCard.Store())
|
|
||||||
return list
|
|
||||||
}()
|
|
||||||
|
|
||||||
private static let _agentStatusChecker = AgentStatusChecker()
|
|
||||||
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
|
||||||
private static let _updater: any UpdaterProtocol = {
|
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
|
||||||
return Updater(checkOnLaunch: hasRunSetup)
|
|
||||||
}()
|
|
||||||
@Entry var updater: any UpdaterProtocol = _updater
|
|
||||||
|
|
||||||
private static let _justUpdatedChecker = JustUpdatedChecker()
|
|
||||||
@Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker
|
|
||||||
|
|
||||||
@MainActor var secretStoreList: SecretStoreList {
|
|
||||||
EnvironmentValues._secretStoreList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Secretive: App {
|
struct Secretive: App {
|
||||||
|
|
||||||
@Environment(\.agentStatusChecker) var agentStatusChecker
|
@Environment(\.agentStatusChecker) var agentStatusChecker
|
||||||
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
@State private var showingSetup = false
|
|
||||||
@State private var showingIntegrations = false
|
|
||||||
@State private var showingCreation = false
|
|
||||||
|
|
||||||
@SceneBuilder var body: some Scene {
|
@SceneBuilder var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup)
|
ContentView()
|
||||||
.environment(EnvironmentValues._secretStoreList)
|
.environment(EnvironmentValues._secretStoreList)
|
||||||
.onAppear {
|
|
||||||
if !hasRunSetup {
|
|
||||||
showingSetup = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||||
guard hasRunSetup else { return }
|
guard hasRunSetup else { return }
|
||||||
agentStatusChecker.check()
|
agentStatusChecker.check()
|
||||||
@ -62,25 +25,41 @@ struct Secretive: App {
|
|||||||
forceLaunchAgent()
|
forceLaunchAgent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showingIntegrations) {
|
|
||||||
IntegrationsView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.commands {
|
.commands {
|
||||||
|
AppCommands()
|
||||||
|
}
|
||||||
|
WindowGroup(id: String(describing: IntegrationsView.self)) {
|
||||||
|
IntegrationsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Secretive {
|
||||||
|
|
||||||
|
struct AppCommands: Commands {
|
||||||
|
|
||||||
|
@Environment(\.openWindow) var openWindow
|
||||||
|
@Environment(\.openURL) var openURL
|
||||||
|
@FocusedValue(\.showCreateSecret) var showCreateSecret
|
||||||
|
|
||||||
|
var body: some Commands {
|
||||||
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
||||||
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
||||||
showingIntegrations = true
|
openWindow(id: String(describing: IntegrationsView.self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandGroup(after: CommandGroupPlacement.newItem) {
|
CommandGroup(after: CommandGroupPlacement.newItem) {
|
||||||
Button(.appMenuNewSecretButton) {
|
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
||||||
showingCreation = true
|
showCreateSecret?()
|
||||||
}
|
}
|
||||||
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
||||||
|
.disabled(showCreateSecret?.isEnabled == false)
|
||||||
}
|
}
|
||||||
CommandGroup(replacing: .help) {
|
CommandGroup(replacing: .help) {
|
||||||
Button(.appMenuHelpButton) {
|
Button(.appMenuHelpButton) {
|
||||||
NSWorkspace.shared.open(Constants.helpURL)
|
openURL(Constants.helpURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SidebarCommands()
|
SidebarCommands()
|
||||||
@ -113,8 +92,56 @@ extension Secretive {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private enum Constants {
|
private enum Constants {
|
||||||
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
|
||||||
|
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
||||||
|
@MainActor fileprivate static let _secretStoreList: SecretStoreList = {
|
||||||
|
let list = SecretStoreList()
|
||||||
|
let cryptoKit = SecureEnclave.Store()
|
||||||
|
let migrator = SecureEnclave.CryptoKitMigrator()
|
||||||
|
try? migrator.migrate(to: cryptoKit)
|
||||||
|
list.add(store: cryptoKit)
|
||||||
|
list.add(store: SmartCard.Store())
|
||||||
|
return list
|
||||||
|
}()
|
||||||
|
|
||||||
|
private static let _agentStatusChecker = AgentStatusChecker()
|
||||||
|
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
||||||
|
private static let _updater: any UpdaterProtocol = {
|
||||||
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
|
return Updater(checkOnLaunch: hasRunSetup)
|
||||||
|
}()
|
||||||
|
@Entry var updater: any UpdaterProtocol = _updater
|
||||||
|
|
||||||
|
private static let _justUpdatedChecker = JustUpdatedChecker()
|
||||||
|
@Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker
|
||||||
|
|
||||||
|
@MainActor var secretStoreList: SecretStoreList {
|
||||||
|
EnvironmentValues._secretStoreList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FocusedValues {
|
||||||
|
@Entry var showCreateSecret: OpenSheet?
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OpenSheet {
|
||||||
|
|
||||||
|
let closure: () -> Void
|
||||||
|
let isEnabled: Bool
|
||||||
|
|
||||||
|
init(isEnabled: Bool = true, closure: @escaping () -> Void) {
|
||||||
|
self.isEnabled = isEnabled
|
||||||
|
self.closure = closure
|
||||||
|
}
|
||||||
|
|
||||||
|
func callAsFunction() {
|
||||||
|
closure()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ struct ConfigurationItemView<Content: View>: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
switch action {
|
switch action {
|
||||||
case .copy(let string):
|
case .copy(let string):
|
||||||
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
||||||
NSPasteboard.general.declareTypes([.string], owner: nil)
|
NSPasteboard.general.declareTypes([.string], owner: nil)
|
||||||
NSPasteboard.general.setString(string, forType: .string)
|
NSPasteboard.general.setString(string, forType: .string)
|
||||||
}
|
}
|
||||||
|
@ -21,46 +21,18 @@ struct IntegrationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} detail: {
|
} detail: {
|
||||||
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
||||||
.fauxToolbar {
|
|
||||||
Button(.setupDoneButton) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
.normalButton()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.toolbar {
|
||||||
|
Button(.setupDoneButton) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hiddenToolbar()
|
||||||
|
.windowBackgroundStyle(.thinMaterial)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
selectedInstruction = instructions.gettingStarted
|
selectedInstruction = instructions.gettingStarted
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func fauxToolbar<Content: View>(content: () -> Content) -> some View {
|
|
||||||
modifier(FauxToolbarModifier(toolbarContent: content()))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FauxToolbarModifier<ToolbarContent: View>: ViewModifier {
|
|
||||||
|
|
||||||
var toolbarContent: ToolbarContent
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
content
|
|
||||||
Divider()
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
toolbarContent
|
|
||||||
.padding(.top, 8)
|
|
||||||
.padding(.trailing, 16)
|
|
||||||
.padding(.bottom, 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.frame(minWidth: 400, minHeight: 400)
|
.frame(minWidth: 400, minHeight: 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct WindowBackgroundStyleModifier: ViewModifier {
|
||||||
|
|
||||||
|
let shapeStyle: any ShapeStyle
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
if #available(macOS 15.0, *) {
|
||||||
|
content
|
||||||
|
.containerBackground(
|
||||||
|
shapeStyle, for: .window
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func windowBackgroundStyle(_ style: some ShapeStyle) -> some View {
|
||||||
|
modifier(WindowBackgroundStyleModifier(shapeStyle: style))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HiddenToolbarModifier: ViewModifier {
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
if #available(macOS 15.0, *) {
|
||||||
|
content
|
||||||
|
.toolbarBackgroundVisibility(.hidden, for: .automatic)
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func hiddenToolbar() -> some View {
|
||||||
|
modifier(HiddenToolbarModifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,9 +6,9 @@ import Brief
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
|
||||||
@Binding var showingCreation: Bool
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
@Binding var runningSetup: Bool
|
@State var showingCreation = false
|
||||||
@Binding var hasRunSetup: Bool
|
@State var runningSetup = true
|
||||||
@State var showingAgentInfo = false
|
@State var showingAgentInfo = false
|
||||||
@State var activeSecret: AnySecret?
|
@State var activeSecret: AnySecret?
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@ -35,6 +35,23 @@ struct ContentView: View {
|
|||||||
toolbarItem(appPathNoticeView, id: "appPath")
|
toolbarItem(appPathNoticeView, id: "appPath")
|
||||||
toolbarItem(newItemView, id: "new")
|
toolbarItem(newItemView, id: "new")
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
if !hasRunSetup {
|
||||||
|
runningSetup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) {
|
||||||
|
showingCreation = true
|
||||||
|
})
|
||||||
|
.sheet(isPresented: $showingCreation) {
|
||||||
|
if let modifiable = storeList.modifiableStore {
|
||||||
|
CreateSecretView(store: modifiable) { created in
|
||||||
|
if let created {
|
||||||
|
activeSecret = created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.sheet(isPresented: $runningSetup) {
|
.sheet(isPresented: $runningSetup) {
|
||||||
SetupView(setupComplete: $hasRunSetup)
|
SetupView(setupComplete: $hasRunSetup)
|
||||||
}
|
}
|
||||||
@ -114,15 +131,6 @@ extension ContentView {
|
|||||||
showingCreation = true
|
showingCreation = true
|
||||||
}
|
}
|
||||||
.menuButton()
|
.menuButton()
|
||||||
.sheet(isPresented: $showingCreation) {
|
|
||||||
if let modifiable = storeList.modifiableStore {
|
|
||||||
CreateSecretView(store: modifiable) { created in
|
|
||||||
if let created {
|
|
||||||
activeSecret = created
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ struct CopyableView: View {
|
|||||||
var copyButton: some View {
|
var copyButton: some View {
|
||||||
switch interactionState {
|
switch interactionState {
|
||||||
case .hovering:
|
case .hovering:
|
||||||
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
// Button will eat the click, so we set interaction state manually.
|
// Button will eat the click, so we set interaction state manually.
|
||||||
interactionState = .clicking
|
interactionState = .clicking
|
||||||
|
Loading…
Reference in New Issue
Block a user