refactor: restyle journey columns to match Pulse native patterns

This commit is contained in:
Usman Baig
2026-03-15 13:27:20 +01:00
parent b000d0e1f7
commit 3954ee0a97

View File

@@ -115,41 +115,29 @@ function buildColumns(
function ColumnHeader({ function ColumnHeader({
column, column,
color,
}: { }: {
column: Column column: Column
color: string
}) { }) {
return ( return (
<div className="flex flex-col items-center gap-1.5 mb-3"> <div className="flex flex-col items-center gap-0.5 mb-4">
<span <span className="text-xs font-medium text-neutral-400 dark:text-neutral-500 uppercase tracking-wider">
className="flex items-center justify-center w-9 h-9 rounded-full text-sm font-bold text-white" Step {column.index + 1}
style={{ backgroundColor: color }}
>
{column.index + 1}
</span> </span>
<div className="flex flex-col items-center"> <div className="flex items-baseline gap-1.5">
<span className="text-lg font-bold text-neutral-900 dark:text-white tabular-nums"> <span className="text-sm font-semibold text-neutral-900 dark:text-white tabular-nums">
{column.totalSessions.toLocaleString()} {column.totalSessions.toLocaleString()} visitors
</span> </span>
<div className="flex items-center gap-1.5"> {column.dropOffPercent !== 0 && (
<span className="text-xs text-neutral-500 dark:text-neutral-400"> <span
visitors className={`text-xs font-medium ${
column.dropOffPercent < 0 ? 'text-red-500' : 'text-emerald-500'
}`}
>
{column.dropOffPercent > 0 ? '+' : ''}
{column.dropOffPercent}%
</span> </span>
{column.dropOffPercent !== 0 && ( )}
<span
className={`text-xs font-semibold ${
column.dropOffPercent < 0 ? 'text-red-500' : 'text-emerald-500'
}`}
>
{column.dropOffPercent > 0 ? '+' : ''}
{column.dropOffPercent}%
</span>
)}
</div>
</div> </div>
{/* Colored connector line from header to cards */}
<div className="w-px h-3" style={{ backgroundColor: color, opacity: 0.3 }} />
</div> </div>
) )
} }
@@ -157,7 +145,6 @@ function ColumnHeader({
function PageRow({ function PageRow({
page, page,
colIndex, colIndex,
colColor,
columnTotal, columnTotal,
maxCount, maxCount,
isSelected, isSelected,
@@ -166,13 +153,13 @@ function PageRow({
}: { }: {
page: ColumnPage page: ColumnPage
colIndex: number colIndex: number
colColor: string
columnTotal: number columnTotal: number
maxCount: number maxCount: number
isSelected: boolean isSelected: boolean
isOther: boolean isOther: boolean
onClick: () => void onClick: () => void
}) { }) {
const pct = columnTotal > 0 ? Math.round((page.sessionCount / columnTotal) * 100) : 0
const barWidth = maxCount > 0 ? (page.sessionCount / maxCount) * 100 : 0 const barWidth = maxCount > 0 ? (page.sessionCount / maxCount) * 100 : 0
return ( return (
@@ -185,29 +172,24 @@ function PageRow({
data-path={page.path} data-path={page.path}
className={` className={`
group flex items-center justify-between w-full relative overflow-hidden group flex items-center justify-between w-full relative overflow-hidden
px-3 py-2.5 rounded-lg text-left transition-all h-9 px-2 -mx-2 rounded-lg text-left transition-colors
${isOther ? 'cursor-default' : 'cursor-pointer'} ${isOther ? 'cursor-default' : 'cursor-pointer'}
${ ${isSelected
isSelected ? 'bg-brand-orange/10 dark:bg-brand-orange/10'
? 'border-l-[3px] border border-neutral-200 dark:border-neutral-700' : isOther
: isOther ? ''
? 'border border-neutral-100 dark:border-neutral-800' : 'hover:bg-neutral-50 dark:hover:bg-neutral-800/50'
: 'border border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600 hover:shadow-sm'
} }
`} `}
style={isSelected ? { borderLeftColor: colColor, backgroundColor: `${colColor}08` } : undefined}
> >
{/* Background bar */} {/* Background bar */}
{!isOther && !isSelected && ( {!isOther && (
<div <div
className="absolute inset-y-0 left-0 bg-neutral-100 dark:bg-neutral-800/50 transition-all" className="absolute inset-y-0 left-0 rounded-lg transition-all"
style={{ width: `${barWidth}%` }} style={{
/> width: `${barWidth}%`,
)} backgroundColor: isSelected ? 'rgba(253, 94, 15, 0.15)' : 'rgba(253, 94, 15, 0.08)',
{isSelected && ( }}
<div
className="absolute inset-y-0 left-0 transition-all"
style={{ width: `${barWidth}%`, backgroundColor: `${colColor}15` }}
/> />
)} )}
<span <span
@@ -216,44 +198,47 @@ function PageRow({
? 'text-neutral-900 dark:text-white font-medium' ? 'text-neutral-900 dark:text-white font-medium'
: isOther : isOther
? 'italic text-neutral-400 dark:text-neutral-500' ? 'italic text-neutral-400 dark:text-neutral-500'
: 'text-neutral-700 dark:text-neutral-200' : 'text-neutral-900 dark:text-white'
}`} }`}
> >
{isOther ? page.path : smartLabel(page.path)} {isOther ? page.path : smartLabel(page.path)}
</span> </span>
<span <div className="relative flex items-center gap-2 ml-2 shrink-0">
className={`relative ml-3 shrink-0 text-xs tabular-nums font-semibold px-1.5 py-0.5 rounded ${ {!isOther && (
isSelected <span className="text-xs font-medium text-brand-orange opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200">
? 'text-neutral-900 dark:text-white bg-white/60 dark:bg-neutral-800/60' {pct}%
: isOther </span>
)}
<span
className={`text-sm tabular-nums font-semibold ${
isOther
? 'text-neutral-400 dark:text-neutral-500' ? 'text-neutral-400 dark:text-neutral-500'
: 'text-neutral-600 dark:text-neutral-300 bg-neutral-100 dark:bg-neutral-800 group-hover:bg-neutral-200 dark:group-hover:bg-neutral-700' : 'text-neutral-600 dark:text-neutral-400'
}`} }`}
> >
{page.sessionCount.toLocaleString()} {page.sessionCount.toLocaleString()}
</span> </span>
</div>
</button> </button>
) )
} }
function JourneyColumn({ function JourneyColumn({
column, column,
color,
selectedPath, selectedPath,
exitCount, exitCount,
onSelect, onSelect,
}: { }: {
column: Column column: Column
color: string
selectedPath: string | undefined selectedPath: string | undefined
exitCount: number exitCount: number
onSelect: (path: string) => void onSelect: (path: string) => void
}) { }) {
if (column.pages.length === 0 && exitCount === 0) { if (column.pages.length === 0 && exitCount === 0) {
return ( return (
<div className="w-60 shrink-0"> <div className="w-56 shrink-0">
<ColumnHeader column={column} color={color} /> <ColumnHeader column={column} />
<div className="flex items-center justify-center h-20 rounded-xl border border-dashed border-neutral-200 dark:border-neutral-700"> <div className="flex items-center justify-center h-16 px-2">
<span className="text-xs text-neutral-400 dark:text-neutral-500"> <span className="text-xs text-neutral-400 dark:text-neutral-500">
No onward traffic No onward traffic
</span> </span>
@@ -265,9 +250,9 @@ function JourneyColumn({
const maxCount = Math.max(...column.pages.map((p) => p.sessionCount), 0) const maxCount = Math.max(...column.pages.map((p) => p.sessionCount), 0)
return ( return (
<div className="w-60 shrink-0"> <div className="w-56 shrink-0 px-2">
<ColumnHeader column={column} color={color} /> <ColumnHeader column={column} />
<div className="space-y-1.5 max-h-[500px] overflow-y-auto"> <div className="space-y-0.5 max-h-[500px] overflow-y-auto">
{column.pages.map((page) => { {column.pages.map((page) => {
const isOther = page.path === '(other)' const isOther = page.path === '(other)'
return ( return (
@@ -275,7 +260,6 @@ function JourneyColumn({
key={page.path} key={page.path}
page={page} page={page}
colIndex={column.index} colIndex={column.index}
colColor={color}
columnTotal={column.totalSessions} columnTotal={column.totalSessions}
maxCount={maxCount} maxCount={maxCount}
isSelected={selectedPath === page.path} isSelected={selectedPath === page.path}
@@ -290,12 +274,12 @@ function JourneyColumn({
<div <div
data-col={column.index} data-col={column.index}
data-path="(exit)" data-path="(exit)"
className="flex items-center justify-between w-full px-3 py-2.5 rounded-lg border border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10" className="flex items-center justify-between h-9 px-2 -mx-2 rounded-lg bg-red-50 dark:bg-red-500/10"
> >
<span className="text-sm text-red-600 dark:text-red-400"> <span className="text-sm text-red-500 dark:text-red-400">
(exit) (exit)
</span> </span>
<span className="ml-3 shrink-0 text-sm tabular-nums font-semibold text-red-600 dark:text-red-400"> <span className="text-sm tabular-nums font-semibold text-red-500 dark:text-red-400">
{exitCount.toLocaleString()} {exitCount.toLocaleString()}
</span> </span>
</div> </div>
@@ -518,11 +502,10 @@ export default function ColumnJourney({
return ( return (
<Fragment key={col.index}> <Fragment key={col.index}>
{i > 0 && ( {i > 0 && (
<div className="w-px shrink-0 mx-4 bg-neutral-200 dark:bg-neutral-700" /> <div className="w-px shrink-0 bg-neutral-100 dark:bg-neutral-800" />
)} )}
<JourneyColumn <JourneyColumn
column={col} column={col}
color={colorForColumn(col.index)}
selectedPath={selections.get(col.index)} selectedPath={selections.get(col.index)}
exitCount={exitCount} exitCount={exitCount}
onSelect={(path) => handleSelect(col.index, path)} onSelect={(path) => handleSelect(col.index, path)}