Building Detail
A condo building page (Panda /condos/<name>--<city>): photo gallery, description, facts and sales stats, amenities, the listings inside by status (for sale / for rent / sold, IDX cards with like, collections and the…
Anatomy
Every piece of Building Detail 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<BuildingDetail />complete widgetA condo building page (Panda /condos/<name>--<city>): photo gallery, description, facts and sales stats, amenities, the listings inside by…
<BuildingDetailRoot>Holds the data and state its parts and hooks read<BuildingDetailBlock>One block per id, in any orderid="header"id="header-mobile"id="nav"id="gallery"id="stats"id="about"id="sales"id="listings"id="amenities"id="mortgage"id="neighborhood"id="schools"</BuildingDetailBlock><BuildingDetailBlock />One block of the Panda building page by id (header | header-mobile | nav | gallery | stats | about | sales | listings |…
<BuildingListingsStatusTabs />The units' status tabs (For sale, For rent, Pending, Sold, Rented) with live counts.
<BuildingListingsToolbar />The units' filters (beds, unit, baths, price), sort and view toggle (hideViewToggle).
<BuildingListingsResults />The units on screen in the chosen view (table, two-per-row cards, photo grid), the empty state and Load more.
<BuildingListingCard />One unit as the two-per-row card (listing); a tap opens it the site's way.
<BuildingListingsHeading />"Listings at <building>" (buildingName).
<BuildingAmenityList />Amenity rows (items from amenityRows.building / .additional, optional title): icon + label, the value beside it when…
</BuildingDetailRoot></PandaProvider>Installation
- 1npm install <tarball or @pandaidx/widgets>Terminalnpm install https://github.com/pandaidx/panda-widgets/releases/download/v1.14.1/pandaidx-widgets-1.14.1.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/condos/[slug]/page.tsximport { notFound } from "next/navigation"import { BuildingDetail } from "@pandaidx/widgets/client"import { fetchBuildingDetail } from "@pandaidx/widgets/server"export default async function BuildingPage({ params }: { params: Promise<{ slug: string }> }) {const { slug } = await paramsconst data = await fetchBuildingDetail({ publicKey: process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!, apiUrl: process.env.NEXT_PUBLIC_PANDA_API_URL, slug })if (!data) notFound()return <BuildingDetail {...data} galleryVariant="grid" contactPlacement="sidebar" searchPath="/properties" />// Panda websites look: <BuildingDetail {...data} variant="panda" />}
Examples
Own gallery in the full building page
"use client"import Image from "next/image"import { useBuildingDetail } from "@pandaidx/widgets/client"export const BuildingMosaic = () => {const { photos, displayName } = useBuildingDetail()return (<div className="mx-auto grid h-96 max-w-7xl grid-cols-4 grid-rows-2 gap-2 px-4 lg:px-6">{photos.slice(0, 5).map((src, i) => (<div key={src} className={i === 0 ? "relative col-span-2 row-span-2" : "relative"}><Image src={src} alt={`${displayName} photo ${i + 1}`} fill sizes={i === 0 ? "50vw" : "25vw"} className="object-cover" /></div>))}</div>)}// <BuildingDetail {...data} variant="panda" parts={{ gallery: <BuildingMosaic /> }} />
Own units section and amenities, Panda data and actions
"use client"import { BuildingAmenityList, BuildingListingCard, BuildingListingsStatusTabs, useBuildingDetail, useBuildingListings } from "@pandaidx/widgets/client"export const BuildingUnits = () => {const { filtered, bedsTab, setBedsTab, bedOptions, hasMore, loadMore } = useBuildingListings()return (<section><BuildingListingsStatusTabs /><div className="flex gap-2">{bedOptions.map((o) => <button key={o.key} aria-pressed={bedsTab === o.key} onClick={() => setBedsTab(o.key)}>{o.label}</button>)}</div><div className="grid gap-3 lg:grid-cols-2">{filtered.map((l) => <BuildingListingCard key={l.ListingId} listing={l} />)}</div>{hasMore ? <button onClick={() => void loadMore()}>Load more</button> : null}</section>)}export const BuildingAmenities = () => {const { amenityRows } = useBuildingDetail()return <div className="grid gap-12 lg:grid-cols-2"><BuildingAmenityList title="Building amenities" items={amenityRows.building} /><BuildingAmenityList title="Additional details" items={amenityRows.additional} /></div>}// <BuildingDetailRoot {...data}><BuildingDetailBlock id="header" /><BuildingUnits /><BuildingAmenities /></BuildingDetailRoot>
Root + blocks in your own order
<BuildingDetailRoot {...data}><BuildingDetailBlock id="header" /><BuildingDetailBlock id="gallery" /><BuildingDetailBlock id="listings" /><BuildingDetailBlock id="about" /><BuildingDetailBlock id="neighborhood" /></BuildingDetailRoot>
The websites building page composed block by block (same blocks and order as BuildingDetail variant="panda")
<BuildingDetailRoot {...data}>{/* The websites page, block by block in its order: wrap or interleave your own sections anywhere. */}<BuildingDetailBlock id="header" /><BuildingDetailBlock id="nav" /><BuildingDetailBlock id="gallery" /><BuildingDetailBlock id="stats" /><BuildingDetailBlock id="about" /><BuildingDetailBlock id="sales" /><BuildingDetailBlock id="listings" /><BuildingDetailBlock id="amenities" /><BuildingDetailBlock id="mortgage" /><BuildingDetailBlock id="neighborhood" /><BuildingDetailBlock id="schools" /></BuildingDetailRoot>
API reference
BuildingDetail
A condo building page (Panda /condos/<name>--<city>): photo gallery, description, facts and sales stats, amenities, the listings inside by status (for sale / for rent / sold, IDX cards with like, collections and the detail dialog), location map and a lead form (request-showing spot).
Import from @pandaidx/widgets/client · renders inside PandaProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| building* | object | — | Building detail. |
| listings* | object | — | First page of for-sale listings. |
| application* | object | — | Public application (listing cards, detail dialog). |
| leadForm | object | — | Lead form (null hides the contact block). |
| sections | "gallery" | "overview" | "facts" | "amenities" | "listings" | "map" | "contact" | ["gallery","overview","facts","amenities","listings","map","contact"] | Blocks and their order. |
| galleryVariant | "carousel" | "grid" | "masonry" | "carousel" | Photo presentation: slider strip, one large photo + four tiles, or a masonry wall (all open a fullscreen viewer). |
| contactPlacement | "sidebar" | "bottom" | "sidebar" | Lead form as a sticky sidebar or after the content. |
| listingStatuses | "sale" | "rent" | "pending" | "sold" | "rented" | ["sale","rent","sold"] | Listing tabs and their order (pending and rented match the Panda building tabs). |
| listingsPageSize | number | — | Listings per tab page (pass the loader's listingsPageSize). |
| listingColumns | object | 2 | Listing card columns (default 2 with the sidebar, else 3). |
| listingCardVariant | "horizontal" | "classic" | "overlay" | "minimal" | "classic" | Listing card design (same as PropertySearch). |
| searchPath | string | "/properties" | Host page of <PropertyDetail>; listing links are <searchPath>/<ListingId>. |
| leadFormTitle | string | "Request Information" | Heading of the lead form. |
| classNames | object | — | Host classes appended to any slot (see slots). |
| preset | "default" | "template1" | — | Preset for this widget only (defaults to the provider preset). |
| variant | "default" | "template1" | "panda" | — | panda: the Panda websites building page (breadcrumb + Share, sticky section nav, adaptive gallery with logo, clickable stats, About + agent card, sales stats + history, listings browser with status/beds tabs, unit filter, sort and grid/cards/table views, amenities, mortgage calculator, neighborhood map, schools, lead form in a dialog; default preset). default / template1: deprecated alias of preset (template design; the props above). |
| theme | object | — | Token overrides for this widget only, merged over the provider theme (same shape as PandaProvider.theme). |
| pandaSections | "header" | "nav" | "gallery" | "stats" | "about" | "sales" | "listings" | "amenities" | "mortgage" | "neighborhood" | "schools" | ["header","nav","gallery","stats","about","sales","listings","amenities","mortgage","neighborhood","schools"] | panda only: blocks to render (the websites order is kept). With panda, listingStatuses defaults to all five tabs. |
| navOffset | number | 96 | panda only: sticky offset (px) of the section nav under the host's sticky header. --panda-host-header-height (CSS length, set by the host) wins over it. |
| directoryHref | string | "/condos" | panda only: breadcrumb root and property-type link base (?types=). |
| areaHref | string | "/area/{slug}" | panda only: condo area page ({slug}) of the breadcrumb and the Neighborhood row. |
| parts | object | — | panda only: your own element in place of one piece, keeping the rest of the building page: { gallery: <Mosaic /> } (photos from useBuildingDetail().photos), { agent: <MyCard /> } (the agent block of About; call useBuildingDetail().openRequest). The element renders inside the page, so the hook works. |
| listingFilters | "beds" | "unit" | "baths" | "price" | ["beds","unit","baths","price"] | panda only: filters of the units table (bed tabs, unit number, minimum baths, price range); pass fewer to simplify it. |
| listingViews | "grid" | "cards" | "list" | ["grid","cards","list"] | panda only: views of the units the toggle offers, in order (grid big photos, cards two per row with the photo on the side, list table). It opens on cards when offered; one view hides the toggle. |
Parts
<BuildingDetailBlock />One block of the Panda building page by id (header | header-mobile | nav | gallery | stats | about | sales | listings | amenities | mortgage | neighborhood | schools), websites markup, anywhere inside the root (about, neighborhood and schools render nothing without their data).
<BuildingListingsStatusTabs />The units' status tabs (For sale, For rent, Pending, Sold, Rented) with live counts.
<BuildingListingsToolbar />The units' filters (beds, unit, baths, price), sort and view toggle (hideViewToggle).
<BuildingListingsResults />The units on screen in the chosen view (table, two-per-row cards, photo grid), the empty state and Load more.
<BuildingListingCard />One unit as the two-per-row card (listing); a tap opens it the site's way. For your own grid of useBuildingListings().filtered.
<BuildingListingsHeading />"Listings at <building>" (buildingName).
<BuildingAmenityList />Amenity rows (items from amenityRows.building / .additional, optional title): icon + label, the value beside it when short, under it when a list.
Hooks
useBuildingDetail()inside BuildingDetailRoot{ building (amenities, features, salesStats, hoa, units…), listings, application, leadForm, displayName, address, cityHref, photos, agent, status, setStatus, openRequest(), goToListings(status), downloadSheet(), amenityData, amenityRows { building, additional }: { key, label, icon, value, short }[], stats, details (key details: label + value), priceRange, t } — import from @pandaidx/widgets/client; inside BuildingDetailRoot or the parts of BuildingDetail variant="panda"
useBuildingListings()inside BuildingDetailRoot{ statuses, counts, status, selectStatus(s), statusInfo { closed, group }, filters, bedsTab, setBedsTab, bedOptions, unitFilter, setUnitFilter, bathsMin, setBathsMin, bathOptions, priceRange { min, max }, setPriceRange(patch), priceSteps, sort, chooseSort(s), sortOptions, view, chooseView(v), views, listings, filtered (draw these), hasMore, loading, loadMore(), openListing(listing), listingHref(listing), statusLabel(listing), unitLabel(listing), isRentGroup(listing) } — the units browser headless; inside BuildingDetailRoot or NewDevelopmentRoot
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.
fetchBuildingDetail()widget loaderBuilding (GET /v1/buildings/:slug), its first for-sale page, the public application and the lead form; null when unknown (call notFound()). Returns BuildingDetailData | null → spread into the widget ({...data})
fetchBuildingDetail()typed dataTyped building for a page you design yourself: render it inside <BuildingDetailRoot {...data}> (Request Information, listings tab, printable sheet) with BuildingDetailBlock id blocks or useBuildingDetail().