Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,39 @@ export interface ComparisonTableProps {
competitor: CompetitorProfile
}

/**
* Pins the row-label column during horizontal scroll on genuinely spacious
* viewports (the standard pattern for responsive data tables, e.g.
* Stripe/GitHub/Notion comparison tables) so a reader keeps row context while
* scrolling to see the Sim/competitor values. Below `lg` (this page's own
* tablet-and-below tier, per `.claude/rules` for this route group) the table
* switches to a stacked layout instead (see `MOBILE_STACK_*`) rather than
* relying on horizontal scroll at a width too narrow to render a 3-column
* table comfortably, so sticky positioning is scoped to `lg:` only. The
* shadow is a permanent CSS-only affordance (no scroll-position JS) so this
* stays a zero-hydration server component.
*/
const STICKY_LABEL_COL = 'lg:sticky lg:left-0 lg:z-10 lg:shadow-[2px_0_4px_-2px_rgba(0,0,0,0.08)]'

/**
* Below `lg` (1024px) a 3-column grid doesn't reliably have room to be
* legible, so each fact instead stacks as label -> Sim's value -> the
* competitor's value, with a small name tag on each value since the column
* headers are no longer directly above. Applied to the label cell.
*/
const MOBILE_STACK_LABEL = 'max-lg:border-r-0 max-lg:border-b-0 max-lg:pt-3 max-lg:pb-1'

/**
* Applied to a value cell (Sim's or the competitor's) in the stacked mobile
* layout. `items-stretch` overrides the cell's base `items-center` (which
* would otherwise shrink-wrap and center each child horizontally in a
* flex-col): stretching gives the name tag and the value their own
* full-width box to left-align and truncate within, instead of a
* content-sized box with no boundary to clip against.
*/
const MOBILE_STACK_VALUE =
'max-lg:flex-col max-lg:items-stretch max-lg:gap-0.5 max-lg:border-r-0 max-lg:px-4'

function ColumnHeader({
name,
iconTile,
Expand All @@ -22,12 +55,14 @@ function ColumnHeader({
return (
<div
className={cn(
'flex flex-col items-center gap-2 border-[var(--border-1)] border-b px-3 py-4 text-center',
'flex min-w-0 flex-col items-center gap-2 border-[var(--border-1)] border-b px-3 py-4 text-center',
isSim ? 'bg-[var(--surface-2)]' : 'bg-[var(--surface-1)]'
)}
>
{iconTile}
<span className='font-medium text-[var(--text-primary)] text-base'>{name}</span>
<span className='w-full truncate font-medium text-[var(--text-primary)] text-base'>
{name}
</span>
</div>
)
}
Expand All @@ -49,15 +84,21 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
<div
role='table'
aria-label={`Sim vs ${competitor.name} feature comparison`}
className='grid min-w-[560px] grid-cols-[1.2fr_1fr_1fr]'
className='grid grid-cols-1 lg:min-w-[560px] lg:grid-cols-[minmax(140px,max-content)_1fr_1fr]'
>
<div className='contents' role='row'>
<div
role='columnheader'
className='flex flex-col justify-center border-[var(--border)] border-r border-b bg-[var(--surface-1)] px-4 py-4'
className={cn(
'flex min-w-0 flex-col justify-center border-[var(--border)] border-r border-b bg-[var(--surface-1)] px-4 py-4',
STICKY_LABEL_COL,
'max-lg:border-r-0'
)}
>
<span className='font-medium text-[var(--text-primary)] text-base'>Compare</span>
<span className='text-[var(--text-muted)] text-small'>
<span className='truncate font-medium text-[var(--text-primary)] text-base'>
Compare
</span>
<span className='truncate text-[var(--text-muted)] text-small'>
{sim.name} vs {competitor.name}
</span>
</div>
Expand Down Expand Up @@ -89,6 +130,8 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
role='columnheader'
className={cn(
'border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2',
STICKY_LABEL_COL,
'max-lg:border-r-0',
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
)}
>
Expand All @@ -99,7 +142,7 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
<div
role='presentation'
className={cn(
'col-span-2 bg-[var(--surface-1)]',
'col-span-2 bg-[var(--surface-1)] max-lg:hidden',
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
)}
/>
Expand All @@ -115,28 +158,42 @@ export function ComparisonTable({ sim, competitor }: ComparisonTableProps) {
<div
role='rowheader'
className={cn(
'flex items-center border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2.5',
'flex min-w-0 items-center border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2.5',
STICKY_LABEL_COL,
MOBILE_STACK_LABEL,
isNotLastRow && 'border-[var(--border-1)] border-b'
)}
>
<span className='text-[var(--text-body)] text-small'>{row.label}</span>
<span className='text-[var(--text-body)] text-small max-lg:font-medium max-lg:text-[var(--text-primary)]'>
{row.label}
</span>
</div>
<div
role='cell'
className={cn(
'flex items-center border-[var(--border)] border-r bg-[var(--surface-2)] px-3 py-2.5',
'flex min-w-0 items-center gap-1 border-[var(--border)] border-r bg-[var(--surface-2)] px-3 py-2.5',
MOBILE_STACK_VALUE,
'max-lg:border-b-0 max-lg:pt-1 max-lg:pb-1',
isNotLastRow && 'border-[var(--border-1)] border-b'
)}
>
<span className='font-medium text-[var(--text-muted)] text-caption lg:hidden'>
{sim.name}
</span>
<FactValue fact={simFact} />
</div>
<div
role='cell'
className={cn(
'flex items-center bg-[var(--surface-2)] px-3 py-2.5',
'flex min-w-0 items-center gap-1 bg-[var(--surface-2)] px-3 py-2.5',
MOBILE_STACK_VALUE,
'max-lg:pt-1 max-lg:pb-3',
isNotLastRow && 'border-[var(--border-1)] border-b'
)}
>
<span className='font-medium text-[var(--text-muted)] text-caption lg:hidden'>
{competitor.name}
</span>
<FactValue fact={competitorFact} />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import type { ReactNode } from 'react'
import { Tooltip } from '@sim/emcn'
import { cn, Tooltip } from '@sim/emcn'
import type { FactSource } from '@/lib/compare/data'

export interface SourceLinkProps {
Expand Down Expand Up @@ -29,7 +29,7 @@ export function SourceLink({ source, children, className }: SourceLinkProps) {
target='_blank'
rel='noopener noreferrer'
aria-label={`${source.label} (opens source)`}
className={className}
className={cn('block min-w-0', className)}
>
{children}
</a>
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1565,8 +1565,8 @@ export function LangChainIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
fill='currentColor'
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'
fill='#7FC8FF'
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'
/>
</svg>
)
Expand Down
Loading