- Move 8 stray .md (+5 .txt) from ~/Desktop into docs/explorations/from-desktop/ - Reorganize 27 .md/.txt at workspace root: - audit reports -> docs/audits/ - exploration reports -> docs/explorations/ - design system -> docs/design-system/ - Keep only README/CHANGELOG/CONTRIBUTING/CLAUDE at repo root - Refresh docs/README.md as canonical index with links to all groups - Note: pre-existing docs/audits/AUDIT_INDEX.md and AUDIT_SUMMARY.md were overwritten by the newer root-level versions during the move Co-Authored-By: Paperclip <noreply@paperclip.ing>
5.6 KiB
5.6 KiB
Design System Quick Reference
Component Imports
import {
StatCard,
PriceDelta,
MarketIndex,
DataTable,
CompactHeader,
DashboardLayout,
TickerStrip,
} from '@/components/design-system';
Quick Examples
StatCard - KPI Display
<StatCard
label="Giá TB/m²"
value="45.2"
unit="tr/m²"
delta={+2.5}
sublabel="24h"
icon={<TrendingUp />}
/>
PriceDelta - Change Indicator
<PriceDelta value={5.2} size="md" /> {/* +5.20% ↑ Green */}
<PriceDelta value={-2.1} size="sm" /> {/* -2.10% ↓ Red */}
<PriceDelta value={0} direction="neutral" /> {/* 0.00% - Yellow */}
MarketIndex - Hero Metric
<MarketIndex
name="GGX Market"
value="1,240"
changePercent={3.5}
change={"+35"}
window="24h"
/>
DataTable - Sortable Data
const columns: DataTableColumn<District>[] = [
{
id: 'name',
header: 'District',
cell: (row) => row.name,
sortable: true,
sortValue: (row) => row.name,
},
{
id: 'price',
header: 'Price/m²',
cell: (row) => `${row.avgPrice} tr`,
align: 'right',
numeric: true,
sortable: true,
sortValue: (row) => row.avgPrice,
},
];
<DataTable columns={columns} data={districts} dense defaultSortId="price" />
TickerStrip - Scrolling Ticker
<TickerStrip
items={[
{ id: 'q1', label: 'Quan 1', changePercent: 2.1 },
{ id: 'q2', label: 'Quan 2', changePercent: -1.3 },
]}
/>
DashboardLayout - Full Page Frame
<DashboardLayout
header={<CompactHeader breadcrumb="Market" />}
sidebar={<Navigation />}
ticker={<TickerStrip items={items} />}
sidebarCollapsed={collapsed}
>
<div className="space-y-6">
{/* Main content */}
</div>
</DashboardLayout>
Design Tokens
Colors (CSS Variables)
Light Mode (:root)
--foreground: 220 20% 12%— Dark navy text--background: 0 0% 97%— Off-white background--background-elevated: 0 0% 100%— Pure white cards--primary: 142 72% 42%— Green (up)--signal-down: 0 84% 55%— Red (down)--signal-neutral: 45 93% 45%— Yellow (neutral)
Dark Mode (.dark)
--foreground: 210 20% 90%— Off-white text--background: 220 20% 4%— Very dark background- Same primary/signal colors (brightness-adjusted)
Typography
/* Font families */
font-sans /* Inter */
font-mono /* JetBrains Mono (tabular-nums) */
/* Data-specific sizes */
text-ticker /* 0.8125rem — ticker animation */
text-data-sm /* 0.75rem — compact stats */
text-data-md /* 0.875rem — default metric */
text-data-lg /* 1.25rem — large KPI values */
Spacing
h-row /* 2.25rem (36px) — table row height */
h-ticker-bar /* 2rem (32px) — ticker height */
h-header-compact /* 3rem (48px) — dashboard header */
Utilities
[data-numeric] /* Applies font-variant-numeric: tabular-nums */
elevation-1 /* Subtle shadow: 0 1px 2px rgba(0,0,0,0.3) */
elevation-2 /* Prominent shadow: 0 4px 12px rgba(0,0,0,0.4) */
Analytics API
import { analyticsApi } from '@/lib/analytics-api';
import {
useMarketReport,
useHeatmap,
usePriceTrend,
useDistrictStats,
} from '@/lib/hooks/use-analytics';
// Raw API calls
const report = await analyticsApi.getMarketReport('Ho Chi Minh', 'month');
const heatmap = await analyticsApi.getHeatmap('Ho Chi Minh', 'month');
// React Query hooks
const { data: districts } = useDistrictStats('Ho Chi Minh', 'month');
const { data: trend } = usePriceTrend('Quan 1', 'Ho Chi Minh', 'apartment', ['month-1', 'month']);
Charts (Recharts)
import { PriceTrendChart } from '@/components/charts/price-trend-chart';
import { DistrictBarChart } from '@/components/charts/district-bar-chart';
import { AgentPerformance } from '@/components/charts/agent-performance';
// Line chart with dual Y-axis
<PriceTrendChart
data={[{ period: 'Jan', 'Gia/m2': 45, 'Tin đăng': 120 }]}
height={350}
/>
// Bar chart
<DistrictBarChart
data={[{ district: 'Q1', price: 45, listings: 50 }]}
dataKey="price"
/>
// Mixed dashboard (with mock data)
<AgentPerformance />
Maps (Mapbox)
import { DistrictHeatmap } from '@/components/charts/district-heatmap';
import { ListingMap } from '@/components/map/listing-map';
import { LocationPicker } from '@/components/map/location-picker';
// Heatmap
<DistrictHeatmap
data={[
{ district: 'Quan 1', avgPriceM2: 150000, totalListings: 25, medianPrice: '7 tỷ' },
]}
city="Ho Chi Minh"
onDistrictClick={(name) => console.log(name)}
/>
// Listing markers
<ListingMap
listings={listings}
selectedListingId={selected?.id}
onMarkerClick={(listing) => navigate(`/listing/${listing.id}`)}
/>
// Interactive location picker
<LocationPicker
lat={currentLat}
lng={currentLng}
onChange={({ lat, lng }, resolved) => {
// resolved.city, resolved.district, resolved.ward available
setCoords({ lat, lng });
}}
height="400px"
/>
Best Practices
- Always use
[data-numeric]orfont-monofor numbers — ensures proper alignment - Signal colors: Green (up), Red (down), Yellow (neutral)
- Spacing: Use Tailwind gap classes;
gap-4for cards,gap-6for sections - Sticky headers:
sticky top-0 z-10pattern - Responsive: Mobile-first;
md:for tablet breakpoints - Dark mode: CSS variables handle both modes automatically
- No hardcoded colors: Use
hsl(var(--primary))pattern for dynamic theming
Environment Setup
# Required for maps
NEXT_PUBLIC_MAPBOX_TOKEN=your_token_here
# Optional
NEXT_PUBLIC_ANALYTICS_ENDPOINT=...