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…

Headless hookClient component@pandaidx/widgets 1.14.1

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 widget

The 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

  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

TeamGrid
// app/team/page.tsx
import { 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

TeamGrid
// app/team/page.tsx
import { 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

PropTypeDefaultDescription
data*object—Members and the owner card (null → notFound()).
showJoinButtonbooleantrue"Join the Team" button above the grids.
joinHrefstring"/contact"Where the join button links.
joinLabelstring"Join the Team"Text of the join button.
managementTitlestring"Management"Title of the management group.
associatesTitlestring"Realtor Associates"Title of the other members.
memberHrefstring"/team/{slug}"Member page route ({slug}).
ownerHrefstring"/profile"Where the owner card links.
columnsobject—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).
classNamesobject—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.
themeobject—Token overrides for this widget only, merged over the provider theme (same shape as PandaProvider.theme).

Hooks

useTeamGrid()inside PandaProvider

useTeamGrid({ 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