Skip to content

Commit b026969

Browse files
pohyclaude
andcommitted
Redesign UI with utilitarian industrial theme
IBM Plex Mono globally, warm dark palette (#111110 base), amber accents, uppercase tracking-wider labels. Extract useFilePicker hook, lift diffsOnly to App toolbar, restyle DropZone/ComparisonTable with theme tokens, add section header accent borders and centered table layout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b6bc136 commit b026969

6 files changed

Lines changed: 256 additions & 138 deletions

File tree

index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="preconnect" href="https://fonts.googleapis.com" />
7+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
8+
<link
9+
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap"
10+
rel="stylesheet"
11+
/>
612
<title>RBR Setup Compare</title>
713
</head>
814
<body>

src/App.tsx

Lines changed: 104 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,119 @@
1-
import { useState, useCallback } from 'react'
2-
import type { CarSetup } from './lib/lsp-parser.ts'
3-
import { compareSetups } from './lib/compare.ts'
4-
import { sanitizeSetup } from './lib/sanitize.ts'
5-
import { DropZone } from './components/DropZone.tsx'
6-
import { ComparisonTable } from './components/ComparisonTable.tsx'
1+
import { useState, useCallback } from "react";
2+
import type { CarSetup } from "./lib/lsp-parser.ts";
3+
import { compareSetups } from "./lib/compare.ts";
4+
import { useFilePicker } from "./lib/use-file-picker.ts";
5+
import { DropZone } from "./components/DropZone.tsx";
6+
import { ComparisonTable } from "./components/ComparisonTable.tsx";
77

88
function App() {
9-
const [setups, setSetups] = useState<CarSetup[]>([])
9+
const [setups, setSetups] = useState<CarSetup[]>([]);
10+
const [diffsOnly, setDiffsOnly] = useState(true);
1011

11-
const handleFilesAdded = useCallback((newSetups: CarSetup[]) => {
12-
setSetups((prev) => [...prev, ...newSetups.map(sanitizeSetup)])
13-
}, [])
12+
const handleFilesReady = useCallback((newSetups: CarSetup[]) => {
13+
setSetups((prev) => [...prev, ...newSetups]);
14+
}, []);
15+
16+
const { processFiles, triggerFilePicker, error } =
17+
useFilePicker(handleFilesReady);
1418

1519
const handleRemoveSetup = useCallback((index: number) => {
16-
setSetups((prev) => prev.filter((_, i) => i !== index))
17-
}, [])
20+
setSetups((prev) => prev.filter((_, i) => i !== index));
21+
}, []);
22+
23+
const handleReorderSetup = useCallback(
24+
(fromIndex: number, toIndex: number) => {
25+
setSetups((prev) => {
26+
const next = [...prev];
27+
const [moved] = next.splice(fromIndex, 1);
28+
next.splice(toIndex, 0, moved);
29+
return next;
30+
});
31+
},
32+
[],
33+
);
1834

19-
const handleReorderSetup = useCallback((fromIndex: number, toIndex: number) => {
20-
setSetups((prev) => {
21-
const next = [...prev]
22-
const [moved] = next.splice(fromIndex, 1)
23-
next.splice(toIndex, 0, moved)
24-
return next
25-
})
26-
}, [])
35+
const comparison = setups.length >= 1 ? compareSetups(setups) : null;
2736

28-
const comparison = setups.length >= 1 ? compareSetups(setups) : null
37+
if (setups.length === 0) {
38+
return (
39+
<DropZone
40+
hasFiles={false}
41+
onFilesSelected={processFiles}
42+
onBrowse={triggerFilePicker}
43+
error={error}
44+
/>
45+
);
46+
}
2947

3048
return (
31-
<div className="min-h-screen bg-gray-900 text-gray-100">
32-
<DropZone onFilesAdded={handleFilesAdded} hasFiles={setups.length > 0} />
33-
34-
{setups.length > 0 && (
35-
<div className="p-4">
36-
<div className="flex items-center justify-between mb-4">
37-
<h1 className="text-xl font-bold">RBR Setup Compare</h1>
38-
<button
39-
onClick={() => setSetups([])}
40-
className="text-sm text-gray-400 hover:text-gray-200 cursor-pointer"
41-
>
42-
Clear all
43-
</button>
44-
</div>
45-
46-
{comparison && (
47-
<ComparisonTable
48-
result={comparison}
49-
setupNames={setups.map((s) => s.name)}
50-
onRemoveSetup={handleRemoveSetup}
51-
onReorderSetup={handleReorderSetup}
49+
<div className="h-screen flex flex-col bg-base text-text-primary">
50+
<DropZone
51+
hasFiles={true}
52+
onFilesSelected={processFiles}
53+
onBrowse={triggerFilePicker}
54+
error={null}
55+
/>
56+
57+
{/* Toolbar */}
58+
<div className="sticky top-0 z-20 flex items-center justify-between px-4 py-2 bg-surface border-b border-border">
59+
<span className="text-[10px] uppercase tracking-widest text-text-muted font-medium select-none">
60+
RBR Setup Compare
61+
</span>
62+
63+
<div className="flex items-center gap-4">
64+
<span className="text-xs text-text-secondary">
65+
{setups.length} file{setups.length !== 1 ? "s" : ""}
66+
</span>
67+
<label className="flex items-center gap-1.5 cursor-pointer">
68+
<input
69+
type="checkbox"
70+
checked={diffsOnly}
71+
onChange={(e) => setDiffsOnly(e.target.checked)}
72+
className="cursor-pointer accent-accent"
5273
/>
53-
)}
74+
<span className="text-xs text-text-secondary uppercase tracking-wider">
75+
Diffs only
76+
</span>
77+
</label>
78+
</div>
79+
80+
<div className="flex items-center gap-3">
81+
<button
82+
onClick={() => setSetups([])}
83+
className="text-xs text-text-muted hover:text-text-secondary cursor-pointer uppercase tracking-wider"
84+
>
85+
Clear all
86+
</button>
87+
<button
88+
onClick={triggerFilePicker}
89+
className="text-xs font-medium text-accent hover:text-text-primary cursor-pointer uppercase tracking-wider"
90+
>
91+
+ Add files
92+
</button>
93+
</div>
94+
</div>
95+
96+
{/* Error bar */}
97+
{error && (
98+
<div className="px-4 py-1.5 bg-diff-negative/10 border-b border-diff-negative/30 text-diff-negative text-xs">
99+
{error}
54100
</div>
55101
)}
102+
103+
{/* Table */}
104+
<div className="flex-1 overflow-auto p-4">
105+
{comparison && (
106+
<ComparisonTable
107+
result={comparison}
108+
setupNames={setups.map((s) => s.name)}
109+
onRemoveSetup={handleRemoveSetup}
110+
onReorderSetup={handleReorderSetup}
111+
diffsOnly={diffsOnly}
112+
/>
113+
)}
114+
</div>
56115
</div>
57-
)
116+
);
58117
}
59118

60-
export default App
119+
export default App;

src/components/ComparisonTable.tsx

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ type Props = {
77
setupNames: string[];
88
onRemoveSetup: (index: number) => void;
99
onReorderSetup: (from: number, to: number) => void;
10+
diffsOnly: boolean;
1011
};
1112

1213
export function ComparisonTable({
1314
result,
1415
setupNames,
1516
onRemoveSetup,
1617
onReorderSetup,
18+
diffsOnly,
1719
}: Props) {
1820
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
19-
const [diffsOnly, setDiffsOnly] = useState(true);
2021
const [dragIndex, setDragIndex] = useState<number | null>(null);
2122
const dragIndexRef = useRef<number | null>(null);
2223
const containerRef = useRef<HTMLDivElement>(null);
@@ -58,28 +59,20 @@ export function ComparisonTable({
5859
const colCount = setupNames.length + 1;
5960

6061
return (
61-
<div ref={containerRef} className="overflow-auto max-h-[calc(100vh-8rem)]">
62+
<div ref={containerRef} className="mx-auto w-fit">
6263
<div
63-
className="grid text-sm font-mono w-fit"
64+
className="grid text-sm"
6465
style={{
6566
gridTemplateColumns: `auto repeat(${setupNames.length}, auto)`,
6667
}}
6768
>
6869
{/* Header */}
6970
<div
70-
className="sticky top-0 z-10 grid grid-cols-subgrid bg-gray-800 text-gray-200"
71+
className="sticky top-0 z-10 grid grid-cols-subgrid bg-elevated text-text-secondary"
7172
style={{ gridColumn: `span ${colCount}` }}
7273
>
73-
<div className="p-2 border border-gray-700">
74-
<label className="flex items-center gap-2 cursor-pointer font-normal">
75-
<input
76-
type="checkbox"
77-
checked={diffsOnly}
78-
onChange={(e) => setDiffsOnly(e.target.checked)}
79-
className="cursor-pointer"
80-
/>
81-
<span className="text-xs text-gray-400">Diffs only</span>
82-
</label>
74+
<div className="p-2 border border-border text-[10px] uppercase tracking-widest text-text-muted font-medium">
75+
Parameter
8376
</div>
8477
{setupNames.map((name, i) => (
8578
<div
@@ -93,7 +86,7 @@ export function ComparisonTable({
9386
const container = containerRef.current;
9487
if (container) {
9588
const ghost = document.createElement("div");
96-
ghost.className = "text-sm font-mono";
89+
ghost.className = "text-sm";
9790
for (const cell of container.querySelectorAll(
9891
`[data-col="${i}"]`,
9992
)) {
@@ -117,17 +110,17 @@ export function ComparisonTable({
117110
onDrop={handleDrop}
118111
onDragEnd={clearDragState}
119112
className={clsx(
120-
"p-2 border border-gray-700 whitespace-nowrap cursor-grab",
113+
"p-2 border border-border whitespace-nowrap cursor-grab",
121114
dragIndex !== null && dragIndex !== i && "opacity-50",
122115
)}
123116
>
124117
<div className="flex items-center justify-between gap-2">
125-
<span className="truncate" title={name}>
118+
<span className="truncate text-text-primary" title={name}>
126119
{name.replace(/\.lsp$/, "")}
127120
</span>
128121
<button
129122
onClick={() => onRemoveSetup(i)}
130-
className="text-xs text-gray-500 hover:text-red-400 shrink-0 cursor-pointer"
123+
className="text-xs text-text-muted hover:text-diff-negative shrink-0 cursor-pointer"
131124
>
132125
remove
133126
</button>
@@ -183,20 +176,20 @@ function Section({
183176
>
184177
{/* Section header */}
185178
<div
186-
className="sticky top-[37px] z-[5] bg-gray-700 cursor-pointer hover:bg-gray-600 select-none p-2 border border-gray-600 font-semibold text-gray-100"
179+
className="sticky top-[37px] z-[5] bg-elevated cursor-pointer hover:bg-[#2e2e28] select-none p-2 border border-border border-l-2 border-l-accent uppercase tracking-wider text-xs font-medium text-text-primary"
187180
style={{ gridColumn: `span ${colCount}` }}
188181
onClick={onToggle}
189182
>
190183
<span className="flex justify-between">
191184
<span>
192-
<span className="mr-2 inline-block w-4 text-center">
193-
{isCollapsed ? "+" : "-"}
185+
<span className="mr-2 inline-block w-4 text-center text-accent">
186+
{isCollapsed ? "+" : "\u2212"}
194187
</span>
195188
{section.sectionName}
196189
</span>
197190
{diffCount > 0 && (
198-
<span className="text-xs text-yellow-400">
199-
({diffCount} diff{diffCount > 1 ? "s" : ""})
191+
<span className="text-accent-dim">
192+
{diffCount} diff{diffCount > 1 ? "s" : ""}
200193
</span>
201194
)}
202195
</span>
@@ -209,12 +202,12 @@ function Section({
209202
key={`${section.sectionName}-${row.key}`}
210203
className={clsx(
211204
"grid grid-cols-subgrid",
212-
"hover:bg-gray-800/50",
213-
row.isDifferent ? "bg-gray-800/30" : "",
205+
"hover:bg-elevated/50",
206+
row.isDifferent && "bg-diff-bg",
214207
)}
215208
style={{ gridColumn: `span ${colCount}` }}
216209
>
217-
<div className="p-2 border border-gray-700 text-gray-300 whitespace-nowrap">
210+
<div className="p-2 border border-border text-text-secondary whitespace-nowrap">
218211
{row.key}
219212
</div>
220213
{(() => {
@@ -250,28 +243,28 @@ function Section({
250243
})
251244
.replace(",", ".");
252245

253-
let cellColor = "text-gray-200";
246+
let cellColor = "text-text-primary";
254247
let diffSpan: React.ReactNode = null;
255248
const displayVal = !isNaN(numVal) ? fmtVal(numVal) : val;
256249

257250
if (val === null) {
258-
cellColor = "text-gray-600 italic";
251+
cellColor = "text-text-muted italic";
259252
} else if (i > 0 && row.isDifferent) {
260253
if (bothNumeric) {
261254
if (diff > 0) {
262255
diffSpan = (
263-
<span className="text-green-400">
256+
<span className="text-diff-positive">
264257
{" "}
265258
(+{fmtDiff(diff)}{unit})
266259
</span>
267260
);
268261
} else if (diff < 0) {
269262
diffSpan = (
270-
<span className="text-red-400"> ({fmtDiff(diff)}{unit})</span>
263+
<span className="text-diff-negative"> ({fmtDiff(diff)}{unit})</span>
271264
);
272265
}
273266
} else if (String(val) !== String(ref)) {
274-
cellColor = "text-yellow-300";
267+
cellColor = "text-accent";
275268
}
276269
}
277270

@@ -280,13 +273,13 @@ function Section({
280273
key={i}
281274
data-col={i}
282275
className={clsx(
283-
"p-2 border border-gray-700 whitespace-nowrap",
276+
"p-2 border border-border whitespace-nowrap",
284277
cellColor,
285278
dragIndex !== null && dragIndex !== i && "opacity-50",
286279
)}
287280
>
288281
{val === null ? (
289-
"-"
282+
"\u2014"
290283
) : diffSpan ? (
291284
<span className="flex justify-between gap-2">
292285
<span>{displayVal}{unit}</span>

0 commit comments

Comments
 (0)