Team
The Panda websites /team page: a black "Join the Team" button, the Management group (the owner card first when shown as a member, then members oldest first) and the Realtor Associates, as centered wrapped rows of 360px…
Anatomy
Every piece of Team 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<TeamGrid />complete widgetThe Panda websites /team page: a black "Join the Team" button, the Management group (the owner card first when shown as a member, then…
</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/team/page.tsximport { notFound } from "next/navigation"import { TeamGrid } from "@pandaidx/widgets/client"import { fetchTeam } from "@pandaidx/widgets/server"export default async function TeamPage() {const data = await fetchTeam({ publicKey: process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!, apiUrl: process.env.NEXT_PUBLIC_PANDA_API_URL })if (!data) notFound()return <TeamGrid data={data} />}
Examples
A team page in the site's own groups and cards
// app/team/page.tsximport { notFound } from "next/navigation"import { fetchTeam } from "@pandaidx/widgets/server"import { TeamView } from "@/components/team-view"export default async function TeamPage() {const data = await fetchTeam({ publicKey: process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!, apiUrl: process.env.NEXT_PUBLIC_PANDA_API_URL })if (!data) notFound()return <TeamView data={data} />}// components/team-view.tsx"use client"import Image from "next/image"import Link from "next/link"import { useTeamGrid, type TeamGridData } from "@pandaidx/widgets/client"export const TeamView = ({ data }: { data: TeamGridData }) => {const team = useTeamGrid({ data })return (<main className="mx-auto max-w-6xl px-4 py-16">{[["Management", team.management], ["Associates", team.associates]].map(([title, people]) => (<section key={title as string} className="mb-16"><h2 className="mb-8 text-3xl">{title as string}</h2><div className="grid gap-8 md:grid-cols-3">{(people as typeof team.all).map((person) => (<Link key={person.key} href={person.href}>{person.image ? <Image src={person.image} alt={person.name} width={360} height={540} sizes="(min-width: 768px) 33vw, 100vw" className="aspect-[2/3] w-full object-cover" /> : null}<p className="mt-3 text-xl">{person.name}</p><p className="opacity-70">{person.title}</p></Link>))}</div></section>))}</main>)}
API reference
TeamGrid
The Panda websites /team page: a black "Join the Team" button, the Management group (the owner card first when shown as a member, then members oldest first) and the Realtor Associates, as centered wrapped rows of 360px 2:3 portrait cards (uppercase name, title, email, phone) linking to each member page. columns turns the rows into an equal-column grid and classNames restyles each part (portrait shape, name case, titles, gaps) to the site's design.
Import from @pandaidx/widgets/client · renders inside PandaProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | object | — | Members and the owner card (null → notFound()). |
| showJoinButton | boolean | true | "Join the Team" button above the grids. |
| joinHref | string | "/contact" | Where the join button links. |
| joinLabel | string | "Join the Team" | Text of the join button. |
| managementTitle | string | "Management" | Title of the management group. |
| associatesTitle | string | "Realtor Associates" | Title of the other members. |
| memberHref | string | "/team/{slug}" | Member page route ({slug}). |
| ownerHref | string | "/profile" | Where the owner card links. |
| columns | object | — | Cards per row: a number 1-4 (desktop; phones 1, tablets up to 2) or { base, sm, md, lg, xl }. Unset keeps the centered wrapped rows of 360px cards; set, the cards fill an equal-column grid (e.g. columns={4} for a four-across team). |
| classNames | object | — | Host classes per part (they win conflicts), to match DESIGN.md: e.g. { image: "rounded-full aspect-square", name: "normal-case font-heading text-h3", title: "font-heading text-h2" }. |
| 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). |
Hooks
useTeamGrid()inside PandaProvideruseTeamGrid({ data, memberHref?: "/team/{slug}", ownerHref?: "/profile" }) → { management: { key, name, title, image, email, emailHref, phones, phoneHref, href, isOwner }[] (the owner first when shown as a member, then the management members oldest first), associates (the rest, newest first), all (website order), total, empty } — data comes from fetchTeam (null → team page off, notFound()). Link each person with href (/team/<slug>; the owner's /profile), use emailHref / phoneHref for the contact links and next/image with sizes for the portrait. TeamGrid stays the ready-made page. Client component only.
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.
fetchTeam()widget loader/v1/team members + the owner card from /v1/site (when shown as a member); null when the site has no team (not shared, or no member yet). Returns TeamGridData | null → data