fix(a11y): improve contrast and add page titles and labels

This commit is contained in:
lakshit verma 2026-08-17 02:06:48 +05:30
parent bf76061847
commit 0a4574551a
No known key found for this signature in database
12 changed files with 46 additions and 9 deletions

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Work Order Desk — track field work from dispatch to done." />
<meta name="theme-color" content="#121317" />
<meta name="color-scheme" content="dark" />
<title>Work Order Desk</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

View file

@ -5,11 +5,22 @@ export function Card({ className, children }: { className?: string; children: Re
return <div className={cn('rounded-lg border border-line bg-ink-900 shadow-[0_1px_0_0_rgba(255,255,255,0.03)_inset]', className)}>{children}</div>;
}
export function CardHeader({ title, description, children }: { title: string; description?: string; children?: ReactNode }) {
export function CardHeader({
title,
description,
children,
as = 'h2',
}: {
title: string;
description?: string;
children?: ReactNode;
as?: 'h1' | 'h2' | 'h3';
}) {
const Heading = as;
return (
<div className="flex items-start justify-between gap-4 border-b border-line px-5 py-4">
<div>
<h2 className="font-display text-xl font-semibold uppercase tracking-wide text-ice">{title}</h2>
<Heading className="font-display text-xl font-semibold uppercase tracking-wide text-ice">{title}</Heading>
{description && <p className="mt-0.5 text-sm text-steel-300">{description}</p>}
</div>
{children}

View file

@ -15,7 +15,7 @@ export function FilterTabs({
onChange: (value: string) => void;
}) {
return (
<div role="group" className="inline-flex flex-wrap gap-1 rounded-lg border border-line bg-ink-900 p-1">
<div role="group" aria-label="Filter by status" className="inline-flex flex-wrap gap-1 rounded-lg border border-line bg-ink-900 p-1">
{options.map((o) => (
<button
key={o.value}

View file

@ -1,3 +1,4 @@
import { usePageTitle } from '../../hooks/usePageTitle';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { forgotPasswordSchema } from '@workorders/shared';
@ -10,6 +11,7 @@ import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
import { ErrorBanner } from '../../components/primitives/Feedback';
export function ForgotPasswordPage() {
usePageTitle('Reset your password');
const [email, setEmail] = useState('');
const [error, setError] = useState<string | null>(null);
const [submitted, setSubmitted] = useState(false);
@ -37,7 +39,7 @@ export function ForgotPasswordPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-ink-950 px-4">
<Card className="w-full max-w-md">
<CardHeader title="Reset your password" description="We will email you a reset link if the account exists." />
<CardHeader as="h1" title="Reset your password" description="We will email you a reset link if the account exists." />
<CardBody>
{submitted ? (
<p className="text-sm text-steel-300">

View file

@ -1,3 +1,4 @@
import { usePageTitle } from '../../hooks/usePageTitle';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { registerSchema } from '@workorders/shared';
@ -9,6 +10,7 @@ import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
import { ErrorBanner } from '../../components/primitives/Feedback';
export function RegisterPage() {
usePageTitle('Create account');
const register = useRegister();
const navigate = useNavigate();
const [values, setValues] = useState({ name: '', email: '', password: '' });
@ -37,7 +39,7 @@ export function RegisterPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-ink-950 px-4">
<Card className="w-full max-w-md">
<CardHeader title="Create account" description="Join the work order desk." />
<CardHeader as="h1" title="Create account" description="Join the work order desk." />
<CardBody>
{formError && <ErrorBanner className="mb-4" message={formError} />}
<form onSubmit={onSubmit} className="space-y-4" noValidate>

View file

@ -1,3 +1,4 @@
import { usePageTitle } from '../../hooks/usePageTitle';
import { useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { resetPasswordSchema } from '@workorders/shared';
@ -11,6 +12,7 @@ import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
import { ErrorBanner } from '../../components/primitives/Feedback';
export function ResetPasswordPage() {
usePageTitle('Choose a new password');
const [params] = useSearchParams();
const token = params.get('token') ?? '';
const [password, setPassword] = useState('');
@ -42,7 +44,7 @@ export function ResetPasswordPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-ink-950 px-4">
<Card className="w-full max-w-md">
<CardHeader title="Choose a new password" />
<CardHeader as="h1" title="Choose a new password" />
<CardBody>
{!token && <ErrorBanner message="This reset link is missing its token. Request a new one." />}
{token && (

View file

@ -9,7 +9,10 @@ import { Button } from '../../components/primitives/Spinner';
import { Card, CardBody, CardHeader } from '../../components/primitives/Card';
import { Field, Input } from '../../components/primitives/Input';
import { usePageTitle } from '../../hooks/usePageTitle';
export function ProfilePage() {
usePageTitle('Profile');
const { data: user } = useMe();
const [name, setName] = useState(user?.name ?? '');
const [nameError, setNameError] = useState<string | null>(null);

View file

@ -5,7 +5,10 @@ import { PageHeader } from '../../components/primitives/Feedback';
import { Card, CardBody } from '../../components/primitives/Card';
import { messageFromError } from '../../lib/errors';
import { usePageTitle } from '../../hooks/usePageTitle';
export function WorkOrderCreatePage() {
usePageTitle('New work order');
const create = useCreateWorkOrder();
const navigate = useNavigate();

View file

@ -0,0 +1,7 @@
import { useEffect } from 'react';
export function usePageTitle(title: string): void {
useEffect(() => {
document.title = title ? `${title} · Work Order Desk` : 'Work Order Desk';
}, [title]);
}

View file

@ -8,7 +8,10 @@ const boardTickets = [
{ title: 'Seal window draft — Unit 3A', no: '№ 4B81D', priority: 'low' as const, status: 'done' as const },
];
import { usePageTitle } from '../hooks/usePageTitle';
export function LandingPage() {
usePageTitle('');
return (
<div className="flex min-h-screen flex-col bg-ink-950 text-ice">
<header className="border-b border-line bg-ink-900/70">

View file

@ -1,7 +1,10 @@
import { Link } from 'react-router-dom';
import { Button } from '../components/primitives/Spinner';
import { usePageTitle } from '../hooks/usePageTitle';
export function NotFoundPage() {
usePageTitle('Page not found');
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-ink-950 px-4 text-center">
<p className="font-display text-8xl font-bold uppercase leading-none text-steel-600">404</p>

View file

@ -20,13 +20,13 @@ export default {
},
signal: {
400: '#FF6B3D',
500: '#FF5C33',
600: '#E94F28',
500: '#E04820',
600: '#C73D18',
},
steel: {
300: '#A9B2BF',
400: '#8A94A3',
500: '#6A7484',
500: '#7C8794',
600: '#4A5360',
},
work: {