mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
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.
This commit is contained in:
+68
-11
@@ -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,
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
)}
|
||||
>
|
||||
@@ -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'
|
||||
)}
|
||||
/>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user