fix(frontend): sprint 4 design-review — slab token + UsersAdmin alignment + dark hairlines + badge contrast
- bump dark hairline from #374151 → #4b5563 for visible table borders - topbar header bg-canvas → bg-paper for dark-mode lift vs canvas body - UsersAdminPage create-form: Option A structural 3-row grid (labels / inputs / hints) to fix AC-17.3 alignment; removes FormField wrapper that caused row-height misalignment - EngagementsListPage: replace text "+ New" with lucide Plus icon per design spec Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,8 +59,8 @@ export function Layout(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* nav-bar-top — canvas with hairline */}
|
{/* nav-bar-top — paper gives dark-mode lift vs canvas body */}
|
||||||
<header className="bg-canvas border-b border-hairline">
|
<header className="bg-paper border-b border-hairline">
|
||||||
<div className="mx-auto w-full max-w-page px-xl h-16 flex items-center justify-between">
|
<div className="mx-auto w-full max-w-page px-xl h-16 flex items-center justify-between">
|
||||||
<Link to="/engagements" className="flex items-center gap-sm" aria-label="Mimic home">
|
<Link to="/engagements" className="flex items-center gap-sm" aria-label="Mimic home">
|
||||||
<span className="inline-block h-6 w-6 rotate-12 bg-primary" aria-hidden />
|
<span className="inline-block h-6 w-6 rotate-12 bg-primary" aria-hidden />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
import { extractApiError } from '@/api/client';
|
import { extractApiError } from '@/api/client';
|
||||||
import type { Engagement } from '@/api/types';
|
import type { Engagement } from '@/api/types';
|
||||||
import { useDeleteEngagement, useEngagementsList } from '@/hooks/useEngagements';
|
import { useDeleteEngagement, useEngagementsList } from '@/hooks/useEngagements';
|
||||||
@@ -41,7 +42,7 @@ export function EngagementsListPage(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
{canEditEngagements ? (
|
{canEditEngagements ? (
|
||||||
<Link to="/engagements/new" className="btn-primary">
|
<Link to="/engagements/new" className="btn-primary">
|
||||||
+ New
|
<Plus size={14} aria-hidden /> New
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
@@ -59,7 +60,7 @@ export function EngagementsListPage(): JSX.Element {
|
|||||||
action={
|
action={
|
||||||
canEditEngagements ? (
|
canEditEngagements ? (
|
||||||
<Link to="/engagements/new" className="btn-primary">
|
<Link to="/engagements/new" className="btn-primary">
|
||||||
+ New engagement
|
<Plus size={14} aria-hidden /> New engagement
|
||||||
</Link>
|
</Link>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,19 +111,33 @@ export function UsersAdminPage(): JSX.Element {
|
|||||||
<section className="card-product flex flex-col gap-md">
|
<section className="card-product flex flex-col gap-md">
|
||||||
<h2 className="text-[20px] font-medium">Create account</h2>
|
<h2 className="text-[20px] font-medium">Create account</h2>
|
||||||
{/*
|
{/*
|
||||||
items-start so each cell top-aligns; the button is wrapped in a flex column
|
Option A structural fix (AC-17.3): labels / inputs / hints in 3 explicit grid rows
|
||||||
that pushes it to align with the input row (label + 4px gap = ~22px offset).
|
so the browser can never misalign them by collapsing different-height cells.
|
||||||
|
grid-rows-[auto_auto_auto] ensures row 1 = labels, row 2 = inputs, row 3 = hints.
|
||||||
*/}
|
*/}
|
||||||
<form onSubmit={onCreate} className="grid grid-cols-1 md:grid-cols-4 gap-md items-start">
|
<form
|
||||||
<FormField label="Username" htmlFor="new-username" required>
|
onSubmit={onCreate}
|
||||||
|
className="grid grid-cols-1 md:grid-cols-4 md:grid-rows-[auto_auto_auto] gap-x-md gap-y-xs"
|
||||||
|
>
|
||||||
|
{/* Row 1 — labels */}
|
||||||
|
<label htmlFor="new-username" className="text-[14px] font-medium text-ink">
|
||||||
|
Username <span className="text-bloom-deep">*</span>
|
||||||
|
</label>
|
||||||
|
<label htmlFor="new-password" className="text-[14px] font-medium text-ink">
|
||||||
|
Password <span className="text-bloom-deep">*</span>
|
||||||
|
</label>
|
||||||
|
<label htmlFor="new-role" className="text-[14px] font-medium text-ink">
|
||||||
|
Role <span className="text-bloom-deep">*</span>
|
||||||
|
</label>
|
||||||
|
<div aria-hidden="true" />
|
||||||
|
|
||||||
|
{/* Row 2 — inputs + button (all same height = h-11) */}
|
||||||
<TextInput
|
<TextInput
|
||||||
id="new-username"
|
id="new-username"
|
||||||
value={createForm.username}
|
value={createForm.username}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, username: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, username: e.target.value })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</FormField>
|
|
||||||
<FormField label="Password" htmlFor="new-password" required hint="≥ 8 characters">
|
|
||||||
<TextInput
|
<TextInput
|
||||||
id="new-password"
|
id="new-password"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -132,22 +146,21 @@ export function UsersAdminPage(): JSX.Element {
|
|||||||
required
|
required
|
||||||
minLength={8}
|
minLength={8}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
|
||||||
<FormField label="Role" htmlFor="new-role" required>
|
|
||||||
<Select
|
<Select
|
||||||
id="new-role"
|
id="new-role"
|
||||||
value={createForm.role}
|
value={createForm.role}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, role: e.target.value as Role })}
|
onChange={(e) => setCreateForm({ ...createForm, role: e.target.value as Role })}
|
||||||
options={ROLE_OPTIONS}
|
options={ROLE_OPTIONS}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
|
||||||
{/* Button column: spacer matches label row height so input + button baselines align */}
|
|
||||||
<div className="flex flex-col gap-xs">
|
|
||||||
<div className="h-[22px]" aria-hidden="true" />
|
|
||||||
<button type="submit" className="btn-primary w-full" disabled={createMutation.isPending}>
|
<button type="submit" className="btn-primary w-full" disabled={createMutation.isPending}>
|
||||||
{createMutation.isPending ? 'Creating…' : 'Create'}
|
{createMutation.isPending ? 'Creating…' : 'Create'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
{/* Row 3 — hints */}
|
||||||
|
<div aria-hidden="true" />
|
||||||
|
<span className="text-[12px] text-graphite">≥ 8 characters</span>
|
||||||
|
<div aria-hidden="true" />
|
||||||
|
<div aria-hidden="true" />
|
||||||
</form>
|
</form>
|
||||||
{createError ? (
|
{createError ? (
|
||||||
<div role="alert" className="text-[14px] text-bloom-deep">
|
<div role="alert" className="text-[14px] text-bloom-deep">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
--color-cloud: #1f2937;
|
--color-cloud: #1f2937;
|
||||||
--color-fog: #374151;
|
--color-fog: #374151;
|
||||||
--color-steel: #4b5563;
|
--color-steel: #4b5563;
|
||||||
--color-hairline: #374151;
|
--color-hairline: #4b5563;
|
||||||
--color-ink: #f9fafb;
|
--color-ink: #f9fafb;
|
||||||
--color-ink-soft: #e5e7eb;
|
--color-ink-soft: #e5e7eb;
|
||||||
--color-ink-deep: #ffffff;
|
--color-ink-deep: #ffffff;
|
||||||
|
|||||||
Reference in New Issue
Block a user