Profile sidebar: replace raw 'Choose File' input with styled 'Upload profile image' button #619

Closed
opened 2026-09-16 11:33:33 +00:00 by crueber · 1 comment
Owner

What's requested

Replace the raw <input type="file"> ("Choose File") control in the user profile sidebar with a styled "Upload profile image" button using the hidden-input pattern, and render the current constraint text as helper copy beneath the button.

Evidence

web/src/pages/Repos.jsx (~lines 759–774, inside the profile-sidebar action stack, gated on isSelf()):

<label class="flex w-full flex-col items-center gap-1 text-center text-xs">
  <span class="muted block">upload avatar (PNG/JPEG/GIF, ≤ 2 MiB — cropped square)</span>
  <input
    type="file"
    accept="image/png,image/jpeg,image/gif"
    class="w-full text-xs"
    onChange={(e) => { uploadAvatar(e.currentTarget.files?.[0]); e.currentTarget.value = ""; }}
  />
</label>

The browser's native file input renders unstyled and clashes with the surrounding button stack ("Edit profile", "Regenerate avatar", "Remove avatar" — all .btn w-full justify-center px-3 py-1).

Architecture notes

  • Static diagnosis only (code reading; no runtime claims).
  • The sidebar already has the canonical button idiom one row away: .btn w-full justify-center px-3 py-1. Reuse it — Tailwind utilities only, no ad-hoc CSS.
  • Sibling pages have the same raw-input pattern (web/src/pages/Org.jsx ~197 org avatar, web/src/pages/Release.jsx ~342, web/src/pages/Repos.jsx ~769 import). Out of scope here; if a row-wide standard is wanted, file separately and cross-reference.
  • Keep the existing behavior byte-for-byte: same accept list, same onChange (upload + reset e.currentTarget.value = ""), same isSelf() gate and placement in the action stack.

Proposed shape

<label class="w-full">
  <input type="file" accept="image/png,image/jpeg,image/gif" class="hidden"
    onChange={(e) => { uploadAvatar(e.currentTarget.files?.[0]); e.currentTarget.value = ""; }} />
  <span class="btn w-full justify-center px-3 py-1 cursor-pointer" role="button">
    Upload profile image
  </span>
  <span class="muted mt-1 block text-center text-xs">PNG/JPEG/GIF, ≤ 2 MiB — cropped square</span>
</label>

Acceptance criteria

  • The sidebar shows a styled "Upload profile image" button (.btn idiom, full width, matching the sibling buttons) — no raw "Choose File" input visible.
  • The file input is functional (clicking the button opens the picker) and resets its value after each upload.
  • accept="image/png,image/jpeg,image/gif" unchanged; upload still gated on isSelf() and stays above "Regenerate avatar".
  • Constraint text renders as centered muted helper copy below the button.
  • Tailwind utilities only; no new ad-hoc CSS.
  • Render verification before close: headless DOM assertions (input is hidden, button text and helper copy present) and/or screenshot review of the profile sidebar.
## What's requested Replace the raw `<input type="file">` ("Choose File") control in the user profile sidebar with a styled **"Upload profile image"** button using the hidden-input pattern, and render the current constraint text as helper copy beneath the button. ## Evidence `web/src/pages/Repos.jsx` (~lines 759–774, inside the profile-sidebar action stack, gated on `isSelf()`): ```jsx <label class="flex w-full flex-col items-center gap-1 text-center text-xs"> <span class="muted block">upload avatar (PNG/JPEG/GIF, ≤ 2 MiB — cropped square)</span> <input type="file" accept="image/png,image/jpeg,image/gif" class="w-full text-xs" onChange={(e) => { uploadAvatar(e.currentTarget.files?.[0]); e.currentTarget.value = ""; }} /> </label> ``` The browser's native file input renders unstyled and clashes with the surrounding button stack ("Edit profile", "Regenerate avatar", "Remove avatar" — all `.btn w-full justify-center px-3 py-1`). ## Architecture notes - Static diagnosis only (code reading; no runtime claims). - The sidebar already has the canonical button idiom one row away: `.btn w-full justify-center px-3 py-1`. Reuse it — Tailwind utilities only, no ad-hoc CSS. - Sibling pages have the same raw-input pattern (`web/src/pages/Org.jsx` ~197 org avatar, `web/src/pages/Release.jsx` ~342, `web/src/pages/Repos.jsx` ~769 import). Out of scope here; if a row-wide standard is wanted, file separately and cross-reference. - Keep the existing behavior byte-for-byte: same `accept` list, same `onChange` (upload + reset `e.currentTarget.value = ""`), same `isSelf()` gate and placement in the action stack. ## Proposed shape ```jsx <label class="w-full"> <input type="file" accept="image/png,image/jpeg,image/gif" class="hidden" onChange={(e) => { uploadAvatar(e.currentTarget.files?.[0]); e.currentTarget.value = ""; }} /> <span class="btn w-full justify-center px-3 py-1 cursor-pointer" role="button"> Upload profile image </span> <span class="muted mt-1 block text-center text-xs">PNG/JPEG/GIF, ≤ 2 MiB — cropped square</span> </label> ``` ## Acceptance criteria - [ ] The sidebar shows a styled "Upload profile image" button (`.btn` idiom, full width, matching the sibling buttons) — no raw "Choose File" input visible. - [ ] The file input is functional (clicking the button opens the picker) and resets its value after each upload. - [ ] `accept="image/png,image/jpeg,image/gif"` unchanged; upload still gated on `isSelf()` and stays above "Regenerate avatar". - [ ] Constraint text renders as centered muted helper copy below the button. - [ ] Tailwind utilities only; no new ad-hoc CSS. - [ ] Render verification before close: headless DOM assertions (input is `hidden`, button text and helper copy present) and/or screenshot review of the profile sidebar.
crueber added this to the v1 milestone 2026-09-16 11:33:38 +00:00
Author
Owner

Fixed by #620 (merged): hidden-input pattern with styled Upload profile image button (.btn idiom) + muted helper copy; accept/onChange/isSelf/placement byte-identical. Review fixed a keyboard regression pre-merge (display:none removed Tab access → peer sr-only + focus-visible ring). Verified: 1599 unit green (smoke excluded, pre-existing), vite/esbuild green, independent review APPROVE.

Fixed by #620 (merged): hidden-input pattern with styled Upload profile image button (.btn idiom) + muted helper copy; accept/onChange/isSelf/placement byte-identical. Review fixed a keyboard regression pre-merge (display:none removed Tab access → peer sr-only + focus-visible ring). Verified: 1599 unit green (smoke excluded, pre-existing), vite/esbuild green, independent review APPROVE.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#619
No description provided.