ui-polish 5/6: a11y semantics and remaining labels
- All 28 <div className="form-section-title"> section markers are real <h3> headings now — the class carries every visual property, so this is semantics-only, zero visual change. - Remaining icon-only .act-btn buttons labeled: CandidateProfile (download/edit note/revise scorecard, incl. one that had neither tooltip nor label), CandidateForms amend button. - PasswordField show/hide toggle is keyboard-reachable (tabIndex -1 removed) with aria-pressed state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>pull/29/head
parent
af6ef715c5
commit
c4793479a2
|
|
@ -35,7 +35,7 @@ export default function PasswordField({
|
|||
className="pw-toggle"
|
||||
onClick={() => setVisible((v) => !v)}
|
||||
aria-label={visible ? 'Hide password' : 'Show password'}
|
||||
tabIndex={-1}
|
||||
aria-pressed={visible}
|
||||
>
|
||||
{visible ? (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default function AiStudio() {
|
|||
</div>
|
||||
<div className="card" style={{ boxShadow: 'none', background: 'var(--bg-sunken)' }}>
|
||||
<div className="card-body">
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>API Contract (preview)</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>API Contract (preview)</h3>
|
||||
<pre className="resume-thumb" style={{ maxHeight: 'none' }}>
|
||||
{`POST /api/ai/${detail.name.toLowerCase().replace(/ /g, '-')}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ export default function Assessments() {
|
|||
<div className="mb-18"><ProgressBar pct={viewing.score} /></div>
|
||||
{viewing.sectionScores.length > 0 && (
|
||||
<>
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Section Breakdown</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Section Breakdown</h3>
|
||||
{viewing.sectionScores.map((s) => (
|
||||
<div className="flex items-center gap-12" style={{ marginBottom: 10 }} key={s.label}>
|
||||
<span style={{ width: 130, fontSize: 13 }}>{s.label}</span>
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ function FormRowList({ rows, defs, onEdit, canEdit }) {
|
|||
</Badge>
|
||||
)}
|
||||
{canEdit && (
|
||||
<button className="act-btn" data-tip="Amend this form" onClick={() => onEdit(r)}>
|
||||
<button className="act-btn" data-tip="Amend this form" aria-label="Amend this form" onClick={() => onEdit(r)}>
|
||||
<Icon name="edit" />
|
||||
</button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -413,13 +413,13 @@ export default function CandidateProfile({
|
|||
<h3 style={{ marginBottom: 4 }}>{c.name}</h3>
|
||||
<p className="text-muted">{c.currentTitle} · {c.location}</p>
|
||||
<div className="divider" />
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Summary</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Summary</h3>
|
||||
<p className="text-muted">
|
||||
Results-driven {c.currentTitle.toLowerCase()} with {c.experience} years of experience
|
||||
across {c.department.toLowerCase()}. Passionate about building high-quality products
|
||||
and collaborating with cross-functional teams.
|
||||
</p>
|
||||
<div className="form-section-title">Experience</div>
|
||||
<h3 className="form-section-title">Experience</h3>
|
||||
<div className="info-item">
|
||||
<div className="iv">{c.currentTitle} — {c.currentCompany}</div>
|
||||
<div className="il" style={{ textTransform: 'none' }}>2021 – Present</div>
|
||||
|
|
@ -428,7 +428,7 @@ export default function CandidateProfile({
|
|||
<div className="iv">Associate — {priorCompany}</div>
|
||||
<div className="il" style={{ textTransform: 'none' }}>2018 – 2021</div>
|
||||
</div>
|
||||
<div className="form-section-title">Education</div>
|
||||
<h3 className="form-section-title">Education</h3>
|
||||
<div className="iv">{c.education}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -572,7 +572,7 @@ export default function CandidateProfile({
|
|||
<Icon name="file" />
|
||||
</span>
|
||||
<div className="lr-main"><div className="lr-title">{d.n}</div><div className="lr-sub">{d.s}</div></div>
|
||||
<button className="act-btn" onClick={() => toast(`Downloading ${d.n}`, 'info')}>
|
||||
<button className="act-btn" data-tip="Download" aria-label={`Download ${d.n}`} onClick={() => toast(`Downloading ${d.n}`, 'info')}>
|
||||
<Icon name="download" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -921,7 +921,7 @@ function InterviewTab({ userId, inboxId, rows }) {
|
|||
)}
|
||||
|
||||
<div className="divider" />
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Schedule an interview</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Schedule an interview</h3>
|
||||
<div className="form-grid">
|
||||
<div className="form-field">
|
||||
<label>Type</label>
|
||||
|
|
@ -1065,7 +1065,7 @@ function NoteRow({ note: n, userId }) {
|
|||
</div>
|
||||
{mine && (
|
||||
<div className="lr-right">
|
||||
<button className="act-btn" data-tip="Edit note" onClick={() => setEditing(true)}>
|
||||
<button className="act-btn" data-tip="Edit note" aria-label="Edit note" onClick={() => setEditing(true)}>
|
||||
<Icon name="edit" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -1124,7 +1124,7 @@ function ActivityTab({ userId, inboxId, rows }) {
|
|||
)}
|
||||
|
||||
<div className="divider" />
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Log activity</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Log activity</h3>
|
||||
<div className="form-grid">
|
||||
<div className="form-field">
|
||||
<label>Type</label>
|
||||
|
|
@ -1188,6 +1188,7 @@ function DocumentsTab({ rows, inboxId }) {
|
|||
className="act-btn"
|
||||
disabled={!inboxId || download.isPending}
|
||||
title={!inboxId ? 'No application id for download' : 'Download'}
|
||||
aria-label={`Download ${d.name}`}
|
||||
onClick={() => download.mutate({ index: i, filename: d.name })}
|
||||
>
|
||||
<Icon name="download" />
|
||||
|
|
@ -1301,7 +1302,7 @@ function FeedbackRow({ row: f, userId }) {
|
|||
<div className="lr-right" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
{f.review ? <Badge>{f.review}</Badge> : null}
|
||||
{mine && (
|
||||
<button className="act-btn" data-tip="Revise scorecard" onClick={() => setEditing(true)}>
|
||||
<button className="act-btn" data-tip="Revise scorecard" aria-label="Revise scorecard" onClick={() => setEditing(true)}>
|
||||
<Icon name="edit" />
|
||||
</button>
|
||||
)}
|
||||
|
|
@ -1347,7 +1348,7 @@ function FeedbackTab({ userId, inboxId, rows }) {
|
|||
)}
|
||||
|
||||
<div className="divider" />
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Submit a scorecard</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Submit a scorecard</h3>
|
||||
<div className="form-grid">
|
||||
<div className="form-field">
|
||||
<label>Recommendation</label>
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@ export function AtsMatch({ candidate: c, jobTitle, onClose, onProfile }) {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Assessment</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Assessment</h3>
|
||||
<p className="text-muted" style={{ fontSize: 13 }}>{critique ?? '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -570,9 +570,9 @@ export function AtsMatch({ candidate: c, jobTitle, onClose, onProfile }) {
|
|||
<div className="info-item"><div className="il">Scored On</div><div className="iv">{scoredOn ?? '—'}</div></div>
|
||||
</div>
|
||||
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>
|
||||
Matched Skills ({matched.length})
|
||||
</div>
|
||||
</h3>
|
||||
<div className="k-tags" style={{ marginBottom: 16 }}>
|
||||
{matched.length
|
||||
? matched.map((s) => (
|
||||
|
|
@ -581,9 +581,9 @@ export function AtsMatch({ candidate: c, jobTitle, onClose, onProfile }) {
|
|||
: <span className="text-muted">—</span>}
|
||||
</div>
|
||||
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>
|
||||
Missing Skills ({missing.length})
|
||||
</div>
|
||||
</h3>
|
||||
<div className="k-tags">
|
||||
{missing.length
|
||||
? missing.map((s) => (
|
||||
|
|
@ -844,9 +844,9 @@ function AddCandidate({ onClose, onSave, onInvalid }) {
|
|||
{/* Role selection sits directly above the CV because it is what the CV
|
||||
gets scored against — same card UI as Job Matching's role list.
|
||||
(.req is scoped to `.form-field label .req`, so tint it here.) */}
|
||||
<div className="form-section-title">
|
||||
<h3 className="form-section-title">
|
||||
Applied Job <span className="req" style={{ color: 'var(--danger)' }}>*</span>
|
||||
</div>
|
||||
</h3>
|
||||
{posts.length > 0 && (
|
||||
<div className="toolbar-search" style={{ maxWidth: 'none', marginBottom: 10 }}>
|
||||
<Icon name="search" />
|
||||
|
|
@ -892,9 +892,9 @@ function AddCandidate({ onClose, onSave, onInvalid }) {
|
|||
)}
|
||||
<FieldError>{form.errors.job}</FieldError>
|
||||
|
||||
<div className="form-section-title">
|
||||
<h3 className="form-section-title">
|
||||
CV / Resume <span className="req" style={{ color: 'var(--danger)' }}>*</span>
|
||||
</div>
|
||||
</h3>
|
||||
<input
|
||||
ref={fileInput}
|
||||
type="file"
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
|
|||
</div>
|
||||
{completed ? (
|
||||
<>
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>AI Assessment</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>AI Assessment</h3>
|
||||
<p className="text-muted">{c.critique ?? '—'}</p>
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -194,7 +194,7 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
|
|||
|
||||
{tab === 'Job Match' && completed && (
|
||||
<>
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Job-Match Score</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Job-Match Score</h3>
|
||||
<p className="text-muted text-sm mb-12">
|
||||
Match this candidate against the job the CV was scored for.
|
||||
</p>
|
||||
|
|
@ -209,9 +209,9 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>
|
||||
Matched must-have skills ({c.matchedSkills.length})
|
||||
</div>
|
||||
</h3>
|
||||
<div className="k-tags mb-16">
|
||||
{c.matchedSkills.length
|
||||
? c.matchedSkills.map((s) => (
|
||||
|
|
@ -220,9 +220,9 @@ export function ScoredCandidateDetail({ candidate: c, jobTitle, onClose }) {
|
|||
: <span className="text-muted">—</span>}
|
||||
</div>
|
||||
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>
|
||||
Missing must-have skills ({c.missingSkills.length})
|
||||
</div>
|
||||
</h3>
|
||||
<div className="k-tags mb-16">
|
||||
{c.missingSkills.length
|
||||
? c.missingSkills.map((s) => (
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ function ManagerDetail({ manager: m, jobs, canSend, onClose, navigate, toast })
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Hiring Manager Portal</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Hiring Manager Portal</h3>
|
||||
<div className="grid g-2" style={{ gap: 10, marginBottom: 16 }}>
|
||||
<button className="btn btn-secondary" onClick={() => go('/jobs', { openCreate: true })}>
|
||||
<Icon name="plus" /> Raise Requisition
|
||||
|
|
@ -219,7 +219,7 @@ function ManagerDetail({ manager: m, jobs, canSend, onClose, navigate, toast })
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-section-title">Open requisitions</div>
|
||||
<h3 className="form-section-title">Open requisitions</h3>
|
||||
<p className="text-muted text-sm" style={{ marginBottom: 10 }}>
|
||||
Jobs are not linked to a hiring-manager id yet, so this list is the current open requisitions rather than this manager's own.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ function OfferDetail({ offer: o, busy, onClose, onIssue, onStatus }) {
|
|||
|
||||
<div className="card" style={{ boxShadow: 'none', background: 'var(--bg-sunken)', marginBottom: 18 }}>
|
||||
<div className="card-body">
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Compensation Package</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Compensation Package</h3>
|
||||
<div className="info-grid">
|
||||
<div className="info-item">
|
||||
<div className="il">Base Salary</div>
|
||||
|
|
|
|||
|
|
@ -436,12 +436,12 @@ function RoleForm({ title, subtitle, role, bundles, bundlesLoading, busy, onClos
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-section-title">
|
||||
<h3 className="form-section-title">
|
||||
Permission bundles
|
||||
<span className="text-muted text-sm" style={{ marginLeft: 8, fontWeight: 400 }}>
|
||||
{picked.size} selected · {grantedTags.size} tags resolved
|
||||
</span>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
{bundlesLoading && <p className="text-muted">Loading bundles…</p>}
|
||||
{!bundlesLoading && bundles.length === 0 && (
|
||||
|
|
|
|||
|
|
@ -815,12 +815,12 @@ function Notifications({ registerSave }) {
|
|||
return (
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Email Notifications</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Email Notifications</h3>
|
||||
<ToggleRow title="New applications" desc="Get notified when a candidate applies" checked={draft['notifications.email_new_applications']} onChange={(v) => setField('notifications.email_new_applications', v)} />
|
||||
<ToggleRow title="Interview reminders" desc="Reminders 30 minutes before interviews" checked={draft['notifications.email_interview_reminders']} onChange={(v) => setField('notifications.email_interview_reminders', v)} />
|
||||
<ToggleRow title="Offer responses" desc="When candidates accept or decline offers" checked={draft['notifications.email_offer_responses']} onChange={(v) => setField('notifications.email_offer_responses', v)} />
|
||||
<ToggleRow title="Weekly digest" desc="A summary of hiring activity every Monday" checked={draft['notifications.email_weekly_digest']} onChange={(v) => setField('notifications.email_weekly_digest', v)} />
|
||||
<div className="form-section-title">In-App Notifications</div>
|
||||
<h3 className="form-section-title">In-App Notifications</h3>
|
||||
<ToggleRow title="Mentions" desc="When a teammate @mentions you" checked={draft['notifications.inapp_mentions']} onChange={(v) => setField('notifications.inapp_mentions', v)} />
|
||||
<ToggleRow title="Stage changes" desc="When a candidate moves stages" checked={draft['notifications.inapp_stage_changes']} onChange={(v) => setField('notifications.inapp_stage_changes', v)} />
|
||||
<ToggleRow title="Task assignments" desc="When you are assigned a task" checked={draft['notifications.inapp_task_assignments']} onChange={(v) => setField('notifications.inapp_task_assignments', v)} />
|
||||
|
|
@ -1040,7 +1040,7 @@ function Appearance() {
|
|||
return (
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="form-section-title" style={{ marginTop: 0 }}>Theme</div>
|
||||
<h3 className="form-section-title" style={{ marginTop: 0 }}>Theme</h3>
|
||||
<div className="grid g-3" style={{ marginBottom: 8 }}>
|
||||
<div className="card theme-opt" style={{ cursor: 'pointer', overflow: 'hidden' }} onClick={() => pick('light')}>
|
||||
<div style={{ height: 80, background: '#f1f7f4', borderBottom: '1px solid var(--border)', display: 'flex' }}>
|
||||
|
|
|
|||
|
|
@ -271,14 +271,14 @@ function TalentProfileDetail({ profileId, onClose }) {
|
|||
|
||||
{p.summary && (
|
||||
<>
|
||||
<div className="form-section-title">About</div>
|
||||
<h3 className="form-section-title">About</h3>
|
||||
<p className="text-muted" style={{ whiteSpace: 'pre-line' }}>{p.summary}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{p.skills.length > 0 && (
|
||||
<>
|
||||
<div className="form-section-title">Skills ({p.skills.length})</div>
|
||||
<h3 className="form-section-title">Skills ({p.skills.length})</h3>
|
||||
<div className="k-tags" style={{ marginBottom: 16 }}>
|
||||
{p.skills.map((s) => <span className="tag" key={s}>{s}</span>)}
|
||||
</div>
|
||||
|
|
@ -287,7 +287,7 @@ function TalentProfileDetail({ profileId, onClose }) {
|
|||
|
||||
{p.experience.length > 0 && (
|
||||
<>
|
||||
<div className="form-section-title">Experience ({p.experience.length})</div>
|
||||
<h3 className="form-section-title">Experience ({p.experience.length})</h3>
|
||||
{p.experience.map((e, i) => (
|
||||
<div key={i} style={{ marginBottom: 14 }}>
|
||||
<div className="fw-600">
|
||||
|
|
@ -311,7 +311,7 @@ function TalentProfileDetail({ profileId, onClose }) {
|
|||
|
||||
{p.education.length > 0 && (
|
||||
<>
|
||||
<div className="form-section-title">Education ({p.education.length})</div>
|
||||
<h3 className="form-section-title">Education ({p.education.length})</h3>
|
||||
{p.education.map((e, i) => (
|
||||
<div key={i} style={{ marginBottom: 12 }}>
|
||||
<div className="fw-600">{e.school ?? '—'}</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue