All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
147 lines
5.6 KiB
Bash
Executable File
147 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# audit-touch-targets.sh — Static analysis for 44px minimum touch targets on mobile.
|
|
# Scans JS templates for clickable elements, cross-references CSS for mobile sizing.
|
|
# Usage: bash scripts/audit-touch-targets.sh
|
|
|
|
set -euo pipefail
|
|
|
|
CSS_DIR="${1:-src/css}"
|
|
JS_DIR="${2:-src/js}"
|
|
PKG_DIR="${3:-packages}"
|
|
|
|
VIOLATIONS=0
|
|
|
|
# ── Phase 1: Build a map of class -> mobile sizing from CSS ──
|
|
# For each class, record whether it has min-width/min-height >= 44px in mobile breakpoint
|
|
declare -A HAS_MOBILE_SIZE # class -> "1" if it has adequate mobile touch target
|
|
declare -A CLOSE_BUTTONS # class -> "file:line" for close-button-like elements
|
|
|
|
# Known close button classes to audit specifically
|
|
CLOSE_CLASSES=(
|
|
"sw-banner__close"
|
|
"sw-toast__close"
|
|
"sw-dialog__close"
|
|
"sw-drawer__close"
|
|
"sw-tabs__arrow"
|
|
"modal-close"
|
|
)
|
|
|
|
# Scan CSS for mobile breakpoint rules with min-height/min-width >= 44px
|
|
for f in "$CSS_DIR"/*.css "$PKG_DIR"/*/css/main.css; do
|
|
[ -f "$f" ] || continue
|
|
in_mobile=0
|
|
lineno=0
|
|
current_selector=""
|
|
|
|
while IFS= read -r line; do
|
|
lineno=$((lineno + 1))
|
|
trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
|
|
# Enter mobile @media block
|
|
if [[ "$trimmed" =~ ^@media.*max-width.*768 ]]; then
|
|
in_mobile=1
|
|
continue
|
|
fi
|
|
|
|
if (( in_mobile )); then
|
|
# Track brace depth to know when we leave @media
|
|
if [[ "$trimmed" == "}" ]] && [[ -z "$current_selector" ]]; then
|
|
in_mobile=0
|
|
continue
|
|
fi
|
|
|
|
# Selector line
|
|
if [[ "$trimmed" == *"."* ]] && [[ "$trimmed" == *"{"* ]]; then
|
|
sel="${trimmed%%\{*}"
|
|
if [[ "$sel" =~ \.([a-zA-Z_][a-zA-Z0-9_-]*) ]]; then
|
|
current_selector="${BASH_REMATCH[1]}"
|
|
fi
|
|
# Check inline properties
|
|
if [[ "$trimmed" =~ min-height.*44px ]] || [[ "$trimmed" =~ min-height.*2\.75rem ]] || [[ "$trimmed" =~ min-height.*var\(--sp-12\) ]]; then
|
|
HAS_MOBILE_SIZE["$current_selector"]=1
|
|
fi
|
|
if [[ "$trimmed" =~ min-width.*44px ]] || [[ "$trimmed" =~ min-width.*2\.75rem ]]; then
|
|
HAS_MOBILE_SIZE["$current_selector"]=1
|
|
fi
|
|
[[ "$trimmed" == *"}"* ]] && current_selector=""
|
|
continue
|
|
fi
|
|
|
|
# Property line inside mobile block
|
|
if [[ -n "$current_selector" ]]; then
|
|
if [[ "$trimmed" =~ min-height.*44px ]] || [[ "$trimmed" =~ min-height.*2\.75rem ]] || [[ "$trimmed" =~ min-height.*var\(--sp-12\) ]]; then
|
|
HAS_MOBILE_SIZE["$current_selector"]=1
|
|
fi
|
|
if [[ "$trimmed" =~ min-width.*44px ]] || [[ "$trimmed" =~ min-width.*2\.75rem ]]; then
|
|
HAS_MOBILE_SIZE["$current_selector"]=1
|
|
fi
|
|
[[ "$trimmed" == "}" ]] && current_selector=""
|
|
fi
|
|
fi
|
|
done < "$f"
|
|
done
|
|
|
|
echo "╔══════════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Touch Target Audit Report ║"
|
|
echo "╠══════════════════════════════════════════════════════════════════════╣"
|
|
echo ""
|
|
|
|
# ── Phase 2: Check known close-button classes ──
|
|
echo "## Close Button Classes"
|
|
echo ""
|
|
for cls in "${CLOSE_CLASSES[@]}"; do
|
|
if [[ -n "${HAS_MOBILE_SIZE[$cls]:-}" ]]; then
|
|
printf " ✓ .%-30s — has mobile touch target\n" "$cls"
|
|
else
|
|
printf " ✗ .%-30s — MISSING min-width/min-height: 44px in mobile breakpoint\n" "$cls"
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
done
|
|
echo ""
|
|
|
|
# ── Phase 3: Scan JS for small interactive elements ──
|
|
echo "## Interactive Elements in JS Templates"
|
|
echo ""
|
|
|
|
# Find elements with onClick or <button that have small/no padding and no mobile override
|
|
for f in $(find "$JS_DIR" "$PKG_DIR" -name '*.js' -type f 2>/dev/null); do
|
|
[ -f "$f" ] || continue
|
|
lineno=0
|
|
while IFS= read -r line; do
|
|
lineno=$((lineno + 1))
|
|
|
|
# Look for onClick with a class
|
|
if [[ "$line" =~ onClick ]] && [[ "$line" =~ class.*\"([a-zA-Z_][a-zA-Z0-9_\ -]*)\" ]]; then
|
|
classes="${BASH_REMATCH[1]}"
|
|
for cls in $classes; do
|
|
# Skip known-good patterns (buttons with adequate padding)
|
|
[[ "$cls" == sw-btn* ]] && continue
|
|
[[ "$cls" == sw-menu__item* ]] && continue
|
|
[[ "$cls" == sw-dropdown__option* ]] && continue
|
|
[[ "$cls" == sw-tabs__tab* ]] && continue
|
|
# Check if this class has a mobile override
|
|
if [[ -z "${HAS_MOBILE_SIZE[$cls]:-}" ]]; then
|
|
# Only flag if it looks like a small interactive element
|
|
if [[ "$cls" == *close* ]] || [[ "$cls" == *btn* ]] || [[ "$cls" == *toggle* ]] || [[ "$cls" == *trigger* ]] || [[ "$cls" == *action* ]] || [[ "$cls" == *clear* ]] || [[ "$cls" == *delete* ]] || [[ "$cls" == *remove* ]]; then
|
|
printf " ? %-50s — .%s (no mobile min-size found)\n" "$(basename "$f"):$lineno" "$cls"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
done < "$f"
|
|
done
|
|
|
|
echo ""
|
|
echo "╚══════════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "Summary: $VIOLATIONS known violations in close-button classes"
|
|
echo ""
|
|
|
|
if (( VIOLATIONS > 0 )); then
|
|
echo "RESULT: FAIL — $VIOLATIONS close button(s) need min-width/min-height: 44px in @media (max-width: 768px)"
|
|
exit 1
|
|
else
|
|
echo "RESULT: PASS — all known interactive elements have adequate mobile touch targets"
|
|
exit 0
|
|
fi
|