native macOS codings agent orchestrator
6
fork

Configure Feed

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

fix(repo-appearance): make icon-tile menu hover and tint actually work

Three regressions from the menu-conversion commit, all rooted in
`.menuStyle(.borderlessButton)` on `Menu`:
- the menu's button chrome silently overrode `foregroundStyle` on the
preview, so the user-picked color stopped tinting the icon
- the menu intercepted pointer events on its label, so the inner
`.onHover` never fired and neither the accent border nor the
pointing-hand cursor showed
- the borderless menu style added implicit padding around the label
that varied with content, so the row's title and description text
shifted horizontally when the icon was cleared

Fix:
- Switch to `.buttonStyle(.plain)` (preserves `foregroundStyle`,
doesn't intercept hover, no extra padding).
- Move `.onHover`, the accent overlay, `.pointerStyle(.link)`, and the
tooltip to the **outer** Menu — the Menu's bounds (with `.fixedSize`
hugging the label) match the tile, so the ring overlays exactly.
- Lock `.frame(width: previewSize, height: previewSize)` and
`.contentShape(.rect(cornerRadius: 8))` on the inner label so the
tile is a rigid 40×40 click target whether icon is set or cleared.

UX additions:
- Empty state now shows a 1pt dashed secondary-color border around
the questionmark — the macOS "drop zone" / empty-avatar pattern.
Combined with the existing hover ring (solid accent on top), the
affordance reads at rest *and* on hover.
- Help tooltip and the no-icon description text both call out the
preview as the click target ("Click the icon preview to …") so a
user who didn't catch the hover affordance still gets pointed at it.

onevcat c4b3e907 86be9903

+42 -13
+42 -13
supacode/Features/RepositorySettings/Views/RepositoryAppearancePickerView.swift
··· 78 78 /// Settings windows don't truncate "Choose Symbol…" / "Clear Icon". 79 79 /// Pattern matches macOS native flows (System Settings user picture, 80 80 /// Finder "Get Info" icon). 81 + /// 82 + /// Implementation notes — `.buttonStyle(.plain)` (NOT 83 + /// `.menuStyle(.borderlessButton)`) is critical: the borderless 84 + /// menu style applies its own padding and a system tint that 85 + /// silently overrides the icon's `foregroundStyle`, so the user's 86 + /// chosen color stops appearing on the preview. Plain button style 87 + /// lets the label render exactly as authored. Hover detection, 88 + /// overlay border, pointer style, and tooltip all sit on the 89 + /// **outer** Menu — `.onHover` placed inside the Menu's label is 90 + /// swallowed by the menu's pointer interception. 81 91 @ViewBuilder 82 92 private var iconMenu: some View { 83 93 Menu { ··· 96 106 } label: { 97 107 iconPreviewTile 98 108 } 99 - .menuStyle(.borderlessButton) 109 + .buttonStyle(.plain) 100 110 .menuIndicator(.hidden) 101 111 .fixedSize() 102 - .help("Click to choose an icon for this repository") 112 + .overlay { 113 + RoundedRectangle(cornerRadius: 8, style: .continuous) 114 + .stroke( 115 + Color.accentColor.opacity(isHoveringIconTile ? 0.65 : 0), 116 + lineWidth: 1.5 117 + ) 118 + } 119 + .onHover { isHoveringIconTile = $0 } 120 + .pointerStyle(.link) 121 + .animation(.easeOut(duration: 0.12), value: isHoveringIconTile) 122 + .help("Click the icon preview to pick a symbol or import an image") 103 123 } 104 124 125 + /// Visual label for the menu trigger. The frame is locked to 126 + /// `previewSize × previewSize` so the row layout (and the title / 127 + /// description text alignment to its right) doesn't shift when the 128 + /// inner content swaps between the user's icon and the 129 + /// no-icon placeholder. `.contentShape` confines the click hit-test 130 + /// to the rounded rectangle so clicks just outside the visible 131 + /// tile don't open the menu. 132 + /// 133 + /// When no icon is set, a dashed border replaces the questionmark's 134 + /// silence with a "drop zone"-style affordance — the same pattern 135 + /// macOS uses for empty avatar / drag-target slots — so users see 136 + /// the tile is interactive even before they hover. 105 137 @ViewBuilder 106 138 private var iconPreviewTile: some View { 107 139 let frame = RoundedRectangle(cornerRadius: 8, style: .continuous) 108 140 let fill = Color.secondary.opacity(0.12) 141 + let hasIcon = store.appearance.icon != nil 109 142 Group { 110 143 if let icon = store.appearance.icon { 111 144 RepositoryIconImage( ··· 124 157 .frame(width: previewSize, height: previewSize) 125 158 .background(fill, in: frame) 126 159 .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 - ) 160 + if !hasIcon { 161 + frame.stroke( 162 + Color.secondary.opacity(0.55), 163 + style: StrokeStyle(lineWidth: 1, dash: [3, 3]) 164 + ) 165 + } 134 166 } 135 167 .contentShape(.rect(cornerRadius: 8, style: .continuous)) 136 - .onHover { isHoveringIconTile = $0 } 137 - .pointerStyle(.link) 138 - .animation(.easeOut(duration: 0.12), value: isHoveringIconTile) 139 168 .accessibilityLabel("Icon picker") 140 169 } 141 170 ··· 150 179 case .bundledAsset: 151 180 return "Bundled icons keep their original artwork." 152 181 case nil: 153 - return "No icon set. Click the tile to pick a symbol or import an image." 182 + return "No icon set. Click the icon preview to pick a symbol or import an image." 154 183 } 155 184 } 156 185