UI usability tweaks (#758)

This commit is contained in:
Leendert de Borst
2025-04-02 21:33:18 +02:00
committed by Leendert de Borst
parent 74c0ace2b5
commit 708cffc49e
2 changed files with 217 additions and 103 deletions

View File

@@ -666,44 +666,47 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
// Create the main content
popup.innerHTML = `
<button class="av-create-popup-close">
<svg class="av-icon" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div class="av-create-popup-mode-select">
<h3 class="av-create-popup-title">Create new alias</h3>
<div class="av-create-popup-modes">
<button class="av-create-popup-mode-btn av-create-popup-mode-random">
<div class="av-create-popup-mode-icon">
<svg class="av-icon" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
<path d="M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/>
</svg>
</div>
<div class="av-create-popup-mode-content">
<h4>Random alias</h4>
<p>Generate a random identity with random email</p>
</div>
</button>
<button class="av-create-popup-mode-btn av-create-popup-mode-custom">
<div class="av-create-popup-mode-icon">
<svg class="av-icon" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
<path d="M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/>
</svg>
</div>
<div class="av-create-popup-mode-content">
<h4>Username/password</h4>
<p>Create a custom username and password</p>
</div>
<div class="av-create-popup-header">
<div class="av-create-popup-title-container">
<h3 class="av-create-popup-title">Create random alias</h3>
<button class="av-create-popup-mode-dropdown">
<svg class="av-icon" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"/>
</svg>
</button>
</div>
</div>
<div class="av-create-popup-random-mode" style="display: none;">
<h3 class="av-create-popup-title">Create random alias</h3>
<div class="av-create-popup-help-text">Generate a random identity and AliasVault email address.</div>
<div class="av-create-popup-mode-dropdown-menu" style="display: none;">
<button class="av-create-popup-mode-option" data-mode="random">
<div class="av-create-popup-mode-icon">
<svg class="av-icon" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
<path d="M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/>
</svg>
</div>
<div class="av-create-popup-mode-content">
<h4>Random alias</h4>
<p>Random identity with random email</p>
</div>
</button>
<button class="av-create-popup-mode-option" data-mode="custom">
<div class="av-create-popup-mode-icon">
<svg class="av-icon" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
<path d="M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/>
</svg>
</div>
<div class="av-create-popup-mode-content">
<h4>Username/password</h4>
<p>Manual username and password</p>
</div>
</button>
</div>
<div class="av-create-popup-mode av-create-popup-random-mode">
<div class="av-create-popup-field-group">
<label for="service-name-input">Service name</label>
<input
@@ -716,14 +719,12 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
>
</div>
<div class="av-create-popup-actions">
<button id="back-btn" class="av-create-popup-back">Back</button>
<button id="cancel-btn" class="av-create-popup-cancel">Cancel</button>
<button id="save-btn" class="av-create-popup-save">Create alias</button>
<button id="save-btn" class="av-create-popup-save">Create and save alias</button>
</div>
</div>
<div class="av-create-popup-custom-mode" style="display: none;">
<h3 class="av-create-popup-title">Create username/password</h3>
<div class="av-create-popup-mode av-create-popup-custom-mode" style="display: none;">
<div class="av-create-popup-field-group">
<label for="custom-service-name">Service name</label>
<input
@@ -773,9 +774,8 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
</div>
</div>
<div class="av-create-popup-actions">
<button id="custom-back-btn" class="av-create-popup-back">Back</button>
<button id="custom-cancel-btn" class="av-create-popup-cancel">Cancel</button>
<button id="custom-save-btn" class="av-create-popup-save">Create credential</button>
<button id="custom-save-btn" class="av-create-popup-save">Create and save credential</button>
</div>
</div>
`;
@@ -789,13 +789,11 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
});
// Get all the elements
const modeSelect = popup.querySelector('.av-create-popup-mode-select') as HTMLElement;
const randomMode = popup.querySelector('.av-create-popup-random-mode') as HTMLElement;
const customMode = popup.querySelector('.av-create-popup-custom-mode') as HTMLElement;
const randomBtn = popup.querySelector('.av-create-popup-mode-random') as HTMLButtonElement;
const customBtn = popup.querySelector('.av-create-popup-mode-custom') as HTMLButtonElement;
const backBtn = popup.querySelector('#back-btn') as HTMLButtonElement;
const customBackBtn = popup.querySelector('#custom-back-btn') as HTMLButtonElement;
const dropdownMenu = popup.querySelector('.av-create-popup-mode-dropdown-menu') as HTMLElement;
const titleContainer = popup.querySelector('.av-create-popup-title-container') as HTMLElement;
const titleElement = popup.querySelector('.av-create-popup-title') as HTMLElement;
const cancelBtn = popup.querySelector('#cancel-btn') as HTMLButtonElement;
const customCancelBtn = popup.querySelector('#custom-cancel-btn') as HTMLButtonElement;
const saveBtn = popup.querySelector('#save-btn') as HTMLButtonElement;
@@ -806,7 +804,6 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
const customUsername = popup.querySelector('#custom-username') as HTMLInputElement;
const passwordPreview = popup.querySelector('#password-preview') as HTMLInputElement;
const regenerateBtn = popup.querySelector('#regenerate-password') as HTMLButtonElement;
const closeBtn = popup.querySelector('.av-create-popup-close') as HTMLButtonElement;
// Initialize password generator
const passwordGenerator = new PasswordGenerator({
@@ -829,28 +826,41 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
// Handle regenerate button click
regenerateBtn.addEventListener('click', generatePassword);
// Handle mode selection
randomBtn.addEventListener('click', () => {
modeSelect.style.display = 'none';
randomMode.style.display = 'block';
input.select();
// Handle mode switching via dropdown
const toggleDropdown = () => {
dropdownMenu.style.display = dropdownMenu.style.display === 'none' ? 'block' : 'none';
};
// Make both title container and dropdown button trigger the dropdown
titleContainer.addEventListener('click', (e) => {
e.stopPropagation();
toggleDropdown();
});
customBtn.addEventListener('click', () => {
modeSelect.style.display = 'none';
customMode.style.display = 'block';
customInput.select();
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!titleContainer.contains(e.target as Node) && !dropdownMenu.contains(e.target as Node)) {
dropdownMenu.style.display = 'none';
}
});
// Handle back buttons
backBtn.addEventListener('click', () => {
randomMode.style.display = 'none';
modeSelect.style.display = 'block';
});
customBackBtn.addEventListener('click', () => {
customMode.style.display = 'none';
modeSelect.style.display = 'block';
// Handle mode option clicks
dropdownMenu.querySelectorAll('.av-create-popup-mode-option').forEach(option => {
option.addEventListener('click', () => {
const mode = (option as HTMLElement).dataset.mode;
if (mode === 'random') {
titleElement.textContent = 'Create random alias';
popup.querySelector('.av-create-popup-help-text')!.textContent = 'Generate a random identity and AliasVault email address.';
randomMode.style.display = 'block';
customMode.style.display = 'none';
} else if (mode === 'custom') {
titleElement.textContent = 'Create username/password';
popup.querySelector('.av-create-popup-help-text')!.textContent = 'Create a traditional credential with manual email/username.';
randomMode.style.display = 'none';
customMode.style.display = 'block';
}
dropdownMenu.style.display = 'none';
});
});
/**
@@ -965,17 +975,15 @@ export async function createEditNamePopup(defaultName: string, rootContainer: HT
}
});
// Handle close button
closeBtn.addEventListener('click', () => {
closePopup(null);
});
// Handle click outside
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closePopup(null);
}
});
// Focus the input field
input.select();
});
}

View File

@@ -302,8 +302,7 @@ body {
max-width: 90vw;
transform: scale(0.95);
opacity: 0;
padding: 24px;
padding-top: 16px;
padding: 16px 24px;
transition: transform 0.2s ease, opacity 0.2s ease;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
@@ -314,41 +313,19 @@ body {
}
.av-create-popup-title {
margin: 0 0 24px 0;
font-size: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
font-size: 18px;
font-weight: 600;
color: #f8f9fa;
text-align: center;
}
.av-create-popup-close {
position: absolute;
right: 16px;
top: 16px;
padding: 6px;
background: #374151;
border: none;
cursor: pointer;
.av-create-popup-help-text {
margin: 4px 0 0;
font-size: 13px;
color: #9ca3af;
border-radius: 4px;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
z-index: 1;
}
.av-create-popup-close:hover {
color: #f8f9fa;
background: #4b5563;
}
.av-create-popup-close .av-icon {
width: 16px;
height: 16px;
text-align: center;
line-height: 1.4;
padding: 0 16px;
}
.av-create-popup-modes {
@@ -413,7 +390,6 @@ body {
.av-create-popup-input {
width: 100%;
padding: 8px 12px;
margin-bottom: 16px;
border: 1px solid #374151;
border-radius: 6px;
background: #374151;
@@ -453,7 +429,7 @@ body {
}
.av-create-popup-field-group {
margin-bottom: 2px;
margin-bottom: 24px;
}
.av-create-popup-field-group label {
@@ -635,4 +611,134 @@ body {
@keyframes fadeOut {
0% { opacity: 1; transform: scale(1.02); }
100% { opacity: 0; transform: scale(1); }
}
/* Create Popup Styles */
.av-create-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.av-create-popup-mode {
margin-top: 20px;
}
.av-create-popup-title-container {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
justify-content: center;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.av-create-popup-title-container:hover {
background-color: #374151;
}
.av-create-popup-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #f8f9fa;
}
.av-create-popup-mode-dropdown {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #9ca3af;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s;
}
.av-create-popup-mode-dropdown:hover {
background-color: #4b5563;
}
.av-create-popup-mode-dropdown .av-icon {
width: 16px;
height: 16px;
}
.av-create-popup-mode-dropdown-menu {
position: absolute;
left: 50%;
transform: translateX(-50%);
background: #1f2937;
border: 1px solid #374151;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
z-index: 1000;
min-width: 280px;
}
.av-create-popup-mode-dropdown-menu::before {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background: #1f2937;
border-left: 1px solid #374151;
border-top: 1px solid #374151;
transform: translateX(-50%) rotate(45deg);
}
.av-create-popup-mode-option {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
width: 100%;
border: none;
background: none;
cursor: pointer;
text-align: left;
transition: background-color 0.2s;
position: relative;
z-index: 100;
}
.av-create-popup-mode-option:hover {
background-color: #374151;
}
.av-create-popup-mode-option .av-create-popup-mode-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background-color: #374151;
border-radius: 8px;
color: #d68338;
}
.av-create-popup-mode-option .av-create-popup-mode-content {
flex: 1;
}
.av-create-popup-mode-option .av-create-popup-mode-content h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #f8f9fa;
}
.av-create-popup-mode-option .av-create-popup-mode-content p {
margin: 4px 0 0;
font-size: 12px;
color: #9ca3af;
}