Skip to content

Commit 974e04a

Browse files
committed
feat(catalog): order deprecated products last
1 parent 1d7b6d8 commit 974e04a

3 files changed

Lines changed: 73 additions & 8 deletions

File tree

src/components/product/ComparisonTable.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use client'
22

3-
import { useEffect, useRef, useState } from 'react'
3+
import { useEffect, useMemo, useRef, useState } from 'react'
4+
import { DeprecatedBadge } from '@/components/controls/DeprecatedBadge'
45
import { Link } from '@/i18n/navigation'
6+
import { sortDeprecatedLast } from '@/lib/deprecated'
57

68
export interface ComparisonColumn {
79
key: string
@@ -41,6 +43,7 @@ export default function ComparisonTable({
4143
const [scrollLeft, setScrollLeft] = useState<number>(0)
4244
const [calculatedOffset, setCalculatedOffset] = useState<number>(0)
4345
const columnWidthsMeasured = useRef(false)
46+
const sortedItems = useMemo(() => sortDeprecatedLast(items), [items])
4447

4548
useEffect(() => {
4649
// Calculate sticky offset based on header and breadcrumb heights
@@ -231,20 +234,23 @@ export default function ComparisonTable({
231234
</tr>
232235
</thead>
233236
<tbody>
234-
{items.map((item, index) => (
237+
{sortedItems.map((item, index) => (
235238
<tr
236239
key={item[itemIdKey] as string}
237240
className={`border-b border-[var(--color-border)] hover:bg-[var(--color-hover)] transition-colors ${
238241
index % 2 === 0 ? 'bg-[var(--color-bg)]' : 'bg-[var(--color-hover)]'
239242
}`}
240243
>
241244
<td className="sticky left-0 z-10 bg-inherit px-[var(--spacing-md)] py-[var(--spacing-sm)] font-medium border-r border-[var(--color-border)] whitespace-nowrap">
242-
<Link
243-
href={`${itemLinkPrefix}/${item[itemIdKey] as string}`}
244-
className="text-[var(--color-text)] hover:text-[var(--color-text-secondary)] hover:underline transition-colors"
245-
>
246-
{item[itemNameKey] as string}
247-
</Link>
245+
<div className="inline-flex items-center gap-[var(--spacing-xs)]">
246+
<Link
247+
href={`${itemLinkPrefix}/${item[itemIdKey] as string}`}
248+
className="text-[var(--color-text)] hover:text-[var(--color-text-secondary)] hover:underline transition-colors"
249+
>
250+
{item[itemNameKey] as string}
251+
</Link>
252+
{item.deprecated === true && <DeprecatedBadge />}
253+
</div>
248254
</td>
249255
{columns.map(column => (
250256
<td

src/lib/deprecated.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
type Deprecatable = {
2+
deprecated?: boolean
3+
}
4+
5+
export function sortDeprecatedLast<T extends Deprecatable>(items: readonly T[]): T[] {
6+
return items
7+
.map((item, index) => ({ item, index }))
8+
.sort((a, b) => {
9+
const deprecatedOrder =
10+
Number(a.item.deprecated === true) - Number(b.item.deprecated === true)
11+
12+
return deprecatedOrder || a.index - b.index
13+
})
14+
.map(({ item }) => item)
15+
}

tests/deprecated-order.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { sortDeprecatedLast } from '@/lib/deprecated'
3+
4+
describe('deprecated product ordering', () => {
5+
it('pins deprecated products after active products', () => {
6+
const items = [
7+
{ id: 'deprecated-a', deprecated: true },
8+
{ id: 'active-a' },
9+
{ id: 'deprecated-b', deprecated: true },
10+
{ id: 'active-b', deprecated: false },
11+
]
12+
13+
expect(sortDeprecatedLast(items).map(item => item.id)).toEqual([
14+
'active-a',
15+
'active-b',
16+
'deprecated-a',
17+
'deprecated-b',
18+
])
19+
})
20+
21+
it('preserves the existing order within each lifecycle group', () => {
22+
const items = [
23+
{ id: 'active-b' },
24+
{ id: 'active-a' },
25+
{ id: 'deprecated-b', deprecated: true },
26+
{ id: 'deprecated-a', deprecated: true },
27+
]
28+
29+
expect(sortDeprecatedLast(items).map(item => item.id)).toEqual([
30+
'active-b',
31+
'active-a',
32+
'deprecated-b',
33+
'deprecated-a',
34+
])
35+
})
36+
37+
it('does not mutate the input array', () => {
38+
const items = [{ id: 'deprecated', deprecated: true }, { id: 'active' }]
39+
40+
sortDeprecatedLast(items)
41+
42+
expect(items.map(item => item.id)).toEqual(['deprecated', 'active'])
43+
})
44+
})

0 commit comments

Comments
 (0)