mirror of
https://github.com/hanzoai/community.git
synced 2026-10-30 14:39:19 +00:00
The page mounts the docs.hanzo.ai wiring: @hanzo/event sends the pageview to api.hanzo.ai/v1/event with project hanzo-community's publishable key, startTags loads the site's GA4 and Meta tags from cloud once the visitor consents, and @hanzo/ui/consent is the banner. @hanzo/ui 8.29.12 is the first version this repo takes that has the banner. It registers its gui config, so stacks type against the v5 shorthands, and the page moves to them. Its stylesheet is imported in the layout; without it the export rendered unstyled. Overrides keep one gui and one event client in the tree. hanzo.yml and the hanzoai/ci caller type-check, build out/ and publish it as Sites project hanzo-community on every push to main.
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
'use client'
|
|
|
|
import type { ComponentProps } from 'react'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
import { ArrowRight, ExternalLink } from 'lucide-react'
|
|
import { Anchor, Text, XStack, YStack } from '@hanzo/ui'
|
|
|
|
/**
|
|
* Channel — one place the community lives, as a link you can click anywhere on.
|
|
*
|
|
* The page drew this twice: a big two-up card and a small four-up card. They
|
|
* read as different components and are not. Comparing the two by the set of
|
|
* class names each used scores .26, which is what made them look distinct;
|
|
* normalising the SCALE away — sizes and opacities to a placeholder — takes the
|
|
* same pair to .49. What is left over is emphasis, and `featured` is already a
|
|
* field on the data, so the discriminant did not have to be invented either.
|
|
*
|
|
* The anchor IS the surface. Wrapping a card in a positioning <a> is one element
|
|
* and one hover story too many, and the version that nests them is the version
|
|
* where the hover lights the card while the cursor sits in the gap around it.
|
|
*/
|
|
export interface Channel {
|
|
icon: LucideIcon
|
|
name: string
|
|
description: string
|
|
href: string
|
|
label: string
|
|
external: boolean
|
|
featured: boolean
|
|
}
|
|
|
|
/** A theme colour, as `Text` takes one. */
|
|
type Ink = ComponentProps<typeof Text>['color']
|
|
|
|
/** Icons stroke `currentColor`, so a colour set here IS the icon's colour, and
|
|
* `$group-hover` on the card reaches it without the icon knowing anything. */
|
|
const Glyph = ({
|
|
icon: Icon,
|
|
size,
|
|
color,
|
|
hover,
|
|
}: {
|
|
icon: LucideIcon
|
|
size: number
|
|
color: Ink
|
|
hover: Ink
|
|
}) => (
|
|
<Text display="flex" color={color} $group-hover={{ color: hover }}>
|
|
<Icon size={size} />
|
|
</Text>
|
|
)
|
|
|
|
const Tile = ({ size, children }: { size: number; children: React.ReactNode }) => (
|
|
<YStack
|
|
width={size}
|
|
height={size}
|
|
rounded={size > 32 ? 12 : 8}
|
|
bg="$raised"
|
|
items="center"
|
|
justify="center"
|
|
shrink={0}
|
|
$group-hover={{ bg: '$rim' }}
|
|
>
|
|
{children}
|
|
</YStack>
|
|
)
|
|
|
|
export const Channel = ({ icon, name, description, href, label, external, featured }: Channel) => (
|
|
<Anchor
|
|
href={href}
|
|
{...(external ? { target: '_blank', rel: 'noopener noreferrer' } : null)}
|
|
group
|
|
display="flex"
|
|
flexDirection={featured ? 'column' : 'row'}
|
|
items={featured ? 'stretch' : 'flex-start'}
|
|
height="100%"
|
|
gap={featured ? 16 : 12}
|
|
p={featured ? 24 : 16}
|
|
rounded={featured ? 16 : 12}
|
|
bg="$panel"
|
|
borderWidth={1}
|
|
borderColor="$edge"
|
|
textDecorationLine="none"
|
|
cursor="pointer"
|
|
hoverStyle={{ bg: '$hover', borderColor: '$dim' }}
|
|
>
|
|
{featured ? (
|
|
<XStack items="center" justify="space-between">
|
|
<Tile size={40}>
|
|
<Glyph icon={icon} size={20} color="$soft" hover="$ink" />
|
|
</Tile>
|
|
<Glyph icon={ExternalLink} size={16} color="$faint" hover="$quiet" />
|
|
</XStack>
|
|
) : (
|
|
<Tile size={32}>
|
|
<Glyph icon={icon} size={16} color="$quiet" hover="$ink" />
|
|
</Tile>
|
|
)}
|
|
|
|
{/* `flex-1` meant `flex: 1 1 0%`, and the half that matters here is the
|
|
SHRINK. A gui stack inherits React Native's default of `flex-shrink: 0`
|
|
where CSS's is 1, so a column that only says "grow" never gives width
|
|
back: measured at 365px of description inside a 247px card, and the page
|
|
got a horizontal scrollbar instead of a second line of text.
|
|
|
|
Only in the row. Along a column those same two values are the HEIGHT, and
|
|
a basis of 0 that is not allowed to grow collapses the text to nothing. */}
|
|
<YStack
|
|
gap={featured ? 4 : 2}
|
|
minW={0}
|
|
{...(featured ? null : { grow: 1, shrink: 1, flexBasis: 0 })}
|
|
>
|
|
<Text fontSize={featured ? 18 : 14} fontWeight={featured ? '600' : '500'} color="$ink">
|
|
{name}
|
|
</Text>
|
|
<Text fontSize={featured ? 14 : 12} lineHeight={featured ? 23 : 16} color="$quiet">
|
|
{description}
|
|
</Text>
|
|
</YStack>
|
|
|
|
{featured ? (
|
|
<XStack items="center" gap={6} mt="auto">
|
|
<Text fontSize={14} fontWeight="500" color="$soft" $group-hover={{ color: '$ink' }}>
|
|
{label}
|
|
</Text>
|
|
<Glyph icon={ArrowRight} size={14} color="$soft" hover="$ink" />
|
|
</XStack>
|
|
) : null}
|
|
</Anchor>
|
|
)
|