From ae9f7fb9f3edcaf5df8661c5a3298e1b5acd480a Mon Sep 17 00:00:00 2001 From: m5r Date: Wed, 12 Aug 2026 22:44:23 +0200 Subject: [PATCH] milestone 23 s3: convert features/lookup/LookupPage.tsx to stylex --- web/src/features/lookup/LookupPage.test.tsx | 6 +- web/src/features/lookup/LookupPage.tsx | 205 +++++++++++++++----- 2 files changed, 165 insertions(+), 46 deletions(-) diff --git a/web/src/features/lookup/LookupPage.test.tsx b/web/src/features/lookup/LookupPage.test.tsx index c62e259..9d6a7f2 100644 --- a/web/src/features/lookup/LookupPage.test.tsx +++ b/web/src/features/lookup/LookupPage.test.tsx @@ -88,6 +88,8 @@ test("fetches nothing until submit, then renders the blocked verdict", async () test("defaults the group select to the default group (id 1)", async () => { renderPage(); - const select = (await screen.findByLabelText("Group")) as HTMLSelectElement; - expect(select.value).toBe("1"); + // A RAC Select names its trigger with the current value and then the label, so + // the selected group's name is the only thing the trigger shows. + const trigger = await screen.findByRole("button", { name: /Group$/ }); + expect(trigger.textContent).toContain("default"); }); diff --git a/web/src/features/lookup/LookupPage.tsx b/web/src/features/lookup/LookupPage.tsx index ae2b6ab..adcb8e6 100644 --- a/web/src/features/lookup/LookupPage.tsx +++ b/web/src/features/lookup/LookupPage.tsx @@ -1,10 +1,15 @@ import { useState, type FormEvent, type ReactNode } from "react"; import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import * as stylex from "@stylexjs/stylex"; import { ApiError } from "@/lib/api"; import { groupsQuery, lookupQuery } from "@/lib/queries"; import type { Group, LookupResult } from "@/lib/types"; import { defaultGroupId } from "@/lib/defaultGroup"; -import { inputClass, largePrimaryButtonClass } from "@/ui/classes"; +import Select from "@/ui/Select"; +import { styles as shared } from "@/ui/styles"; +import { colors } from "@/ui/tokens.stylex"; + +const DARK = "@media (prefers-color-scheme: dark)"; interface Submitted { domain: string; @@ -13,10 +18,123 @@ interface Submitted { interface Verdict { label: string; - className: string; + tone: "local" | "blocked" | "forwarded" | "allowed"; description: string; } +const styles = stylex.create({ + heading: { + fontSize: "1.5rem", + lineHeight: "2rem", + fontWeight: 600, + }, + intro: { + marginTop: "0.5rem", + color: colors.textMuted, + }, + form: { + marginTop: "1.5rem", + display: "flex", + maxWidth: "42rem", + flexWrap: "wrap", + alignItems: "flex-end", + gap: "0.75rem", + }, + domainField: { + minWidth: "14rem", + flexGrow: 1, + }, + fieldLabel: { + display: "block", + fontSize: "0.875rem", + lineHeight: "1.25rem", + fontWeight: 500, + }, + note: { + marginTop: "1.5rem", + color: colors.textMuted, + }, + error: { + marginTop: "1.5rem", + fontSize: "0.875rem", + lineHeight: "1.25rem", + color: colors.danger, + }, + card: { + marginTop: "1.5rem", + borderRadius: "0.25rem", + borderWidth: 1, + borderStyle: "solid", + borderColor: colors.border, + }, + banner: { + borderStartStartRadius: "0.25rem", + borderStartEndRadius: "0.25rem", + paddingInline: "1rem", + paddingBlock: "0.75rem", + }, + /** Four verdicts need four tints; only "blocked" maps onto a token role. */ + local: { + backgroundColor: { default: "oklch(93.2% 0.032 255.585)", [DARK]: "oklch(28.2% 0.091 267.935)" }, + color: { default: "oklch(42.4% 0.199 265.638)", [DARK]: "oklch(80.9% 0.105 251.813)" }, + }, + blocked: { + backgroundColor: { default: "oklch(93.6% 0.032 17.717)", [DARK]: "oklch(25.8% 0.092 26.042)" }, + color: { default: "oklch(44.4% 0.177 26.899)", [DARK]: "oklch(80.8% 0.114 19.571)" }, + }, + forwarded: { + backgroundColor: { default: "oklch(96.2% 0.059 95.617)", [DARK]: "oklch(27.9% 0.077 45.635)" }, + color: { default: "oklch(47.3% 0.137 46.201)", [DARK]: "oklch(87.9% 0.169 91.605)" }, + }, + allowed: { + backgroundColor: { default: "oklch(96.2% 0.044 156.743)", [DARK]: "oklch(26.6% 0.065 152.934)" }, + color: { default: "oklch(44.8% 0.119 151.328)", [DARK]: "oklch(87.1% 0.15 154.449)" }, + }, + verdictLabel: { + fontSize: "1.125rem", + lineHeight: "1.75rem", + fontWeight: 600, + }, + verdictDescription: { + fontSize: "0.875rem", + lineHeight: "1.25rem", + }, + details: { + paddingInline: "1rem", + paddingBlock: "0.5rem", + fontSize: "0.875rem", + lineHeight: "1.25rem", + }, + /** `divide-y`: a hairline between rows, so the first row carries none. */ + detailRow: { + display: "flex", + gap: "1rem", + paddingBlock: "0.5rem", + borderTopWidth: { default: 1, ":first-child": 0 }, + borderTopStyle: "solid", + borderTopColor: colors.border, + }, + detailTerm: { + width: "10rem", + flexShrink: 0, + color: colors.textMuted, + }, + detailValue: { + minWidth: 0, + overflowWrap: "break-word", + }, + sourceLink: { + color: colors.primaryOnSurface, + textDecorationLine: "underline", + }, +}); + +function toneStyle(tone: Verdict["tone"]) { + if (tone === "local") return styles.local; + if (tone === "blocked") return styles.blocked; + return tone === "forwarded" ? styles.forwarded : styles.allowed; +} + /** * Header priority follows the pipeline order the lookup handler documents * (PLAN §6): local records answer first, then the block decision, then @@ -26,27 +144,27 @@ export function verdictOf(result: LookupResult): Verdict { if (result.local_records) { return { label: "Local answer", - className: "bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300", + tone: "local", description: "A local record answers this name directly.", }; } if (result.blocked) { return { label: "Blocked", - className: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300", + tone: "blocked", description: "Queries for this name get a blocked response.", }; } if (result.forward_zone !== null) { return { label: "Forwarded", - className: "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300", + tone: "forwarded", description: `Queries go to the resolver for zone ${result.forward_zone}.`, }; } return { label: "Allowed", - className: "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-300", + tone: "allowed", description: "Queries resolve through the upstream pool.", }; } @@ -68,9 +186,9 @@ function errorMessage(error: unknown): string { function DetailRow({ label, children }: { label: string; children: ReactNode }) { return ( -
-
{label}
-
{children}
+
+
{label}
+
{children}
); } @@ -80,26 +198,30 @@ function VerdictCard({ result, groups }: { result: LookupResult; groups: Group[] const groupName = groups.find((group) => group.id === result.group_id)?.name ?? `#${result.group_id}`; return ( -
-
-

{verdict.label}

-

{verdict.description}

+
+
+

{verdict.label}

+

{verdict.description}

-
+
- {result.domain} + {result.domain} {groupName} {result.local_records ? "Yes" : "No"} - {result.forward_zone !== null ? {result.forward_zone} : "—"} + {result.forward_zone !== null ? ( + {result.forward_zone} + ) : ( + "—" + )} {result.blocked ? "Yes" : "No"} - {result.reason} + {result.reason} - {result.matched !== "" ? {result.matched} : "—"} + {result.matched !== "" ? {result.matched} : "—"} {result.source_url !== null ? ( @@ -107,7 +229,7 @@ function VerdictCard({ result, groups }: { result: LookupResult; groups: Group[] href={result.source_url} target="_blank" rel="noreferrer" - className="text-blue-600 underline dark:text-blue-400" + {...stylex.props(styles.sourceLink, shared.focusRing)} > {result.source_url} @@ -117,7 +239,7 @@ function VerdictCard({ result, groups }: { result: LookupResult; groups: Group[] {result.safe_search_rewrite !== null ? ( - {result.safe_search_rewrite} + {result.safe_search_rewrite} ) : ( "—" )} @@ -153,13 +275,13 @@ export default function LookupPage() { return (
-

Lookup

-

+

Lookup

+

What the pipeline would do with a domain: local records, forward zones, block decision, safe search.

-
-
-