mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
fix(goals/new): avatar default icon + restore .goal-avatar color-mix
Two interlocking bugs on the new-goal modal's color/icon preview.
1. Avatar fell back to a literal "?" when icon + name were both
blank — `form.object.name.to_s.strip.first&.upcase || "?"`. User
reported the avatar looked empty on a fresh modal because the
"?" disappears against many palette tints. Categories handle
this by always showing the category icon. Replace the "?"
fallback chain with a default `target` icon (matches the goal
creation header's iconography):
• icon present → render that icon
• icon blank, name → render first letter
• icon blank, no name → render default "target" icon
2. Picking a color via the Pickr color picker called
`updateAvatarColors(color)` which inlined `style.backgroundColor`
+ `style.color = color` — overriding the `.goal-avatar` class's
`color-mix(in oklab, var(--avatar-color) 55%, black)` rule. The
class handles theme-aware contrast (darken text in light mode,
full color in dark mode); the inline override killed it and
text rendered at the same lightness as the 10% tint background.
Update only the `--avatar-color` CSS variable; let the class
continue computing the resolved colors.
Wire the avatar to the goal-stepper controller properly:
`_color_picker.html.erb` gains `data-goal-stepper-target="avatarPreview"`
on the span. `nameChanged` now updates the avatar directly (the
previous selector queried `[data-testid="goal-avatar"]` which
doesn't exist on the color_picker span) and:
- swaps to the first letter as the user types,
- restores the default-icon HTML (captured at connect) when the
name is cleared,
- bails when the user has explicitly checked an icon radio (don't
undo their choice).
This commit is contained in:
@@ -51,6 +51,11 @@ export default class extends Controller {
|
||||
connect() {
|
||||
this.currentStep = 1;
|
||||
this.refreshSubmitState();
|
||||
// Capture the default avatar contents (the "target" icon SVG) so we
|
||||
// can restore it when the user clears the name field after typing.
|
||||
if (this.hasAvatarPreviewTarget) {
|
||||
this._defaultAvatarHTML = this.avatarPreviewTarget.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
blockEnter(event) {
|
||||
@@ -108,10 +113,20 @@ export default class extends Controller {
|
||||
this.clearFieldError(this.nameInputTarget, this.hasNameErrorTarget ? this.nameErrorTarget : null);
|
||||
}
|
||||
if (!this.hasAvatarPreviewTarget || !this.hasNameInputTarget) return;
|
||||
|
||||
// If the user has explicitly picked an icon, leave it alone — name
|
||||
// changes shouldn't undo an explicit choice.
|
||||
const iconPicked = this.element.querySelector('input[name="goal[icon]"]:checked');
|
||||
if (iconPicked) return;
|
||||
|
||||
const name = this.nameInputTarget.value.trim();
|
||||
const initial = name ? name.charAt(0).toUpperCase() : "?";
|
||||
const inner = this.avatarPreviewTarget.querySelector('[data-testid="goal-avatar"]');
|
||||
if (inner) inner.textContent = initial;
|
||||
if (name) {
|
||||
this.avatarPreviewTarget.textContent = name.charAt(0).toUpperCase();
|
||||
} else if (this._defaultAvatarHTML) {
|
||||
// Captured at connect — restore the default "target" icon from the
|
||||
// server-rendered template, not a "?" character.
|
||||
this.avatarPreviewTarget.innerHTML = this._defaultAvatarHTML;
|
||||
}
|
||||
}
|
||||
|
||||
validateStep1() {
|
||||
|
||||
Reference in New Issue
Block a user