Audit buttons and selector controls

This commit is contained in:
2026-08-05 17:53:44 +02:00
parent 310e1e239f
commit 5882edb994
7 changed files with 39 additions and 18 deletions
+1
View File
@@ -52,6 +52,7 @@ This project replaces the Excel/VBA workflow named `Catalog maker - 20` with a N
- Use one shared icon size, gap, alignment, hover state and disabled state for equivalent controls. Do not mix icon-only, text-only and icon-after-text variants for the same control family.
- Before finishing any UI change, inspect the rendered page with Playwright and verify that icons are visible, precede their labels, remain centered, and do not overlap or drift between controls. Update this file when a new reusable visual rule is discovered.
- Custom select controls may use a native select as an interaction layer, but the native label must be visually transparent when a separate display label is rendered; never show the same control text twice.
- Runtime button labels must update the existing direct child label span instead of replacing `button.textContent`; state changes must never remove the button icon.
## TypeScript workflow
+15 -6
View File
@@ -351,6 +351,15 @@ function createOption(value, label) {
option.textContent = label;
return option;
}
function setButtonLabel(button, label) {
const labelElement = button.querySelector(":scope > span");
if (labelElement) {
labelElement.textContent = label;
}
else {
button.textContent = label;
}
}
async function loadLanguages() {
languageSelect.replaceChildren(createOption("", "Select language"));
try {
@@ -621,7 +630,7 @@ async function findSourcesForCurrentProduct() {
}
const supplierColorGroup = getFirstSupplierColorGroup();
findSourcesButton.disabled = true;
findSourcesButton.textContent = "Finding sources...";
setButtonLabel(findSourcesButton, "Finding sources...");
try {
const params = new URLSearchParams({
manufacturerId: manufacturerSelect.value,
@@ -667,7 +676,7 @@ async function getPicturesForCurrentProduct(sourceKey = "") {
}
const supplierColorGroup = getFirstSupplierColorGroup();
getPicturesButton.disabled = true;
getPicturesButton.textContent = "Loading pictures...";
setButtonLabel(getPicturesButton, "Loading pictures...");
try {
const params = new URLSearchParams({
manufacturerId: manufacturerSelect.value,
@@ -695,7 +704,7 @@ async function getPicturesForCurrentProduct(sourceKey = "") {
}
finally {
getPicturesButton.disabled = false;
getPicturesButton.textContent = "Get pictures";
setButtonLabel(getPicturesButton, "Get pictures");
updateNavigationState();
}
}
@@ -820,7 +829,7 @@ function clearLocalDraft() {
}
function updateFindSourcesButtonState() {
const hasSources = Boolean(readLocalDraft()?.sources);
findSourcesButton.textContent = hasSources ? "Find sources ✓" : "Find sources";
setButtonLabel(findSourcesButton, hasSources ? "Find sources ✓" : "Find sources");
findSourcesButton.disabled = !manufacturerSelect.value || !currentProduct;
}
function renderSourceCandidateRow(candidate) {
@@ -1212,7 +1221,7 @@ async function loadProductInfoForCurrentProduct() {
return;
}
loadProductButton.disabled = true;
loadProductButton.textContent = "Loading info...";
setButtonLabel(loadProductButton, "Loading info...");
try {
const params = new URLSearchParams({
productId: currentProduct.idProduct,
@@ -1235,7 +1244,7 @@ async function loadProductInfoForCurrentProduct() {
setTableMessage(error.message);
}
finally {
loadProductButton.textContent = "Get product info - 00";
setButtonLabel(loadProductButton, "Get product info - 00");
updateNavigationState();
}
}
+15 -6
View File
@@ -371,6 +371,15 @@ function createOption(value, label) {
return option;
}
function setButtonLabel(button, label) {
const labelElement = button.querySelector(":scope > span");
if (labelElement) {
labelElement.textContent = label;
} else {
button.textContent = label;
}
}
async function loadLanguages() {
languageSelect.replaceChildren(createOption("", "Select language"));
try {
@@ -675,7 +684,7 @@ async function findSourcesForCurrentProduct() {
const supplierColorGroup = getFirstSupplierColorGroup();
findSourcesButton.disabled = true;
findSourcesButton.textContent = "Finding sources...";
setButtonLabel(findSourcesButton, "Finding sources...");
try {
const params = new URLSearchParams({
@@ -725,7 +734,7 @@ async function getPicturesForCurrentProduct(sourceKey = "") {
const supplierColorGroup = getFirstSupplierColorGroup();
getPicturesButton.disabled = true;
getPicturesButton.textContent = "Loading pictures...";
setButtonLabel(getPicturesButton, "Loading pictures...");
try {
const params = new URLSearchParams({
@@ -752,7 +761,7 @@ async function getPicturesForCurrentProduct(sourceKey = "") {
setTableMessage(error.message || "Picture lookup failed.");
} finally {
getPicturesButton.disabled = false;
getPicturesButton.textContent = "Get pictures";
setButtonLabel(getPicturesButton, "Get pictures");
updateNavigationState();
}
}
@@ -900,7 +909,7 @@ function clearLocalDraft() {
function updateFindSourcesButtonState() {
const hasSources = Boolean(readLocalDraft()?.sources);
findSourcesButton.textContent = hasSources ? "Find sources ✓" : "Find sources";
setButtonLabel(findSourcesButton, hasSources ? "Find sources ✓" : "Find sources");
findSourcesButton.disabled = !manufacturerSelect.value || !currentProduct;
}
@@ -1328,7 +1337,7 @@ async function loadProductInfoForCurrentProduct() {
}
loadProductButton.disabled = true;
loadProductButton.textContent = "Loading info...";
setButtonLabel(loadProductButton, "Loading info...");
try {
const params = new URLSearchParams({
@@ -1354,7 +1363,7 @@ async function loadProductInfoForCurrentProduct() {
} catch (error) {
setTableMessage(error.message);
} finally {
loadProductButton.textContent = "Get product info - 00";
setButtonLabel(loadProductButton, "Get product info - 00");
updateNavigationState();
}
}
+1
View File
@@ -15,4 +15,5 @@
<symbol id="globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M3 12h18M12 3c2.3 2.4 3.4 5.4 3.4 9S14.3 18.6 12 21M12 3c-2.3 2.4-3.4 5.4-3.4 9S9.7 18.6 12 21" fill="none" stroke="currentColor" stroke-width="1.4"/></symbol>
<symbol id="tag" viewBox="0 0 24 24"><path d="m4 5 8.5-.5L20 12l-8 8-7.5-7.5L4 5Z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="1.8"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/></symbol>
<symbol id="close" viewBox="0 0 24 24"><path d="m6 6 12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.8"/></symbol>
<symbol id="refresh" viewBox="0 0 24 24"><path d="M20 11a8 8 0 0 0-14.8-3L3 11m0 0V5m0 6h6M4 13a8 8 0 0 0 14.8 3L21 13m0 0v6m0-6h-6" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8"/></symbol>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

+4 -4
View File
@@ -134,7 +134,7 @@
<section class="suggested-dialog" aria-labelledby="suggestedTitle">
<header class="suggested-header">
<h2 id="suggestedTitle">Sugested product for</h2>
<button class="modal-close" id="suggestedCloseButton" type="button">Close</button>
<button class="modal-close" id="suggestedCloseButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#close"></use></svg><span>Close</span></button>
</header>
<div class="suggested-list" id="suggestedList"></div>
</section>
@@ -144,7 +144,7 @@
<section class="source-dialog" aria-labelledby="sourceTitle">
<header class="suggested-header">
<h2 id="sourceTitle">Source candidates</h2>
<button class="modal-close" id="sourceCloseButton" type="button">Close</button>
<button class="modal-close" id="sourceCloseButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#close"></use></svg><span>Close</span></button>
</header>
<div class="source-list" id="sourceList"></div>
</section>
@@ -154,7 +154,7 @@
<section class="settings-dialog" aria-labelledby="settingsTitle">
<header class="suggested-header">
<h2 id="settingsTitle">Settings</h2>
<button class="modal-close" id="settingsCloseButton" type="button">Close</button>
<button class="modal-close" id="settingsCloseButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#close"></use></svg><span>Close</span></button>
</header>
<div class="settings-content">
<fieldset class="settings-group">
@@ -192,7 +192,7 @@
<span>Password</span>
<input id="localDbPassword" type="text" autocomplete="off" />
</label>
<button class="settings-test-button" id="testLocalDbButton" type="button">Test connection</button>
<button class="settings-test-button" id="testLocalDbButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#refresh"></use></svg><span>Test connection</span></button>
<p class="settings-note" id="localDbTestResult">Connection not tested.</p>
</div>
<p class="settings-note" id="databaseModeNote">Local 9bplus database selected.</p>
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -172,7 +172,8 @@
.settings-dialog { @apply max-w-md; }
.suggested-header { @apply flex items-center justify-between border-b border-slate-300 bg-slate-200 px-3 py-2; }
.suggested-header h2 { @apply m-0 text-sm font-semibold text-slate-800; }
.modal-close, .settings-test-button { @apply rounded border border-slate-300 bg-white px-2 py-1 text-[11px] text-slate-800 shadow-sm hover:bg-slate-100; }
.modal-close, .settings-test-button { @apply inline-flex items-center justify-center gap-1.5 rounded border border-slate-300 bg-white px-2 py-1 text-[11px] text-slate-800 shadow-sm hover:bg-slate-100; }
.modal-close .sidebar-icon, .settings-test-button .sidebar-icon { @apply h-3.5 w-3.5 text-slate-500; }
.suggested-list, .source-list, .settings-content { @apply max-h-[calc(90vh-48px)] overflow-y-auto p-2; }
.settings-content { @apply grid gap-2; }
.settings-group { @apply m-0 rounded border border-slate-300 p-2; }