Skip to content

Commit 0249516

Browse files
authored
fix(compare): swap LangChain logo, fix comparison-table horizontal scroll (#5409)
* fix(compare): swap LangChain logo, fix comparison-table horizontal scroll Replace LangChainIcon's path with the official brand mark (light-blue link icon) instead of the prior monochrome recreation. The comparison table's grid cells were missing min-w-0, so a grid item without it sizes to its content's max-content width instead of respecting its column's fr track, pushing the whole table wider than its container and forcing a horizontal scrollbar. Add min-w-0 to every grid cell/header, and size the row-label column to minmax(140px, max-content) instead of a guessed fr ratio, so it's exactly as wide as its longest label ("Vetted first-party integrations") needs and no wider, leaving the Sim/competitor value columns their full share. * fix(compare): stacked mobile layout for the comparison table Below sm (640px) a 3-column table has no room to be legible even with the sticky label column, so switch to the standard responsive-table pattern instead: each fact stacks as label -> Sim's value -> the competitor's value, each value tagged with its product name since the column headers are no longer directly above. Pure CSS (max-sm:/sm: variants), no JS, keeping this a zero-hydration server component. * fix(compare): fix SourceLink inline-anchor overflow, align table breakpoint Root cause of the persistent table-overflow/mobile-scroll issues: SourceLink renders a plain <a> with no explicit display, so it defaults to 'display: inline'. min-width and truncate are no-ops on inline elements, so any fact with a source (nearly all of them) ignored its flex/grid parent's width constraint and could force the whole row (and thus the whole table) wider than intended, regardless of any container-level min-w-0/max-content fix. Give the anchor 'block min-w-0' so width constraints and truncation actually cascade down to the wrapped value. Also aligns the table's mobile-stack breakpoint from an ad hoc sm (640px) to lg (1024px), matching this route group's own tablet-and-below convention (.claude/rules for the (landing) group), and fixes the stacked mobile cells to override the cell's base items-center with items-stretch so the name tag and value get a real full-width box to truncate within instead of shrinking to their own content size with no boundary. * fix(compare): add min-w-0 to ColumnHeader per Greptile review ColumnHeader (the Sim/competitor logo header cells in the two fr columns) still had default min-width: auto, so an unusually long competitor name could size the header to its min-content width and push the grid wider than its column allows, same root cause as the rows already fixed.
1 parent 759dddb commit 0249516

3 files changed

Lines changed: 72 additions & 15 deletions

File tree

apps/sim/app/(landing)/comparison/components/comparison-table/comparison-table.tsx

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,39 @@ export interface ComparisonTableProps {
1010
competitor: CompetitorProfile
1111
}
1212

13+
/**
14+
* Pins the row-label column during horizontal scroll on genuinely spacious
15+
* viewports (the standard pattern for responsive data tables, e.g.
16+
* Stripe/GitHub/Notion comparison tables) so a reader keeps row context while
17+
* scrolling to see the Sim/competitor values. Below `lg` (this page's own
18+
* tablet-and-below tier, per `.claude/rules` for this route group) the table
19+
* switches to a stacked layout instead (see `MOBILE_STACK_*`) rather than
20+
* relying on horizontal scroll at a width too narrow to render a 3-column
21+
* table comfortably, so sticky positioning is scoped to `lg:` only. The
22+
* shadow is a permanent CSS-only affordance (no scroll-position JS) so this
23+
* stays a zero-hydration server component.
24+
*/
25+
const STICKY_LABEL_COL = 'lg:sticky lg:left-0 lg:z-10 lg:shadow-[2px_0_4px_-2px_rgba(0,0,0,0.08)]'
26+
27+
/**
28+
* Below `lg` (1024px) a 3-column grid doesn't reliably have room to be
29+
* legible, so each fact instead stacks as label -> Sim's value -> the
30+
* competitor's value, with a small name tag on each value since the column
31+
* headers are no longer directly above. Applied to the label cell.
32+
*/
33+
const MOBILE_STACK_LABEL = 'max-lg:border-r-0 max-lg:border-b-0 max-lg:pt-3 max-lg:pb-1'
34+
35+
/**
36+
* Applied to a value cell (Sim's or the competitor's) in the stacked mobile
37+
* layout. `items-stretch` overrides the cell's base `items-center` (which
38+
* would otherwise shrink-wrap and center each child horizontally in a
39+
* flex-col): stretching gives the name tag and the value their own
40+
* full-width box to left-align and truncate within, instead of a
41+
* content-sized box with no boundary to clip against.
42+
*/
43+
const MOBILE_STACK_VALUE =
44+
'max-lg:flex-col max-lg:items-stretch max-lg:gap-0.5 max-lg:border-r-0 max-lg:px-4'
45+
1346
function ColumnHeader({
1447
name,
1548
iconTile,
@@ -22,12 +55,14 @@ function ColumnHeader({
2255
return (
2356
<div
2457
className={cn(
25-
'flex flex-col items-center gap-2 border-[var(--border-1)] border-b px-3 py-4 text-center',
58+
'flex min-w-0 flex-col items-center gap-2 border-[var(--border-1)] border-b px-3 py-4 text-center',
2659
isSim ? 'bg-[var(--surface-2)]' : 'bg-[var(--surface-1)]'
2760
)}
2861
>
2962
{iconTile}
30-
<span className='font-medium text-[var(--text-primary)] text-base'>{name}</span>
63+
<span className='w-full truncate font-medium text-[var(--text-primary)] text-base'>
64+
{name}
65+
</span>
3166
</div>
3267
)
3368
}
@@ -49,15 +84,21 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
4984
<div
5085
role='table'
5186
aria-label={`Sim vs ${competitor.name} feature comparison`}
52-
className='grid min-w-[560px] grid-cols-[1.2fr_1fr_1fr]'
87+
className='grid grid-cols-1 lg:min-w-[560px] lg:grid-cols-[minmax(140px,max-content)_1fr_1fr]'
5388
>
5489
<div className='contents' role='row'>
5590
<div
5691
role='columnheader'
57-
className='flex flex-col justify-center border-[var(--border)] border-r border-b bg-[var(--surface-1)] px-4 py-4'
92+
className={cn(
93+
'flex min-w-0 flex-col justify-center border-[var(--border)] border-r border-b bg-[var(--surface-1)] px-4 py-4',
94+
STICKY_LABEL_COL,
95+
'max-lg:border-r-0'
96+
)}
5897
>
59-
<span className='font-medium text-[var(--text-primary)] text-base'>Compare</span>
60-
<span className='text-[var(--text-muted)] text-small'>
98+
<span className='truncate font-medium text-[var(--text-primary)] text-base'>
99+
Compare
100+
</span>
101+
<span className='truncate text-[var(--text-muted)] text-small'>
61102
{sim.name} vs {competitor.name}
62103
</span>
63104
</div>
@@ -89,6 +130,8 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
89130
role='columnheader'
90131
className={cn(
91132
'border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2',
133+
STICKY_LABEL_COL,
134+
'max-lg:border-r-0',
92135
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
93136
)}
94137
>
@@ -99,7 +142,7 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
99142
<div
100143
role='presentation'
101144
className={cn(
102-
'col-span-2 bg-[var(--surface-1)]',
145+
'col-span-2 bg-[var(--surface-1)] max-lg:hidden',
103146
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
104147
)}
105148
/>
@@ -115,28 +158,42 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
115158
<div
116159
role='rowheader'
117160
className={cn(
118-
'flex items-center border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2.5',
161+
'flex min-w-0 items-center border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2.5',
162+
STICKY_LABEL_COL,
163+
MOBILE_STACK_LABEL,
119164
isNotLastRow && 'border-[var(--border-1)] border-b'
120165
)}
121166
>
122-
<span className='text-[var(--text-body)] text-small'>{row.label}</span>
167+
<span className='text-[var(--text-body)] text-small max-lg:font-medium max-lg:text-[var(--text-primary)]'>
168+
{row.label}
169+
</span>
123170
</div>
124171
<div
125172
role='cell'
126173
className={cn(
127-
'flex items-center border-[var(--border)] border-r bg-[var(--surface-2)] px-3 py-2.5',
174+
'flex min-w-0 items-center gap-1 border-[var(--border)] border-r bg-[var(--surface-2)] px-3 py-2.5',
175+
MOBILE_STACK_VALUE,
176+
'max-lg:border-b-0 max-lg:pt-1 max-lg:pb-1',
128177
isNotLastRow && 'border-[var(--border-1)] border-b'
129178
)}
130179
>
180+
<span className='font-medium text-[var(--text-muted)] text-caption lg:hidden'>
181+
{sim.name}
182+
</span>
131183
<FactValue fact={simFact} />
132184
</div>
133185
<div
134186
role='cell'
135187
className={cn(
136-
'flex items-center bg-[var(--surface-2)] px-3 py-2.5',
188+
'flex min-w-0 items-center gap-1 bg-[var(--surface-2)] px-3 py-2.5',
189+
MOBILE_STACK_VALUE,
190+
'max-lg:pt-1 max-lg:pb-3',
137191
isNotLastRow && 'border-[var(--border-1)] border-b'
138192
)}
139193
>
194+
<span className='font-medium text-[var(--text-muted)] text-caption lg:hidden'>
195+
{competitor.name}
196+
</span>
140197
<FactValue fact={competitorFact} />
141198
</div>
142199
</div>

apps/sim/app/(landing)/comparison/components/source-info/source-info.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import type { ReactNode } from 'react'
4-
import { Tooltip } from '@sim/emcn'
4+
import { cn, Tooltip } from '@sim/emcn'
55
import type { FactSource } from '@/lib/compare/data'
66

77
export interface SourceLinkProps {
@@ -29,7 +29,7 @@ export function SourceLink({ source, children, className }: SourceLinkProps) {
2929
target='_blank'
3030
rel='noopener noreferrer'
3131
aria-label={`${source.label} (opens source)`}
32-
className={className}
32+
className={cn('block min-w-0', className)}
3333
>
3434
{children}
3535
</a>

apps/sim/components/icons.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,8 +1565,8 @@ export function LangChainIcon(props: SVGProps<SVGSVGElement>) {
15651565
return (
15661566
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
15671567
<path
1568-
fill='currentColor'
1569-
d='M13.796 0a6.93 6.93 0 0 0-4.91 2.019L5.451 5.455l3.273 3.27 3.432-3.432a2.284 2.284 0 0 1 3.277 0 2.28 2.28 0 0 1 0 3.275L12 12.001l3.273 3.273 3.433-3.435c2.692-2.692 2.692-7.127 0-9.82A6.92 6.92 0 0 0 13.796 0m-5.07 8.728-3.433 3.434c-2.692 2.693-2.692 7.126 0 9.819A6.92 6.92 0 0 0 10.203 24a6.93 6.93 0 0 0 4.911-2.02l3.432-3.432-3.271-3.272-3.433 3.433a2.284 2.284 0 0 1-3.277 0 2.28 2.28 0 0 1 0-3.276L12 12z'
1568+
fill='#7FC8FF'
1569+
d='M7.531 15.976a7.534 7.534 0 000-10.651L2.206 0A7.537 7.537 0 000 5.326c0 1.996.794 3.913 2.206 5.325l5.325 5.325zM18.674 16.469a7.535 7.535 0 00-10.65 0l5.325 5.325a7.536 7.536 0 0010.651 0l-5.326-5.325zM2.218 21.782a7.536 7.536 0 005.326 2.206v-7.531H.012c0 1.996.795 3.914 2.206 5.325zM20.73 8.595a7.534 7.534 0 00-10.651.001l5.325 5.326 5.326-5.327z'
15701570
/>
15711571
</svg>
15721572
)

0 commit comments

Comments
 (0)