Skip to content

Commit cfab08a

Browse files
dermatzCopilot
andcommitted
feat: add warning styles and enhance audit checks for toolbar elements
Co-authored-by: Copilot <copilot@github.com>
1 parent 877edcd commit cfab08a

7 files changed

Lines changed: 78 additions & 18 deletions

File tree

src/view/frontend/web/css/toolbar.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
--mageforge-color-orange: #fb923c;
2424
--mageforge-color-pink: #C850C0;
2525
--mageforge-color-amber: #edb04d;
26+
--mageforge-color-amber-alpha-15: rgba(237, 176, 77, 0.15);
27+
--mageforge-color-amber-alpha-35: rgba(237, 176, 77, 0.35);
2628
--mageforge-bg-dark: rgba(15, 23, 42, 0.98);
2729
--mageforge-bg-dark-alt: rgba(30, 41, 59, 0.98);
2830
--mageforge-border-color: rgba(148, 163, 184, 0.15);
@@ -262,6 +264,15 @@
262264
color: var(--mageforge-color-red);
263265
}
264266

267+
.mageforge-toolbar-menu-item.mageforge-active--warning {
268+
background: var(--mageforge-color-amber-alpha-15);
269+
border-color: var(--mageforge-color-amber-alpha-35);
270+
}
271+
272+
.mageforge-toolbar-menu-item.mageforge-active--warning .mageforge-toolbar-menu-label {
273+
color: var(--mageforge-color-amber);
274+
}
275+
265276
.mageforge-toolbar-menu-icon {
266277
font-size: 16px;
267278
flex-shrink: 0;
@@ -367,6 +378,12 @@
367378
border: 1px solid var(--mageforge-color-red-alpha-35);
368379
}
369380

381+
.mageforge-toolbar-menu-status--warning {
382+
color: var(--mageforge-color-amber);
383+
background: var(--mageforge-color-amber-alpha-15);
384+
border: 1px solid var(--mageforge-color-amber-alpha-35);
385+
}
386+
370387
/* ============================================================================
371388
Menu Groups
372389
========================================================================== */
@@ -437,6 +454,12 @@
437454
z-index: 9999997;
438455
}
439456

457+
.mageforge-audit-overlay--warning {
458+
background-color: var(--mageforge-color-amber-alpha-35);
459+
outline-color: var(--mageforge-color-amber);
460+
outline-style: dashed;
461+
}
462+
440463
/* ============================================================================
441464
Feedback Toast
442465
========================================================================== */

src/view/frontend/web/js/toolbar/audits.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ export const auditMethods = {
129129
if (!status) return;
130130
status.textContent = message;
131131
status.className = `mageforge-toolbar-menu-status mageforge-toolbar-menu-status--${type}`;
132-
// Reflect error/success on the active item background
132+
// Reflect error/warning/success on the active item background
133133
item.classList.toggle('mageforge-active--error', type === 'error');
134+
item.classList.toggle('mageforge-active--warning', type === 'warning');
134135
},
135136
};

src/view/frontend/web/js/toolbar/audits/buttons-without-type.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { applyHighlight, clearHighlight } from './highlight.js';
1010

1111
/** @type {import('./index.js').AuditDefinition} */
1212
export default {
13-
key: 'button-without-type',
13+
key: 'buttons-without-type',
1414
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 13v-8.5a1.5 1.5 0 0 1 3 0v7.5"></path><path d="M11 11.5a1.5 1.5 0 0 1 3 0v1.5"></path><path d="M14 12a1.5 1.5 0 0 1 3 0v2"></path><path d="M17 13.5a1.5 1.5 0 0 1 3 0v3.5a6 6 0 0 1 -6 6h-2h.208a6 6 0 0 1 -5.012 -2.7l-.196 -.3c-.312 -.479 -1.407 -2.388 -3.286 -5.728a1.5 1.5 0 0 1 .536 -2.022a1.867 1.867 0 0 1 2.28 .28l1.47 1.47"></path></svg>',
1515
label: 'Buttons without a type',
1616
description: 'Highlight a button missing an explicit type attribute (defaults to submit)',
@@ -25,7 +25,9 @@ export default {
2525
return;
2626
}
2727

28-
const buttons = Array.from(document.querySelectorAll('button:not([type])')).filter(btn => {
28+
const buttons = Array.from(document.querySelectorAll('button')).filter(btn => {
29+
const type = btn.getAttribute('type');
30+
if (type !== null && type.trim() !== '') return false;
2931
if (!btn.offsetParent && getComputedStyle(btn).position !== 'fixed') return false;
3032
const style = getComputedStyle(btn);
3133
if (style.visibility === 'hidden' || style.display === 'none' || style.opacity === '0') return false;

src/view/frontend/web/js/toolbar/audits/duplicate-ids.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export default {
5656
this.key,
5757
`Duplicate: ${duplicateIdNames.join(', ')}`
5858
);
59+
} else {
60+
context.setAuditDescription(this.key, this.description);
5961
}
6062

6163
applyHighlight(duplicates, this.key, context);

src/view/frontend/web/js/toolbar/audits/empty-interactive.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export default {
3838
// Text content (excluding whitespace-only)
3939
if (el.textContent.trim()) return false;
4040

41-
// Child <img> with non-empty alt
42-
if (el.querySelector('img[alt]:not([alt=""])')) return false;
41+
// Child <img> with non-empty alt (trimmed)
42+
if (Array.from(el.querySelectorAll('img[alt]')).some(img => img.getAttribute('alt')?.trim())) return false;
4343

4444
// Child <svg> with a <title> element
4545
if (el.querySelector('svg title')?.textContent.trim()) return false;

src/view/frontend/web/js/toolbar/audits/highlight.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ function scheduleUpdate() {
5050
* Returns a cleanup function that removes the overlay and deregisters it.
5151
*
5252
* @param {Element} el
53+
* @param {'error'|'warning'} [severity='error']
5354
* @returns {function} cleanup
5455
*/
55-
function createOverlay(el) {
56+
function createOverlay(el, severity = 'error') {
5657
const overlay = document.createElement('span');
5758
overlay.className = AUDIT_OVERLAY_CLASS;
59+
if (severity === 'warning') overlay.classList.add('mageforge-audit-overlay--warning');
5860
document.body.appendChild(overlay);
5961

6062
function update() {
@@ -122,16 +124,22 @@ export function clearHighlight(key) {
122124
* the first result, and updates the counter badge on the toolbar menu item.
123125
* Works for any element type – no special casing required in audit code.
124126
*
125-
* @param {Element[]} elements - Elements to mark
126-
* @param {string} key - Audit key (e.g. 'images-without-alt')
127-
* @param {object} context - Alpine toolbar component instance
127+
* @param {Element[]} elements - Elements to mark
128+
* @param {string} key - Audit key (e.g. 'images-without-alt')
129+
* @param {object} context - Alpine toolbar component instance
130+
* @param {object} [options={}] - Options
131+
* @param {'error'|'warning'} [options.severity='error'] - Visual severity level
132+
* @param {boolean} [options.skipBadge=false] - Skip badge + scroll update
128133
*/
129-
export function applyHighlight(elements, key, context) {
134+
export function applyHighlight(elements, key, context, options = {}) {
135+
const severity = options.severity ?? 'error';
136+
const skipBadge = options.skipBadge ?? false;
137+
130138
// Never flag elements that are part of the MageForge Toolbar itself
131139
elements = elements.filter(el => !el.closest('.mageforge-toolbar'));
132140

133141
if (elements.length === 0) {
134-
context.setAuditCounterBadge(key, '0', 'success');
142+
if (!skipBadge) context.setAuditCounterBadge(key, '0', 'success');
135143
return;
136144
}
137145
const cls = `mageforge-audit-${key}`;
@@ -142,11 +150,13 @@ export function applyHighlight(elements, key, context) {
142150
existing.keys.add(key);
143151
} else {
144152
overlayRegistry.set(el, {
145-
cleanup: createOverlay(el),
153+
cleanup: createOverlay(el, severity),
146154
keys: new Set([key]),
147155
});
148156
}
149157
});
150-
elements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
151-
context.setAuditCounterBadge(key, `${elements.length}`, 'error');
158+
if (!skipBadge) {
159+
elements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
160+
context.setAuditCounterBadge(key, `${elements.length}`, severity);
161+
}
152162
}

src/view/frontend/web/js/toolbar/audits/images-without-alt.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,35 @@ export default {
2121
return;
2222
}
2323

24-
const images = Array.from(document.querySelectorAll('img')).filter(img => {
24+
const visible = Array.from(document.querySelectorAll('img')).filter(img => {
2525
if (!img.offsetParent && getComputedStyle(img).position !== 'fixed') return false;
2626
const style = getComputedStyle(img);
27-
if (style.visibility === 'hidden' || style.display === 'none' || style.opacity === '0') return false;
28-
return !img.hasAttribute('alt') || img.getAttribute('alt').trim() === '';
27+
return style.visibility !== 'hidden' && style.display !== 'none' && style.opacity !== '0';
2928
});
3029

31-
applyHighlight(images, this.key, context);
30+
// Errors: missing alt attribute or whitespace-only alt (e.g. alt=" ")
31+
const errors = visible.filter(img => {
32+
if (!img.hasAttribute('alt')) return true;
33+
const val = img.getAttribute('alt');
34+
return val.length > 0 && val.trim() === '';
35+
});
36+
37+
// Warnings: explicit alt="" – intentionally decorative, but flagged for review
38+
const warnings = visible.filter(img => img.getAttribute('alt') === '');
39+
40+
const total = errors.length + warnings.length;
41+
if (total === 0) {
42+
context.setAuditCounterBadge(this.key, '0', 'success');
43+
return;
44+
}
45+
46+
applyHighlight(errors, this.key, context, { skipBadge: true });
47+
applyHighlight(warnings, this.key, context, { severity: 'warning', skipBadge: true });
48+
49+
// Scroll to first issue (errors take priority)
50+
const first = errors[0] ?? warnings[0];
51+
if (first) first.scrollIntoView({ behavior: 'smooth', block: 'center' });
52+
53+
context.setAuditCounterBadge(this.key, `${total}`, errors.length > 0 ? 'error' : 'warning');
3254
},
3355
};

0 commit comments

Comments
 (0)