Skip to content

Commit d32aca2

Browse files
committed
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.
1 parent e4949dd commit d32aca2

2 files changed

Lines changed: 36 additions & 26 deletions

File tree

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

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,37 @@ export interface ComparisonTableProps {
1111
}
1212

1313
/**
14-
* Pins the row-label column during horizontal scroll at tablet width and up
15-
* (the standard pattern for responsive data tables, e.g. Stripe/GitHub/Notion
16-
* comparison tables) so a reader keeps row context while scrolling to see the
17-
* Sim/competitor values. Below `sm` the table switches to a stacked layout
18-
* instead (see `MOBILE_STACK`), so sticky positioning is scoped to `sm:` only.
19-
* The shadow is a permanent CSS-only affordance (no scroll-position JS) so
20-
* this stays a zero-hydration server component.
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.
2124
*/
22-
const STICKY_LABEL_COL = 'sm:sticky sm:left-0 sm:z-10 sm:shadow-[2px_0_4px_-2px_rgba(0,0,0,0.08)]'
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)]'
2326

2427
/**
25-
* Below `sm` (640px) a 3-column grid has no room to be legible even with a
26-
* pinned label column, so each fact instead stacks as label -> Sim's value ->
27-
* the competitor's value, with a small name tag on each value since the
28-
* column headers are no longer directly above. Applied to the label cell.
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.
2932
*/
30-
const MOBILE_STACK_LABEL = 'max-sm:border-r-0 max-sm:border-b-0 max-sm:pt-3 max-sm:pb-1'
33+
const MOBILE_STACK_LABEL = 'max-lg:border-r-0 max-lg:border-b-0 max-lg:pt-3 max-lg:pb-1'
3134

32-
/** Applied to a value cell (Sim's or the competitor's) in the stacked mobile layout. */
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+
*/
3343
const MOBILE_STACK_VALUE =
34-
'max-sm:flex-col max-sm:items-start max-sm:gap-0.5 max-sm:border-r-0 max-sm:px-4'
44+
'max-lg:flex-col max-lg:items-stretch max-lg:gap-0.5 max-lg:border-r-0 max-lg:px-4'
3545

3646
function ColumnHeader({
3747
name,
@@ -72,15 +82,15 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
7282
<div
7383
role='table'
7484
aria-label={`Sim vs ${competitor.name} feature comparison`}
75-
className='grid grid-cols-1 sm:min-w-[560px] sm:grid-cols-[minmax(140px,max-content)_1fr_1fr]'
85+
className='grid grid-cols-1 lg:min-w-[560px] lg:grid-cols-[minmax(140px,max-content)_1fr_1fr]'
7686
>
7787
<div className='contents' role='row'>
7888
<div
7989
role='columnheader'
8090
className={cn(
8191
'flex min-w-0 flex-col justify-center border-[var(--border)] border-r border-b bg-[var(--surface-1)] px-4 py-4',
8292
STICKY_LABEL_COL,
83-
'max-sm:border-r-0'
93+
'max-lg:border-r-0'
8494
)}
8595
>
8696
<span className='truncate font-medium text-[var(--text-primary)] text-base'>
@@ -119,7 +129,7 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
119129
className={cn(
120130
'border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2',
121131
STICKY_LABEL_COL,
122-
'max-sm:border-r-0',
132+
'max-lg:border-r-0',
123133
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
124134
)}
125135
>
@@ -130,7 +140,7 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
130140
<div
131141
role='presentation'
132142
className={cn(
133-
'col-span-2 bg-[var(--surface-1)] max-sm:hidden',
143+
'col-span-2 bg-[var(--surface-1)] max-lg:hidden',
134144
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
135145
)}
136146
/>
@@ -152,7 +162,7 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
152162
isNotLastRow && 'border-[var(--border-1)] border-b'
153163
)}
154164
>
155-
<span className='text-[var(--text-body)] text-small max-sm:font-medium max-sm:text-[var(--text-primary)]'>
165+
<span className='text-[var(--text-body)] text-small max-lg:font-medium max-lg:text-[var(--text-primary)]'>
156166
{row.label}
157167
</span>
158168
</div>
@@ -161,11 +171,11 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
161171
className={cn(
162172
'flex min-w-0 items-center gap-1 border-[var(--border)] border-r bg-[var(--surface-2)] px-3 py-2.5',
163173
MOBILE_STACK_VALUE,
164-
'max-sm:border-b-0 max-sm:pt-1 max-sm:pb-1',
174+
'max-lg:border-b-0 max-lg:pt-1 max-lg:pb-1',
165175
isNotLastRow && 'border-[var(--border-1)] border-b'
166176
)}
167177
>
168-
<span className='font-medium text-[var(--text-muted)] text-caption sm:hidden'>
178+
<span className='font-medium text-[var(--text-muted)] text-caption lg:hidden'>
169179
{sim.name}
170180
</span>
171181
<FactValue fact={simFact} />
@@ -175,11 +185,11 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
175185
className={cn(
176186
'flex min-w-0 items-center gap-1 bg-[var(--surface-2)] px-3 py-2.5',
177187
MOBILE_STACK_VALUE,
178-
'max-sm:pt-1 max-sm:pb-3',
188+
'max-lg:pt-1 max-lg:pb-3',
179189
isNotLastRow && 'border-[var(--border-1)] border-b'
180190
)}
181191
>
182-
<span className='font-medium text-[var(--text-muted)] text-caption sm:hidden'>
192+
<span className='font-medium text-[var(--text-muted)] text-caption lg:hidden'>
183193
{competitor.name}
184194
</span>
185195
<FactValue fact={competitorFact} />

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>

0 commit comments

Comments
 (0)