#!/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