diff --git a/app/(home)/overview/_pages/page.en.tsx b/app/(home)/overview/_pages/page.en.tsx index b0b7208d..cc2e98eb 100644 --- a/app/(home)/overview/_pages/page.en.tsx +++ b/app/(home)/overview/_pages/page.en.tsx @@ -10,8 +10,10 @@ import { Cloud, Container, GeminiIcon, + GoIcon, OpenAIIcon, PythonIcon, + RustIcon, TSIcon, } from '@/components/ui/icon'; import SteelLogo from '@/public/images/logo.png'; @@ -160,6 +162,18 @@ export default function HomePage() { title="Steel Python SDK" description="Python SDK for building applications on Steel." /> + } + href="/steel-rust-sdk" + title="Steel Rust SDK" + description="Rust SDK for building applications on Steel." + /> + } + href="/steel-go-sdk" + title="Steel Go SDK" + description="Go SDK for building applications on Steel." + />
diff --git a/components/docskit/code.client.tsx b/components/docskit/code.client.tsx index 35ecabcb..cb2b57aa 100644 --- a/components/docskit/code.client.tsx +++ b/components/docskit/code.client.tsx @@ -18,7 +18,7 @@ export function MultiCode({ group, className }: { group: CodeGroup; className?: return ( ) => ReactElement; + +const LANG_ICONS: Record = { + typescript: { Icon: TSIcon, className: '!size-[13px]' }, + python: { Icon: PythonIcon, className: '!size-[15px]' }, + go: { Icon: GoIcon, className: '!size-[18px]' }, + rust: { Icon: RustIcon, className: '!size-[15px]' }, +}; + +function escapeValue(value: string) { + return value.toLowerCase().replace(/\s/, '-'); +} + +export function Tabs({ + items, + defaultIndex = 0, + children, + ...props +}: ComponentProps) { + if (!items) { + return {children}; + } + + const values = items.map(escapeValue); + const tabs = Children.toArray(children).filter(isValidElement) as ReactElement<{ + id?: string; + value?: string; + }>[]; + const withValues = tabs.map((child, i) => + cloneElement(child, { value: child.props.value ?? child.props.id ?? values[i] }), + ); + + return ( + + + {items.map((item, i) => { + const icon = LANG_ICONS[values[i]]; + return ( + + {icon ? : null} + {item} + + ); + })} + + {withValues} + + ); +} diff --git a/components/recipe-card.tsx b/components/recipe-card.tsx index 7986d9cd..d17838c7 100644 --- a/components/recipe-card.tsx +++ b/components/recipe-card.tsx @@ -1,5 +1,6 @@ import Link from 'next/link'; -import type { ReactNode } from 'react'; +import type { ReactNode, SVGProps } from 'react'; +import { GoIcon, PythonIcon, RustIcon, TSIcon } from '@/components/ui/icon'; import { cn } from '@/lib/utils'; interface RecipeCardProps { @@ -7,9 +8,23 @@ interface RecipeCardProps { title: string; description: string; topics: string[]; + languages?: string[]; date?: string; } +// Per-language icon with optical-size tuning so the marks read as one +// consistent size inside the chip (TS fills its box; the Go mark is +// wide-and-short). +const LANG_ICONS: Record< + string, + { Icon: (p: SVGProps) => ReactNode; size: string } +> = { + TypeScript: { Icon: TSIcon, size: 'size-3' }, + Python: { Icon: PythonIcon, size: 'size-3.5' }, + Go: { Icon: GoIcon, size: 'size-4' }, + Rust: { Icon: RustIcon, size: 'size-3.5' }, +}; + // Render a registry date (YYYY-MM-DD) as the short English month-day-year // form ("Apr 23, 2026"). Falls back to the raw string if parsing fails. function formatDate(iso: string): string { @@ -34,14 +49,26 @@ function topicSlug(topic: string): string { .replace(/^-|-$/g, ''); } -export function RecipeCard({ slug, title, description, topics, date }: RecipeCardProps) { +export function RecipeCard({ + slug, + title, + description, + topics, + languages, + date, +}: RecipeCardProps) { // "Stretched link" pattern: an absolute overlay covers the whole // card, so clicking anywhere navigates to the recipe. Inner contents are // pointer-events-none by default; interactive children (title, topic // pills) re-enable pointer events and sit above the overlay so they stay // clickable as distinct links. return ( -
+
{description}

- {topics.length > 0 ? ( -
- {topics.map((topic) => ( - - {topic} - - ))} -
- ) : ( - - )} +
+ {languages && languages.length > 0 && ( +
+ {languages.map((language) => { + const icon = LANG_ICONS[language]; + if (!icon) return null; + return ( + + + + ); + })} +
+ )} + {topics.map((topic) => ( + + {topic} + + ))} +
{date && (