@@ -22,7 +22,7 @@ const ROW_HEIGHT = 32
2222const ROW_GAP_HEIGHT = 4
2323const DATA_CONTAINER_HEIGHT =
2424 ( ROW_HEIGHT + ROW_GAP_HEIGHT ) * ( MAX_ITEMS - 1 ) + ROW_HEIGHT
25- const COL_MIN_WIDTH = 70
25+ const COL_MIN_WIDTH = 64
2626
2727function ExternalLink < T > ( {
2828 item,
@@ -38,15 +38,21 @@ function ExternalLink<T>({
3838 target = "_blank"
3939 rel = "noreferrer"
4040 href = { dest }
41- className = "w-4 h-4 invisible group-hover:visible"
41+ className = "invisible group-hover:visible"
4242 >
4343 < svg
44- className = "inline w-full h-full ml-1 -mt-1 text-gray-600 dark:text-gray-400"
45- fill = "currentColor"
46- viewBox = "0 0 20 20"
44+ xmlns = "http://www.w3.org/2000/svg"
45+ fill = "none"
46+ viewBox = "0 0 24 24"
47+ className = "inline size-3.5 mb-0.5 text-gray-600 dark:text-gray-400"
4748 >
48- < path d = "M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" > </ path >
49- < path d = "M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" > </ path >
49+ < path
50+ stroke = "currentColor"
51+ strokeLinecap = "round"
52+ strokeLinejoin = "round"
53+ strokeWidth = "2"
54+ d = "M9 5H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4M12 12l9-9-.303.303M14 3h7v7"
55+ />
5056 </ svg >
5157 </ a >
5258 )
@@ -129,6 +135,7 @@ export default function ListReport<
129135 meta : BreakdownResultMeta | null
130136 } > ( { loading : true , list : null , meta : null } )
131137 const [ visible , setVisible ] = useState ( false )
138+ const [ tappedRow , setTappedRow ] = useState < string | null > ( null )
132139
133140 const isRealtime = isRealTimeDashboard ( query )
134141 const goalFilterApplied = hasConversionGoalFilter ( query )
@@ -194,6 +201,38 @@ export default function ListReport<
194201 }
195202 }
196203
204+ function showOnHoverClass ( metric : Metric , listItemName : string ) {
205+ if ( ! metric . meta . showOnHover ) {
206+ return ''
207+ }
208+
209+ // On mobile: show if row is tapped, hide otherwise
210+ // On desktop: slide in from right when hovering
211+ if ( tappedRow === listItemName ) {
212+ return 'translate-x-0 opacity-100 transition-all duration-300'
213+ } else {
214+ return 'translate-x-[100%] opacity-0 transition-all duration-300 md:group-hover/devices:translate-x-0 md:group-hover/devices:opacity-100'
215+ }
216+ }
217+
218+ function slideLeftClass (
219+ metricIndex : number ,
220+ showOnHoverIndex : number ,
221+ hasShowOnHoverMetric : boolean ,
222+ listItemName : string
223+ ) {
224+ // Columns before the showOnHover column should slide left when it appears
225+ if ( ! hasShowOnHoverMetric || metricIndex >= showOnHoverIndex ) {
226+ return ''
227+ }
228+
229+ if ( tappedRow === listItemName ) {
230+ return 'transition-transform duration-300 translate-x-0'
231+ } else {
232+ return 'transition-transform duration-300 translate-x-[100%] md:group-hover/devices:translate-x-0'
233+ }
234+ }
235+
197236 function renderReport ( ) {
198237 if ( state . list && state . list . length > 0 ) {
199238 return (
@@ -223,17 +262,19 @@ export default function ListReport<
223262 }
224263
225264 function renderReportHeader ( ) {
226- const metricLabels = getAvailableMetrics ( ) . map ( ( metric ) => {
227- return (
228- < div
229- key = { metric . key }
230- className = { `${ metric . key } text-right ${ hiddenOnMobileClass ( metric ) } ` }
231- style = { { minWidth : colMinWidth } }
232- >
233- { metric . renderLabel ( query ) }
234- </ div >
235- )
236- } )
265+ const metricLabels = getAvailableMetrics ( )
266+ . filter ( ( metric ) => ! metric . meta . showOnHover )
267+ . map ( ( metric ) => {
268+ return (
269+ < div
270+ key = { metric . key }
271+ className = { `${ metric . key } text-right ${ hiddenOnMobileClass ( metric ) } ` }
272+ style = { { minWidth : colMinWidth } }
273+ >
274+ { metric . renderLabel ( query ) }
275+ </ div >
276+ )
277+ } )
237278
238279 return (
239280 < div className = "pt-3 w-full text-xs font-bold tracking-wide text-gray-500 flex items-center dark:text-gray-400" >
@@ -244,11 +285,22 @@ export default function ListReport<
244285 }
245286
246287 function renderRow ( listItem : TListItem ) {
288+ const handleRowClick = ( e : React . MouseEvent ) => {
289+ if ( window . innerWidth < 768 && ! ( e . target as HTMLElement ) . closest ( 'a' ) ) {
290+ if ( tappedRow === listItem . name ) {
291+ setTappedRow ( null )
292+ } else {
293+ setTappedRow ( listItem . name )
294+ }
295+ }
296+ }
297+
247298 return (
248299 < div key = { listItem . name } style = { { minHeight : ROW_HEIGHT } } >
249300 < div
250- className = "group flex w-full items-center hover:bg-gray-100/60 dark:hover:bg-gray-850 rounded-sm transition-colors duration-150"
301+ className = "group/row flex w-full items-center hover:bg-gray-100/60 dark:hover:bg-gray-850 rounded-sm transition-colors duration-150 md:cursor-default cursor-pointer "
251302 style = { { marginTop : ROW_GAP_HEIGHT } }
303+ onClick = { handleRowClick }
252304 >
253305 { renderBarFor ( listItem ) }
254306 { renderMetricValuesFor ( listItem ) }
@@ -258,7 +310,7 @@ export default function ListReport<
258310 }
259311
260312 function renderBarFor ( listItem : TListItem ) {
261- const lightBackground = color || 'bg-green-50 group-hover:bg-green-100'
313+ const lightBackground = color || 'bg-green-50 group-hover/row :bg-green-100'
262314 const metricToPlot = metrics . find ( ( metric ) => metric . meta . plot ) ?. key
263315
264316 return (
@@ -267,10 +319,10 @@ export default function ListReport<
267319 maxWidthDeduction = { undefined }
268320 count = { listItem [ metricToPlot ] }
269321 all = { state . list }
270- bg = { `${ lightBackground } dark:bg-gray-500/15 dark:group-hover:bg-gray-500/30` }
322+ bg = { `${ lightBackground } dark:bg-gray-500/15 dark:group-hover/row :bg-gray-500/30` }
271323 plot = { metricToPlot }
272324 >
273- < div className = "flex justify-start px-2 py-1.5 group text-sm dark:text-gray-300 relative z-9 break-all w-full" >
325+ < div className = "flex justify-start items-center gap-x-1.5 px-2 py-1.5 text-sm dark:text-gray-300 relative z-9 break-all w-full" >
274326 < DrilldownLink
275327 filterInfo = { getFilterInfo ( listItem ) }
276328 onClick = { onClick }
@@ -299,19 +351,33 @@ export default function ListReport<
299351 }
300352
301353 function renderMetricValuesFor ( listItem : TListItem ) {
302- return getAvailableMetrics ( ) . map ( ( metric ) => {
303- return (
304- < div
305- key = { `${ listItem . name } __${ metric . key } ` }
306- className = { `text-right ${ hiddenOnMobileClass ( metric ) } ` }
307- style = { { width : colMinWidth , minWidth : colMinWidth } }
308- >
309- < span className = "font-medium text-sm dark:text-gray-200 text-right" >
310- { metric . renderValue ( listItem , state . meta ) }
311- </ span >
312- </ div >
313- )
314- } )
354+ const availableMetrics = getAvailableMetrics ( )
355+ const showOnHoverIndex = availableMetrics . findIndex (
356+ ( m ) => m . meta . showOnHover
357+ )
358+ const hasShowOnHoverMetric = showOnHoverIndex !== - 1
359+
360+ return (
361+ < >
362+ { availableMetrics . map ( ( metric , index ) => {
363+ const isShowOnHover = metric . meta . showOnHover
364+
365+ return (
366+ < div
367+ key = { `${ listItem . name } __${ metric . key } ` }
368+ className = { `text-right ${ hiddenOnMobileClass ( metric ) } ${ showOnHoverClass ( metric , listItem . name ) } ${ slideLeftClass ( index , showOnHoverIndex , hasShowOnHoverMetric , listItem . name ) } ` }
369+ style = { { width : colMinWidth , minWidth : colMinWidth } }
370+ >
371+ < span
372+ className = { `font-medium text-sm text-right ${ isShowOnHover ? 'text-gray-500 dark:text-gray-400' : 'text-gray-800 dark:text-gray-200' } ` }
373+ >
374+ { metric . renderValue ( listItem , state . meta ) }
375+ </ span >
376+ </ div >
377+ )
378+ } ) }
379+ </ >
380+ )
315381 }
316382
317383 function renderLoading ( ) {
0 commit comments