summaryrefslogtreecommitdiff
path: root/ui/tailwindcss/.claude/hooks/pre-commit
blob: c7e85b231e8d182d3a6d39a7cd5f114c2c05ba62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/bin/bash

# TailwindCSS Pre-commit Hook
# Validates TailwindCSS usage and optimizations before commits

set -e

echo "🎨 Running TailwindCSS pre-commit checks..."

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Function to print colored output
print_status() {
    local color=$1
    local message=$2
    echo -e "${color}${message}${NC}"
}

# Check if TailwindCSS config exists
check_tailwind_config() {
    print_status $BLUE "Checking TailwindCSS configuration..."
    
    if [[ ! -f "tailwind.config.js" && ! -f "tailwind.config.ts" ]]; then
        print_status $RED "❌ No TailwindCSS configuration file found"
        exit 1
    fi
    
    print_status $GREEN "✅ TailwindCSS configuration found"
}

# Validate CSS utility usage patterns
validate_utility_patterns() {
    print_status $BLUE "Validating TailwindCSS utility patterns..."
    
    # Check for overly long class strings (potential refactoring candidates)
    local long_classes=$(grep -r "class[Name]*=['\"][^'\"]*['\"]" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null | \
                        sed -E 's/.*class[Name]*=["'\''`]([^"'\''`]*)["'\''`].*/\1/' | \
                        awk 'length($0) > 150 { print FILENAME ":" FNR ":" $0 }' || true)
    
    if [[ -n "$long_classes" ]]; then
        print_status $YELLOW "⚠️  Found potentially complex utility combinations (>150 characters):"
        echo "$long_classes"
        print_status $YELLOW "Consider extracting these into components or using @apply directive"
    fi
    
    # Check for hardcoded colors (should use design tokens)
    local hardcoded_colors=$(grep -r "bg-\(red\|blue\|green\|yellow\|purple\|pink\|indigo\)-[0-9]" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null || true)
    
    if [[ -n "$hardcoded_colors" ]]; then
        print_status $YELLOW "⚠️  Found hardcoded color utilities. Consider using semantic color tokens:"
        echo "$hardcoded_colors" | head -5
    fi
    
    print_status $GREEN "✅ Utility patterns validated"
}

# Check for responsive design patterns
validate_responsive_patterns() {
    print_status $BLUE "Checking responsive design patterns..."
    
    # Look for mobile-first violations (desktop-first patterns)
    local desktop_first=$(grep -r "class[Name]*=['\"][^'\"]*['\"]" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null | \
                         sed -E 's/.*class[Name]*=["'\''`]([^"'\''`]*)["'\''`].*/\1/' | \
                         grep -E "(^| )(block|flex|grid|hidden)" | \
                         grep -E "(lg|xl|2xl):(block|flex|grid|hidden)" | \
                         grep -vE "(sm|md):" | head -5 || true)
    
    if [[ -n "$desktop_first" ]]; then
        print_status $YELLOW "⚠️  Potential desktop-first patterns detected. Consider mobile-first approach:"
        echo "$desktop_first"
    fi
    
    print_status $GREEN "✅ Responsive patterns checked"
}

# Build and analyze CSS bundle size
analyze_bundle_size() {
    print_status $BLUE "Analyzing CSS bundle size..."
    
    # Check if build script exists
    if npm run --silent 2>/dev/null | grep -q "build\|build:css"; then
        # Build CSS
        npm run build:css >/dev/null 2>&1 || npm run build >/dev/null 2>&1 || {
            print_status $YELLOW "⚠️  Could not run CSS build command"
            return 0
        }
        
        # Find the generated CSS file
        local css_file=$(find . -name "*.css" -path "*/dist/*" -o -path "*/build/*" -o -path "*/.next/static/css/*" 2>/dev/null | head -1)
        
        if [[ -n "$css_file" && -f "$css_file" ]]; then
            local size=$(wc -c < "$css_file")
            local size_kb=$((size / 1024))
            
            print_status $GREEN "📊 CSS bundle size: ${size_kb}KB"
            
            # Warn if bundle is large
            if [[ $size_kb -gt 100 ]]; then
                print_status $YELLOW "⚠️  CSS bundle is large (${size_kb}KB). Consider optimization:"
                print_status $YELLOW "   - Review unused utilities"
                print_status $YELLOW "   - Optimize content paths in tailwind.config.js"
                print_status $YELLOW "   - Use CSS purging effectively"
            fi
        else
            print_status $YELLOW "⚠️  Could not find generated CSS file"
        fi
    else
        print_status $YELLOW "⚠️  No build script found in package.json"
    fi
}

# Check for accessibility considerations
validate_accessibility() {
    print_status $BLUE "Checking accessibility patterns..."
    
    # Check for focus states on interactive elements
    local missing_focus=$(grep -r "class[Name]*=['\"][^'\"]*['\"]" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null | \
                         grep -E "(button|input|select|textarea)" | \
                         grep -v "focus:" | head -3 || true)
    
    if [[ -n "$missing_focus" ]]; then
        print_status $YELLOW "⚠️  Interactive elements without focus states detected:"
        echo "$missing_focus"
        print_status $YELLOW "Consider adding focus: states for accessibility"
    fi
    
    # Check for proper contrast utilities
    local low_contrast=$(grep -r "text-gray-[123]00" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null || true)
    
    if [[ -n "$low_contrast" ]]; then
        print_status $YELLOW "⚠️  Potentially low contrast text colors found:"
        echo "$low_contrast" | head -3
        print_status $YELLOW "Verify accessibility contrast ratios"
    fi
    
    print_status $GREEN "✅ Accessibility patterns checked"
}

# Check for performance anti-patterns
validate_performance() {
    print_status $BLUE "Checking performance patterns..."
    
    # Check for layout-shifting animations
    local layout_animations=$(grep -r "transition-\(width\|height\|top\|left\)" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null || true)
    
    if [[ -n "$layout_animations" ]]; then
        print_status $YELLOW "⚠️  Layout-affecting transitions found (may cause performance issues):"
        echo "$layout_animations" | head -3
        print_status $YELLOW "Consider using transform-based animations instead"
    fi
    
    # Check for excessive arbitrary values
    local arbitrary_values=$(grep -r "\[\w*\]" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" 2>/dev/null | wc -l)
    
    if [[ $arbitrary_values -gt 10 ]]; then
        print_status $YELLOW "⚠️  High usage of arbitrary values ($arbitrary_values instances)"
        print_status $YELLOW "Consider adding values to your TailwindCSS configuration"
    fi
    
    print_status $GREEN "✅ Performance patterns checked"
}

# Validate content configuration
validate_content_config() {
    print_status $BLUE "Validating content configuration..."
    
    local config_file="tailwind.config.js"
    if [[ -f "tailwind.config.ts" ]]; then
        config_file="tailwind.config.ts"
    fi
    
    # Check if content paths are specific enough
    if ! grep -q "components" "$config_file" 2>/dev/null; then
        print_status $YELLOW "⚠️  Consider adding specific content paths for better purging"
    fi
    
    # Check for safelist configuration for dynamic classes
    if grep -r "class[Name]*=.*\${" src/ --include="*.jsx" --include="*.tsx" >/dev/null 2>&1; then
        if ! grep -q "safelist" "$config_file" 2>/dev/null; then
            print_status $YELLOW "⚠️  Dynamic class generation detected but no safelist configured"
            print_status $YELLOW "Consider adding a safelist to prevent CSS purging of dynamic classes"
        fi
    fi
    
    print_status $GREEN "✅ Content configuration validated"
}

# Main execution
main() {
    local start_time=$(date +%s)
    
    # Run all checks
    check_tailwind_config
    validate_utility_patterns
    validate_responsive_patterns
    validate_accessibility
    validate_performance
    validate_content_config
    analyze_bundle_size
    
    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    
    print_status $GREEN "✅ All TailwindCSS checks completed in ${duration}s"
    print_status $BLUE "Ready to commit! 🚀"
}

# Run the main function
main