mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Every product page ended on "Start building with X" and a filled Get your API key, then one screen later on the layout's "Build what's next." and a filled Try Hanzo, in a different button shape: two closing calls a reader had to choose between. StartBuilding becomes Related: the product's docs link and its related capabilities, no heading and no button, so the closing call below is the one way in. And the open-source row no longer shows a "0 stars" pill, which read as an abandoned repository.
70 lines
3.3 KiB
TypeScript
70 lines
3.3 KiB
TypeScript
/**
|
|
* The last block on a product page, above the site's one closing call: this
|
|
* product's docs, and what sits beside it in the catalog.
|
|
*
|
|
* It carries no call of its own. It used to open on "Start building with X"
|
|
* and a filled "Get your API key", which put two closing calls a screen apart
|
|
* on every product page — this one, then the layout's "Build what's next."
|
|
* with Try Hanzo — in two different button shapes. The way in is the closing
|
|
* call's; what only this block knows is which docs page is the product's and
|
|
* which products are its neighbours.
|
|
*
|
|
* `docs` IS A LINK OR IT IS NOTHING. `lib/constants/products-metadata` answers
|
|
* null for every product docs does not publish a page for, and this block drops
|
|
* the link on null rather than aiming at the index. A near-miss page is worse
|
|
* than no link, because it looks like it worked.
|
|
*
|
|
* The related rows are read, never typed. Membership comes from the commerce
|
|
* catalog (`lib/data/catalog.json`, through `cloud-primitives`), visibility from
|
|
* `lib/publish`, and the descriptor from the same copy deck the mega-menu sets
|
|
* under a leaf. With no docs page and no neighbours the block does not render.
|
|
*/
|
|
|
|
import { XStack, YStack } from '@/components/ui'
|
|
import { Eyebrow, Leaf, Lift, Line, More } from '@hanzo/ui/marketing'
|
|
import { Band } from '@/components/marketing/band'
|
|
import { cloudCategories, getPrimitiveCategory } from '@/lib/data/cloud-primitives'
|
|
import type { Product } from '@/lib/product'
|
|
|
|
/** The rest of this product's corner of the catalog, at most three. */
|
|
function near(slug: string) {
|
|
const home = getPrimitiveCategory(slug)
|
|
const shown = home && cloudCategories.find((c) => c.id === home.id)
|
|
return (shown?.items ?? []).filter((i) => i.slug !== slug).slice(0, 3)
|
|
}
|
|
|
|
/** A small grey line, on the leading its utility paired with `text-sm`. */
|
|
const SMALL = { fontSize: '$2', color: 'var(--muted-foreground)', lineHeight: '1.4286' } as const
|
|
|
|
/** A row, as `Leaf` — the role whose reason to exist is the hover. `height`
|
|
* is auto here: the role stretches to a grid cell, and these are a column. */
|
|
const ROW = { py: 14, px: 20, borderColor: "var(--border)", display: "flex", flexWrap: "wrap", items: "baseline", gap: 12, height: "auto", rounded: "var(--radius-xl)", bg: "var(--card)", hoverStyle: { borderColor: "var(--border-selected)" } } as const
|
|
|
|
/** The name states no ink: it rests in the row's and lifts with the card. */
|
|
const NAME = { fontSize: "$2", lineHeight: "1.4286", fontWeight: "600" } as const
|
|
|
|
export function Related({ product }: { product: Product }) {
|
|
const rows = near(product.slug)
|
|
if (rows.length === 0 && !product.docs) return null
|
|
return (
|
|
<Band measure={896}>
|
|
<XStack flexWrap="wrap" justify="space-between" items="baseline" gap={12} mb={16}>
|
|
<Eyebrow>{rows.length > 0 ? 'Related capabilities' : product.name}</Eyebrow>
|
|
{product.docs ? <More href={product.docs}>{`${product.name} docs →`}</More> : null}
|
|
</XStack>
|
|
{rows.length > 0 ? (
|
|
<YStack gap={8}>
|
|
{rows.map((i) => (
|
|
<Leaf href={i.href} key={i.slug} {...ROW} hoverStyle={{ borderColor: "var(--border-selected)" }}>
|
|
<Lift {...NAME}>
|
|
{i.title}
|
|
</Lift>
|
|
{i.desc ? <Line {...SMALL}>{i.desc}</Line> : null}
|
|
</Leaf>
|
|
))}
|
|
</YStack>
|
|
) : null}
|
|
</Band>
|
|
)
|
|
}
|