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…

Root + blocksClient component@pandaidx/widgets 1.14.1

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 widget

A 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 order
id="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

  1. 1
    npm install <tarball or @pandaidx/widgets>
    Terminal
    npm install https://github.com/pandaidx/panda-widgets/releases/download/v1.14.1/pandaidx-widgets-1.14.1.tgz
  2. 2
    import "@pandaidx/widgets/styles.css" in app/layout.tsx
  3. 3
    Wrap the site in <PandaProvider publicKey apiUrl preset theme> (app/layout.tsx)
  4. 4
    In each page (Server Component) call the widget's server loader, then render the widget with its data
  5. 5
    Allow the images: next.config images.remotePatterns [{ protocol: "https", hostname: "**" }] (listing and blog photos come from several CDNs)

Usage

BuildingDetail
// app/condos/[slug]/page.tsx
import { 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 params
const 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

BuildingDetail
"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

BuildingDetail
"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

BuildingDetail
<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")

BuildingDetail
<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

PropTypeDefaultDescription
building*object—Building detail.
listings*object—First page of for-sale listings.
application*object—Public application (listing cards, detail dialog).
leadFormobject—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).
listingsPageSizenumber—Listings per tab page (pass the loader's listingsPageSize).
listingColumnsobject2Listing card columns (default 2 with the sidebar, else 3).
listingCardVariant"horizontal" | "classic" | "overlay" | "minimal""classic"Listing card design (same as PropertySearch).
searchPathstring"/properties"Host page of <PropertyDetail>; listing links are <searchPath>/<ListingId>.
leadFormTitlestring"Request Information"Heading of the lead form.
classNamesobject—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).
themeobject—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.
navOffsetnumber96panda 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.
directoryHrefstring"/condos"panda only: breadcrumb root and property-type link base (?types=).
areaHrefstring"/area/{slug}"panda only: condo area page ({slug}) of the breadcrumb and the Neighborhood row.
partsobject—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 loader

Building (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 data

Typed building for a page you design yourself: render it inside <BuildingDetailRoot {...data}> (Request Information, listings tab, printable sheet) with BuildingDetailBlock id blocks or useBuildingDetail().