Files
school_compare/nextjs-app/components/ComparisonToast.tsx
Tudor 8aca0a7a53
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Build Integrator (push) Successful in 57s
Build and Push Docker Images / Build Kestra Init (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
feat(ui): site-wide UX/UI audit — unified buttons, tokens, accessibility
- Add shared button system (.btn-primary/secondary/tertiary/active) in globals.css
- Replace 40+ hardcoded rgba() values with design tokens across all CSS modules
- Add skip link, :focus-visible indicators, and ARIA landmarks
- Standardise button labels ("+ Compare" / "✓ Comparing") across all components
- Add collapse/minimize toggle to ComparisonToast
- Fix heading hierarchy (h3→h2 in ComparisonView)
- Add aria-live on search results, aria-label on trend SVGs
- Add "Search" nav link, fix footer empty section, unify max-widths
- Darken --text-muted for WCAG AA compliance (4.6:1 contrast ratio)
- Net reduction of ~180 lines through button style deduplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 20:28:03 +00:00

74 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { useComparison } from '@/hooks/useComparison';
import { usePathname } from 'next/navigation';
import styles from './ComparisonToast.module.css';
export function ComparisonToast() {
const { selectedSchools, clearAll, removeSchool } = useComparison();
const [mounted, setMounted] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const pathname = usePathname();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
// Don't show toast on the compare page itself
if (pathname === '/compare') return null;
if (selectedSchools.length === 0) return null;
return (
<div className={styles.toastContainer}>
<div className={`${styles.toastContent} ${collapsed ? styles.toastCollapsed : ''}`}>
<div className={styles.toastHeader}>
<span className={styles.toastTitle}>
<span className={styles.toastBadge}>{selectedSchools.length}</span>
{selectedSchools.length === 1 ? 'school' : 'schools'} selected
</span>
<button
onClick={() => setCollapsed(!collapsed)}
className={styles.collapseBtn}
aria-label={collapsed ? 'Expand comparison panel' : 'Minimize comparison panel'}
>
<svg viewBox="0 0 16 16" fill="none" width="14" height="14">
{collapsed ? (
<path d="M4 10L8 6L12 10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
) : (
<path d="M4 6L8 10L12 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
)}
</svg>
</button>
</div>
{!collapsed && (
<>
<div className={styles.schoolList}>
{selectedSchools.map(school => (
<div key={school.urn} className={styles.schoolItem}>
<span className={styles.schoolName} title={school.school_name}>
{school.school_name.length > 28 ? school.school_name.slice(0, 28) + '…' : school.school_name}
</span>
<button
onClick={() => removeSchool(school.urn)}
className={styles.removeSchoolBtn}
aria-label={`Remove ${school.school_name}`}
>×</button>
</div>
))}
</div>
<div className={styles.toastActions}>
<button onClick={clearAll} className="btn btn-tertiary btn-sm" style={{ color: 'rgba(250,247,242,0.7)', borderColor: 'rgba(255,255,255,0.15)' }}>Clear all</button>
<Link href="/compare" className={styles.btnCompare}>Compare Now</Link>
</div>
</>
)}
</div>
</div>
);
}