1- import { ArrowUturnLeftIcon , PlusIcon , ViewColumnsIcon } from "@heroicons/react/20/solid" ;
1+ import {
2+ ArrowUturnLeftIcon ,
3+ CodeBracketIcon ,
4+ PencilSquareIcon ,
5+ PlusIcon ,
6+ ViewColumnsIcon ,
7+ } from "@heroicons/react/20/solid" ;
28import { GripVerticalIcon } from "lucide-react" ;
39import { useMemo , useState } from "react" ;
410import { Button } from "~/components/primitives/Buttons" ;
@@ -18,19 +24,22 @@ import {
1824 type SmartColumnDef ,
1925} from "./runColumns" ;
2026import { AddSmartColumnDialog } from "./AddSmartColumnDialog" ;
21- import { SMART_SOURCE_DOT_COLOR } from "./smartColumnData" ;
2227
2328function keyFor ( col : ResolvedColumn ) : string {
2429 return col . kind === "standard" ? `std:${ col . def . id } ` : `smart:${ col . index } ` ;
2530}
2631
32+ type SmartEditTarget = { index : number ; def : SmartColumnDef } ;
33+
2734export function RunsDisplayOptions ( ) {
2835 const environment = useEnvironment ( ) ;
2936 const { isManagedCloud } = useFeatures ( ) ;
3037 const location = useOptimisticLocation ( ) ;
3138 const { values, replace } = useSearchParams ( ) ;
3239 const [ addOpen , setAddOpen ] = useState ( false ) ;
40+ const [ editing , setEditing ] = useState < SmartEditTarget | null > ( null ) ;
3341 const [ dragKey , setDragKey ] = useState < string | null > ( null ) ;
42+ const [ overKey , setOverKey ] = useState < string | null > ( null ) ;
3443
3544 const runtime : RunColumnRuntime = {
3645 isManagedCloud,
@@ -47,7 +56,6 @@ export function RunsDisplayOptions() {
4756
4857 const available = availableStandardColumns ( runtime ) ;
4958 const visibleStandardCount = layout . visible . filter ( ( c ) => c . kind === "standard" ) . length ;
50- const smartCount = layout . visible . filter ( ( c ) => c . kind === "smart" ) . length ;
5159
5260 const applyVisible = ( nextVisible : ResolvedColumn [ ] ) => {
5361 const encoded = encodeColumnLayout ( nextVisible , runtime ) ;
@@ -71,9 +79,16 @@ export function RunsDisplayOptions() {
7179 applyVisible ( layout . visible . filter ( ( c ) => ! ( c . kind === "smart" && c . index === index ) ) ) ;
7280 } ;
7381
74- const addSmart = ( def : SmartColumnDef ) => {
75- const nextIndex = layout . smartColumns . length ;
76- applyVisible ( [ ...layout . visible , { kind : "smart" , index : nextIndex , def } ] ) ;
82+ const submitSmart = ( def : SmartColumnDef ) => {
83+ if ( editing ) {
84+ applyVisible (
85+ layout . visible . map ( ( c ) =>
86+ c . kind === "smart" && c . index === editing . index ? { ...c , def } : c
87+ )
88+ ) ;
89+ } else {
90+ applyVisible ( [ ...layout . visible , { kind : "smart" , index : layout . smartColumns . length , def } ] ) ;
91+ }
7792 } ;
7893
7994 const reset = ( ) => replace ( { cols : undefined , sc : undefined } ) ;
@@ -89,17 +104,17 @@ export function RunsDisplayOptions() {
89104 applyVisible ( arr ) ;
90105 } ;
91106
107+ const endDrag = ( ) => {
108+ setDragKey ( null ) ;
109+ setOverKey ( null ) ;
110+ } ;
111+
92112 return (
93113 < >
94114 < Popover >
95115 < PopoverTrigger asChild >
96116 < Button variant = "secondary/small" LeadingIcon = { ViewColumnsIcon } className = "ml-auto" >
97- < span className = "flex items-center gap-1.5" >
98- Display
99- { smartCount > 0 && (
100- < span className = "text-xs text-text-dimmed" > { smartCount } smart</ span >
101- ) }
102- </ span >
117+ Display
103118 </ Button >
104119 </ PopoverTrigger >
105120 < PopoverContent align = "end" className = "w-64 p-0" >
@@ -118,16 +133,23 @@ export function RunsDisplayOptions() {
118133 draggable
119134 locked = { col . kind === "standard" && ! ! col . def . locked }
120135 dragging = { dragKey === keyFor ( col ) }
136+ isOver = { overKey === keyFor ( col ) && dragKey !== keyFor ( col ) }
121137 onDragStart = { ( ) => setDragKey ( keyFor ( col ) ) }
122- onDragEnd = { ( ) => setDragKey ( null ) }
138+ onDragEnter = { ( ) => setOverKey ( keyFor ( col ) ) }
139+ onDragEnd = { endDrag }
123140 onDrop = { ( ) => {
124141 if ( dragKey ) reorder ( dragKey , keyFor ( col ) ) ;
125- setDragKey ( null ) ;
142+ endDrag ( ) ;
126143 } }
127144 onToggle = { ( ) => {
128145 if ( col . kind === "smart" ) removeSmart ( col . index ) ;
129146 else if ( ! col . def . locked ) hideStandard ( col . def . id ) ;
130147 } }
148+ onEdit = {
149+ col . kind === "smart"
150+ ? ( ) => setEditing ( { index : col . index , def : col . def } )
151+ : undefined
152+ }
131153 />
132154 ) ) }
133155 { layout . hiddenStandard . map ( ( def ) => (
@@ -138,6 +160,7 @@ export function RunsDisplayOptions() {
138160 draggable = { false }
139161 locked = { false }
140162 dragging = { false }
163+ isOver = { false }
141164 onToggle = { ( ) => showStandard ( def . id ) }
142165 />
143166 ) ) }
@@ -164,9 +187,15 @@ export function RunsDisplayOptions() {
164187 </ PopoverContent >
165188 </ Popover >
166189 < AddSmartColumnDialog
167- open = { addOpen }
168- onOpenChange = { setAddOpen }
169- onAdd = { addSmart }
190+ open = { addOpen || editing !== null }
191+ editing = { editing ?. def ?? null }
192+ onOpenChange = { ( next ) => {
193+ if ( ! next ) {
194+ setAddOpen ( false ) ;
195+ setEditing ( null ) ;
196+ }
197+ } }
198+ onSubmit = { submitSmart }
170199 currentSearch = { location . search }
171200 />
172201 </ >
@@ -179,8 +208,11 @@ function ColumnRow({
179208 draggable,
180209 locked,
181210 dragging,
211+ isOver,
182212 onToggle,
213+ onEdit,
183214 onDragStart,
215+ onDragEnter,
184216 onDragEnd,
185217 onDrop,
186218} : {
@@ -189,45 +221,49 @@ function ColumnRow({
189221 draggable : boolean ;
190222 locked : boolean ;
191223 dragging : boolean ;
224+ isOver : boolean ;
192225 onToggle : ( ) => void ;
226+ onEdit ?: ( ) => void ;
193227 onDragStart ?: ( ) => void ;
228+ onDragEnter ?: ( ) => void ;
194229 onDragEnd ?: ( ) => void ;
195230 onDrop ?: ( ) => void ;
196231} ) {
197232 const isSmart = col . kind === "smart" ;
198- const label = col . def . label ;
199- const isDuration = col . kind === "standard" && col . def . id === "dur" ;
200233
201234 return (
202235 < div
203236 className = { cn (
204- "flex h-8 items-center gap-2 px-3 transition-colors hover:bg-charcoal-750" ,
237+ "relative flex h-8 items-center gap-2 px-3 transition-colors hover:bg-charcoal-750" ,
205238 dragging && "opacity-40"
206239 ) }
207240 draggable = { draggable }
208241 onDragStart = { onDragStart }
242+ onDragEnter = { onDragEnter }
209243 onDragEnd = { onDragEnd }
210244 onDragOver = { ( e ) => {
211245 if ( draggable ) e . preventDefault ( ) ;
212246 } }
213247 onDrop = { onDrop }
214248 >
215- { locked ? (
216- < Checkbox checked disabled />
217- ) : (
218- < Checkbox checked = { checked } onChange = { onToggle } />
219- ) }
220- { isSmart && (
221- < span
222- className = { cn ( "size-2 flex-none rounded-full" , SMART_SOURCE_DOT_COLOR [ col . def . source ] ) }
223- />
224- ) }
249+ { isOver && < div className = "absolute inset-x-0 top-0 h-0.5 bg-blue-500" /> }
250+ { locked ? < Checkbox checked disabled /> : < Checkbox checked = { checked } onChange = { onToggle } /> }
251+ { isSmart && < CodeBracketIcon className = "size-4 flex-none text-text-dimmed" /> }
225252 < span
226253 className = { cn ( "flex-1 truncate text-sm" , checked ? "text-text-bright" : "text-text-dimmed" ) }
227254 >
228- { label }
255+ { col . def . label }
229256 </ span >
230- { isDuration && < span className = "text-xs text-text-dimmed" > 3 cells</ span > }
257+ { onEdit && (
258+ < button
259+ type = "button"
260+ onClick = { onEdit }
261+ aria-label = { `Edit ${ col . def . label } ` }
262+ className = "flex size-5 items-center justify-center rounded text-text-dimmed transition-colors hover:text-text-bright focus-custom"
263+ >
264+ < PencilSquareIcon className = "size-3.5" />
265+ </ button >
266+ ) }
231267 { draggable ? (
232268 < GripVerticalIcon className = "size-4 cursor-grab text-text-dimmed active:cursor-grabbing" />
233269 ) : (
0 commit comments