Skip to content

Commit 2e60d29

Browse files
refactor(web): consistent spacing scale + type scale across all components
Replace 105 ad-hoc inline style={{}} blocks and 8 arbitrary font sizes with a single design system, aligning the codebase with the CLAUDE.md rule to compose from tokens rather than hand-write CSS. - Spacing snaps to an 8pt grid (4/8/12/16px); no more off-grid 6/10/18px. - One harmonized type scale (xs/sm/base/lg/xl); drops 0.72-1.2rem one-offs. - Add reusable compound-layout classes (.spread, .divider, .actions, .field-submit) for recipes that recurred inline 5-6x each. - Reset .panel > h2:first-child so panel headings drop the manual override. Result: 0 inline styles, 0 arbitrary font sizes. Build + typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3b8e05d commit 2e60d29

8 files changed

Lines changed: 137 additions & 127 deletions

File tree

web/src/components/ClaimForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function ClaimForm({ onClaimed }: { onClaimed: () => void }) {
2424

2525
return (
2626
<div>
27-
<p className="muted" style={{ marginTop: 0 }}>
27+
<p className="muted mt-0">
2828
Enter the <strong>order reference</strong> from your Humanitix confirmation email (the short
2929
code, e.g. <code>7QVD6HEL</code>) and the <strong>surname</strong> on the ticket. If your
3030
whole team is on one order, each teammate claims their own seat with the same reference.
@@ -40,7 +40,7 @@ export function ClaimForm({ onClaimed }: { onClaimed: () => void }) {
4040
</div>
4141
</div>
4242
{error && <p className="error">{error}</p>}
43-
<div style={{ marginTop: 12 }}>
43+
<div className="actions">
4444
<button onClick={submit} disabled={busy || !orderReference || !surname}>
4545
{busy ? "Checking…" : "Claim my ticket"}
4646
</button>

web/src/components/CustomFieldsForm.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ export function CustomFieldsForm({
3737

3838
return (
3939
<div className="panel">
40-
<h2 style={{ marginTop: 0 }}>{title}</h2>
40+
<h2>{title}</h2>
4141
{fields.map((f) => (
42-
<div key={f.id} style={{ marginBottom: 10 }}>
42+
<div key={f.id} className="mb-3">
4343
<label>
4444
{f.label}
4545
{f.required && <span className="error"> *</span>}
@@ -48,7 +48,7 @@ export function CustomFieldsForm({
4848
<input type="text" value={String(values[f.id] ?? "")} onChange={(e) => set(f.id, e.target.value)} />
4949
)}
5050
{f.type === "checkbox" && (
51-
<label style={{ display: "flex", alignItems: "center", gap: 8, color: "var(--text)" }}>
51+
<label className="flex items-center gap-2 text-text">
5252
<input type="checkbox" checked={values[f.id] === true} onChange={(e) => set(f.id, e.target.checked)} />
5353
Yes
5454
</label>
@@ -66,7 +66,7 @@ export function CustomFieldsForm({
6666
{f.options.map((o) => {
6767
const arr = Array.isArray(values[f.id]) ? (values[f.id] as string[]) : [];
6868
return (
69-
<label key={o} style={{ display: "inline-flex", alignItems: "center", gap: 6, marginRight: 12, color: "var(--text)" }}>
69+
<label key={o} className="mr-3 inline-flex items-center gap-1.5 text-text">
7070
<input
7171
type="checkbox"
7272
checked={arr.includes(o)}
@@ -80,8 +80,10 @@ export function CustomFieldsForm({
8080
)}
8181
</div>
8282
))}
83-
<button onClick={save} disabled={busy}>Save answers</button>
84-
{msg && <span className={msg === "Saved." ? "ok" : "error"} style={{ marginLeft: 12 }}>{msg}</span>}
83+
<div className="actions">
84+
<button onClick={save} disabled={busy}>Save answers</button>
85+
{msg && <span className={msg === "Saved." ? "ok" : "error"}>{msg}</span>}
86+
</div>
8587
</div>
8688
);
8789
}

web/src/components/SignInPanel.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ export function SignInPanel({ message }: { message?: string }) {
1010
if (DEV_AUTH) return <DevSignIn message={message} />;
1111
return (
1212
<div className="panel">
13-
<h2 style={{ marginTop: 0 }}>Sign in</h2>
14-
<p className="muted" style={{ marginTop: 0 }}>
13+
<h2>Sign in</h2>
14+
<p className="muted mt-0">
1515
{message ?? "Sign in with your MAC account to continue."}
1616
</p>
17-
<div className="row" style={{ flex: "0 0 auto" }}>
17+
<div className="actions">
1818
<button onClick={() => signIn("google")}>Sign in with Google</button>
1919
<button className="secondary" onClick={() => signIn("microsoft")}>
2020
Sign in with Microsoft
2121
</button>
2222
</div>
23-
<p className="muted" style={{ marginBottom: 0 }}>
23+
<p className="muted mb-0 mt-3">
2424
It doesn't need to be the email you bought your ticket with.
2525
</p>
2626
</div>
@@ -42,9 +42,9 @@ function DevSignIn({ message }: { message?: string }) {
4242
}
4343

4444
return (
45-
<div className="panel" style={{ borderColor: "var(--accent)" }}>
46-
<h2 style={{ marginTop: 0 }}>Dev sign-in</h2>
47-
<p className="muted" style={{ marginTop: 0 }}>
45+
<div className="panel border-accent">
46+
<h2>Dev sign-in</h2>
47+
<p className="muted mt-0">
4848
{message ?? "Local dev mode — real mac-auth SSO only works on hackathons.monashcoding.com."}
4949
</p>
5050
<div className="row">
@@ -57,11 +57,11 @@ function DevSignIn({ message }: { message?: string }) {
5757
<input type="text" value={email} onChange={(e) => setEmail(e.target.value)} />
5858
</div>
5959
</div>
60-
<label style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 8, color: "var(--text)" }}>
60+
<label className="mt-3 flex items-center gap-2 text-text">
6161
<input type="checkbox" checked={organiser} onChange={(e) => setOrganiser(e.target.checked)} />
6262
Organiser (committee role)
6363
</label>
64-
<div style={{ marginTop: 10 }}>
64+
<div className="actions">
6565
<button onClick={go} disabled={!name.trim() || !email.trim()}>Sign in (dev)</button>
6666
</div>
6767
</div>

web/src/components/TeamPanels.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function InvitesPanel({ data, onChanged }: { data: DashboardResponse; onC
3232
{emailInvites.map((i) => (
3333
<div className="event" key={i.id}>
3434
<div>You've been invited to <strong>{i.teamName}</strong>.</div>
35-
<div className="row" style={{ flex: "0 0 auto" }}>
35+
<div className="row flex-none">
3636
<button onClick={run(() => api.respondInvite(i.id, true))} disabled={busy}>Accept</button>
3737
<button className="secondary" onClick={run(() => api.respondInvite(i.id, false))} disabled={busy}>Decline</button>
3838
</div>
@@ -41,7 +41,7 @@ export function InvitesPanel({ data, onChanged }: { data: DashboardResponse; onC
4141
{teamInvitations.map((i) => (
4242
<div className="event" key={i.teamId}>
4343
<div><strong>{i.teamName}</strong> invited you to join them.</div>
44-
<div className="row" style={{ flex: "0 0 auto" }}>
44+
<div className="row flex-none">
4545
<button onClick={run(() => api.respondTeamInvitation(i.teamId, true))} disabled={busy}>Accept</button>
4646
<button className="secondary" onClick={run(() => api.respondTeamInvitation(i.teamId, false))} disabled={busy}>Decline</button>
4747
</div>
@@ -72,8 +72,8 @@ export function NoTeamPanel({ onChanged }: { onChanged: () => void }) {
7272

7373
return (
7474
<div className="panel">
75-
<h2 style={{ marginTop: 0 }}>Your team</h2>
76-
<p className="muted" style={{ marginTop: 0 }}>
75+
<h2>Your team</h2>
76+
<p className="muted mt-0">
7777
You're not in a team yet. A team needs at least 2 people — create one and invite your
7878
friends, join with a code someone shared, or browse the pool below.
7979
</p>
@@ -82,16 +82,16 @@ export function NoTeamPanel({ onChanged }: { onChanged: () => void }) {
8282
<label>Create a team</label>
8383
<input type="text" value={name} placeholder="Team name" onChange={(e) => setName(e.target.value)} />
8484
</div>
85-
<div style={{ display: "flex", alignItems: "flex-end" }}>
85+
<div className="field-submit">
8686
<button onClick={create} disabled={busy || !name.trim()}>Create</button>
8787
</div>
8888
</div>
89-
<div className="row" style={{ marginTop: 8 }}>
89+
<div className="row mt-3">
9090
<div>
9191
<label>Join with an invite code</label>
9292
<input type="text" value={code} placeholder="e.g. 7QVD6HEL" onChange={(e) => setCode(e.target.value)} />
9393
</div>
94-
<div style={{ display: "flex", alignItems: "flex-end" }}>
94+
<div className="field-submit">
9595
<button className="secondary" onClick={join} disabled={busy || !code.trim()}>Join</button>
9696
</div>
9797
</div>
@@ -147,15 +147,15 @@ export function TeamPanel({ team, onChanged }: { team: TeamDetail; onChanged: ()
147147

148148
return (
149149
<div className="panel">
150-
<div className="row" style={{ alignItems: "center", justifyContent: "space-between" }}>
151-
<h2 style={{ margin: 0 }}>{team.name}</h2>
150+
<div className="spread">
151+
<h2 className="m-0">{team.name}</h2>
152152
<span className={`badge ${statusBadge}`}>{team.status}</span>
153153
</div>
154154

155155
{team.isLead && (team.status === "forming" || team.status === "confirmed") && (
156-
<div className="row" style={{ marginTop: 8, alignItems: "center" }}>
157-
<div style={{ flex: "0 0 auto" }} className="muted">Team status</div>
158-
<div style={{ flex: "0 0 auto" }}>
156+
<div className="row mt-3 items-center">
157+
<div className="muted flex-none">Team status</div>
158+
<div className="flex-none">
159159
<select
160160
value={team.status}
161161
disabled={busy}
@@ -168,22 +168,22 @@ export function TeamPanel({ team, onChanged }: { team: TeamDetail; onChanged: ()
168168
</div>
169169
)}
170170
{team.status === "flagged" && (
171-
<div className="muted" style={{ marginTop: 8 }}>
171+
<div className="muted mt-3">
172172
⚠️ A member's ticket is no longer valid. Resolve that to change status again.
173173
</div>
174174
)}
175175

176-
<div style={{ marginTop: 8 }}>
176+
<div className="mt-3">
177177
{team.members.map((m) => (
178178
<div className="event" key={m.participantId}>
179179
<div>
180180
<strong>{m.displayName ?? "(no name)"}</strong>
181-
{m.role === "lead" && <span className="badge" style={{ marginLeft: 8 }}>lead</span>}
181+
{m.role === "lead" && <span className="badge ml-2">lead</span>}
182182
{m.isYou && <span className="muted"> · you</span>}
183183
<div className="muted">{memberChip(m.membershipStatus, m.verificationStatus)}</div>
184184
</div>
185185
{team.isLead && !m.isYou && (
186-
<div className="row" style={{ flex: "0 0 auto" }}>
186+
<div className="row flex-none">
187187
<button className="secondary" onClick={wrap(() => api.reassignLead(team.id, m.participantId))} disabled={busy}>
188188
Make lead
189189
</button>
@@ -201,7 +201,7 @@ export function TeamPanel({ team, onChanged }: { team: TeamDetail; onChanged: ()
201201
))}
202202
</div>
203203

204-
<div style={{ marginTop: 12, borderTop: "1px solid var(--border)", paddingTop: 12 }}>
204+
<div className="divider">
205205
<label>Project submission</label>
206206
{team.isLead ? (
207207
<>
@@ -214,14 +214,14 @@ export function TeamPanel({ team, onChanged }: { team: TeamDetail; onChanged: ()
214214
onChange={(e) => setSubmission(e.target.value)}
215215
/>
216216
</div>
217-
<div style={{ display: "flex", alignItems: "flex-end", gap: 8 }}>
217+
<div className="field-submit gap-2">
218218
<button onClick={() => saveSubmission(submission)} disabled={busy}>Save link</button>
219219
{team.submissionUrl && (
220220
<button className="secondary" onClick={() => saveSubmission("")} disabled={busy}>Clear</button>
221221
)}
222222
</div>
223223
</div>
224-
<div className="muted" style={{ marginTop: 4 }}>
224+
<div className="muted mt-1">
225225
Adding a link moves your team to <strong>Submitted</strong> on the organiser board.
226226
</div>
227227
</>
@@ -235,31 +235,31 @@ export function TeamPanel({ team, onChanged }: { team: TeamDetail; onChanged: ()
235235
</div>
236236

237237
{team.isLead && (
238-
<div style={{ marginTop: 12, borderTop: "1px solid var(--border)", paddingTop: 12 }}>
238+
<div className="divider">
239239
<div className="row">
240240
<div>
241241
<label>Invite by email</label>
242242
<input type="text" value={email} placeholder="teammate@email.com" onChange={(e) => setEmail(e.target.value)} />
243243
</div>
244-
<div style={{ display: "flex", alignItems: "flex-end" }}>
244+
<div className="field-submit">
245245
<button onClick={invite} disabled={busy || !email.trim()}>Invite</button>
246246
</div>
247247
</div>
248-
<label style={{ marginTop: 8 }}>Invite code (share this)</label>
248+
<label>Invite code (share this)</label>
249249
<div className="row">
250250
<div><input type="text" value={code} readOnly /></div>
251-
<div style={{ display: "flex", alignItems: "flex-end" }}>
251+
<div className="field-submit">
252252
<button className="secondary" onClick={regen} disabled={busy}>Regenerate</button>
253253
</div>
254254
</div>
255255
</div>
256256
)}
257257

258-
<div style={{ marginTop: 12 }}>
258+
<div className="actions">
259259
<button className="danger" onClick={wrap(() => api.leaveTeam(team.id))} disabled={busy}>
260260
Leave team
261261
</button>
262-
{msg && <span className="error" style={{ marginLeft: 12 }}>{msg}</span>}
262+
{msg && <span className="error">{msg}</span>}
263263
</div>
264264
</div>
265265
);

0 commit comments

Comments
 (0)