-
Couldn't load subscription status.
- Fork 2.8k
Implementing an inline toggle button to show/hide password. #20611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a password visibility toggle button for the login screen, allowing users to show/hide their password input. The implementation adds an inline toggle button with SVG icons that appears on hover or focus, and refactors the CSS to support the new layout.
- Adds a toggle button with eye/eye-off SVG icons that switches between showing and hiding the password
- Wraps the password input in a span container to accommodate the toggle button
- Updates CSS to target specific input elements and style the new password input container with toggle button
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/Umbraco.Web.UI.Login/src/auth.element.ts | Adds createButton and createShowPasswordToggleItem functions, modifies createFormLayoutItem to optionally include the toggle button, and updates component initialization |
| src/Umbraco.Web.UI.Login/src/auth-styles.css | Refactors CSS to target username and password inputs separately, adds styles for the password container span and toggle button |
…changed the css to accommodate these changes
Added the svg's to their own const for easy reuse Added localization for the arialabel on the button Seperated the createFormLayoutItem so there is a seperate for the password input Moved all the conditional logic in the onclick event to fit inside one if/else statement
…enough for localization to load, and replaced it with a function. The function will try and resolve the promise by checking if the localize.terms methods returns a changed value, if not then it retries every 50ms or untill it hits a max retry of 40/2 seconds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had said in the past that the real solution was to use the <uui-input-password /> component, which supports this OOTB. However, it is not possible to use that component because many password managers do not support Shadow DOM, which is why we create the input elements in the root and move them in through slots to their final place.
I don't mind adding this to the login screen, but we need to ensure that we support the same browsers as the UI library and that we do not introduce the same bugs again. So I left a comment down below:
9139429 to
e294b05
Compare
…he uui libary. Now the button is always visible instead of appearing on hover or when in focus
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
| const closedEyeSVG = ` | ||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"> | ||
| <path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"></path> | ||
| <path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"></path> | ||
| <path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"></path> | ||
| <line x1="2" x2="22" y1="2" y2="22"></line> | ||
| </svg> |
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation between the two SVG definitions. The closedEyeSVG has extra leading tabs compared to openEyeSVG. Align the indentation for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MrHutmat, I think we could benefit a bit by cutting these two SVG codes out into real .svg files, which you then import into the app. Vite will automatically bundle it up based on asset rules and load it most efficiently. This approach also has the added benefit of reducing the number of lines in this file as well as solving this Copilot suggestion.
| async #waitForLocalization(): Promise<void> { | ||
| return new Promise((resolve) => { | ||
| // Check if localization is already available | ||
| if (this.localize.term('auth_showPassword') !== 'auth_showPassword') { | ||
| resolve(); | ||
| return; | ||
| } | ||
|
|
||
| let retryCount = 0; | ||
| //Retries 40 times with a 50ms interval = 2 seconds | ||
| const maxRetries = 40; | ||
|
|
||
| // If not, we check periodically until it it is available or we reach the max retries | ||
| const checkInterval = setInterval(() => { | ||
| if (this.localize.term('auth_showPassword') !== 'auth_showPassword' || retryCount >= maxRetries) { | ||
| clearInterval(checkInterval); | ||
| resolve(); | ||
| } | ||
| retryCount++; | ||
| }, 50); | ||
| }); | ||
| } |
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The polling interval runs indefinitely if localization never loads. Consider adding a timeout or rejection mechanism to handle the case where maxRetries is reached without successful localization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a comment here to consider from Copilot. The logic looks OK to me, so I don't know where it sees that the logic would keep running. I do, however, think we should separate the two checks in the if-sentence into two separate if-sentences, so that you can reject() the Promise if localization is never available. That way, you'll stop the form from rendering.
if (retryCount > maxRetries) {
clearInterval(checkInterval);
reject('Localization not available');
return;
}
if (this.localize.term('auth_showPassword') !== 'auth_showPassword') {
clearInterval(checkInterval);
resolve();
return;
}
retryCount++;Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks OK, but based on Copilot feedback and my own findings, I have made two suggestions:
| const closedEyeSVG = ` | ||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"> | ||
| <path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"></path> | ||
| <path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"></path> | ||
| <path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"></path> | ||
| <line x1="2" x2="22" y1="2" y2="22"></line> | ||
| </svg> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MrHutmat, I think we could benefit a bit by cutting these two SVG codes out into real .svg files, which you then import into the app. Vite will automatically bundle it up based on asset rules and load it most efficiently. This approach also has the added benefit of reducing the number of lines in this file as well as solving this Copilot suggestion.
| async #waitForLocalization(): Promise<void> { | ||
| return new Promise((resolve) => { | ||
| // Check if localization is already available | ||
| if (this.localize.term('auth_showPassword') !== 'auth_showPassword') { | ||
| resolve(); | ||
| return; | ||
| } | ||
|
|
||
| let retryCount = 0; | ||
| //Retries 40 times with a 50ms interval = 2 seconds | ||
| const maxRetries = 40; | ||
|
|
||
| // If not, we check periodically until it it is available or we reach the max retries | ||
| const checkInterval = setInterval(() => { | ||
| if (this.localize.term('auth_showPassword') !== 'auth_showPassword' || retryCount >= maxRetries) { | ||
| clearInterval(checkInterval); | ||
| resolve(); | ||
| } | ||
| retryCount++; | ||
| }, 50); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a comment here to consider from Copilot. The logic looks OK to me, so I don't know where it sees that the logic would keep running. I do, however, think we should separate the two checks in the if-sentence into two separate if-sentences, so that you can reject() the Promise if localization is never available. That way, you'll stop the form from rendering.
if (retryCount > maxRetries) {
clearInterval(checkInterval);
reject('Localization not available');
return;
}
if (this.localize.term('auth_showPassword') !== 'auth_showPassword') {
clearInterval(checkInterval);
resolve();
return;
}
retryCount++;|
@MrHutmat > Apart from the suggestions above, I think we should consider rebasing this PR to v17/dev, so that it will land in 17.1. Otherwise, 16.4 will have a feature that will not be available in 17.0. However, I think this branch is too far ahead to rebase it directly, but we can wait to merge it in until v17/dev has been merged into the main branch, which should happen either next week, when 16.4 is built, or in two weeks, when 16.4-rc is released. |
Changed the logic for waitForLocallization Added the svg's as files that are imported instead of having the raw svg in the code
Implemented an inline toggle button to show/hide your password, also changed the css to accommodate these changes, for the login screen
Description
Created methods to add a button for the toggle as well as a method for creating the span element that contains the button.
The button has an SVG attached that changes depending on the toggle state.
The button's onClick event targets the field for the password and changes its type from "password" to "text"
The button only appears if you hover the span or have focus-within in the span element.
Wrapped the password input in a new span element so I could manipulate the input field's length
I have removed some of the styling that wasn't used, as well as added CSS that specifically targets each input element (username and password).