@@ -7,14 +7,12 @@ import { Dialog, DialogContent, DialogHeader } from "~/components/primitives/Dia
77import { Input } from "~/components/primitives/Input" ;
88import { Label } from "~/components/primitives/Label" ;
99import { Paragraph } from "~/components/primitives/Paragraph" ;
10- import SegmentedControl from "~/components/primitives/SegmentedControl" ;
11- import { Switch } from "~/components/primitives/Switch" ;
1210import { useEnvironment } from "~/hooks/useEnvironment" ;
1311import { useOrganization } from "~/hooks/useOrganizations" ;
1412import { useProject } from "~/hooks/useProject" ;
13+ import { cn } from "~/utils/cn" ;
1514import {
1615 SMART_COLUMN_DISPLAYS ,
17- SMART_COLUMN_SOURCES ,
1816 type SmartColumnDef ,
1917 type SmartColumnDisplay ,
2018 type SmartColumnSource ,
@@ -31,16 +29,19 @@ type AddSmartColumnDialogProps = {
3129 currentSearch : string ;
3230} ;
3331
34- const SOURCE_OPTIONS = SMART_COLUMN_SOURCES . map ( ( source ) => ( {
35- label : source . charAt ( 0 ) . toUpperCase ( ) + source . slice ( 1 ) ,
36- value : source ,
37- } ) ) ;
32+ const SOURCE_CARDS : { value : SmartColumnSource ; label : string ; description : string } [ ] = [
33+ { value : "payload" , label : "Payload" , description : "What you triggered the run with." } ,
34+ { value : "metadata" , label : "Metadata" , description : "What the run writes while it runs." } ,
35+ { value : "output" , label : "Output" , description : "What the run returned." } ,
36+ ] ;
3837
3938const DISPLAY_OPTIONS = SMART_COLUMN_DISPLAYS . map ( ( display ) => ( {
4039 label : display . charAt ( 0 ) . toUpperCase ( ) + display . slice ( 1 ) ,
4140 value : display ,
4241} ) ) ;
4342
43+ const DEFAULT_SOURCE : SmartColumnSource = "payload" ;
44+
4445export function AddSmartColumnDialog ( {
4546 open,
4647 editing,
@@ -53,15 +54,15 @@ export function AddSmartColumnDialog({
5354 const environment = useEnvironment ( ) ;
5455 const sample = useTypedFetcher < typeof sampleLoader > ( ) ;
5556
56- const [ source , setSource ] = useState < SmartColumnSource > ( "metadata" ) ;
57+ const [ source , setSource ] = useState < SmartColumnSource > ( DEFAULT_SOURCE ) ;
5758 const [ path , setPath ] = useState ( "" ) ;
5859 const [ label , setLabel ] = useState ( "" ) ;
5960 const [ labelEdited , setLabelEdited ] = useState ( false ) ;
6061 const [ displayAs , setDisplayAs ] = useState < SmartColumnDisplay > ( "text" ) ;
6162
6263 useEffect ( ( ) => {
6364 if ( ! open ) return ;
64- setSource ( editing ?. source ?? "metadata" ) ;
65+ setSource ( editing ?. source ?? DEFAULT_SOURCE ) ;
6566 setPath ( editing ?. path ?? "" ) ;
6667 setLabel ( editing ?. label ?? "" ) ;
6768 setLabelEdited ( editing !== null ) ;
@@ -122,84 +123,101 @@ export function AddSmartColumnDialog({
122123
123124 return (
124125 < Dialog open = { open } onOpenChange = { onOpenChange } >
125- < DialogContent className = "max-w-2xl " >
126+ < DialogContent className = "sm: max-w-[820px]! " >
126127 < DialogHeader > { editing ? "Edit smart column" : "Add smart column" } </ DialogHeader >
127- < div className = "flex flex-col gap-4 p-1" >
128- < div className = "flex flex-col gap-1.5" >
129- < Label > Source</ Label >
130- < SegmentedControl
131- name = "smart-column-source"
132- value = { source }
133- options = { SOURCE_OPTIONS }
134- onChange = { ( value : string ) => setSource ( value as SmartColumnSource ) }
135- fullWidth
136- />
137- < Paragraph variant = "extra-small" className = "text-text-dimmed" >
138- Metadata is what the run writes about itself while it runs, so it has a value before
139- the run ends. Payload is what you triggered it with; output is what it returned.
140- </ Paragraph >
141- </ div >
128+ < div className = "flex flex-col gap-5 p-1" >
129+ < Callout variant = "info" >
130+ Display only. A smart column shows you a value from a run, but you can't sort or filter
131+ the list by it. To narrow the list, use tags or the query editor.
132+ </ Callout >
142133
143- < div className = "grid grid-cols-2 gap-4" >
144- < div className = "flex flex-col gap-1.5" >
145- < Label > JSON path</ Label >
146- < Input
147- value = { path }
148- onChange = { ( e ) => setPath ( e . target . value ) }
149- placeholder = "$.failed"
150- spellCheck = { false }
151- />
152- < Paragraph variant = "extra-small" className = "text-text-dimmed" >
153- Dot and bracket notation, e.g. < code > $.failed</ code > or{ " " }
154- < code > $.suites[0].name</ code > .
155- </ Paragraph >
156- </ div >
157- < div className = "flex flex-col gap-1.5" >
158- < Label > Column label</ Label >
159- < Input
160- value = { effectiveLabel }
161- onChange = { ( e ) => {
162- setLabel ( e . target . value ) ;
163- setLabelEdited ( true ) ;
164- } }
165- placeholder = { labelFromPath ( path ) }
166- />
167- < Paragraph variant = "extra-small" className = "text-text-dimmed" >
168- Defaults to the last part of the path. Rename it to anything you like.
169- </ Paragraph >
170- </ div >
171- </ div >
134+ < div className = "grid grid-cols-1 gap-6 md:grid-cols-[1fr_300px]" >
135+ < div className = "flex flex-col gap-5" >
136+ < div className = "flex flex-col gap-1.5" >
137+ < Label > Source</ Label >
138+ < div className = "grid grid-cols-3 gap-2" >
139+ { SOURCE_CARDS . map ( ( card ) => (
140+ < SourceCard
141+ key = { card . value }
142+ label = { card . label }
143+ description = { card . description }
144+ selected = { source === card . value }
145+ onSelect = { ( ) => setSource ( card . value ) }
146+ />
147+ ) ) }
148+ </ div >
149+ </ div >
172150
173- < div className = "flex flex-col gap-1.5" >
174- < Label > Display as</ Label >
175- < SegmentedControl
176- name = "smart-column-display"
177- value = { displayAs }
178- options = { DISPLAY_OPTIONS }
179- onChange = { ( value : string ) => setDisplayAs ( value as SmartColumnDisplay ) }
180- fullWidth
181- />
182- < Paragraph variant = "extra-small" className = "text-text-dimmed" >
183- Number right-aligns the column and uses tabular figures. Anything that doesn't parse
184- falls back to text.
185- </ Paragraph >
186- </ div >
151+ < div className = "grid grid-cols-2 gap-4" >
152+ < div className = "flex flex-col gap-1.5" >
153+ < Label > JSON path</ Label >
154+ < Input
155+ value = { path }
156+ onChange = { ( e ) => setPath ( e . target . value ) }
157+ placeholder = "$.order.total"
158+ spellCheck = { false }
159+ />
160+ < Paragraph variant = "extra-small" className = "text-text-dimmed" >
161+ Dot and bracket notation, e.g. < code > $.order.total</ code > or{ " " }
162+ < code > $.items[0].sku</ code > .
163+ </ Paragraph >
164+ </ div >
165+ < div className = "flex flex-col gap-1.5" >
166+ < Label > Column label</ Label >
167+ < Input
168+ value = { effectiveLabel }
169+ onChange = { ( e ) => {
170+ setLabel ( e . target . value ) ;
171+ setLabelEdited ( true ) ;
172+ } }
173+ placeholder = { labelFromPath ( path ) }
174+ />
175+ < Paragraph variant = "extra-small" className = "text-text-dimmed" >
176+ Defaults to the last part of the path.
177+ </ Paragraph >
178+ </ div >
179+ </ div >
180+
181+ < div className = "flex flex-col gap-1.5" >
182+ < Label > Display as</ Label >
183+ < div className = "flex flex-wrap gap-2" >
184+ { DISPLAY_OPTIONS . map ( ( option ) => (
185+ < button
186+ key = { option . value }
187+ type = "button"
188+ onClick = { ( ) => setDisplayAs ( option . value ) }
189+ className = { cn (
190+ "rounded-full border px-3.5 py-1 text-sm transition" ,
191+ displayAs === option . value
192+ ? "border-blue-500 bg-blue-500/10 text-text-bright"
193+ : "border-grid-bright text-text-dimmed hover:text-text-bright"
194+ ) }
195+ >
196+ { option . label }
197+ </ button >
198+ ) ) }
199+ </ div >
200+ < Paragraph variant = "extra-small" className = "text-text-dimmed" >
201+ Number right-aligns the column and uses tabular figures. Anything that doesn't
202+ parse falls back to text.
203+ </ Paragraph >
204+ </ div >
205+ </ div >
187206
188- < div className = "grid grid-cols-2 gap-4 rounded border border-grid-dimmed p-3" >
189- < div className = "flex min-w-0 flex-col gap-1.5" >
207+ < div className = "flex flex-col gap-1.5 self-start rounded-lg border border-grid-dimmed bg-background-dimmed p-3" >
190208 < Paragraph variant = "extra-extra-small/dimmed/caps" >
191209 Sample — { source } of the newest run
192210 </ Paragraph >
193- < pre className = "max-h-40 overflow-auto rounded bg-background-dimmed p-2 text-xs text-text-dimmed" >
211+ < pre className = "max-h-44 overflow-auto rounded bg-charcoal-900 p-2 text-xs text-text-dimmed" >
194212 { sample . state === "loading"
195213 ? "Loading…"
196214 : sampleRun
197215 ? sampleJson
198216 : "// no runs to sample" }
199217 </ pre >
200- </ div >
201- < div className = "flex flex-col gap-1.5" >
202- < Paragraph variant = "extra-extra-small/dimmed/caps" > Resolves to < /Paragraph >
218+ < Paragraph variant = "extra-extra-small/dimmed/caps" className = "mt-2" >
219+ Resolves to
220+ </ Paragraph >
203221 < SmartColumnResolvedPreview label = { effectiveLabel } resolved = { resolved } />
204222 { sampleRun && (
205223 < Paragraph variant = "extra-small" className = "text-text-dimmed" >
@@ -209,19 +227,6 @@ export function AddSmartColumnDialog({
209227 ) }
210228 </ div >
211229 </ div >
212-
213- < div className = "flex items-center gap-6 rounded border border-grid-dimmed px-3 py-2 opacity-60" >
214- < Switch variant = "small" label = "Sort by this column" disabled checked = { false } />
215- < Switch variant = "small" label = "Add to filters" disabled checked = { false } />
216- < Paragraph variant = "extra-small" className = "ml-auto text-text-dimmed" >
217- Both off, and not switchable
218- </ Paragraph >
219- </ div >
220-
221- < Callout variant = "warning" >
222- Display only. A smart column shows you a value, but you can't sort or filter the list by
223- it. To narrow the list, use tags or the query editor.
224- </ Callout >
225230 </ div >
226231 < div className = "flex items-center justify-end gap-2 border-t border-grid-dimmed p-3" >
227232 < Button variant = "tertiary/medium" onClick = { ( ) => onOpenChange ( false ) } >
@@ -236,6 +241,45 @@ export function AddSmartColumnDialog({
236241 ) ;
237242}
238243
244+ function SourceCard ( {
245+ label,
246+ description,
247+ selected,
248+ onSelect,
249+ } : {
250+ label : string ;
251+ description : string ;
252+ selected : boolean ;
253+ onSelect : ( ) => void ;
254+ } ) {
255+ return (
256+ < button
257+ type = "button"
258+ onClick = { onSelect }
259+ aria-pressed = { selected }
260+ className = { cn (
261+ "flex flex-col gap-1 rounded-lg border p-2.5 text-left transition" ,
262+ selected
263+ ? "border-blue-500 bg-blue-500/10"
264+ : "border-grid-bright bg-background-dimmed hover:border-text-dimmed"
265+ ) }
266+ >
267+ < span className = "flex items-center gap-1.5 text-sm font-medium text-text-bright" >
268+ < span
269+ className = { cn (
270+ "grid size-3.5 flex-none place-items-center rounded-full border" ,
271+ selected ? "border-blue-500" : "border-text-dimmed"
272+ ) }
273+ >
274+ { selected && < span className = "size-1.5 rounded-full bg-blue-500" /> }
275+ </ span >
276+ { label }
277+ </ span >
278+ < span className = "text-xs text-text-dimmed" > { description } </ span >
279+ </ button >
280+ ) ;
281+ }
282+
239283function SmartColumnResolvedPreview ( {
240284 label,
241285 resolved,
0 commit comments