feat(ui): add outline/fill icon-swap pattern for aside icon bar
Introduce data-icon-swap attribute with paired outline/fill <i> elements. CSS toggles to filled icon on hover and active state. Module slots auto-derive fill variant from icon name, with explicit icon_fill override for icons without a fill counterpart (e.g. bi-headset). Add enforcement script bin/icon-swap-check.sh. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
90
bin/icon-swap-check.sh
Executable file
90
bin/icon-swap-check.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# icon-swap-check.sh — Enforce outline/fill icon-swap pattern
|
||||
# in the aside icon bar.
|
||||
#
|
||||
# Every <button> and <a> inside aside.aside-icon-bar must use
|
||||
# data-icon-swap with two <i> children:
|
||||
# <i class="bi bi-*" data-icon="outline"></i>
|
||||
# <i class="bi bi-*" data-icon="fill"></i>
|
||||
#
|
||||
# Usage: bin/icon-swap-check.sh
|
||||
# Exit: 0 = OK, 1 = violations found
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ICON_BAR_TEMPLATE="$ROOT/templates/partials/app-main-aside-icon-bar.phtml"
|
||||
ERRORS=0
|
||||
|
||||
echo "=== Icon-swap pattern check ==="
|
||||
|
||||
# ── 1. Core icon-bar template must exist ──
|
||||
if [[ ! -f "$ICON_BAR_TEMPLATE" ]]; then
|
||||
echo "FAIL: Icon bar template not found: $ICON_BAR_TEMPLATE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 2. Every <button> and <a> in icon bar must have data-icon-swap ──
|
||||
BUTTONS_WITH_SWAP=$(grep -c 'data-icon-swap' "$ICON_BAR_TEMPLATE" 2>/dev/null || true)
|
||||
|
||||
if [[ "$BUTTONS_WITH_SWAP" -eq 0 ]]; then
|
||||
echo "FAIL: No data-icon-swap attributes found in icon bar template"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# ── 3. Every data-icon-swap element must have both outline and fill children ──
|
||||
SWAP_COUNT=$(grep -c 'data-icon-swap' "$ICON_BAR_TEMPLATE" 2>/dev/null || true)
|
||||
OUTLINE_COUNT=$(grep -c 'data-icon="outline"' "$ICON_BAR_TEMPLATE" 2>/dev/null || true)
|
||||
FILL_COUNT=$(grep -c 'data-icon="fill"' "$ICON_BAR_TEMPLATE" 2>/dev/null || true)
|
||||
|
||||
if [[ "$SWAP_COUNT" -ne "$OUTLINE_COUNT" ]]; then
|
||||
echo "FAIL: Mismatch — $SWAP_COUNT data-icon-swap elements but $OUTLINE_COUNT data-icon=\"outline\" icons"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
if [[ "$SWAP_COUNT" -ne "$FILL_COUNT" ]]; then
|
||||
echo "FAIL: Mismatch — $SWAP_COUNT data-icon-swap elements but $FILL_COUNT data-icon=\"fill\" icons"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# ── 4. Module manifests with aside.tab_panel must declare icon ──
|
||||
for manifest in "$ROOT"/modules/*/module.php; do
|
||||
[[ -f "$manifest" ]] || continue
|
||||
MODULE_DIR="$(dirname "$manifest")"
|
||||
MODULE_ID="$(basename "$MODULE_DIR")"
|
||||
|
||||
# Skip modules that don't declare aside.tab_panel
|
||||
if ! grep -q "aside\.tab_panel" "$manifest"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check icon is declared
|
||||
if ! grep -qE "'icon'[[:space:]]*=>[[:space:]]*'bi-" "$manifest"; then
|
||||
echo "FAIL: Module '$MODULE_ID' declares aside.tab_panel but has no icon"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# ── 5. CSS must define the data-icon-swap rules ──
|
||||
CSS_FILE="$ROOT/web/css/layout/app-aside-icon-bar.css"
|
||||
if [[ -f "$CSS_FILE" ]]; then
|
||||
if ! grep -q '\[data-icon-swap\]' "$CSS_FILE"; then
|
||||
echo "FAIL: Missing [data-icon-swap] CSS rules in $CSS_FILE"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
else
|
||||
echo "FAIL: Icon bar CSS not found: $CSS_FILE"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# ── Result ──
|
||||
if [[ "$ERRORS" -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "FAILED: $ERRORS violation(s) found."
|
||||
echo "All icon-bar buttons/links must use the data-icon-swap pattern."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OK: All icon-bar elements use the outline/fill swap pattern."
|
||||
exit 0
|
||||
@@ -49,6 +49,7 @@ return [
|
||||
'key' => 'helpdesk',
|
||||
'label' => 'Helpdesk',
|
||||
'icon' => 'bi-headset',
|
||||
'icon_fill' => 'bi-headset',
|
||||
'href' => '',
|
||||
'permission' => 'helpdesk.access',
|
||||
'panel_template' => 'templates/aside-helpdesk-panel.phtml',
|
||||
|
||||
@@ -11,8 +11,9 @@ $hasAdminPanel = layoutHasAdminPanel($layoutAuth);
|
||||
<li>
|
||||
<button type="button" id="aside-tab-explorer" data-aside-target="explorer" role="tab"
|
||||
aria-selected="true" aria-controls="aside-panel-explorer" aria-label="<?php e(t('Explorer')); ?>"
|
||||
data-tooltip="<?php e(t('Explorer')); ?>" data-tooltip-pos="right">
|
||||
<i class="bi bi-house"></i>
|
||||
data-tooltip="<?php e(t('Explorer')); ?>" data-tooltip-pos="right" data-icon-swap>
|
||||
<i class="bi bi-house" data-icon="outline"></i>
|
||||
<i class="bi bi-house-fill" data-icon="fill"></i>
|
||||
</button>
|
||||
</li>
|
||||
<?php
|
||||
@@ -24,6 +25,7 @@ $hasAdminPanel = layoutHasAdminPanel($layoutAuth);
|
||||
$slotKey = $slot['key'] ?? '';
|
||||
$slotLabel = $slot['label'] ?? '';
|
||||
$slotIcon = $slot['icon'] ?? 'bi-puzzle';
|
||||
$slotIconFill = $slot['icon_fill'] ?? ($slotIcon !== '' ? $slotIcon . '-fill' : 'bi-puzzle-fill');
|
||||
$slotHref = $slot['href'] ?? '';
|
||||
$slotPermission = $slot['permission'] ?? '';
|
||||
if ($slotPermission !== '' && empty($layoutAuth[$slotPermission])) { continue; }
|
||||
@@ -39,8 +41,9 @@ $hasAdminPanel = layoutHasAdminPanel($layoutAuth);
|
||||
role="tab" aria-selected="false"
|
||||
aria-controls="aside-panel-<?php e($slotKey); ?>"
|
||||
aria-label="<?php e(t($slotLabel)); ?>"
|
||||
data-tooltip="<?php e(t($slotLabel)); ?>" data-tooltip-pos="right">
|
||||
<i class="bi <?php e($slotIcon); ?>"></i>
|
||||
data-tooltip="<?php e(t($slotLabel)); ?>" data-tooltip-pos="right" data-icon-swap>
|
||||
<i class="bi <?php e($slotIcon); ?>" data-icon="outline"></i>
|
||||
<i class="bi <?php e($slotIconFill); ?>" data-icon="fill"></i>
|
||||
</button>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
@@ -49,8 +52,10 @@ $hasAdminPanel = layoutHasAdminPanel($layoutAuth);
|
||||
<ul role="list">
|
||||
<li>
|
||||
<a href="<?php e($accountUrl); ?>" aria-label="<?php e(t('Account')); ?>"
|
||||
data-tooltip="<?php e($accountTooltip); ?>" data-tooltip-pos="right" data-aside-shortcut="account"><i
|
||||
class="bi bi-person-circle"></i></a>
|
||||
data-tooltip="<?php e($accountTooltip); ?>" data-tooltip-pos="right" data-aside-shortcut="account" data-icon-swap>
|
||||
<i class="bi bi-person" data-icon="outline"></i>
|
||||
<i class="bi bi-person-fill" data-icon="fill"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<?php if ($hasAdminPanel): ?>
|
||||
@@ -58,8 +63,9 @@ $hasAdminPanel = layoutHasAdminPanel($layoutAuth);
|
||||
<li>
|
||||
<button type="button" id="aside-tab-admin" data-aside-target="admin" role="tab" aria-selected="false"
|
||||
aria-controls="aside-panel-admin" aria-label="<?php e(t('Admin')); ?>"
|
||||
data-tooltip="<?php e(t('Admin')); ?>" data-tooltip-pos="right">
|
||||
<i class="bi bi-gear"></i>
|
||||
data-tooltip="<?php e(t('Admin')); ?>" data-tooltip-pos="right" data-icon-swap>
|
||||
<i class="bi bi-gear" data-icon="outline"></i>
|
||||
<i class="bi bi-gear-fill" data-icon="fill"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user