Skip to content

Commit abcb6fa

Browse files
committed
fix(landscape): refine matrix layout and vendor sorting
1 parent 68d1dd6 commit abcb6fa

3 files changed

Lines changed: 232 additions & 100 deletions

File tree

src/components/product/VendorMatrix.tsx

Lines changed: 33 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33
import { useTranslations } from 'next-intl'
44
import { useMemo, useState } from 'react'
55
import { Link } from '@/i18n/navigation'
6-
import type { LandscapeProduct, ProductCategory, VendorMatrixRow } from '@/lib/landscape-data'
6+
import {
7+
compareVendorMatrixRowsByProducts,
8+
LANDSCAPE_PRODUCT_CATEGORIES,
9+
type LandscapeProduct,
10+
type ProductCategory,
11+
type VendorMatrixRow,
12+
} from '@/lib/landscape-data'
713

814
interface VendorMatrixProps {
915
matrixData: VendorMatrixRow[]
1016
}
1117

12-
const PRODUCT_CATEGORIES: ProductCategory[] = ['ide', 'cli', 'extension', 'model', 'provider']
18+
const MATRIX_GRID_STYLE = {
19+
gridTemplateColumns: '200px repeat(6, minmax(0, 1fr))',
20+
} as const
1321

1422
interface MatrixCellProps {
1523
products: LandscapeProduct[]
@@ -30,6 +38,7 @@ function getCategoryLabel(
3038
// Map singular to plural key for shared categories
3139
const pluralMap: Record<ProductCategory, string> = {
3240
cli: 'clis',
41+
desktop: 'desktops',
3342
extension: 'extensions',
3443
ide: 'ides',
3544
model: 'models',
@@ -57,7 +66,7 @@ function MatrixCell({ products, category }: MatrixCellProps) {
5766

5867
if (products.length === 0) {
5968
return (
60-
<div className="h-full min-h-[80px] border border-dashed border-[var(--color-border)] bg-[var(--color-bg-subtle)]" />
69+
<div className="min-h-[80px] border border-dashed border-[var(--color-border)] bg-[var(--color-bg-subtle)]" />
6170
)
6271
}
6372

@@ -67,9 +76,9 @@ function MatrixCell({ products, category }: MatrixCellProps) {
6776
return (
6877
<Link
6978
href={product.path}
70-
className="block h-full min-h-[80px] border border-[var(--color-border)] hover:border-[var(--color-border-strong)] transition-all p-[var(--spacing-sm)] bg-[var(--color-bg-subtle)] hover:bg-[var(--color-hover)] group"
79+
className="block min-h-[80px] border border-[var(--color-border)] hover:border-[var(--color-border-strong)] transition-all p-[var(--spacing-sm)] bg-[var(--color-bg-subtle)] hover:bg-[var(--color-hover)] group"
7180
>
72-
<div className="flex flex-col h-full justify-between">
81+
<div className="flex flex-col justify-between">
7382
<div>
7483
<h4 className="font-medium text-sm tracking-tight mb-1 group-hover:text-[var(--color-text)] transition-colors line-clamp-2">
7584
{product.name}
@@ -83,7 +92,7 @@ function MatrixCell({ products, category }: MatrixCellProps) {
8392
// Two products - display directly without collapse
8493
if (products.length === 2) {
8594
return (
86-
<div className="h-full min-h-[80px] flex flex-col gap-1">
95+
<div className="min-h-[80px] flex flex-col gap-1">
8796
{products.map(product => (
8897
<Link
8998
key={product.id}
@@ -101,13 +110,13 @@ function MatrixCell({ products, category }: MatrixCellProps) {
101110

102111
// Three or more products - show collapse menu
103112
return (
104-
<div className="relative h-full min-h-[80px]">
113+
<div className="relative min-h-[80px]">
105114
<button
106115
type="button"
107116
onClick={() => setIsExpanded(!isExpanded)}
108-
className="w-full h-full border border-[var(--color-border)] hover:border-[var(--color-border-strong)] transition-all p-[var(--spacing-sm)] bg-[var(--color-bg-subtle)] hover:bg-[var(--color-hover)] text-left"
117+
className="absolute inset-0 w-full border border-[var(--color-border)] hover:border-[var(--color-border-strong)] transition-all p-[var(--spacing-sm)] bg-[var(--color-bg-subtle)] hover:bg-[var(--color-hover)] text-left"
109118
>
110-
<div className="flex flex-col h-full justify-between">
119+
<div className="flex flex-col justify-between">
111120
<div>
112121
<div className="flex items-center justify-between mb-1">
113122
<span className="font-medium text-sm tracking-tight">
@@ -168,76 +177,7 @@ export default function VendorMatrix({ matrixData }: VendorMatrixProps) {
168177
return a.vendorName.localeCompare(b.vendorName)
169178
}
170179

171-
// Define column groups: first part (high priority) and second part (low priority)
172-
const FIRST_PART_CATEGORIES = PRODUCT_CATEGORIES.slice(0, 3) // ide, cli, extension
173-
const SECOND_PART_CATEGORIES = PRODUCT_CATEGORIES.slice(3) // model, provider
174-
175-
// Helper function to get column count for a specific set of categories
176-
const getColumnCount = (row: VendorMatrixRow, categories: ProductCategory[]) => {
177-
return categories.filter(cat => row.cells[cat] && row.cells[cat].length > 0).length
178-
}
179-
180-
// Helper function to get column order (left to right) for a specific set of categories
181-
const getColumnOrder = (row: VendorMatrixRow, categories: ProductCategory[]) => {
182-
return categories.filter(cat => row.cells[cat] && row.cells[cat].length > 0).map(cat => cat)
183-
}
184-
185-
// Helper function to compare column order
186-
const compareColumnOrder = (
187-
aOrder: ProductCategory[],
188-
bOrder: ProductCategory[],
189-
categories: ProductCategory[]
190-
) => {
191-
for (let i = 0; i < Math.min(aOrder.length, bOrder.length); i++) {
192-
const aIndex = categories.indexOf(aOrder[i]!)
193-
const bIndex = categories.indexOf(bOrder[i]!)
194-
if (aIndex !== bIndex) {
195-
return aIndex - bIndex
196-
}
197-
}
198-
return 0
199-
}
200-
201-
// 1. Sort by first part column count (descending)
202-
const aFirstPartCount = getColumnCount(a, FIRST_PART_CATEGORIES)
203-
const bFirstPartCount = getColumnCount(b, FIRST_PART_CATEGORIES)
204-
if (aFirstPartCount !== bFirstPartCount) {
205-
return bFirstPartCount - aFirstPartCount
206-
}
207-
208-
// 2. If first part column count is the same, sort by second part column count (descending)
209-
const aSecondPartCount = getColumnCount(a, SECOND_PART_CATEGORIES)
210-
const bSecondPartCount = getColumnCount(b, SECOND_PART_CATEGORIES)
211-
if (aSecondPartCount !== bSecondPartCount) {
212-
return bSecondPartCount - aSecondPartCount
213-
}
214-
215-
// 3. If both column counts are the same, sort by first part column order (left to right)
216-
const aFirstPartOrder = getColumnOrder(a, FIRST_PART_CATEGORIES)
217-
const bFirstPartOrder = getColumnOrder(b, FIRST_PART_CATEGORIES)
218-
const firstPartOrderComparison = compareColumnOrder(
219-
aFirstPartOrder,
220-
bFirstPartOrder,
221-
FIRST_PART_CATEGORIES
222-
)
223-
if (firstPartOrderComparison !== 0) {
224-
return firstPartOrderComparison
225-
}
226-
227-
// 4. If first part order is also the same, sort by second part column order (left to right)
228-
const aSecondPartOrder = getColumnOrder(a, SECOND_PART_CATEGORIES)
229-
const bSecondPartOrder = getColumnOrder(b, SECOND_PART_CATEGORIES)
230-
const secondPartOrderComparison = compareColumnOrder(
231-
aSecondPartOrder,
232-
bSecondPartOrder,
233-
SECOND_PART_CATEGORIES
234-
)
235-
if (secondPartOrderComparison !== 0) {
236-
return secondPartOrderComparison
237-
}
238-
239-
// 5. If everything is the same, sort alphabetically by vendor name
240-
return a.vendorName.localeCompare(b.vendorName)
180+
return compareVendorMatrixRowsByProducts(a, b)
241181
})
242182

243183
return sorted
@@ -322,15 +262,18 @@ export default function VendorMatrix({ matrixData }: VendorMatrixProps) {
322262
</div>
323263

324264
{/* Matrix Table */}
325-
<div className="border border-[var(--color-border)] overflow-hidden">
265+
<div className="border border-[var(--color-border)]">
326266
<div className="overflow-x-auto">
327-
<div className="min-w-[800px]">
267+
<div className="min-w-[1200px]">
328268
{/* Table Header */}
329-
<div className="grid grid-cols-[200px_repeat(5,1fr)] gap-2 p-[var(--spacing-sm)] bg-[var(--color-bg-subtle)] border-b border-[var(--color-border)] sticky top-0 z-20">
269+
<div
270+
className="grid gap-2 p-[var(--spacing-sm)] bg-[var(--color-bg-subtle)] border-b border-[var(--color-border)]"
271+
style={MATRIX_GRID_STYLE}
272+
>
330273
<div className="font-medium text-sm text-[var(--color-text-secondary)] px-2">
331274
{tShared('categories.singular.vendor')}
332275
</div>
333-
{PRODUCT_CATEGORIES.map(cat => (
276+
{LANDSCAPE_PRODUCT_CATEGORIES.map(cat => (
334277
<div key={cat} className="font-medium text-sm text-center px-2">
335278
{tShared(`categories.singular.${cat}`)}
336279
</div>
@@ -346,7 +289,11 @@ export default function VendorMatrix({ matrixData }: VendorMatrixProps) {
346289
</div>
347290
) : (
348291
filteredAndSortedData.map(row => (
349-
<div key={row.vendorId} className="grid grid-cols-[200px_repeat(5,1fr)] gap-2">
292+
<div
293+
key={row.vendorId}
294+
className="grid items-stretch gap-2"
295+
style={MATRIX_GRID_STYLE}
296+
>
350297
{/* Vendor Name */}
351298
<div className="flex flex-col justify-center px-2 border-r border-[var(--color-border)]">
352299
<Link
@@ -361,10 +308,8 @@ export default function VendorMatrix({ matrixData }: VendorMatrixProps) {
361308
</div>
362309

363310
{/* Product Cells */}
364-
{PRODUCT_CATEGORIES.map(cat => (
365-
<div key={cat}>
366-
<MatrixCell products={row.cells[cat]} category={cat} />
367-
</div>
311+
{LANDSCAPE_PRODUCT_CATEGORIES.map(cat => (
312+
<MatrixCell key={cat} products={row.cells[cat]} category={cat} />
368313
))}
369314
</div>
370315
))

0 commit comments

Comments
 (0)