Tạo mới trang /listings dạng bảng ticker-style theo spec TEC-3034. - DataTable compact (row 36px, sticky header, alternating rows) - Cột: #, Mã (GG-xxx), Quận, Loại, Giá, Δ30d, DT m², KL/Views - Sortable theo Giá, Δ30d, DT m², KL/Views - Filter inline: Loại giao dịch, Loại BĐS, Quận, Khoảng giá - Toggle view: Table (default) ↔ Card grid (legacy component cũ) - Pagination restyle compact, giữ nguyên API params - Click row → navigate to detail page - Dùng DataTable + PriceDelta từ @/components/design-system Co-Authored-By: Paperclip <noreply@paperclip.ing>
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import * as React from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
import { PriceDelta, type PriceDeltaDirection } from './price-delta';
|
|
|
|
export interface TickerItem {
|
|
id: string;
|
|
label: string;
|
|
changePercent: number;
|
|
direction?: PriceDeltaDirection;
|
|
}
|
|
|
|
export interface TickerStripProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
items: TickerItem[];
|
|
/** Tắt animation (cho unit test / reduced motion). */
|
|
paused?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Thanh chạy ngang hiển thị biến động giá top quận.
|
|
* Render 2 lần liên tiếp để tạo vòng lặp mượt với animation `-50%`.
|
|
*/
|
|
export function TickerStrip({ items, paused, className, ...rest }: TickerStripProps) {
|
|
const duplicated = React.useMemo(() => [...items, ...items], [items]);
|
|
return (
|
|
<div className={cn('relative h-full overflow-hidden', className)} {...rest}>
|
|
<div
|
|
className={cn(
|
|
'flex h-full w-max items-center gap-6 whitespace-nowrap px-4 font-mono text-ticker',
|
|
!paused && 'animate-ticker',
|
|
)}
|
|
>
|
|
{duplicated.map((item, idx) => (
|
|
<span
|
|
key={`${item.id}-${idx}`}
|
|
className="inline-flex items-center gap-2 text-foreground-muted"
|
|
>
|
|
<span className="text-foreground">{item.label}</span>
|
|
<PriceDelta
|
|
value={item.changePercent}
|
|
size="sm"
|
|
hideIcon={false}
|
|
direction={item.direction}
|
|
/>
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|