diff --git a/app/javascript/controllers/color_icon_picker_controller.js b/app/javascript/controllers/color_icon_picker_controller.js index ec0ab6457..55138dd4a 100644 --- a/app/javascript/controllers/color_icon_picker_controller.js +++ b/app/javascript/controllers/color_icon_picker_controller.js @@ -85,8 +85,12 @@ export default class extends Controller { } updateAvatarColors(color) { - this.avatarTarget.style.backgroundColor = `${this.#backgroundColor(color)}`; - this.avatarTarget.style.color = color; + // Update the `--avatar-color` CSS variable instead of overriding + // `style.color` / `style.backgroundColor` directly. The `.goal-avatar` + // class does theme-aware `color-mix` work off the variable (light mode + // darkens the letter, dark mode uses the full color) — overriding the + // resolved values inline killed that contrast logic. + this.avatarTarget.style.setProperty("--avatar-color", color); } handleIconColorChange(e) { diff --git a/app/javascript/controllers/goal_stepper_controller.js b/app/javascript/controllers/goal_stepper_controller.js index e40737676..69695b648 100644 --- a/app/javascript/controllers/goal_stepper_controller.js +++ b/app/javascript/controllers/goal_stepper_controller.js @@ -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() { diff --git a/app/views/goals/_color_picker.html.erb b/app/views/goals/_color_picker.html.erb index 4462d5ccc..5ccba3b92 100644 --- a/app/views/goals/_color_picker.html.erb +++ b/app/views/goals/_color_picker.html.erb @@ -3,11 +3,14 @@