polish(webapp): tighten smart-column dialog copy and display popover layout
- Trim the Add smart column dialog copy: one-line display-only callout, example-only path hint, drop redundant field helpers, remove an em dash from the sample header. - Move the sample header and run picker outside the box to match the Preview header, and collapse the doubled box into one panel. - Clear the path and label fields when the source changes. - Rework the Display popover smart-column row: labels stay flush-left, the bolt marker sits inline after the label, and the edit/remove/grip controls are a right-aligned group with larger hit targets.
This commit is contained in:
@@ -94,6 +94,14 @@ export function AddSmartColumnDialog({
|
||||
setSampleIndex(0);
|
||||
}, [source]);
|
||||
|
||||
const handleSourceChange = (next: SmartColumnSource) => {
|
||||
if (next === source) return;
|
||||
setSource(next);
|
||||
setPath("");
|
||||
setLabel("");
|
||||
setLabelEdited(false);
|
||||
};
|
||||
|
||||
const effectiveLabel = labelEdited ? label : labelFromPath(path);
|
||||
|
||||
const sampleLoaded = sample.data !== undefined && sample.state === "idle";
|
||||
@@ -145,8 +153,7 @@ export function AddSmartColumnDialog({
|
||||
<DialogHeader>{editing ? "Edit smart column" : "Add smart column"}</DialogHeader>
|
||||
<div className="flex flex-col gap-5 p-1">
|
||||
<Callout variant="info">
|
||||
Display only. A smart column shows you a value from a run, but you can't sort or filter
|
||||
the list by it. To narrow the list, use tags or the query editor.
|
||||
Smart columns are display only. You can't sort or filter by them.
|
||||
</Callout>
|
||||
|
||||
<div className="grid grid-cols-1 gap-5 md:grid-cols-[minmax(0,1fr)_260px_220px]">
|
||||
@@ -160,7 +167,7 @@ export function AddSmartColumnDialog({
|
||||
label={card.label}
|
||||
description={card.description}
|
||||
selected={source === card.value}
|
||||
onSelect={() => setSource(card.value)}
|
||||
onSelect={() => handleSourceChange(card.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -175,10 +182,9 @@ export function AddSmartColumnDialog({
|
||||
placeholder="$.order.total"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Dot and bracket notation, e.g. <code>$.order.total</code> or{" "}
|
||||
<code>$.items[0].sku</code>. Use <code>.length</code> for an array, string, or
|
||||
key count.
|
||||
<Paragraph variant="extra-small" className="text-balance text-text-dimmed">
|
||||
e.g. <code>$.order.total</code>, <code>$.items[0].sku</code>,{" "}
|
||||
<code>$.items.length</code>
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
@@ -191,9 +197,6 @@ export function AddSmartColumnDialog({
|
||||
}}
|
||||
placeholder={labelFromPath(path)}
|
||||
/>
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Defaults to the last part of the path.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -216,16 +219,12 @@ export function AddSmartColumnDialog({
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Number right-aligns the column and uses tabular figures. Anything that doesn't
|
||||
parse falls back to text.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5 self-start rounded-lg border border-grid-dimmed bg-background-dimmed p-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Paragraph variant="extra-extra-small/dimmed/caps">Sample — {source}</Paragraph>
|
||||
<div className="flex flex-col gap-1.5 self-start">
|
||||
<div className="flex h-5 items-center justify-between gap-2">
|
||||
<Paragraph variant="extra-extra-small/dimmed/caps">Sample {source}</Paragraph>
|
||||
{usable.length > 1 && (
|
||||
<SampleRunPicker
|
||||
index={activeIndex}
|
||||
@@ -235,33 +234,37 @@ export function AddSmartColumnDialog({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!sampleLoaded ? (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Loading…
|
||||
</Paragraph>
|
||||
) : activeSample ? (
|
||||
<SmartColumnSample
|
||||
value={activeSample.value}
|
||||
activePath={path.trim()}
|
||||
onSelectPath={setPath}
|
||||
/>
|
||||
) : runCount === 0 ? (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
No runs to sample.
|
||||
</Paragraph>
|
||||
) : anyOffloaded ? (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Recent {source}s are offloaded to object storage, too large to sample here.
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
No recent run has a {source} value to sample.
|
||||
</Paragraph>
|
||||
)}
|
||||
<div className="overflow-hidden rounded-lg border border-grid-dimmed bg-charcoal-900 p-3">
|
||||
{!sampleLoaded ? (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Loading…
|
||||
</Paragraph>
|
||||
) : activeSample ? (
|
||||
<SmartColumnSample
|
||||
value={activeSample.value}
|
||||
activePath={path.trim()}
|
||||
onSelectPath={setPath}
|
||||
/>
|
||||
) : runCount === 0 ? (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
No runs to sample yet.
|
||||
</Paragraph>
|
||||
) : anyOffloaded ? (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
Recent {source}s are too large to sample here.
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph variant="extra-small" className="text-text-dimmed">
|
||||
No recent run has a {source} to sample.
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5 self-start">
|
||||
<Paragraph variant="extra-extra-small/dimmed/caps">Preview</Paragraph>
|
||||
<div className="flex h-5 items-center">
|
||||
<Paragraph variant="extra-extra-small/dimmed/caps">Preview</Paragraph>
|
||||
</div>
|
||||
<SmartColumnPreview rows={perRun} def={previewDef} loaded={sampleLoaded} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -377,7 +380,7 @@ function SmartColumnPreview({
|
||||
{!loaded ? (
|
||||
<div className="px-2.5 py-2 text-xs text-text-dimmed">Loading…</div>
|
||||
) : rows.length === 0 ? (
|
||||
<div className="px-2.5 py-2 text-xs text-text-dimmed">No runs</div>
|
||||
<div className="px-2.5 py-2 text-xs text-text-dimmed">No runs yet</div>
|
||||
) : (
|
||||
rows.map((row, index) => {
|
||||
const cell = def.path
|
||||
|
||||
@@ -199,6 +199,7 @@ function ColumnRow({
|
||||
col,
|
||||
checked,
|
||||
locked,
|
||||
reserveIcon,
|
||||
dragging,
|
||||
isOver,
|
||||
onToggle,
|
||||
@@ -227,7 +228,7 @@ function ColumnRow({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative flex h-8 items-center gap-2 px-3 transition-colors hover:bg-charcoal-750",
|
||||
"group relative flex h-8 items-center gap-2 pl-3 pr-1.5 transition-colors hover:bg-charcoal-750",
|
||||
dragging && "opacity-40"
|
||||
)}
|
||||
draggable
|
||||
@@ -239,33 +240,39 @@ function ColumnRow({
|
||||
>
|
||||
{isOver && <div className="absolute inset-x-0 top-0 h-0.5 bg-blue-500" />}
|
||||
{locked ? <Checkbox checked disabled /> : <Checkbox checked={checked} onChange={onToggle} />}
|
||||
{isSmart && <BoltIcon className="size-4 flex-none text-text-dimmed" />}
|
||||
<span
|
||||
className={cn("flex-1 truncate text-sm", checked ? "text-text-bright" : "text-text-dimmed")}
|
||||
>
|
||||
{col.def.label}
|
||||
<span className="flex min-w-0 flex-1 items-center gap-1.5">
|
||||
<span
|
||||
className={cn("truncate text-sm", checked ? "text-text-bright" : "text-text-dimmed")}
|
||||
>
|
||||
{col.def.label}
|
||||
</span>
|
||||
{isSmart && <BoltIcon className="size-3.5 flex-none text-text-dimmed" />}
|
||||
</span>
|
||||
{onEdit && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onEdit}
|
||||
aria-label={`Edit ${col.def.label}`}
|
||||
className="flex size-5 items-center justify-center rounded text-text-dimmed opacity-0 transition hover:text-text-bright focus-custom group-hover:opacity-100"
|
||||
>
|
||||
<PencilSquareIcon className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{onRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
aria-label={`Remove ${col.def.label}`}
|
||||
className="flex size-5 items-center justify-center rounded text-text-dimmed opacity-0 transition hover:text-error focus-custom group-hover:opacity-100"
|
||||
>
|
||||
<XMarkIcon className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
<GripVerticalIcon className="size-4 flex-none cursor-grab text-text-dimmed opacity-0 transition group-hover:opacity-100 active:cursor-grabbing" />
|
||||
<div className="flex flex-none items-center gap-0.5">
|
||||
{onEdit && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onEdit}
|
||||
aria-label={`Edit ${col.def.label}`}
|
||||
className="flex size-6 items-center justify-center rounded text-text-dimmed opacity-0 transition hover:bg-charcoal-700 hover:text-text-bright focus-custom group-hover:opacity-100"
|
||||
>
|
||||
<PencilSquareIcon className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
{onRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
aria-label={`Remove ${col.def.label}`}
|
||||
className="flex size-6 items-center justify-center rounded text-text-dimmed opacity-0 transition hover:bg-charcoal-700 hover:text-error focus-custom group-hover:opacity-100"
|
||||
>
|
||||
<XMarkIcon className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
<span className="flex size-6 cursor-grab items-center justify-center text-text-dimmed opacity-0 transition group-hover:opacity-100 active:cursor-grabbing">
|
||||
<GripVerticalIcon className="size-4" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function SmartColumnSample({
|
||||
onSelectPath: (path: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="max-h-80 overflow-auto rounded bg-charcoal-900 p-2 font-mono text-xs leading-relaxed scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
|
||||
<div className="max-h-80 overflow-auto font-mono text-xs leading-relaxed scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
|
||||
<JsonNode
|
||||
name={undefined}
|
||||
path="$"
|
||||
|
||||
Reference in New Issue
Block a user