Skip to content

Commit a8f55e4

Browse files
samejrclaude
andcommitted
feat(webapp): use the shared form primitives in the smart column dialog
The dialog had reinvented two components the app already ships. Source and Display as were a hand-rolled "SourceCard" button with a fake radio circle; both now use RadioGroup/RadioGroupItem — the "description" variant for Source (it takes a description) and "button/small" for Display as. The text fields move to the canonical InputGroup + Label + Input + Hint composition instead of a bespoke flex column with a raw Paragraph. SourceCard is deleted. - Halve the gap between the three columns (gap-5 -> gap-2.5). - Swap the smart column bolt for a custom SmartColumnIcon, in all three places it appears: the runs table header, the Columns popover, and the dialog preview (which is a preview of that same header, so they have to match). - Cancel becomes secondary. - Pad above the intro text to match the side padding, and make it a size larger and bright. - Reword the intro to lead with what to do rather than what the feature is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 95827ba commit a8f55e4

5 files changed

Lines changed: 62 additions & 72 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** Marks a smart column: in the runs table header, the Columns popover, and the dialog preview. */
2+
export function SmartColumnIcon({ className }: { className?: string }) {
3+
return (
4+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
5+
<path
6+
d="M5.94723 12.4318L12.3011 3.53646C12.9468 2.63242 14.3689 3.24855 14.1511 4.33794L13.1543 9.32131C13.0905 9.64031 13.3346 9.93793 13.6599 9.93793H17.2138C18.0524 9.93793 18.5402 10.8859 18.0527 11.5682L11.6989 20.4636C11.0532 21.3676 9.63107 20.7515 9.84895 19.6621L10.8456 14.6788C10.9095 14.3598 10.6654 14.0621 10.3401 14.0621H6.78622C5.9476 14.0621 5.45978 13.1142 5.94723 12.4318Z"
7+
stroke="currentColor"
8+
strokeWidth="2"
9+
strokeLinejoin="round"
10+
/>
11+
</svg>
12+
);
13+
}

apps/webapp/app/components/runs/v3/AddSmartColumnDialog.tsx

Lines changed: 43 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { BoltIcon, ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/20/solid";
1+
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/20/solid";
22
import { useEffect, useMemo, useState } from "react";
33
import { useTypedFetcher } from "remix-typedjson";
4+
import { SmartColumnIcon } from "~/assets/icons/SmartColumnIcon";
45
import { Button } from "~/components/primitives/Buttons";
56
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "~/components/primitives/Dialog";
7+
import { Hint } from "~/components/primitives/Hint";
68
import { Input } from "~/components/primitives/Input";
9+
import { InputGroup } from "~/components/primitives/InputGroup";
710
import { Label } from "~/components/primitives/Label";
811
import { Paragraph } from "~/components/primitives/Paragraph";
12+
import { RadioGroup, RadioGroupItem } from "~/components/primitives/RadioButton";
913
import { useEnvironment } from "~/hooks/useEnvironment";
1014
import { useOrganization } from "~/hooks/useOrganizations";
1115
import { useProject } from "~/hooks/useProject";
@@ -167,44 +171,49 @@ export function AddSmartColumnDialog({
167171
header or footer off a short screen. */}
168172
<DialogContent className="max-h-[90vh] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-[860px]!">
169173
<DialogHeader>{editing ? "Edit smart column" : "Add smart column"}</DialogHeader>
170-
<div className="flex min-h-0 flex-col gap-5">
171-
<Paragraph variant="small/dimmed">
172-
Pull a single value out of a run's payload, metadata, or output by JSON path. Smart
173-
columns are display only — you can't sort or filter by them.
174+
<div className="flex min-h-0 flex-col gap-5 pt-3">
175+
<Paragraph variant="base/bright">
176+
Pick a source, then click a value in the sample to turn it into a column. Smart columns
177+
are display only — you can't sort or filter by them.
174178
</Paragraph>
175179

176-
<div className="grid min-h-0 grid-cols-1 items-stretch gap-5 md:grid-cols-3">
180+
<div className="grid min-h-0 grid-cols-1 items-stretch gap-2.5 md:grid-cols-3">
177181
<div className="flex flex-col gap-4 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
178-
<div className="flex flex-col gap-1.5">
182+
<InputGroup fullWidth>
179183
<Label>Source</Label>
180-
<div className="flex flex-col gap-2">
184+
<RadioGroup
185+
className="flex flex-col gap-2"
186+
value={source}
187+
onValueChange={(next) => handleSourceChange(next as SmartColumnSource)}
188+
>
181189
{SOURCE_CARDS.map((card) => (
182-
<SourceCard
190+
<RadioGroupItem
183191
key={card.value}
192+
id={`smart-source-${card.value}`}
193+
value={card.value}
194+
variant="description"
184195
label={card.label}
185196
description={card.description}
186-
selected={source === card.value}
187-
onSelect={() => handleSourceChange(card.value)}
188197
/>
189198
))}
190-
</div>
191-
</div>
199+
</RadioGroup>
200+
</InputGroup>
192201

193-
<div className="flex flex-col gap-1.5">
202+
<InputGroup fullWidth>
194203
<Label>JSON path</Label>
195204
<Input
196205
value={path}
197206
onChange={(e) => setPath(e.target.value)}
198207
placeholder="$.order.total"
199208
spellCheck={false}
200209
/>
201-
<Paragraph variant="extra-small" className="text-balance text-text-dimmed">
210+
<Hint className="text-balance">
202211
e.g. <code>$.order.total</code>, <code>$.items[0].sku</code>,{" "}
203212
<code>$.items.length</code>
204-
</Paragraph>
205-
</div>
213+
</Hint>
214+
</InputGroup>
206215

207-
<div className="flex flex-col gap-1.5">
216+
<InputGroup fullWidth>
208217
<Label>Column label</Label>
209218
<Input
210219
value={effectiveLabel}
@@ -214,21 +223,27 @@ export function AddSmartColumnDialog({
214223
}}
215224
placeholder={labelFromPath(path)}
216225
/>
217-
</div>
226+
</InputGroup>
218227

219-
<div className="flex flex-col gap-1.5">
228+
<InputGroup fullWidth>
220229
<Label>Display as</Label>
221-
<div className="grid grid-cols-2 gap-2">
230+
<RadioGroup
231+
className="grid grid-cols-2 gap-2"
232+
value={displayAs}
233+
onValueChange={(next) => setDisplayAs(next as SmartColumnDisplay)}
234+
>
222235
{DISPLAY_OPTIONS.map((option) => (
223-
<SourceCard
236+
<RadioGroupItem
224237
key={option.value}
238+
id={`smart-display-${option.value}`}
239+
value={option.value}
240+
variant="button/small"
225241
label={option.label}
226-
selected={displayAs === option.value}
227-
onSelect={() => setDisplayAs(option.value)}
242+
className="w-full"
228243
/>
229244
))}
230-
</div>
231-
</div>
245+
</RadioGroup>
246+
</InputGroup>
232247
</div>
233248

234249
<div className="flex min-h-0 flex-col gap-1.5">
@@ -279,7 +294,7 @@ export function AddSmartColumnDialog({
279294
</div>
280295
</div>
281296
<DialogFooter>
282-
<Button variant="tertiary/medium" onClick={() => onOpenChange(false)}>
297+
<Button variant="secondary/medium" onClick={() => onOpenChange(false)}>
283298
Cancel
284299
</Button>
285300
<Button variant="primary/medium" disabled={!canSubmit} onClick={handleSubmit}>
@@ -326,46 +341,6 @@ function SampleRunPicker({
326341
);
327342
}
328343

329-
/** Radio card used for both Source (with a description) and Display as (without). */
330-
function SourceCard({
331-
label,
332-
description,
333-
selected,
334-
onSelect,
335-
}: {
336-
label: string;
337-
description?: string;
338-
selected: boolean;
339-
onSelect: () => void;
340-
}) {
341-
return (
342-
<button
343-
type="button"
344-
onClick={onSelect}
345-
aria-pressed={selected}
346-
className={cn(
347-
"flex cursor-pointer flex-col gap-1 rounded-lg border p-2.5 text-left transition",
348-
selected
349-
? "border-blue-500 bg-blue-500/10"
350-
: "border-grid-bright bg-background-dimmed hover:border-text-dimmed"
351-
)}
352-
>
353-
<span className="flex items-center gap-1.5 text-sm font-medium text-text-bright">
354-
<span
355-
className={cn(
356-
"grid size-3.5 flex-none place-items-center rounded-full border",
357-
selected ? "border-blue-500" : "border-text-dimmed"
358-
)}
359-
>
360-
{selected && <span className="size-1.5 rounded-full bg-blue-500" />}
361-
</span>
362-
{label}
363-
</span>
364-
{description && <span className="text-xs text-text-dimmed">{description}</span>}
365-
</button>
366-
);
367-
}
368-
369344
function SmartColumnPreview({
370345
rows,
371346
def,
@@ -381,7 +356,7 @@ function SmartColumnPreview({
381356
return (
382357
<div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-grid-dimmed">
383358
<div className="flex flex-none items-center gap-1 border-b border-grid-dimmed bg-background-dimmed px-2.5 py-1.5">
384-
<BoltIcon className="size-3.5 flex-none text-text-dimmed" />
359+
<SmartColumnIcon className="size-3.5 flex-none text-text-dimmed" />
385360
<span className="truncate text-xs font-medium text-text-bright">
386361
{def.label || "Column"}
387362
</span>

apps/webapp/app/components/runs/v3/RunsDisplayOptions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import {
22
ArrowUturnLeftIcon,
33
PencilSquareIcon,
44
PlusIcon,
5-
BoltIcon,
65
StarIcon as StarIconSolid,
76
XMarkIcon,
87
} from "@heroicons/react/20/solid";
98
import { StarIcon as StarIconOutline } from "@heroicons/react/24/outline";
109
import { GripVerticalIcon } from "lucide-react";
1110
import { useMemo, useRef, useState } from "react";
1211
import { ColumnsIcon } from "~/assets/icons/ColumnsIcon";
12+
import { SmartColumnIcon } from "~/assets/icons/SmartColumnIcon";
1313
import { useFavoritePageToggle } from "~/components/navigation/favoritePages";
1414
import { Button } from "~/components/primitives/Buttons";
1515
import { Checkbox } from "~/components/primitives/Checkbox";
@@ -355,7 +355,7 @@ function ColumnRow({
355355
>
356356
{col.def.label}
357357
</span>
358-
{isSmart && <BoltIcon className="size-3.5 flex-none text-text-dimmed" />}
358+
{isSmart && <SmartColumnIcon className="size-3.5 flex-none text-text-dimmed" />}
359359
</label>
360360
<div className="flex flex-none items-center gap-0.5 pr-1">
361361
{onEdit && (

apps/webapp/app/components/runs/v3/TaskRunsTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
ArrowPathIcon,
33
ArrowRightIcon,
44
ClockIcon,
5-
BoltIcon,
65
CpuChipIcon,
76
NoSymbolIcon,
87
RectangleStackIcon,
@@ -64,6 +63,7 @@ import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
6463
import { useSearchParams } from "~/hooks/useSearchParam";
6564
import type { TaskTriggerSource } from "@trigger.dev/database";
6665
import { BeakerIcon } from "~/assets/icons/BeakerIcon";
66+
import { SmartColumnIcon } from "~/assets/icons/SmartColumnIcon";
6767
import {
6868
parseColumnParams,
6969
resolveColumnLayout,
@@ -490,7 +490,7 @@ function SmartColumnHeader({ def }: { def: SmartColumnDef }) {
490490
return (
491491
<TableHeaderCell>
492492
<span className="flex items-center gap-1">
493-
<BoltIcon className="size-3.5 flex-none text-text-dimmed" />
493+
<SmartColumnIcon className="size-3.5 flex-none text-text-dimmed" />
494494
<span className="truncate">{def.label}</span>
495495
</span>
496496
</TableHeaderCell>

apps/webapp/app/routes/storybook.icons/route.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import { SideMenuRightClosedIcon } from "~/assets/icons/SideMenuRightClosed";
118118
import { SlackIcon } from "~/assets/icons/SlackIcon";
119119
import { SlackMonoIcon } from "~/assets/icons/SlackMonoIcon";
120120
import { SlidersIcon } from "~/assets/icons/SlidersIcon";
121+
import { SmartColumnIcon } from "~/assets/icons/SmartColumnIcon";
121122
import { SnakedArrowIcon } from "~/assets/icons/SnakedArrowIcon";
122123
import { SparkleListIcon } from "~/assets/icons/SparkleListIcon";
123124
import { StarIcon } from "~/assets/icons/StarIcon";
@@ -266,6 +267,7 @@ const icons: IconEntry[] = [
266267
{ name: "SlackIcon", render: simple(SlackIcon) },
267268
{ name: "SlackMonoIcon", render: simple(SlackMonoIcon) },
268269
{ name: "SlidersIcon", render: simple(SlidersIcon) },
270+
{ name: "SmartColumnIcon", render: simple(SmartColumnIcon) },
269271
{ name: "SnakedArrowIcon", render: simple(SnakedArrowIcon) },
270272
{ name: "SparkleListIcon", render: simple(SparkleListIcon) },
271273
{ name: "StarIcon", render: simple(StarIcon) },

0 commit comments

Comments
 (0)