33import { useTranslations } from 'next-intl'
44import { useMemo , useState } from 'react'
55import { 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
814interface 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
1422interface 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