mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
89 lines
3.3 KiB
TypeScript
89 lines
3.3 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { Text, YStack, XStack } from '@/components/ui'
|
|
import { Action } from '@hanzo/ui/marketing'
|
|
import { Line } from '@/components/type'
|
|
import type { CTA } from '@/components/product/ProductLanding'
|
|
|
|
/**
|
|
* A bordered note: a heading, a paragraph, and the links that go with it.
|
|
*
|
|
* Two different sections on four product pages turn out to be this same shape —
|
|
* the "Built on Open Source" panel naming the upstream project, and the
|
|
* "Need a Custom Solution?" panel under the calls to action. They were written
|
|
* twice per page and four times over, which is eight copies of a box with a
|
|
* heading in it.
|
|
*
|
|
* The body is a node because both of them put a link inside the sentence, and a
|
|
* sentence with a link in it is markup rather than a string. It is a `Text` with
|
|
* its white space put back to `normal`: gui's `Text` is `pre-wrap`, so a sentence
|
|
* a caller authored across three source lines would ship with three hard breaks
|
|
* in it. A heading takes one line and one string, so it is type the site already
|
|
* sets.
|
|
*
|
|
* The links are `Action`, this site's one button shape — the hover is the
|
|
* one thing neither a style attribute nor a gui prop can say.
|
|
*
|
|
* The name lost its `hz-` prefix when this document's rules did, and this call
|
|
* site did not follow. `.hz-action` is declared nowhere, so these links were
|
|
* rendering as bare text; `Start.tsx` carries the measurement.
|
|
*/
|
|
/**
|
|
* The link INSIDE that sentence, brighter than the grey around it.
|
|
*
|
|
* It belongs here rather than beside each caller because it is a fact about
|
|
* this panel's body: three product pages wrote the same pair of utilities on
|
|
* the same link in the same paragraph. Not `More` or `Action` — both are
|
|
* standalone controls with their own box, and stretching one inside a sentence
|
|
* tears the line, which is the rule `app/globals.css` already states for prose.
|
|
*/
|
|
export const LINK = { color: 'var(--foreground)', textDecoration: 'none' } as const
|
|
|
|
export function Aside({ title, children, links }: { title: string; children: ReactNode; links?: CTA[] }) {
|
|
return (
|
|
<YStack
|
|
render="section"
|
|
py={64}
|
|
px={16}
|
|
$sm={{ px: 24 }}
|
|
$lg={{ px: 32 }}
|
|
>
|
|
<YStack
|
|
width="100%"
|
|
maxW={768}
|
|
mx="auto"
|
|
p={32}
|
|
borderWidth={1}
|
|
borderColor="var(--edge, var(--white-08))"
|
|
rounded="var(--radius-2xl)"
|
|
bg="$panel"
|
|
>
|
|
<Line render="h3" size="xl" weight="600" marginBottom={12} color="var(--foreground)">
|
|
{title}
|
|
</Line>
|
|
<Line render="p" display="block" fontSize="$2" color="var(--muted-foreground)" whiteSpace="normal" lineHeight="var(--leading-relaxed)">
|
|
{children}
|
|
</Line>
|
|
{links && links.length > 0 && (
|
|
<XStack mt={24} flexWrap="wrap" gap={12}>
|
|
{links.map((l) => {
|
|
const Icon = l.icon
|
|
return (
|
|
<Action
|
|
key={l.label}
|
|
href={l.href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{Icon && <Icon width={16} height={16} />} {l.label}
|
|
</Action>
|
|
)
|
|
})}
|
|
</XStack>
|
|
)}
|
|
</YStack>
|
|
</YStack>
|
|
)
|
|
}
|
|
|
|
export default Aside
|