native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(repo-appearance): collapse icon-picker buttons into a tile menu

Three trailing buttons (Choose Symbol… / Choose Image… / Clear Icon)
truncated to "Choos…" / "Clear I…" in narrow Settings windows. Drop
them and turn the preview tile itself into the action surface — a
SwiftUI `Menu` with the preview as its label. Click anywhere on the
tile and a popover lists the three options (Clear is omitted when no
icon is set, replaced by `role: .destructive` + Divider when present).

This is the macOS-native pattern (System Settings user picture,
Finder Get Info icon) so the discovery cost is low. To reinforce
clickability, the tile gains:
- a 1.5pt accentColor border on hover (animates in/out at 0.12s)
- the `.link` pointer style so the cursor switches to a pointing hand

Bumped the preview from 36pt to 40pt so the tile reads as a target
rather than a label decoration; help text for the no-icon state now
points at the tile ("Click the tile to pick a symbol or import an
image.") so users don't hunt for vanished buttons.

onevcat 86be9903 d8a85b60

+50 -26
+50 -26
supacode/Features/RepositorySettings/Views/RepositoryAppearancePickerView.swift
··· 19 19 20 20 @State private var isSymbolPickerPresented = false 21 21 @State private var isImageImporterPresented = false 22 + @State private var isHoveringIconTile = false 22 23 23 - private let previewSize: CGFloat = 36 24 + private let previewSize: CGFloat = 40 24 25 private let dotSize: CGFloat = 22 25 26 26 27 var body: some View { ··· 58 59 @ViewBuilder 59 60 private var iconRow: some View { 60 61 HStack(alignment: .center, spacing: 12) { 61 - iconPreview 62 + iconMenu 62 63 VStack(alignment: .leading, spacing: 4) { 63 64 Text("Icon") 64 65 .font(.headline) ··· 67 68 .foregroundStyle(.secondary) 68 69 .fixedSize(horizontal: false, vertical: true) 69 70 } 70 - Spacer(minLength: 8) 71 - iconButtons 71 + Spacer(minLength: 0) 72 72 } 73 73 } 74 74 75 + /// Click target + menu trigger for the icon. The whole preview tile 76 + /// is the action surface — clicking opens a popover menu with the 77 + /// three options. Drops the trailing button cluster so narrow 78 + /// Settings windows don't truncate "Choose Symbol…" / "Clear Icon". 79 + /// Pattern matches macOS native flows (System Settings user picture, 80 + /// Finder "Get Info" icon). 75 81 @ViewBuilder 76 - private var iconPreview: some View { 82 + private var iconMenu: some View { 83 + Menu { 84 + Button("Choose Symbol…") { 85 + isSymbolPickerPresented = true 86 + } 87 + Button("Choose Image…") { 88 + isImageImporterPresented = true 89 + } 90 + if store.appearance.icon != nil { 91 + Divider() 92 + Button("Clear Icon", role: .destructive) { 93 + store.send(.setAppearanceIcon(nil)) 94 + } 95 + } 96 + } label: { 97 + iconPreviewTile 98 + } 99 + .menuStyle(.borderlessButton) 100 + .menuIndicator(.hidden) 101 + .fixedSize() 102 + .help("Click to choose an icon for this repository") 103 + } 104 + 105 + @ViewBuilder 106 + private var iconPreviewTile: some View { 77 107 let frame = RoundedRectangle(cornerRadius: 8, style: .continuous) 78 108 let fill = Color.secondary.opacity(0.12) 79 109 Group { ··· 88 118 Image(systemName: "questionmark") 89 119 .font(.system(size: 16, weight: .semibold)) 90 120 .foregroundStyle(.tertiary) 121 + .accessibilityHidden(true) 91 122 } 92 123 } 93 124 .frame(width: previewSize, height: previewSize) 94 125 .background(fill, in: frame) 95 - .accessibilityLabel("Icon preview") 96 - } 97 - 98 - @ViewBuilder 99 - private var iconButtons: some View { 100 - HStack(spacing: 6) { 101 - Button("Choose Symbol…") { 102 - isSymbolPickerPresented = true 103 - } 104 - .help("Pick from a preset SF Symbol or enter any symbol name.") 105 - Button("Choose Image…") { 106 - isImageImporterPresented = true 107 - } 108 - .help("Import a PNG or SVG file as this repository's icon.") 109 - if store.appearance.icon != nil { 110 - Button("Clear Icon") { 111 - store.send(.setAppearanceIcon(nil)) 112 - } 113 - .help("Remove the current icon and stop showing one for this repo.") 114 - } 126 + .overlay { 127 + // Hover affordance: a 1.5pt accent border tells the user the 128 + // tile is actionable. We pair this with the link pointer style 129 + // below so the cursor change reinforces it. 130 + frame.stroke( 131 + Color.accentColor.opacity(isHoveringIconTile ? 0.65 : 0), 132 + lineWidth: 1.5 133 + ) 115 134 } 135 + .contentShape(.rect(cornerRadius: 8, style: .continuous)) 136 + .onHover { isHoveringIconTile = $0 } 137 + .pointerStyle(.link) 138 + .animation(.easeOut(duration: 0.12), value: isHoveringIconTile) 139 + .accessibilityLabel("Icon picker") 116 140 } 117 141 118 142 private var iconHelpText: String { ··· 126 150 case .bundledAsset: 127 151 return "Bundled icons keep their original artwork." 128 152 case nil: 129 - return "No icon set — the row in the sidebar shows just the repo name." 153 + return "No icon set. Click the tile to pick a symbol or import an image." 130 154 } 131 155 } 132 156