Market Pulse
Live market of several areas side by side ("Market pulse"), over the site's own MLS feeds and refreshed every hour: homes for sale with the change vs last week, the market type (seller's under 4 months of supply,…
Anatomy
Every piece of Market Pulse as it nests in code. A root holds the data and state; its parts and hooks only work inside it.
<PandaProvider>Mounted once in app/layout.tsx<MarketPulse />complete widgetLive market of several areas side by side ("Market pulse"), over the site's own MLS feeds and refreshed every hour: homes for sale with the…
</PandaProvider>Installation
- 1npm install <tarball or @pandaidx/widgets>Terminalnpm install https://github.com/pandaidx/panda-widgets/releases/download/v1.15.0/pandaidx-widgets-1.15.0.tgz
- 2import "@pandaidx/widgets/styles.css" in app/layout.tsx
- 3Wrap the site in <PandaProvider publicKey apiUrl preset theme> (app/layout.tsx)
- 4In each page (Server Component) call the widget's server loader, then render the widget with its data
- 5Allow the images: next.config images.remotePatterns [{ protocol: "https", hostname: "**" }] (listing and blog photos come from several CDNs)
Usage
// app/page.tsx (a section of any page): the areas of the homepage library, each with a CTA to its CMAimport { MarketPulse } from "@pandaidx/widgets/client"import { fetchMarketPulse } from "@pandaidx/widgets/server"const config = { publicKey: process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!, apiUrl: process.env.NEXT_PUBLIC_PANDA_API_URL }export default async function MarketSection() {const data = await fetchMarketPulse({ ...config, limit: 6 })// The CTA opens /home-market-analysis?area=<slug>, the page with <MarketAnalysis valuation={await fetchHomeValuation({ searchParams })} />.return <MarketPulse data={data} title="Market pulse" subtitle="Live numbers from the MLS, updated every hour" />}
API reference
MarketPulse
Live market of several areas side by side ("Market pulse"), over the site's own MLS feeds and refreshed every hour: homes for sale with the change vs last week, the market type (seller's under 4 months of supply, buyer's over 7), the medians of the last three months of closings (sale price, $/sq ft, days on market, sale-to-list), months of supply, closings of the last 30 days with their change, and a CTA per area to its prefilled CMA (MarketAnalysis with ?area=<slug>), plus a link to its homes for sale. layout: cards (one card per area) or table (one row per area). The areas are an area library (library, default the first homepage library, ranked by sort) or a list of slugs (areas). For any other design (a ticker, one big stat, a chart, a quiz that ends on the CMA) use the data level: fetchMarket({ slug }) / fetchMarketPulse() give the typed numbers, formatMarketFact / formatChange / marketTypeLabel the texts and marketCmaHref the CMA link. Never hard-code market numbers in a page: they go stale; read them from these loaders.
Import from @pandaidx/widgets/client · renders inside PandaProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | object | — | { asOf, type, library, areas } (null or empty renders nothing). |
| title | string | — | Section heading (none by default); pass await getText(...). |
| subtitle | string | — | Line under the heading; the update date shows beside it. |
| layout | "cards" | "table" | "cards" | cards: a card per area (1 / 2 / 3 columns). table: a row per area (scrolls sideways on phones). |
| facts | "active" | "pending" | "sold30" | "medianSalePrice" | "medianPricePerSqft" | "medianDaysOnMarket" | "saleToListPct" | "monthsOfSupply" | ["medianSalePrice","medianPricePerSqft","medianDaysOnMarket","monthsOfSupply"] | Numbers of each area, in order (homes for sale always shows on top of a card). |
| cmaHref | string | "/home-market-analysis?area={slug}" | Page of the prefilled CMA (the page with MarketAnalysis); {slug} is the area. null hides the CTA. |
| areaHref | string | "/search?q={area}" | Homes for sale of an area; {area} is its search query, {slug} its slug. null hides the link. |
| source | string | "market-pulse" | source query sent with the CMA link (tells the CMA page or its form which tool sent the visitor). |
| labels | object | — | Copy overrides: fact labels (active, medianSalePrice …), sellers / balanced / buyers, vsLastWeek, vsPrevious30, days, months, cta (with {{area}}), browse, updated (with {{date}}). Pass await getText({ id, label, text }) for each. |
| classNames | object | — | Classes appended to section, container, header, title, subtitle, grid, card, cardBody, cardTitle, chip, factLabel, factValue, button, panel, row. |
| preset | "default" | "template1" | — | Preset for this widget only (defaults to the provider preset). |
| variant | "default" | "template1" | — | Deprecated alias of preset. |
| theme | object | — | Token overrides for this widget only, merged over the provider theme (same shape as PandaProvider.theme). |
Server data
Loaders run in a Server Component and come from @pandaidx/widgets/server. Typed data is for a page you design yourself, rendered inside the root.
fetchMarketPulse()widget loaderGET /v1/market (each area's snapshot, changes and the cma.area of its prefilled CMA, over the site's MLS feeds; cached an hour). Null on failure (never throws). Returns MarketPulseData | null → data
fetchMarket()typed dataOne area's market (GET /v1/market/:slug) for a design of your own: { area: { name, slug, query }, asOf, type, snapshot: { active, pending, sold7, sold30, sold90, medianSalePrice, medianPricePerSqft, medianDaysOnMarket, saleToListPct, monthsOfSupply, marketType }, changes: { activeWeekPct, sold30Pct, pricePerSqftYearPct }, history: { month, sales, medianSalePrice, medianPricePerSqft, medianDaysOnMarket, saleToListPct }[] (15 months), inventory: { date, active }[] (30 days), cma: { area } }; params { slug, type?: "all" | "condo" | "house" }; null for an unknown area. Texts: formatMarketFact(fact, snapshot), formatChange(pct), marketTypeLabel(snapshot); the CTA: marketCmaHref("/home-market-analysis", market).