Audit buttons and selector controls
This commit is contained in:
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
+2
-1
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user